bug 1337950 - work around failing to load a FIPS PKCS#11 module DB in NSS initialization r?jcj,Cykesiopka draft
authorDavid Keeler <dkeeler@mozilla.com>
Thu, 20 Apr 2017 10:31:22 -0700
changeset 569610 abf516f804eb0a1c586bb2256a491f5976d338eb
parent 569501 abe5868346c7abb5b0bdf76f29bc3d9f839461f5
child 626264 604ea14b38b24af6d9e348c1478f3310a782a9ab
push id56237
push userbmo:dkeeler@mozilla.com
push dateThu, 27 Apr 2017 18:31:43 +0000
reviewersjcj, Cykesiopka
bugs1337950, 1047584, 1295937
milestone55.0a1
bug 1337950 - work around failing to load a FIPS PKCS#11 module DB in NSS initialization r?jcj,Cykesiopka Firefox essentially does not support running NSS in FIPS mode any longer. This has always been the case on Android from what I can tell and it has been the case on OS X since at least version 34 (see bug 1047584). It became the case on Windows as of version 53 (see bug 1295937). Unfortunately, before this patch, if a user attempted to run an affected version of Firefox using a profile directory containing an NSS database collection that had FIPS enabled, NSS initialization would fail and fall back to running in no DB mode, which had the side-effect of making any saved passwords and certificates unavailable. This patch attempts to detect and work around this failure mode by moving the PKCS#11 module DB (which is where the FIPS bit is set) to a backup location and basically running with a fresh, non-FIPS module DB. This allows Firefox to initialize NSS with the preexisting key and certificate databases available. MozReview-Commit-ID: 1E4u1ngZyRv
security/certverifier/NSSCertDBTrustDomain.cpp
security/manager/ssl/nsNSSComponent.cpp
security/manager/ssl/tests/unit/test_broken_fips.js
security/manager/ssl/tests/unit/test_broken_fips/key3.db
security/manager/ssl/tests/unit/test_broken_fips/secmod.db
security/manager/ssl/tests/unit/xpcshell.ini
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -1222,16 +1222,18 @@ InitializeNSS(const char* dir, bool read
   // "/usr/lib/nss/libnssckbi.so".
   uint32_t flags = NSS_INIT_NOROOTINIT | NSS_INIT_OPTIMIZESPACE;
   if (readOnly) {
     flags |= NSS_INIT_READONLY;
   }
   if (!loadPKCS11Modules) {
     flags |= NSS_INIT_NOMODDB;
   }
+  MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
+          ("InitializeNSS(%s, %d, %d)", dir, readOnly, loadPKCS11Modules));
   return ::NSS_Initialize(dir, "", "", SECMOD_DB, flags);
 }
 
 void
 DisableMD5()
 {
   NSS_SetAlgorithmPolicy(SEC_OID_MD5,
     0, NSS_USE_ALG_IN_CERT_SIGNATURE | NSS_USE_ALG_IN_CMS_SIGNATURE);
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -1630,16 +1630,195 @@ GetNSSProfilePath(nsAutoCString& aProfil
     return rv;
   }
 
   MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
           ("NSS profile at '%s'\n", aProfilePath.get()));
   return NS_OK;
 }
 
