bug 783994 - use the sqlite-backed certificate and key DBs r?jcj draft
authorDavid Keeler <dkeeler@mozilla.com>
Fri, 22 Sep 2017 14:34:20 -0700
changeset 681815 a5c1dad493fad0599dbf3af928b8a02f3d660598
parent 680782 c6a2643362a67cdf7a87ac165454fce4b383debb
child 736248 ebadd63a24e8a0e759c6184821dff59ccbdf5e29
push id84940
push userbmo:dkeeler@mozilla.com
push dateTue, 17 Oct 2017 20:54:49 +0000
reviewersjcj
bugs783994
milestone58.0a1
bug 783994 - use the sqlite-backed certificate and key DBs r?jcj MozReview-Commit-ID: 2K8JVGc0mAj
security/manager/ssl/PKCS11ModuleDB.cpp
security/manager/ssl/security-prefs.js
security/manager/ssl/tests/unit/test_sdr_preexisting_with_password.js
security/manager/ssl/tests/unit/test_sdr_preexisting_with_password/key3.db
security/manager/ssl/tests/unit/xpcshell.ini
--- a/security/manager/ssl/PKCS11ModuleDB.cpp
+++ b/security/manager/ssl/PKCS11ModuleDB.cpp
@@ -109,16 +109,23 @@ PKCS11ModuleDB::AddModule(const nsAStrin
   if (isAlreadyShutDown()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   if (aModuleName.IsEmpty()) {
     return NS_ERROR_INVALID_ARG;
   }
 
+  // There appears to be a deadlock if we try to load modules concurrently, so
+  // just wait until the loadable roots module has been loaded.
+  nsresult rv = BlockUntilLoadableRootsLoaded();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
   NS_ConvertUTF16toUTF8 moduleName(aModuleName);
   nsCString fullPath;
   // NSS doesn't support Unicode path.  Use native charset
   NS_CopyUnicodeToNative(aLibraryFullPath, fullPath);
   uint32_t mechFlags = SECMOD_PubMechFlagstoInternal(aCryptoMechanismFlags);
   uint32_t cipherFlags = SECMOD_PubCipherFlagstoInternal(aCipherFlags);
   SECStatus srv = SECMOD_AddNewModule(moduleName.get(), fullPath.get(),
                                       mechFlags, cipherFlags);
--- a/security/manager/ssl/security-prefs.js
+++ b/security/manager/ssl/security-prefs.js
@@ -39,17 +39,17 @@ pref("security.ask_for_password",       
 pref("security.password_lifetime",       30);
 
 // If true, use the modern sqlite-backed certificate and key databases in NSS.
 // If false, use the default format. Currently the default in NSS is the old
 // BerkeleyDB format, but this will change in bug 1377940.
 // Changing this requires a restart to take effect.
 // Note that the environment variable MOZPSM_NSSDBDIR_OVERRIDE can override both
 // the behavior of this preference and the NSS default.
-pref("security.use_sqldb", false);
+pref("security.use_sqldb", true);
 
 // The supported values of this pref are:
 // 0: disable detecting Family Safety mode and importing the root
 // 1: only attempt to detect Family Safety mode (don't import the root)
 // 2: detect Family Safety mode and import the root
 // (This is only relevant to Windows 8.1)
 pref("security.family_safety.mode", 2);
 
copy from security/manager/ssl/tests/unit/test_sdr_preexisting.js
copy to security/manager/ssl/tests/unit/test_sdr_preexisting_with_password.js
--- a/security/manager/ssl/tests/unit/test_sdr_preexisting.js
+++ b/security/manager/ssl/tests/unit/test_sdr_preexisting_with_password.js
@@ -1,187 +1,68 @@
 // -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 "use strict";
 
 // Tests that the SDR implementation is able to decrypt strings encrypted using
-// a preexisting NSS key database. Creating the database is straight-forward:
-// simply run Firefox (or xpcshell) and encrypt something using
-// nsISecretDecoderRing (e.g. by saving a password or directly using the
-// interface). The resulting key3.db file (in the profile directory) now
-// contains the private key used to encrypt the data.
-// "Upgrading" a key3.db with certutil to use on Android appears not to work.
-// Because the keys have to be the same for this test to work the way it does,
-// the key from key3.db must be extracted and added to a new key4.db. This can
-// be done with NSS' PK11_* APIs like so (although note that the following code
-// is not guaranteed to compile or work, but is more of a guideline for how to
-// do this in the future if necessary):
-//
-// #include <stdio.h>
-//
-// #include "nss.h"
-// #include "pk11pub.h"
-// #include "prerror.h"
-// #include "secerr.h"
-//
-// void printPRError(const char* message) {
-//   fprintf(stderr, "%s: %s\n", message, PR_ErrorToString(PR_GetError(), 0));
-// }
-//
-// int main(int argc, char* argv[]) {
-//   if (NSS_Initialize(".", "", "", "", NSS_INIT_NOMODDB | NSS_INIT_NOROOTINIT)
-//         != SECSuccess) {
-//     printPRError("NSS_Initialize failed");
-//     return 1;
-//   }
-//
-//   PK11SlotInfo* slot = PK11_GetInternalKeySlot();
-//   if (!slot) {
-//     printPRError("PK11_GetInternalKeySlot failed");
-//     return 1;
-//   }
-//
-//   // Create a key to wrap the SDR key to export it.
-//   unsigned char wrappingKeyIDBytes[] = { 0 };
-//   SECItem wrappingKeyID = {
-//     siBuffer,
-//     wrappingKeyIDBytes,
-//     sizeof(wrappingKeyIDBytes)
-//   };
-//   PK11SymKey* wrappingKey = PK11_TokenKeyGen(slot, CKM_DES3_CBC, 0, 0,
-//                                              &wrappingKeyID, PR_FALSE, NULL);
-//   if (!wrappingKey) {
-//     printPRError("PK11_TokenKeyGen failed");
-//     return 1;
-//   }
-//
-//   // This is the magic identifier NSS uses for the SDR key.
-//   unsigned char sdrKeyIDBytes[] = {
-//     0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-//     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
-//   };
-//   SECItem sdrKeyID = { siBuffer, sdrKeyIDBytes, sizeof(sdrKeyIDBytes) };
-//   PK11SymKey* sdrKey = PK11_FindFixedKey(slot, CKM_DES3_CBC, &sdrKeyID,
-//                                          NULL);
-//   if (!sdrKey) {
-//     printPRError("PK11_FindFixedKey failed");
-//     return 1;
-//   }
-//
-//   // Wrap the SDR key.
-//   unsigned char wrappedKeyBuf[1024];
-//   SECItem wrapped = { siBuffer, wrappedKeyBuf, sizeof(wrappedKeyBuf) };
-//   if (PK11_WrapSymKey(CKM_DES3_ECB, NULL, wrappingKey, sdrKey, &wrapped)
-//         != SECSuccess) {
-//     printPRError("PK11_WrapSymKey failed");
-//     return 1;
-//   }
-//
-//   // Unwrap the SDR key (NSS considers the SDR key "sensitive" and so won't
-//   // just export it as raw key material - we have to export it and then
-//   // re-import it as non-sensitive to get that data.
-//   PK11SymKey* unwrapped = PK11_UnwrapSymKey(wrappingKey, CKM_DES3_ECB, NULL,
-//                                             &wrapped, CKM_DES3_CBC,
-//                                             CKA_ENCRYPT, 0);
-//   if (!unwrapped) {
-//     printPRError("PK11_UnwrapSymKey failed");
-//     return 1;
-//   }
-//   if (PK11_ExtractKeyValue(unwrapped) != SECSuccess) {
-//     printPRError("PK11_ExtractKeyValue failed");
-//     return 1;
-//   }
-//   SECItem* keyData = PK11_GetKeyData(unwrapped);
-//   if (!keyData) {
-//     printPRError("PK11_GetKeyData failed");
-//     return 1;
-//   }
-//   for (int i = 0; i < keyData->len; i++) {
-//     printf("0x%02hhx, ", keyData->data[i]);
-//   }
-//   printf("\n");
-//
-//   PK11_FreeSymKey(unwrapped);
-//   PK11_FreeSymKey(sdrKey);
-//   PK11_FreeSymKey(wrappingKey);
-//   PK11_FreeSlot(slot);
-//
-//   if (NSS_Shutdown() != SECSuccess) {
-//     printPRError("NSS_Shutdown failed");
-//     return 1;
-//   }
-//   return 0;
-// }
-//
-// The output of compiling and running the above should be the bytes of the SDR
-// key. Given that, create a key4.db with an empty password using
-// `certutil -N -d sql:.` and then compile and run the following:
-//
-// #include <stdio.h>
-//
-// #include "nss.h"
-// #include "pk11pub.h"
-// #include "prerror.h"
-// #include "secerr.h"
-// #include "secmod.h"
-//
-// void printPRError(const char* message) {
-//   fprintf(stderr, "%s: %s\n", message, PR_ErrorToString(PR_GetError(), 0));
-// }
-//
-// int main(int argc, char* argv[]) {
-//   if (NSS_Initialize("sql:.", "", "", "",
-//                      NSS_INIT_NOMODDB | NSS_INIT_NOROOTINIT) != SECSuccess) {
-//     printPRError("NSS_Initialize failed");
-//     return 1;
-//   }
-//
-//   PK11SlotInfo* slot = PK11_GetInternalKeySlot();
-//   if (!slot) {
-//     printPRError("PK11_GetInternalKeySlot failed");
-//     return 1;
-//   }
-//
-//   // These are the bytes of the SDR key from the previous step:
-//   unsigned char keyBytes[] = {
-//     0x70, 0xab, 0xea, 0x1f, 0x8f, 0xe3, 0x4a, 0x7a, 0xb5, 0xb0, 0x43, 0xe5,
-//     0x51, 0x83, 0x86, 0xe5, 0xb3, 0x43, 0xa8, 0x1f, 0xc1, 0x57, 0x86, 0x46
-//   };
-//   SECItem keyItem = { siBuffer, keyBytes, sizeof(keyBytes) };
-//   PK11SymKey* key = PK11_ImportSymKey(slot, CKM_DES3_CBC, PK11_OriginUnwrap,
-//                                       CKA_ENCRYPT, &keyItem, NULL);
-//   if (!key) {
-//     printPRError("PK11_ImportSymKey failed");
-//     return 1;
-//   }
-//
-//   PK11_FreeSymKey(key);
-//   PK11_FreeSlot(slot);
-//
-//   if (NSS_Shutdown() != SECSuccess) {
-//     printPRError("NSS_Shutdown failed");
-//     return 1;
-//   }
-//   return 0;
-// }
-//
-// This should create a key4.db file with the SDR key. (Note however that this
-// does not set the magic key ID for the SDR key. Currently this is not a
-// problem because the NSS implementation that backs the SDR simply tries all
-// applicable keys it has when decrypting, so this still works.)
+// a preexisting NSS key database that a) has a password and b) is in the old
+// dbm format.
+// To create such a database, run a version Firefox (or xpcshell) where the
+// default database format is the old dbm format (i.e. pre-bug 783994), set a
+// master password, and then encrypt something using nsISecretDecoderRing.
+// This does not apply to Android as the dbm implementation was never enabled on
+// that platform.
+
+var gMockPrompter = {
+  passwordToTry: "password",
+  numPrompts: 0,
+
+  // This intentionally does not use arrow function syntax to avoid an issue
+  // where in the context of the arrow function, |this != gMockPrompter| due to
+  // how objects get wrapped when going across xpcom boundaries.
+  promptPassword(dialogTitle, text, password, checkMsg, checkValue) {
+    this.numPrompts++;
+    if (this.numPrompts > 1) { // don't keep retrying a bad password
+      return false;
+    }
+    equal(text,
+          "Please enter your master password.",
+          "password prompt text should be as expected");
+    equal(checkMsg, null, "checkMsg should be null");
+    ok(this.passwordToTry, "passwordToTry should be non-null");
+    password.value = this.passwordToTry;
+    return true;
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPrompt]),
+};
+
+// Mock nsIWindowWatcher. PSM calls getNewPrompter on this to get an nsIPrompt
+// to call promptPassword. We return the mock one, above.
+var gWindowWatcher = {
+  getNewPrompter: () => gMockPrompter,
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWindowWatcher]),
+};
 
 function run_test() {
-  const isAndroid = AppConstants.platform == "android";
-  const keyDBName = isAndroid ? "key4.db" : "key3.db";
+  let windowWatcherCID =
+    MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1",
+                           gWindowWatcher);
+  do_register_cleanup(() => {
+    MockRegistrar.unregister(windowWatcherCID);
+  });
+
+  Services.prefs.setBoolPref("security.use_sqldb", true);
+
   let profile = do_get_profile();