+#ifndef ANDROID
+// Given a profile path, attempt to rename the PKCS#11 module DB to
+// "<original name>.fips". In the case of a catastrophic failure (e.g. out of
+// memory), returns a failing nsresult. If execution could conceivably proceed,
+// returns NS_OK even if renaming the file didn't work. This simplifies the
+// logic of the calling code.
+static nsresult
+AttemptToRenamePKCS11ModuleDB(const nsACString& profilePath)
+{
+  // profilePath may come from the environment variable
+  // MOZPSM_NSSDBDIR_OVERRIDE. If so, the user's NSS DBs are most likely not in
+  // their profile directory and we shouldn't mess with them.
+  const char* dbDirOverride = getenv("MOZPSM_NSSDBDIR_OVERRIDE");
+  if (dbDirOverride && strlen(dbDirOverride) > 0) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("MOZPSM_NSSDBDIR_OVERRIDE set - not renaming PKCS#11 module DB"));
+    return NS_OK;
+  }
+  NS_NAMED_LITERAL_CSTRING(moduleDBFilename, "secmod.db");
+  NS_NAMED_LITERAL_CSTRING(destModuleDBFilename, "secmod.db.fips");
+  nsCOMPtr<nsIFile> dbFile = do_CreateInstance("@mozilla.org/file/local;1");
+  if (!dbFile) {
+    return NS_ERROR_FAILURE;
+  }
+  nsresult rv = dbFile->InitWithNativePath(profilePath);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  rv = dbFile->AppendNative(moduleDBFilename);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  // If the PKCS#11 module DB doesn't exist, renaming it won't help.
+  bool exists;
+  rv = dbFile->Exists(&exists);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  // This is strange, but not a catastrophic failure.
+  if (!exists) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("%s doesn't exist?", moduleDBFilename.get()));
+    return NS_OK;
+  }
+  nsCOMPtr<nsIFile> destDBFile = do_CreateInstance("@mozilla.org/file/local;1");
+  if (!destDBFile) {
+    return NS_ERROR_FAILURE;
+  }
+  rv = destDBFile->InitWithNativePath(profilePath);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  rv = destDBFile->AppendNative(destModuleDBFilename);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  // If the destination exists, presumably we've already tried this. Doing it
+  // again won't help.
+  rv = destDBFile->Exists(&exists);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  // Unfortunate, but not a catastrophic failure.
+  if (exists) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("%s already exists - not overwriting",
+             destModuleDBFilename.get()));
+    return NS_OK;
+  }
+  // Now do the actual move.
+  nsCOMPtr<nsIFile> profileDir = do_CreateInstance("@mozilla.org/file/local;1");
+  if (!profileDir) {
+    return NS_ERROR_FAILURE;
+  }
+  rv = profileDir->InitWithNativePath(profilePath);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  // This may fail on, e.g., a read-only file system. This would be unfortunate,
+  // but again it isn't catastropic and we would want to fall back to
+  // initializing NSS in no-DB mode.
+  Unused << dbFile->MoveToNative(profileDir, destModuleDBFilename);
+  return NS_OK;
+}
+#endif // ifndef ANDROID
+
+// Given a profile directory, attempt to initialize NSS. If nocertdb is true,
+// (or if we don't have a profile directory) simply initialize NSS in no DB mode
+// and return. Otherwise, first attempt to initialize in read/write mode, and
+// then read-only mode if that fails. If both attempts fail, we may be failing
+// to initialize an NSS DB collection that has FIPS mode enabled. Attempt to
+// ascertain if this is the case, and if so, rename the offending PKCS#11 module
+// DB so we can (hopefully) initialize NSS in read-write mode. Again attempt
+// read-only mode if that fails. Finally, fall back to no DB mode. On Android
+// we can skip the FIPS workaround since it was never possible to enable FIPS
+// there anyway.
+static nsresult
+InitializeNSSWithFallbacks(const nsACString& profilePath, bool nocertdb,
+                           bool safeMode)
+{
+  if (nocertdb || profilePath.IsEmpty()) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("nocertdb mode or empty profile path -> NSS_NoDB_Init"));
+    SECStatus srv = NSS_NoDB_Init(nullptr);
+    return srv == SECSuccess ? NS_OK : NS_ERROR_FAILURE;
+  }
+
+  const char* profilePathCStr = PromiseFlatCString(profilePath).get();
+  // Try read/write mode. If we're in safeMode, we won't load PKCS#11 modules.
+#ifndef ANDROID
+  PRErrorCode savedPRErrorCode1;
+#endif // ifndef ANDROID
+  SECStatus srv = ::mozilla::psm::InitializeNSS(profilePathCStr, false,
+                                                !safeMode);
+  if (srv == SECSuccess) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("initialized NSS in r/w mode"));
+    return NS_OK;
+  }
+#ifndef ANDROID
+  savedPRErrorCode1 = PR_GetError();
+  PRErrorCode savedPRErrorCode2;
+#endif // ifndef ANDROID
+  // That failed. Try read-only mode.
+  srv = ::mozilla::psm::InitializeNSS(profilePathCStr, true, !safeMode);
+  if (srv == SECSuccess) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("initialized NSS in r-o mode"));
+    return NS_OK;
+  }
+#ifndef ANDROID
+  savedPRErrorCode2 = PR_GetError();
+#endif // ifndef ANDROID
+
+#ifndef ANDROID
+  // That failed as well. Maybe we're trying to load a PKCS#11 module DB that is
+  // in FIPS mode, but we don't support FIPS? Test load NSS without PKCS#11
+  // modules. If that succeeds, that's probably what's going on.
+  if (!safeMode && (savedPRErrorCode1 == SEC_ERROR_LEGACY_DATABASE ||
+                    savedPRErrorCode2 == SEC_ERROR_LEGACY_DATABASE)) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("attempting no-module db init"));
+    // It would make sense to initialize NSS in read-only mode here since this
+    // is just a test to see if the PKCS#11 module DB being in FIPS mode is the
+    // problem, but for some reason the combination of read-only and no-moddb
+    // flags causes NSS initialization to fail, so unfortunately we have to use
+    // read-write mode.
+    srv = ::mozilla::psm::InitializeNSS(profilePathCStr, false, false);
+    if (srv == SECSuccess) {
+      MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("FIPS may be the problem"));
+      // Unload NSS so we can attempt to fix this situation for the user.
+      srv = NSS_Shutdown();
+      if (srv != SECSuccess) {
+        return NS_ERROR_FAILURE;
+      }
+      MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("trying to rename module db"));
+      // If this fails non-catastrophically, we'll attempt to initialize NSS
+      // again in r/w then r-o mode (both of which will fail), and then we'll
+      // fall back to NSS_NoDB_Init, which is the behavior we want.
+      nsresult rv = AttemptToRenamePKCS11ModuleDB(profilePath);
+      if (NS_FAILED(rv)) {
+        return rv;
+      }
+      srv = ::mozilla::psm::InitializeNSS(profilePathCStr, false, true);
+      if (srv == SECSuccess) {
+        MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("initialized in r/w mode"));
+        return NS_OK;
+      }
+      srv = ::mozilla::psm::InitializeNSS(profilePathCStr, true, true);
+      if (srv == SECSuccess) {
+        MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("initialized in r-o mode"));
+        return NS_OK;
+      }
+    }
+  }
+#endif
+
+  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("last-resort NSS_NoDB_Init"));
+  srv = NSS_NoDB_Init(nullptr);
+  return srv == SECSuccess ? NS_OK : NS_ERROR_FAILURE;
+}
+
 nsresult
 nsNSSComponent::InitializeNSS()
 {
   MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsNSSComponent::InitializeNSS\n"));
 
   static_assert(nsINSSErrorsService::NSS_SEC_ERROR_BASE == SEC_ERROR_BASE &&
                 nsINSSErrorsService::NSS_SEC_ERROR_LIMIT == SEC_ERROR_LIMIT &&
                 nsINSSErrorsService::NSS_SSL_ERROR_BASE == SSL_ERROR_BASE &&
@@ -1659,64 +1838,34 @@ nsNSSComponent::InitializeNSS()
   ConfigureInternalPKCS11Token();
 
   nsAutoCString profileStr;
   nsresult rv = GetNSSProfilePath(profileStr);
   if (NS_FAILED(rv)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  SECStatus init_rv = SECFailure;
   bool nocertdb = Preferences::GetBool("security.nocertdb", false);
   bool inSafeMode = true;
   nsCOMPtr<nsIXULRuntime> runtime(do_GetService("@mozilla.org/xre/runtime;1"));
   // There might not be an nsIXULRuntime in embedded situations. This will
   // default to assuming we are in safe mode (as a result, no external PKCS11
   // modules will be loaded).
   if (runtime) {
     rv = runtime->GetInSafeMode(&inSafeMode);
     if (NS_FAILED(rv)) {
       return rv;
     }
   }
   MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("inSafeMode: %u\n", inSafeMode));
 
-  PRErrorCode savedPRErrorCode1 = 0;
-  PRErrorCode savedPRErrorCode2 = 0;
-
-  if (!nocertdb && !profileStr.IsEmpty()) {
-    // First try to initialize the NSS DB in read/write mode.
-    // Only load PKCS11 modules if we're not in safe mode.
-    init_rv = ::mozilla::psm::InitializeNSS(profileStr.get(), false, !inSafeMode);
-    // If that fails, attempt read-only mode.
-    if (init_rv != SECSuccess) {
-      savedPRErrorCode1 = PR_GetError();
-      MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("could not init NSS r/w in %s\n", profileStr.get()));
-      init_rv = ::mozilla::psm::InitializeNSS(profileStr.get(), true, !inSafeMode);
-    }
-    if (init_rv != SECSuccess) {
-      savedPRErrorCode2 = PR_GetError();
-      MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("could not init in r/o either\n"));
-    }
-  }
-  // If we haven't succeeded in initializing the DB in our profile
-  // directory or we don't have a profile at all, or the "security.nocertdb"
-  // pref has been set to "true", attempt to initialize with no DB.
-  if (nocertdb || init_rv != SECSuccess) {
-    init_rv = NSS_NoDB_Init(nullptr);
-    if (init_rv != SECSuccess) {
-      PRErrorCode savedPRErrorCode3 = PR_GetError();
-      MOZ_CRASH_UNSAFE_PRINTF("NSS initialization failed PRErrorCodes %d %d %d",
-                              savedPRErrorCode1, savedPRErrorCode2,
-                              savedPRErrorCode3);
-    }
-  }
-  if (init_rv != SECSuccess) {
-    MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("could not initialize NSS - panicking\n"));
-    return NS_ERROR_NOT_AVAILABLE;
+  rv = InitializeNSSWithFallbacks(profileStr, nocertdb, inSafeMode);
+  if (NS_FAILED(rv)) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("failed to initialize NSS"));
+    return rv;
   }
 
   // ensure we have an initial value for the content signer root
   mContentSigningRootHash =
     Preferences::GetString("security.content.signature.root_hash");
 
   PK11_SetPasswordFunc(PK11PasswordPrompt);
 
copy from security/manager/ssl/tests/unit/test_sdr_preexisting.js
copy to security/manager/ssl/tests/unit/test_broken_fips.js
--- a/security/manager/ssl/tests/unit/test_sdr_preexisting.js
+++ b/security/manager/ssl/tests/unit/test_broken_fips.js
@@ -1,214 +1,41 @@
 // -*- 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.)
+// Tests that if Firefox attempts and fails to load a PKCS#11 module DB that was
+// in FIPS mode, Firefox can still make use of keys in the key database.
+// secomd.db can be created via `certutil -N -d <dir>`. Putting it in FIPS mode
+// involves running `modutil -fips true -dbdir <dir>`. key3.db is from
+// test_sdr_preexisting/key3.db.
 
 function run_test() {
-  const isAndroid = AppConstants.platform == "android";
-  const keyDBName = isAndroid ? "key4.db" : "key3.db";
   let profile = do_get_profile();
-  let keyDBFile = do_get_file(`test_sdr_preexisting/${keyDBName}`);
+
+  let keyDBName = "key3.db";
+  let keyDBFile = do_get_file(`test_broken_fips/${keyDBName}`);
   keyDBFile.copyTo(profile, keyDBName);
 
+  let secmodDBName = "secmod.db";
+  let secmodDBFile = do_get_file(`test_broken_fips/${secmodDBName}`);
+  secmodDBFile.copyTo(profile, secmodDBName);
+
+  let moduleDB = Cc["@mozilla.org/security/pkcs11moduledb;1"]
+                   .getService(Ci.nsIPKCS11ModuleDB);
+  ok(!moduleDB.isFIPSEnabled, "FIPS should not be enabled");
+
   let sdr = Cc["@mozilla.org/security/sdr;1"]
               .getService(Ci.nsISecretDecoderRing);
 
-  let testcases = [
-    // a full padding block
-    { ciphertext: "MDoEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECGeDHwVfyFqzBBAYvqMq/kDMsrARVNdC1C8d",
-      plaintext: "password" },
-    // 7 bytes of padding
-    { ciphertext: "MDIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECCAzLDVmYG2/BAh3IoIsMmT8dQ==",
-      plaintext: "a" },
-    // 6 bytes of padding
-    { ciphertext: "MDIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECPN8zlZzn8FdBAiu2acpT8UHsg==",
-      plaintext: "bb" },
-    // 1 byte of padding
-    { ciphertext: "MDIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECD5px1eMKkJQBAgUPp35GlrDvQ==",
-      plaintext: "!seven!" },
-    // 2 bytes of padding
-    { ciphertext: "MDIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECMh0hLtKDyUdBAixw9UZsMt+vA==",
-      plaintext: "sixsix" },
-    // long plaintext requiring more than two blocks
-    { ciphertext: "MFoEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECDRX1qi+/FX1BDATFIcIneQjvBuq3wdFxzllJt2VtUD69ACdOKAXH3eA87oHDvuHqOeCDwRy4UzoG5s=",
-      plaintext: "thisismuchlongerandsotakesupmultipleblocks" },
-    // this differs from the previous ciphertext by one bit and demonstrates
-    // that this implementation does not enforce message integrity
-    { ciphertext: "MFoEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECDRX1qi+/FX1BDAbFIcIneQjvBuq3wdFxzllJt2VtUD69ACdOKAXH3eA87oHDvuHqOeCDwRy4UzoG5s=",
-      plaintext: "nnLbuwLRkhlongerandsotakesupmultipleblocks" },
-  ];
+  const encrypted = "MDoEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECGeDHwVfyFqzBBAYvqMq/kDMsrARVNdC1C8d";
+  const expectedResult = "password";
+  let decrypted = sdr.decryptString(encrypted);
+  equal(decrypted, expectedResult,
+        "decrypted ciphertext should match expected plaintext");
 
-  for (let testcase of testcases) {
-    let decrypted = sdr.decryptString(testcase.ciphertext);
-    equal(decrypted, testcase.plaintext,
-          "decrypted ciphertext should match expected plaintext");
-  }
+  let secmodDBFileFIPS = do_get_profile();
+  secmodDBFileFIPS.append(`${secmodDBName}.fips`);
+  ok(secmodDBFileFIPS.exists(), "backed-up PKCS#11 module db should now exist");
 }