-  let keyDBFile = do_get_file(`test_sdr_preexisting/${keyDBName}`);
-  keyDBFile.copyTo(profile, keyDBName);
+  let keyDBFile = do_get_file("test_sdr_preexisting_with_password/key3.db");
+  keyDBFile.copyTo(profile, "key3.db");
 
   let sdr = Cc["@mozilla.org/security/sdr;1"]
               .getService(Ci.nsISecretDecoderRing);
 
   let testcases = [
     // a full padding block
     { ciphertext: "MDoEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECGeDHwVfyFqzBBAYvqMq/kDMsrARVNdC1C8d",
       plaintext: "password" },
@@ -206,9 +87,11 @@ function run_test() {
       plaintext: "nnLbuwLRkhlongerandsotakesupmultipleblocks" },
   ];
 
   for (let testcase of testcases) {
     let decrypted = sdr.decryptString(testcase.ciphertext);
     equal(decrypted, testcase.plaintext,
           "decrypted ciphertext should match expected plaintext");
   }
+  equal(gMockPrompter.numPrompts, 1,
+        "Should have been prompted for a password once");
 }
new file mode 100644
index 0000000000000000000000000000000000000000..cac0808ac32c35752b08d886eef4a3d362e56acd
GIT binary patch
literal 16384
zc%1Fn&r8!`90%~v^ZkAsjxC;{3lwj|!`d*up@JQBOv=&_Y72uXXsP*YY_bU(N~E9`
zu_0({6tN%~9)=bbtq~nc^dLbtgw&JZNxSS)Zt$(S+z$2!=n&p7c=LI~b9kQX^C8tK
z2N4NGQj$oQtE44jln<2@icpy1{@gzz>0b}I?rl+?P{@%hVa2!=00000007|lP*H}b
z`3`^2V?4~qbIAe#000000Kn0hotAtJ7j=e)wV4`M?8;b^W_6|8>AvG|R(l=JK~c}n
z-5B|+9c@kxDU-p4rAVW4?XitCrin`vyC<3U1QRV!%hu=o_GsrLtNiV<Wq82h_4?eN
z&gzbyu8w}Oz02e6b-M&g@mt)UOR+Z*000000002Tlt_%^(SU4J9sHQf6_Vb5Hn}~#
zZt8o!ee+}H)v{b#)s_($lWzCcD<dtOCEms^)c^Jet*yGo2UE$V`BZD<s(JFRrXsFC
z6ZqP5-?*`8_~bu-J^IG@<Hc*WIuv~7QZ%RIKPy8|BFe^Rv#;)#vDVhVS(<43Q2sru
efB5Cy%YnIY`u`WQKgHg3000000002Mq1yvX%-vf6
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -25,16 +25,17 @@ support-files =
   test_keysize_ev/**
   test_missing_intermediate/**
   test_name_constraints/**
   test_ocsp_fetch_method/**
   test_ocsp_url/**
   test_onecrl/**
   test_pinning_dynamic/**
   test_sdr_preexisting/**
+  test_sdr_preexisting_with_password/**
   test_signed_apps/**
   test_signed_dir/**
   test_startcom_wosign/**
   test_validity/**
   tlsserver/**
 
 [test_add_preexisting_cert.js]
 [test_baseline_requirements_subject_common_name.js]
@@ -137,16 +138,19 @@ run-sequentially = hardcoded ports
 run-sequentially = hardcoded ports
 # This test can take longer than 300 seconds on B2G emulator debug builds, so
 # give it enough time to finish. See bug 1081128.
 requesttimeoutfactor = 2
 [test_pinning_dynamic.js]
 [test_pinning_header_parsing.js]
 [test_sdr.js]
 [test_sdr_preexisting.js]
+[test_sdr_preexisting_with_password.js]
+# Not relevant to Android. See the comment in the test.
+skip-if = toolkit == 'android'
 [test_session_resumption.js]
 run-sequentially = hardcoded ports
 [test_signed_apps.js]
 [test_signed_dir.js]
 tags = addons psm
 [test_sss_enumerate.js]
 [test_sss_eviction.js]
 [test_sss_originAttributes.js]