copy from security/manager/ssl/tests/unit/test_sdr_preexisting/key3.db
copy to security/manager/ssl/tests/unit/test_broken_fips/key3.db
new file mode 100644
index 0000000000000000000000000000000000000000..c9245b7482960389721c2e49dcb8bfc4c35deaa5
GIT binary patch
literal 16384
zc%1Fp!Aiq07zgmLos1!Fdhs6d&|{hRo+iQ=%Aj66joPLqtV>6df;$Mlgb(3)kKh~l
z0v>#hF}J3$c@Qt+RQ^8*`SK;rSH5$oc^nauOSJbwbhHZTfXE>`s8t7UJvXYk+Omfp
z<^3C3Zascn%h^R$000000000000000004mB!==~5dFQD!@7%Pr_OAa1000000O0Ry
z5_9&Q?T5-|^=xw|S7FHd(r_iCoL%&XAsY^QAv^7M*=3QGIbR)mjtiNJED<X3G>=Oq
z%*>xeGRnB}wa$IRwb6blg{i{Ptt!S`j#4qz9*em$L&Z~Z7kC~U^Vw#xx@=M;>nP2m
zOb4u-CXwM)A+KKaK74L-gY8RfN+g^3dR_gT>sX1Y5rtgu*k|Mqjb-pztG?<4dij05
Q<clQ$00000008`V?_OoI#sB~S
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -1,15 +1,16 @@
 [DEFAULT]
 head = head_psm.js
 tags = psm
 support-files =
   bad_certs/**
   ocsp_certs/**
   test_baseline_requirements/**
+  test_broken_fips/**
   test_cert_eku/**
   test_cert_embedded_null/**
   test_cert_isBuiltInRoot_reload/**
   test_cert_keyUsage/**
   test_cert_sha1/**
   test_cert_signatures/**
   test_cert_trust/**
   test_cert_version/**
@@ -30,16 +31,21 @@ support-files =
   test_signed_apps/**
   test_signed_dir/**
   test_startcom_wosign/**
   test_validity/**
   tlsserver/**
 
 [test_add_preexisting_cert.js]
 [test_baseline_requirements_subject_common_name.js]
+[test_broken_fips.js]
+# FIPS has never been a thing on Android, so the workaround doesn't
+# exist on that platform.
+# FIPS still works on Linux, so this test doesn't make any sense there.
+skip-if = toolkit == 'android' || os == 'linux'
 [test_cert_blocklist.js]
 tags = addons psm blocklist
 [test_cert_chains.js]
 run-sequentially = hardcoded ports
 [test_cert_dbKey.js]
 [test_cert_eku.js]
 [test_cert_embedded_null.js]
 [test_cert_keyUsage.js]