bug 401240 - part 2/2 - reimplement PKCS#12 import/export without goto r=fkiefer
authorDavid Keeler <dkeeler@mozilla.com>
Fri, 11 May 2018 11:37:50 -0700
changeset 418557 da8d4cad05b3
parent 418556 7a2224a146ec
child 418558 278ac3ea0ce5
push id34006
push usercsabou@mozilla.com
push date2018-05-17 09:45 +0000
treeherdermozilla-central@410d3f74efb5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfkiefer
bugs401240
milestone62.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 401240 - part 2/2 - reimplement PKCS#12 import/export without goto r=fkiefer MozReview-Commit-ID: JUMmTPrEYND
security/manager/pki/resources/content/certManager.js
security/manager/ssl/ScopedNSSTypes.h
security/manager/ssl/nsNSSComponent.cpp
security/manager/ssl/nsNSSHelper.h
security/manager/ssl/nsPKCS12Blob.cpp
security/manager/ssl/nsPKCS12Blob.h
security/manager/ssl/tests/unit/test_certDB_export_pkcs12.js
security/manager/ssl/tests/unit/test_certDB_export_pkcs12_with_master_password.js
security/manager/ssl/tests/unit/xpcshell.ini
--- a/security/manager/pki/resources/content/certManager.js
+++ b/security/manager/pki/resources/content/certManager.js
@@ -268,16 +268,17 @@ function backupCerts() {
   var bundle = document.getElementById("pippki_bundle");
   var fp = Cc[nsFilePicker].createInstance(nsIFilePicker);
   fp.init(window,
           bundle.getString("chooseP12BackupFileDialog"),
           nsIFilePicker.modeSave);
   fp.appendFilter(bundle.getString("file_browse_PKCS12_spec"),
                   "*.p12");
   fp.appendFilters(nsIFilePicker.filterAll);
+  fp.defaultExtension = "p12";
   fp.open(rv => {
     if (rv == nsIFilePicker.returnOK || rv == nsIFilePicker.returnReplace) {
       certdb.exportPKCS12File(fp.file, selected_certs.length, selected_certs);
     }
   });
 }
 
 function backupAllCerts() {
--- a/security/manager/ssl/ScopedNSSTypes.h
+++ b/security/manager/ssl/ScopedNSSTypes.h
@@ -351,11 +351,18 @@ MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(Un
 
 MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniqueSGNDigestInfo,
                                       SGNDigestInfo,
                                       SGN_DestroyDigestInfo)
 
 MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniqueVFYContext,
                                       VFYContext,
                                       internal::VFY_DestroyContext_true)
+
+MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniqueSEC_PKCS12DecoderContext,
+                                      SEC_PKCS12DecoderContext,
+                                      SEC_PKCS12DecoderFinish)
+MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniqueSEC_PKCS12ExportContext,
+                                      SEC_PKCS12ExportContext,
+                                      SEC_PKCS12DestroyExportContext)
 } // namespace mozilla
 
 #endif // ScopedNSSTypes_h
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -2640,16 +2640,31 @@ setPassword(PK11SlotInfo* slot, nsIInter
     if (canceled) {
       return NS_ERROR_NOT_AVAILABLE;
     }
   }
 
   return NS_OK;
 }
 
+// NSS will call this during PKCS12 export to potentially switch the endianness
+// of the characters of `inBuf` to big (network) endian. Since we already did
+// that in nsPKCS12Blob::stringToBigEndianBytes, we just perform a memcpy here.
+extern "C" {
+PRBool
+pkcs12StringEndiannessConversion(PRBool, unsigned char* inBuf,
+                                 unsigned int inBufLen, unsigned char* outBuf,
+                                 unsigned int, unsigned int* outBufLen, PRBool)
+{
+  *outBufLen = inBufLen;
+  memcpy(outBuf, inBuf, inBufLen);
+  return true;
+}
+}
+
 namespace mozilla {
 namespace psm {
 
 nsresult
 InitializeCipherSuite()
 {
   MOZ_ASSERT(NS_IsMainThread(),
              "InitializeCipherSuite() can only be accessed on the main thread");
@@ -2675,17 +2690,17 @@ InitializeCipherSuite()
   // Enable ciphers for PKCS#12
   SEC_PKCS12EnableCipher(PKCS12_RC4_40, 1);
   SEC_PKCS12EnableCipher(PKCS12_RC4_128, 1);
   SEC_PKCS12EnableCipher(PKCS12_RC2_CBC_40, 1);
   SEC_PKCS12EnableCipher(PKCS12_RC2_CBC_128, 1);
   SEC_PKCS12EnableCipher(PKCS12_DES_56, 1);
   SEC_PKCS12EnableCipher(PKCS12_DES_EDE3_168, 1);
   SEC_PKCS12SetPreferredCipher(PKCS12_DES_EDE3_168, 1);
-  PORT_SetUCS2_ASCIIConversionFunction(pip_ucs2_ascii_conversion_fn);
+  PORT_SetUCS2_ASCIIConversionFunction(pkcs12StringEndiannessConversion);
 
   // PSM enforces a minimum RSA key size of 1024 bits, which is overridable.
   // NSS has its own minimum, which is not overridable (the default is 1023
   // bits). This sets the NSS minimum to 512 bits so users can still connect to
   // devices like wifi routers with woefully small keys (they would have to add
   // an override to do so, but they already do for such devices).
   NSS_OptionSet(NSS_RSA_MIN_KEY_SIZE, 512);
 
--- a/security/manager/ssl/nsNSSHelper.h
+++ b/security/manager/ssl/nsNSSHelper.h
@@ -1,56 +1,35 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
 
-#ifndef NSS_HELPER_
-#define NSS_HELPER_
+#ifndef nsNSSHelper_h
+#define nsNSSHelper_h
 
 #include "nsIInterfaceRequestor.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "pk11func.h"
 
-//
-// Implementation of an nsIInterfaceRequestor for use
-// as context for NSS calls
-//
+// Implementation of an nsIInterfaceRequestor for use as context for NSS calls.
 class PipUIContext : public nsIInterfaceRequestor
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIINTERFACEREQUESTOR
 
   PipUIContext();
 
 protected:
   virtual ~PipUIContext();
 };
 
-//
-// Function to get the implementor for a certain set of NSS
-// specific dialogs.
-//
-
+// Function to get the implementor for a certain set of NSS specific dialogs.
 nsresult
 getNSSDialogs(void **_result, REFNSIID aIID, const char *contract);
 
-extern "C" {
-// a "fake" unicode conversion function
-PRBool
-pip_ucs2_ascii_conversion_fn(PRBool toUnicode,
-                             unsigned char *inBuf,
-                             unsigned int inBufLen,
-                             unsigned char *outBuf,
-                             unsigned int maxOutBufLen,
-                             unsigned int *outBufLen,
-                             PRBool swapBytes);
-}
-
-//
 // A function that sets the password on an unitialized slot.
-//
 nsresult
 setPassword(PK11SlotInfo* slot, nsIInterfaceRequestor* ctx);
 
-#endif
+#endif // nsNSSHelper_h
--- a/security/manager/ssl/nsPKCS12Blob.cpp
+++ b/security/manager/ssl/nsPKCS12Blob.cpp
@@ -21,424 +21,359 @@
 #include "nsThreadUtils.h"
 #include "p12plcy.h"
 #include "pkix/pkixtypes.h"
 #include "secerr.h"
 
 using namespace mozilla;
 extern LazyLogModule gPIPNSSLog;
 
-#define PIP_PKCS12_TMPFILENAME NS_LITERAL_CSTRING(".pip_p12tmp")
 #define PIP_PKCS12_BUFFER_SIZE 2048
-#define PIP_PKCS12_USER_CANCELED 3
 #define PIP_PKCS12_NOSMARTCARD_EXPORT 4
 #define PIP_PKCS12_RESTORE_FAILED 5
 #define PIP_PKCS12_BACKUP_FAILED 6
 #define PIP_PKCS12_NSS_ERROR 7
 
-// constructor
 nsPKCS12Blob::nsPKCS12Blob()
-  : mCertArray(nullptr)
-  , mTmpFile(nullptr)
+  : mUIContext(new PipUIContext())
 {
-  mUIContext = new PipUIContext();
 }
 
-// nsPKCS12Blob::ImportFromFile
-//
 // Given a file handle, read a PKCS#12 blob from that file, decode it, and
 // import the results into the internal database.
 nsresult
 nsPKCS12Blob::ImportFromFile(nsIFile* file)
 {
-  nsresult rv = NS_OK;
-
+  nsresult rv;
   RetryReason wantRetry;
-
   do {
-    rv = ImportFromFileHelper(file, im_standard_prompt, wantRetry);
+    rv = ImportFromFileHelper(file, ImportMode::StandardPrompt, wantRetry);
 
-    if (NS_SUCCEEDED(rv) && wantRetry == rr_auto_retry_empty_password_flavors) {
-      rv = ImportFromFileHelper(file, im_try_zero_length_secitem, wantRetry);
+    if (NS_SUCCEEDED(rv) && wantRetry == RetryReason::AutoRetryEmptyPassword) {
+      rv = ImportFromFileHelper(file, ImportMode::TryZeroLengthSecitem,
+                                wantRetry);
     }
-  } while (NS_SUCCEEDED(rv) && (wantRetry != rr_do_not_retry));
+  } while (NS_SUCCEEDED(rv) && (wantRetry != RetryReason::DoNotRetry));
 
   return rv;
 }
 
-nsresult
-nsPKCS12Blob::ImportFromFileHelper(nsIFile* file,
-                                   nsPKCS12Blob::ImportMode aImportMode,
-                                   nsPKCS12Blob::RetryReason& aWantRetry)
+void
+nsPKCS12Blob::handleImportError(PRErrorCode nssError, RetryReason& retryReason,
+                                uint32_t passwordLengthInBytes)
 {
-  nsresult rv = NS_OK;
-  SECStatus srv = SECSuccess;
-  SEC_PKCS12DecoderContext* dcx = nullptr;
-  SECItem unicodePw = { siBuffer, nullptr, 0 };
+  if (nssError == SEC_ERROR_BAD_PASSWORD) {
+    // If the password is 2 bytes, it only consists of the wide character null
+    // terminator. In this case we want to retry with a zero-length password.
+    if (passwordLengthInBytes == 2) {
+      retryReason = nsPKCS12Blob::RetryReason::AutoRetryEmptyPassword;
+    } else {
+      retryReason = RetryReason::BadPassword;
+      handleError(PIP_PKCS12_NSS_ERROR, nssError);
+    }
+  } else {
+    handleError(PIP_PKCS12_NSS_ERROR, nssError);
+  }
+}
 
-  aWantRetry = rr_do_not_retry;
+// Returns a failing nsresult if some XPCOM operation failed, and NS_OK
+// otherwise. Returns by reference whether or not we want to retry the operation
+// immediately.
+nsresult
+nsPKCS12Blob::ImportFromFileHelper(nsIFile* file, ImportMode aImportMode,
+                                   RetryReason& aWantRetry)
+{
+  aWantRetry = RetryReason::DoNotRetry;
 
   UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
   if (!slot) {
-    srv = SECFailure;
-    goto finish;
+    return NS_ERROR_FAILURE;
   }
 
-  if (aImportMode == im_try_zero_length_secitem) {
-    unicodePw.len = 0;
+  uint32_t passwordBufferLength;
+  UniquePtr<uint8_t[]> passwordBuffer;
+  if (aImportMode == ImportMode::TryZeroLengthSecitem) {
+    passwordBufferLength = 0;
+    passwordBuffer = nullptr;
   } else {
     // get file password (unicode)
-    rv = getPKCS12FilePassword(&unicodePw);
-    if (NS_FAILED(rv))
-      goto finish;
-    if (!unicodePw.data) {
-      handleError(PIP_PKCS12_USER_CANCELED);
+    nsresult rv = getPKCS12FilePassword(passwordBufferLength, passwordBuffer);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+    if (!passwordBuffer) {
       return NS_OK;
     }
   }
 
   // initialize the decoder
-  dcx = SEC_PKCS12DecoderStart(&unicodePw,
-                               slot.get(),
-                               nullptr,
-                               nullptr,
-                               nullptr,
-                               nullptr,
-                               nullptr,
-                               nullptr);
+  SECItem unicodePw = { siBuffer, passwordBuffer.get(), passwordBufferLength };
+  UniqueSEC_PKCS12DecoderContext dcx(
+    SEC_PKCS12DecoderStart(&unicodePw, slot.get(), nullptr, nullptr, nullptr,
+                           nullptr, nullptr, nullptr));
   if (!dcx) {
-    srv = SECFailure;
-    goto finish;
+    return NS_ERROR_FAILURE;
   }
   // read input file and feed it to the decoder
-  rv = inputToDecoder(dcx, file);
+  PRErrorCode nssError;
+  nsresult rv = inputToDecoder(dcx, file, nssError);
   if (NS_FAILED(rv)) {
-    if (NS_ERROR_ABORT == rv) {
-      // inputToDecoder indicated a NSS error
-      srv = SECFailure;
-    }
-    goto finish;
+    return rv;
+  }
+  if (nssError != 0) {
+    handleImportError(nssError, aWantRetry, unicodePw.len);
+    return NS_OK;
   }
   // verify the blob
-  srv = SEC_PKCS12DecoderVerify(dcx);
-  if (srv)
-    goto finish;
+  SECStatus srv = SEC_PKCS12DecoderVerify(dcx.get());
+  if (srv != SECSuccess) {
+    handleImportError(PR_GetError(), aWantRetry, unicodePw.len);
+    return NS_OK;
+  }
   // validate bags
-  srv = SEC_PKCS12DecoderValidateBags(dcx, nickname_collision);
-  if (srv)
-    goto finish;
-  // import cert and key
-  srv = SEC_PKCS12DecoderImportBags(dcx);
-  if (srv)
-    goto finish;
-  // Later - check to see if this should become default email cert
-finish:
-  // If srv != SECSuccess, NSS probably set a specific error code.
-  // We should use that error code instead of inventing a new one
-  // for every error possible.
+  srv = SEC_PKCS12DecoderValidateBags(dcx.get(), nicknameCollision);
   if (srv != SECSuccess) {
-    if (SEC_ERROR_BAD_PASSWORD == PORT_GetError()) {
-      if (unicodePw.len == sizeof(char16_t)) {
-        // no password chars available,
-        // unicodeToItem allocated space for the trailing zero character only.
-        aWantRetry = rr_auto_retry_empty_password_flavors;
-      } else {
-        aWantRetry = rr_bad_password;
-        handleError(PIP_PKCS12_NSS_ERROR);
-      }
-    } else {
-      handleError(PIP_PKCS12_NSS_ERROR);
-    }
-  } else if (NS_FAILED(rv)) {
-    handleError(PIP_PKCS12_RESTORE_FAILED);
+    handleImportError(PR_GetError(), aWantRetry, unicodePw.len);
+    return NS_OK;
   }
-  // finish the decoder
-  if (dcx)
-    SEC_PKCS12DecoderFinish(dcx);
-  SECITEM_ZfreeItem(&unicodePw, false);
+  // import cert and key
+  srv = SEC_PKCS12DecoderImportBags(dcx.get());
+  if (srv != SECSuccess) {
+    handleImportError(PR_GetError(), aWantRetry, unicodePw.len);
+  }
   return NS_OK;
 }
 
 static bool
-isExtractable(SECKEYPrivateKey* privKey)
+isExtractable(UniqueSECKEYPrivateKey& privKey)
 {
   ScopedAutoSECItem value;
-  SECStatus rv =
-    PK11_ReadRawAttribute(PK11_TypePrivKey, privKey, CKA_EXTRACTABLE, &value);
+  SECStatus rv = PK11_ReadRawAttribute(
+    PK11_TypePrivKey, privKey.get(), CKA_EXTRACTABLE, &value);
   if (rv != SECSuccess) {
     return false;
   }
 
   bool isExtractable = false;
   if ((value.len == 1) && value.data) {
     isExtractable = !!(*(CK_BBOOL*)value.data);
   }
   return isExtractable;
 }
 
-// nsPKCS12Blob::ExportToFile
-//
 // Having already loaded the certs, form them into a blob (loading the keys
 // also), encode the blob, and stuff it into the file.
 nsresult
 nsPKCS12Blob::ExportToFile(nsIFile* file, nsIX509Cert** certs, int numCerts)
 {
-  nsresult rv;
-  SECStatus srv = SECSuccess;
-  SEC_PKCS12ExportContext* ecx = nullptr;
-  SEC_PKCS12SafeInfo *certSafe = nullptr, *keySafe = nullptr;
-  SECItem unicodePw;
-  nsAutoString filePath;
-  int i;
-  nsCOMPtr<nsIFile> localFileRef;
-  // init slot
-
-  bool InformedUserNoSmartcardBackup = false;
-  int numCertsExported = 0;
+  bool informedUserNoSmartcardBackup = false;
 
   // get file password (unicode)
-  unicodePw.data = nullptr;
-  rv = newPKCS12FilePassword(&unicodePw);
-  if (NS_FAILED(rv))
-    goto finish;
-  if (!unicodePw.data) {
-    handleError(PIP_PKCS12_USER_CANCELED);
+  uint32_t passwordBufferLength;
+  UniquePtr<uint8_t[]> passwordBuffer;
+  nsresult rv = newPKCS12FilePassword(passwordBufferLength, passwordBuffer);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (!passwordBuffer) {
     return NS_OK;
   }
-  // what about slotToUse in psm 1.x ???
-  // create export context
-  ecx =
-    SEC_PKCS12CreateExportContext(nullptr, nullptr, nullptr /*slot*/, nullptr);
+  UniqueSEC_PKCS12ExportContext ecx(
+    SEC_PKCS12CreateExportContext(nullptr, nullptr, nullptr, nullptr));
   if (!ecx) {
-    srv = SECFailure;
-    goto finish;
+    handleError(PIP_PKCS12_BACKUP_FAILED, PR_GetError());
+    return NS_ERROR_FAILURE;
   }
   // add password integrity
-  srv = SEC_PKCS12AddPasswordIntegrity(ecx, &unicodePw, SEC_OID_SHA1);
-  if (srv)
-    goto finish;
-  for (i = 0; i < numCerts; i++) {
-    nsNSSCertificate* cert = (nsNSSCertificate*)certs[i];
-    // get it as a CERTCertificate XXX
-    UniqueCERTCertificate nssCert(cert->GetCert());
+  SECItem unicodePw = { siBuffer, passwordBuffer.get(), passwordBufferLength };
+  SECStatus srv = SEC_PKCS12AddPasswordIntegrity(ecx.get(), &unicodePw,
+                                                 SEC_OID_SHA1);
+  if (srv != SECSuccess) {
+    handleError(PIP_PKCS12_BACKUP_FAILED, PR_GetError());
+    return NS_ERROR_FAILURE;
+  }
+  for (int i = 0; i < numCerts; i++) {
+    UniqueCERTCertificate nssCert(certs[i]->GetCert());
     if (!nssCert) {
-      rv = NS_ERROR_FAILURE;
-      goto finish;
+      handleError(PIP_PKCS12_BACKUP_FAILED, PR_GetError());
+      return NS_ERROR_FAILURE;
     }
-    // We can only successfully export certs that are on
-    // internal token.  Most, if not all, smart card vendors
-    // won't let you extract the private key (in any way
-    // shape or form) from the card.  So let's punt if
+    // We can probably only successfully export certs that are on the internal
+    // token. Most, if not all, smart card vendors won't let you extract the
+    // private key (in any way shape or form) from the card. So let's punt if
     // the cert is not in the internal db.
     if (nssCert->slot && !PK11_IsInternal(nssCert->slot)) {
-      // we aren't the internal token, see if the key is extractable.
-      SECKEYPrivateKey* privKey =
-        PK11_FindKeyByDERCert(nssCert->slot, nssCert.get(), this);
-
-      if (privKey) {
-        bool privKeyIsExtractable = isExtractable(privKey);
-
-        SECKEY_DestroyPrivateKey(privKey);
-
-        if (!privKeyIsExtractable) {
-          if (!InformedUserNoSmartcardBackup) {
-            InformedUserNoSmartcardBackup = true;
-            handleError(PIP_PKCS12_NOSMARTCARD_EXPORT);
-          }
-          continue;
+      // We aren't the internal token, see if the key is extractable.
+      UniqueSECKEYPrivateKey privKey(
+        PK11_FindKeyByDERCert(nssCert->slot, nssCert.get(), mUIContext));
+      if (privKey && !isExtractable(privKey)) {
+        if (!informedUserNoSmartcardBackup) {
+          informedUserNoSmartcardBackup = true;
+          handleError(PIP_PKCS12_NOSMARTCARD_EXPORT, PR_GetError());
         }
+        continue;
       }
     }
 
-    // XXX this is why, to verify the slot is the same
-    // PK11_FindObjectForCert(nssCert, nullptr, slot);
-    // create the cert and key safes
-    keySafe = SEC_PKCS12CreateUnencryptedSafe(ecx);
+    // certSafe and keySafe are owned by ecx.
+    SEC_PKCS12SafeInfo* certSafe;
+    SEC_PKCS12SafeInfo* keySafe = SEC_PKCS12CreateUnencryptedSafe(ecx.get());
     if (!SEC_PKCS12IsEncryptionAllowed() || PK11_IsFIPS()) {
       certSafe = keySafe;
     } else {
       certSafe = SEC_PKCS12CreatePasswordPrivSafe(
-        ecx, &unicodePw, SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC);
+        ecx.get(), &unicodePw,
+        SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC);
     }
     if (!certSafe || !keySafe) {
-      rv = NS_ERROR_FAILURE;
-      goto finish;
+      handleError(PIP_PKCS12_BACKUP_FAILED, PR_GetError());
+      return NS_ERROR_FAILURE;
     }
     // add the cert and key to the blob
     srv = SEC_PKCS12AddCertAndKey(
-      ecx,
+      ecx.get(),
       certSafe,
       nullptr,
       nssCert.get(),
-      CERT_GetDefaultCertDB(), // XXX
+      CERT_GetDefaultCertDB(),
       keySafe,
       nullptr,
       true,
       &unicodePw,
       SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC);
-    if (srv)
-      goto finish;
-    // cert was dup'ed, so release it
-    ++numCertsExported;
+    if (srv != SECSuccess) {
+      handleError(PIP_PKCS12_BACKUP_FAILED, PR_GetError());
+      return NS_ERROR_FAILURE;
+    }
   }
 
-  if (!numCertsExported)
-    goto finish;
-
-  // prepare the instance to write to an export file
-  this->mTmpFile = nullptr;
-  file->GetPath(filePath);
-  // Use the nsCOMPtr var localFileRef so that
-  // the reference to the nsIFile we create gets released as soon as
-  // we're out of scope, ie when this function exits.
-  if (filePath.RFind(".p12", true, -1, 4) < 0) {
-    // We're going to add the .p12 extension to the file name just like
-    // Communicator used to.  We create a new nsIFile and initialize
-    // it with the new patch.
-    filePath.AppendLiteral(".p12");
-    localFileRef = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
-    if (NS_FAILED(rv))
-      goto finish;
-    localFileRef->InitWithPath(filePath);
-    file = localFileRef;
+  UniquePRFileDesc prFile;
+  PRFileDesc* rawPRFile;
+  rv = file->OpenNSPRFileDesc(
+    PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0664, &rawPRFile);
+  if (NS_FAILED(rv) || !rawPRFile) {
+    handleError(PIP_PKCS12_BACKUP_FAILED, PR_GetError());
+    return NS_ERROR_FAILURE;
   }
-  rv = file->OpenNSPRFileDesc(
-    PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0664, &mTmpFile);
-  if (NS_FAILED(rv) || !this->mTmpFile)
-    goto finish;
+  prFile.reset(rawPRFile);
   // encode and write
-  srv = SEC_PKCS12Encode(ecx, write_export_file, this);
-  if (srv)
-    goto finish;
-finish:
-  if (NS_FAILED(rv) || srv != SECSuccess) {
-    handleError(PIP_PKCS12_BACKUP_FAILED);
+  srv = SEC_PKCS12Encode(ecx.get(), writeExportFile, prFile.get());
+  if (srv != SECSuccess) {
+    handleError(PIP_PKCS12_BACKUP_FAILED, PR_GetError());
+    return NS_ERROR_FAILURE;
   }
-  if (ecx)
-    SEC_PKCS12DestroyExportContext(ecx);
-  if (this->mTmpFile) {
-    PR_Close(this->mTmpFile);
-    this->mTmpFile = nullptr;
-  }
-  SECITEM_ZfreeItem(&unicodePw, false);
-  return rv;
+  return NS_OK;
 }
 
-///////////////////////////////////////////////////////////////////////
-//
-//  private members
-//
-///////////////////////////////////////////////////////////////////////
-
-// unicodeToItem
-//
-// For the NSS PKCS#12 library, must convert PRUnichars (shorts) to
-// a buffer of octets.  Must handle byte order correctly.
-nsresult
-nsPKCS12Blob::unicodeToItem(const nsString& uni, SECItem* item)
+// For the NSS PKCS#12 library, must convert PRUnichars (shorts) to a buffer of
+// octets. Must handle byte order correctly.
+UniquePtr<uint8_t[]>
+nsPKCS12Blob::stringToBigEndianBytes(const nsString& uni, uint32_t& bytesLength)
 {
-  uint32_t len = uni.Length() + 1; // +1 for the null terminator.
-  if (!SECITEM_AllocItem(nullptr, item, sizeof(char16_t) * len)) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
+  uint32_t wideLength = uni.Length() + 1; // +1 for the null terminator.
+  bytesLength = wideLength * 2;
+  auto buffer = MakeUnique<uint8_t[]>(bytesLength);
 
   // We have to use a cast here because on Windows, uni.get() returns
   // char16ptr_t instead of char16_t*.
   mozilla::NativeEndian::copyAndSwapToBigEndian(
-    item->data, static_cast<const char16_t*>(uni.get()), len);
+    buffer.get(), static_cast<const char16_t*>(uni.get()), wideLength);
 
-  return NS_OK;
+  return buffer;
 }
 
-// newPKCS12FilePassword
-//
 // Launch a dialog requesting the user for a new PKCS#12 file passowrd.
 // Handle user canceled by returning null password (caller must catch).
 nsresult
-nsPKCS12Blob::newPKCS12FilePassword(SECItem* unicodePw)
+nsPKCS12Blob::newPKCS12FilePassword(uint32_t& passwordBufferLength,
+                                    UniquePtr<uint8_t[]>& passwordBuffer)
 {
-  nsresult rv = NS_OK;
   nsAutoString password;
   nsCOMPtr<nsICertificateDialogs> certDialogs;
-  rv = ::getNSSDialogs(getter_AddRefs(certDialogs),
-                       NS_GET_IID(nsICertificateDialogs),
-                       NS_CERTIFICATEDIALOGS_CONTRACTID);
-  if (NS_FAILED(rv))
+  nsresult rv = ::getNSSDialogs(getter_AddRefs(certDialogs),
+                                NS_GET_IID(nsICertificateDialogs),
+                                NS_CERTIFICATEDIALOGS_CONTRACTID);
+  if (NS_FAILED(rv)) {
     return rv;
-  bool pressedOK;
+  }
+  bool pressedOK = false;
   rv = certDialogs->SetPKCS12FilePassword(mUIContext, password, &pressedOK);
-  if (NS_FAILED(rv) || !pressedOK)
+  if (NS_FAILED(rv)) {
     return rv;
-  return unicodeToItem(password, unicodePw);
+  }
+  if (!pressedOK) {
+    return NS_OK;
+  }
+  passwordBuffer = Move(stringToBigEndianBytes(password, passwordBufferLength));
+  return NS_OK;
 }
 
-// getPKCS12FilePassword
-//
 // Launch a dialog requesting the user for the password to a PKCS#12 file.
 // Handle user canceled by returning null password (caller must catch).
 nsresult
-nsPKCS12Blob::getPKCS12FilePassword(SECItem* unicodePw)
+nsPKCS12Blob::getPKCS12FilePassword(uint32_t& passwordBufferLength,
+                                    UniquePtr<uint8_t[]>& passwordBuffer)
 {
-  nsresult rv = NS_OK;
-  nsAutoString password;
   nsCOMPtr<nsICertificateDialogs> certDialogs;
-  rv = ::getNSSDialogs(getter_AddRefs(certDialogs),
-                       NS_GET_IID(nsICertificateDialogs),
-                       NS_CERTIFICATEDIALOGS_CONTRACTID);
-  if (NS_FAILED(rv))
+  nsresult rv = ::getNSSDialogs(getter_AddRefs(certDialogs),
+                                NS_GET_IID(nsICertificateDialogs),
+                                NS_CERTIFICATEDIALOGS_CONTRACTID);
+  if (NS_FAILED(rv)) {
     return rv;
-  bool pressedOK;
+  }
+  nsAutoString password;
+  bool pressedOK = false;
   rv = certDialogs->GetPKCS12FilePassword(mUIContext, password, &pressedOK);
-  if (NS_FAILED(rv) || !pressedOK)
+  if (NS_FAILED(rv)) {
     return rv;
-  return unicodeToItem(password, unicodePw);
+  }
+  if (!pressedOK) {
+    return NS_OK;
+  }
+  passwordBuffer = Move(stringToBigEndianBytes(password, passwordBufferLength));
+  return NS_OK;
 }
 
-// inputToDecoder
-//
 // Given a decoder, read bytes from file and input them to the decoder.
 nsresult
-nsPKCS12Blob::inputToDecoder(SEC_PKCS12DecoderContext* dcx, nsIFile* file)
+nsPKCS12Blob::inputToDecoder(UniqueSEC_PKCS12DecoderContext& dcx, nsIFile* file,
+                             PRErrorCode& nssError)
 {
-  nsresult rv;
-  SECStatus srv;
-  uint32_t amount;
-  char buf[PIP_PKCS12_BUFFER_SIZE];
+  nssError = 0;
 
   nsCOMPtr<nsIInputStream> fileStream;
-  rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), file);
-
+  nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), file);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
+  char buf[PIP_PKCS12_BUFFER_SIZE];
+  uint32_t amount;
   while (true) {
     rv = fileStream->Read(buf, PIP_PKCS12_BUFFER_SIZE, &amount);
     if (NS_FAILED(rv)) {
       return rv;
     }
     // feed the file data into the decoder
-    srv = SEC_PKCS12DecoderUpdate(dcx, (unsigned char*)buf, amount);
-    if (srv) {
-      // don't allow the close call to overwrite our precious error code
-      int pr_err = PORT_GetError();
-      PORT_SetError(pr_err);
-      return NS_ERROR_ABORT;
+    SECStatus srv = SEC_PKCS12DecoderUpdate(
+      dcx.get(), (unsigned char*)buf, amount);
+    if (srv != SECSuccess) {
+      nssError = PR_GetError();
+      return NS_OK;
     }
-    if (amount < PIP_PKCS12_BUFFER_SIZE)
+    if (amount < PIP_PKCS12_BUFFER_SIZE) {
       break;
+    }
   }
   return NS_OK;
 }
 
-// nickname_collision
-// what to do when the nickname collides with one already in the db.
-// TODO: not handled, throw a dialog allowing the nick to be changed?
+// What to do when the nickname collides with one already in the db.
 SECItem*
-nsPKCS12Blob::nickname_collision(SECItem* oldNick, PRBool* cancel, void* wincx)
+nsPKCS12Blob::nicknameCollision(SECItem* oldNick, PRBool* cancel, void* wincx)
 {
   *cancel = false;
   int count = 1;
   nsCString nickname;
   nsAutoString nickFromProp;
   nsresult rv = GetPIPNSSBundleString("P12DefaultNickname", nickFromProp);
   if (NS_FAILED(rv)) {
     return nullptr;
@@ -474,90 +409,66 @@ nsPKCS12Blob::nickname_collision(SECItem
     }
     UniqueCERTCertificate cert(
       CERT_FindCertByNickname(CERT_GetDefaultCertDB(), nickname.get()));
     if (!cert) {
       break;
     }
     count++;
   }
-  SECItem* newNick = new SECItem;
-  if (!newNick)
+  UniqueSECItem newNick(SECITEM_AllocItem(nullptr, nullptr,
+                                          nickname.Length() + 1));
+  if (!newNick) {
     return nullptr;
+  }
+  memcpy(newNick->data, nickname.get(), nickname.Length());
+  newNick->data[nickname.Length()] = 0;
 
-  newNick->type = siAsciiString;
-  newNick->data = (unsigned char*)strdup(nickname.get());
-  newNick->len = strlen((char*)newNick->data);
-  return newNick;
+  return newNick.release();
 }
 
-// write_export_file
 // write bytes to the exported PKCS#12 file
 void
-nsPKCS12Blob::write_export_file(void* arg, const char* buf, unsigned long len)
+nsPKCS12Blob::writeExportFile(void* arg, const char* buf, unsigned long len)
 {
-  nsPKCS12Blob* cx = (nsPKCS12Blob*)arg;
-  PR_Write(cx->mTmpFile, buf, len);
-}
-
-// pip_ucs2_ascii_conversion_fn
-// required to be set by NSS (to do PKCS#12), but since we've already got
-// unicode make this a no-op.
-PRBool
-pip_ucs2_ascii_conversion_fn(PRBool toUnicode,
-                             unsigned char* inBuf,
-                             unsigned int inBufLen,
-                             unsigned char* outBuf,
-                             unsigned int maxOutBufLen,
-                             unsigned int* outBufLen,
-                             PRBool swapBytes)
-{
-  // do a no-op, since I've already got unicode.  Hah!
-  *outBufLen = inBufLen;
-  memcpy(outBuf, inBuf, inBufLen);
-  return true;
+  PRFileDesc* file = static_cast<PRFileDesc*>(arg);
+  MOZ_RELEASE_ASSERT(file);
+  PR_Write(file, buf, len);
 }
 
 void
-nsPKCS12Blob::handleError(int myerr)
+nsPKCS12Blob::handleError(int myerr, PRErrorCode prerr)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!NS_IsMainThread()) {
     return;
   }
 
-  int prerr = PORT_GetError();
   MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("PKCS12: NSS/NSPR error(%d)", prerr));
   MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("PKCS12: I called(%d)", myerr));
 
   const char* msgID = nullptr;
 
   switch (myerr) {
-    case PIP_PKCS12_USER_CANCELED:
-      return; /* Just ignore it for now */
     case PIP_PKCS12_NOSMARTCARD_EXPORT:
       msgID = "PKCS12InfoNoSmartcardBackup";
       break;
     case PIP_PKCS12_RESTORE_FAILED:
       msgID = "PKCS12UnknownErrRestore";
       break;
     case PIP_PKCS12_BACKUP_FAILED:
       msgID = "PKCS12UnknownErrBackup";
       break;
     case PIP_PKCS12_NSS_ERROR:
       switch (prerr) {
-        // The following errors have the potential to be "handled", by asking
-        // the user (via a dialog) whether s/he wishes to continue
         case 0:
           break;
         case SEC_ERROR_PKCS12_CERT_COLLISION:
-          /* pop a dialog saying the cert is already in the database */
-          /* ask to keep going?  what happens if one collision but others ok? */
-          // The following errors cannot be "handled", notify the user (via an
-          // alert) that the operation failed.
+          msgID = "PKCS12DupData";
+          break;
         case SEC_ERROR_BAD_PASSWORD:
           msgID = "PK11BadPassword";
           break;
 
         case SEC_ERROR_BAD_DER:
         case SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE:
         case SEC_ERROR_PKCS12_INVALID_MAC:
           msgID = "PKCS12DecodeErr";
@@ -565,18 +476,19 @@ nsPKCS12Blob::handleError(int myerr)
 
         case SEC_ERROR_PKCS12_DUPLICATE_DATA:
           msgID = "PKCS12DupData";
           break;
       }
       break;
   }
 
-  if (!msgID)
+  if (!msgID) {
     msgID = "PKCS12UnknownErr";
+  }
 
   nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
   if (!wwatch) {
     return;
   }
   nsCOMPtr<nsIPrompt> prompter;
   if (NS_FAILED(wwatch->GetNewPrompter(nullptr, getter_AddRefs(prompter)))) {
     return;
--- a/security/manager/ssl/nsPKCS12Blob.h
+++ b/security/manager/ssl/nsPKCS12Blob.h
@@ -1,81 +1,80 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #ifndef nsPKCS12Blob_h
 #define nsPKCS12Blob_h
 
 #include "nsCOMPtr.h"
 #include "nsIMutableArray.h"
 #include "nsString.h"
 #include "p12.h"
 #include "seccomon.h"
 
 class nsIFile;
 class nsIX509Cert;
 
-//
-// nsPKCS12Blob
-//
 // Class for importing/exporting PKCS#12 blobs
-//
 class nsPKCS12Blob
 {
 public:
   nsPKCS12Blob();
-  virtual ~nsPKCS12Blob() {}
+  ~nsPKCS12Blob() {}
 
   // PKCS#12 Import
   nsresult ImportFromFile(nsIFile* file);
 
   // PKCS#12 Export
   nsresult ExportToFile(nsIFile* file, nsIX509Cert** certs, int numCerts);
 
 private:
-  nsCOMPtr<nsIMutableArray> mCertArray;
   nsCOMPtr<nsIInterfaceRequestor> mUIContext;
 
   // local helper functions
-  nsresult getPKCS12FilePassword(SECItem*);
-  nsresult newPKCS12FilePassword(SECItem*);
-  nsresult inputToDecoder(SEC_PKCS12DecoderContext*, nsIFile*);
-  nsresult unicodeToItem(const nsString& uni, SECItem* item);
-  void handleError(int myerr = 0);
+  nsresult getPKCS12FilePassword(uint32_t& passwordBufferLength,
+                                 UniquePtr<uint8_t[]>& passwordBuffer);
+  nsresult newPKCS12FilePassword(uint32_t& passwordBufferLength,
+                                 UniquePtr<uint8_t[]>& passwordBuffer);
+  nsresult inputToDecoder(UniqueSEC_PKCS12DecoderContext& dcx, nsIFile* file,
+                          PRErrorCode& nssError);
+  UniquePtr<uint8_t[]> stringToBigEndianBytes(const nsString& uni,
+                                              uint32_t& bytesLength);
+  void handleError(int myerr, PRErrorCode prerr);
 
   // RetryReason and ImportMode are used when importing a PKCS12 file.
   // There are two reasons that cause us to retry:
   // - When the password entered by the user is incorrect.
   //   The user will be prompted to try again.
   // - When the user entered a zero length password.
-  //   An empty password should be represented as an empty
-  //   string (a SECItem that contains a single terminating
-  //   null UTF16 character), but some applications use a
-  //   zero length SECItem.
-  //   We try both variations, zero length item and empty string,
-  //   without giving a user prompt when trying the different empty password
-  //   flavors.
-
-  enum RetryReason
+  //   An empty password should be represented as an empty string (a SECItem
+  //   that contains a single terminating null UTF16 character), but some
+  //   applications use a zero length SECItem. We try both variations, zero
+  //   length item and empty string, without giving a user prompt when trying
+  //   the different empty password flavors.
+  enum class RetryReason
   {
-    rr_do_not_retry,
-    rr_bad_password,
-    rr_auto_retry_empty_password_flavors
+    DoNotRetry,
+    BadPassword,
+    AutoRetryEmptyPassword,
   };
-  enum ImportMode
+  enum class ImportMode
   {
-    im_standard_prompt,
-    im_try_zero_length_secitem
+    StandardPrompt,
+    TryZeroLengthSecitem
   };
 
+  void handleImportError(PRErrorCode nssError, RetryReason& retryReason,
+                         uint32_t passwordLengthInBytes);
+
   nsresult ImportFromFileHelper(nsIFile* file,
                                 ImportMode aImportMode,
                                 RetryReason& aWantRetry);
 
-  // NSPR file I/O for export file
-  PRFileDesc* mTmpFile;
-
-  static SECItem* nickname_collision(SECItem*, PRBool*, void*);
-  static void write_export_file(void* arg, const char* buf, unsigned long len);
+  static SECItem* nicknameCollision(SECItem* oldNick, PRBool* cancel,
+                                    void* wincx);
+  static void writeExportFile(void* arg, const char* buf, unsigned long len);
 };
 
 #endif // nsPKCS12Blob_h
copy from security/manager/ssl/tests/unit/test_certDB_import_with_master_password.js
copy to security/manager/ssl/tests/unit/test_certDB_export_pkcs12.js
--- a/security/manager/ssl/tests/unit/test_certDB_import_with_master_password.js
+++ b/security/manager/ssl/tests/unit/test_certDB_export_pkcs12.js
@@ -1,130 +1,115 @@
 // -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 // Any copyright is dedicated to the Public Domain.
 // http://creativecommons.org/publicdomain/zero/1.0/
 "use strict";
 
-// Tests that a CA certificate can still be imported if the user has a master
-// password set.
+// Tests exporting a certificate and key as a PKCS#12 blob and importing it
+// again with a new password set.
 
 do_get_profile();
 
 const gCertDB = Cc["@mozilla.org/security/x509certdb;1"]
                   .getService(Ci.nsIX509CertDB);
 
-const CA_CERT_COMMON_NAME = "importedCA";
+const PKCS12_FILE = "test_certDB_import/cert_from_windows.pfx";
+const CERT_COMMON_NAME = "test_cert_from_windows";
+const TEST_CERT_PASSWORD = "黒い";
+const TEST_OUTPUT_PASSWORD = "other password";
 
-let gCACertImportDialogCount = 0;
+let gPasswordToUse = TEST_CERT_PASSWORD;
 
 // Mock implementation of nsICertificateDialogs.
 const gCertificateDialogs = {
-  confirmDownloadCACert: (ctx, cert, trust) => {
-    gCACertImportDialogCount++;
-    equal(cert.commonName, CA_CERT_COMMON_NAME,
-          "CA cert to import should have the correct CN");
-    trust.value = Ci.nsIX509CertDB.TRUSTED_EMAIL;
+  confirmDownloadCACert: () => {
+    // We don't test anything that calls this method.
+    ok(false, "confirmDownloadCACert() should not have been called");
+  },
+  setPKCS12FilePassword: (ctx, password) => {
+    password.value = gPasswordToUse;
     return true;
   },
-  setPKCS12FilePassword: (ctx, password) => {
-    // This is only relevant to exporting.
-    ok(false, "setPKCS12FilePassword() should not have been called");
-  },
   getPKCS12FilePassword: (ctx, password) => {
-    // We don't test anything that calls this method yet.
-    ok(false, "getPKCS12FilePassword() should not have been called");
+    password.value = gPasswordToUse;
+    return true;
   },
   viewCert: (ctx, cert) => {
     // This shouldn't be called for import methods.
     ok(false, "viewCert() should not have been called");
   },
 
   QueryInterface: ChromeUtils.generateQI([Ci.nsICertificateDialogs])
 };
 
-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;
-  },
+var gPrompt = {
+  clickOk: true,
 
   QueryInterface: ChromeUtils.generateQI([Ci.nsIPrompt]),
 
-  // Again with the arrow function issue.
-  getInterface(iid) {
-    if (iid.equals(Ci.nsIPrompt)) {
-      return this;
-    }
+  // This intentionally does not use arrow function syntax to avoid an issue
+  // where in the context of the arrow function, |this != gPrompt| due to
+  // how objects get wrapped when going across xpcom boundaries.
+  alert(title, text) {
+    ok(false, "Not expecting alert to be called.");
+  },
 
-    throw new Error(Cr.NS_ERROR_NO_INTERFACE);
-  }
+  promptPassword(dialogTitle, text, password, checkMsg, checkValue) {
+    ok(false, "Not expecting a password prompt.");
+    return false;
+  },
 };
 
-function getCertAsByteArray(certPath) {
-  let certFile = do_get_file(certPath, false);
-  let certBytes = readFile(certFile);
-
-  let byteArray = [];
-  for (let i = 0; i < certBytes.length; i++) {
-    byteArray.push(certBytes.charCodeAt(i));
-  }
-
-  return byteArray;
-}
+const gPromptFactory = {
+  QueryInterface: ChromeUtils.generateQI([Ci.nsIPromptFactory]),
+  getPrompt: (aWindow, aIID) => gPrompt,
+};
 
 function findCertByCommonName(commonName) {
   let certEnumerator = gCertDB.getCerts().getEnumerator();
   while (certEnumerator.hasMoreElements()) {
     let cert = certEnumerator.getNext().QueryInterface(Ci.nsIX509Cert);
     if (cert.commonName == commonName) {
       return cert;
     }
   }
   return null;
 }
 
 function run_test() {
   let certificateDialogsCID =
     MockRegistrar.register("@mozilla.org/nsCertificateDialogs;1",
                            gCertificateDialogs);
+  let promptFactoryCID =
+    MockRegistrar.register("@mozilla.org/prompter;1", gPromptFactory);
+
   registerCleanupFunction(() => {
     MockRegistrar.unregister(certificateDialogsCID);
+    MockRegistrar.unregister(promptFactoryCID);
   });
 
-  // Set a master password.
-  let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"]
-                  .getService(Ci.nsIPK11TokenDB);
-  let token = tokenDB.getInternalKeyToken();
-  token.initPassword("password");
-  token.logoutSimple();
-
-  // Sanity check the CA cert is missing.
-  equal(findCertByCommonName(CA_CERT_COMMON_NAME), null,
-        "CA cert should not be in the database before import");
+  // Import the certificate and key so we have something to export.
+  let cert = findCertByCommonName(CERT_COMMON_NAME);
+  equal(cert, null, "cert should not be found before import");
+  let certFile = do_get_file(PKCS12_FILE);
+  ok(certFile, `${PKCS12_FILE} should exist`);
+  gPasswordToUse = TEST_CERT_PASSWORD;
+  gCertDB.importPKCS12File(certFile);
+  cert = findCertByCommonName(CERT_COMMON_NAME);
+  notEqual(cert, null, "cert should be found now");
 
-  // Import and check for success.
-  let caArray = getCertAsByteArray("test_certDB_import/importedCA.pem");
-  gCertDB.importCertificates(caArray, caArray.length, Ci.nsIX509Cert.CA_CERT,
-                             gMockPrompter);
-  equal(gCACertImportDialogCount, 1,
-        "Confirmation dialog for the CA cert should only be shown once");
+  // Export the certificate and key.
+  let output = do_get_tempdir();
+  output.append("output.p12");
+  ok(!output.exists(), "output shouldn't exist before exporting PKCS12 file");
+  gPasswordToUse = TEST_OUTPUT_PASSWORD;
+  gCertDB.exportPKCS12File(output, 1, [cert]);
+  ok(output.exists(), "output should exist after exporting PKCS12 file");
 
-  let caCert = findCertByCommonName(CA_CERT_COMMON_NAME);
-  notEqual(caCert, null, "CA cert should now be found in the database");
-  ok(gCertDB.isCertTrusted(caCert, Ci.nsIX509Cert.CA_CERT,
-                           Ci.nsIX509CertDB.TRUSTED_EMAIL),
-     "CA cert should be trusted for e-mail");
+  // We should be able to import the exported blob again using the new password.
+  gCertDB.importPKCS12File(output);
+  output.remove(false /* not a directory; recursive doesn't apply */);
+
+  // Ideally there would be some way to confirm that this actually did anything.
+  // Unfortunately, since deleting a certificate currently doesn't actually do
+  // anything until the platform is restarted, we can't confirm that we
+  // successfully re-imported the certificate.
 }
copy from security/manager/ssl/tests/unit/test_certDB_import_with_master_password.js
copy to security/manager/ssl/tests/unit/test_certDB_export_pkcs12_with_master_password.js
--- a/security/manager/ssl/tests/unit/test_certDB_import_with_master_password.js
+++ b/security/manager/ssl/tests/unit/test_certDB_export_pkcs12_with_master_password.js
@@ -1,130 +1,138 @@
 // -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 // Any copyright is dedicated to the Public Domain.
 // http://creativecommons.org/publicdomain/zero/1.0/
 "use strict";
 
-// Tests that a CA certificate can still be imported if the user has a master
-// password set.
+// Tests exporting a certificate and key as a PKCS#12 blob if the user has a
+// master password set.
 
 do_get_profile();
 
 const gCertDB = Cc["@mozilla.org/security/x509certdb;1"]
                   .getService(Ci.nsIX509CertDB);
 
-const CA_CERT_COMMON_NAME = "importedCA";
-
-let gCACertImportDialogCount = 0;
+const PKCS12_FILE = "test_certDB_import/cert_from_windows.pfx";
+const CERT_COMMON_NAME = "test_cert_from_windows";
+const TEST_CERT_PASSWORD = "黒い";
 
 // Mock implementation of nsICertificateDialogs.
 const gCertificateDialogs = {
-  confirmDownloadCACert: (ctx, cert, trust) => {
-    gCACertImportDialogCount++;
-    equal(cert.commonName, CA_CERT_COMMON_NAME,
-          "CA cert to import should have the correct CN");
-    trust.value = Ci.nsIX509CertDB.TRUSTED_EMAIL;
+  confirmDownloadCACert: () => {
+    // We don't test anything that calls this method.
+    ok(false, "confirmDownloadCACert() should not have been called");
+  },
+  setPKCS12FilePassword: (ctx, password) => {
+    password.value = TEST_CERT_PASSWORD;
     return true;
   },
-  setPKCS12FilePassword: (ctx, password) => {
-    // This is only relevant to exporting.
-    ok(false, "setPKCS12FilePassword() should not have been called");
-  },
   getPKCS12FilePassword: (ctx, password) => {
-    // We don't test anything that calls this method yet.
-    ok(false, "getPKCS12FilePassword() should not have been called");
+    password.value = TEST_CERT_PASSWORD;
+    return true;
   },
   viewCert: (ctx, cert) => {
     // This shouldn't be called for import methods.
     ok(false, "viewCert() should not have been called");
   },
 
   QueryInterface: ChromeUtils.generateQI([Ci.nsICertificateDialogs])
 };
 
-var gMockPrompter = {
-  passwordToTry: "password",
-  numPrompts: 0,
+var gPrompt = {
+  password: "password",
+  clickOk: true,
+  expectingAlert: false,
+  expectedAlertRegexp: null,
+
+  QueryInterface: ChromeUtils.generateQI([Ci.nsIPrompt]),
 
   // This intentionally does not use arrow function syntax to avoid an issue
-  // where in the context of the arrow function, |this != gMockPrompter| due to
+  // where in the context of the arrow function, |this != gPrompt| due to
   // how objects get wrapped when going across xpcom boundaries.
+  alert(title, text) {
+    info(`alert('${text}')`);
+    ok(this.expectingAlert,
+       "alert() should only be called if we're expecting it");
+    ok(this.expectedAlertRegexp.test(text),
+       "alert text should match expected message");
+  },
+
   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;
+    password.value = this.password;
+    return this.clickOk;
   },
-
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIPrompt]),
-
-  // Again with the arrow function issue.
-  getInterface(iid) {
-    if (iid.equals(Ci.nsIPrompt)) {
-      return this;
-    }
-
-    throw new Error(Cr.NS_ERROR_NO_INTERFACE);
-  }
 };
 
-function getCertAsByteArray(certPath) {
-  let certFile = do_get_file(certPath, false);
-  let certBytes = readFile(certFile);
-
-  let byteArray = [];
-  for (let i = 0; i < certBytes.length; i++) {
-    byteArray.push(certBytes.charCodeAt(i));
-  }
-
-  return byteArray;
-}
+const gPromptFactory = {
+  QueryInterface: ChromeUtils.generateQI([Ci.nsIPromptFactory]),
+  getPrompt: (aWindow, aIID) => gPrompt,
+};
 
 function findCertByCommonName(commonName) {
   let certEnumerator = gCertDB.getCerts().getEnumerator();
   while (certEnumerator.hasMoreElements()) {
     let cert = certEnumerator.getNext().QueryInterface(Ci.nsIX509Cert);
     if (cert.commonName == commonName) {
       return cert;
     }
   }
   return null;
 }
 
 function run_test() {
   let certificateDialogsCID =
     MockRegistrar.register("@mozilla.org/nsCertificateDialogs;1",
                            gCertificateDialogs);
+  let promptFactoryCID =
+    MockRegistrar.register("@mozilla.org/prompter;1", gPromptFactory);
+
   registerCleanupFunction(() => {
     MockRegistrar.unregister(certificateDialogsCID);
+    MockRegistrar.unregister(promptFactoryCID);
   });
 
   // Set a master password.
   let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"]
                   .getService(Ci.nsIPK11TokenDB);
   let token = tokenDB.getInternalKeyToken();
   token.initPassword("password");
   token.logoutSimple();
 
-  // Sanity check the CA cert is missing.
-  equal(findCertByCommonName(CA_CERT_COMMON_NAME), null,
-        "CA cert should not be in the database before import");
+  // Import the certificate and key so we have something to export.
+  let cert = findCertByCommonName(CERT_COMMON_NAME);
+  equal(cert, null, "cert should not be found before import");
+  let certFile = do_get_file(PKCS12_FILE);
+  ok(certFile, `${PKCS12_FILE} should exist`);
+  gCertDB.importPKCS12File(certFile);
+  cert = findCertByCommonName(CERT_COMMON_NAME);
+  notEqual(cert, null, "cert should be found now");
+
+  // Log out so we're prompted for the password.
+  token.logoutSimple();
 
-  // Import and check for success.
-  let caArray = getCertAsByteArray("test_certDB_import/importedCA.pem");
-  gCertDB.importCertificates(caArray, caArray.length, Ci.nsIX509Cert.CA_CERT,
-                             gMockPrompter);
-  equal(gCACertImportDialogCount, 1,
-        "Confirmation dialog for the CA cert should only be shown once");
+  // Export the certificate and key (and don't cancel the password request
+  // dialog).
+  let output = do_get_tempdir();
+  output.append("output.p12");
+  ok(!output.exists(), "output shouldn't exist before exporting PKCS12 file");
+  gCertDB.exportPKCS12File(output, 1, [cert]);
+  ok(output.exists(), "output should exist after exporting PKCS12 file");
+  output.remove(false /* not a directory; recursive doesn't apply */);
+
+  // Log out again so we're prompted for the password.
+  token.logoutSimple();
 
-  let caCert = findCertByCommonName(CA_CERT_COMMON_NAME);
-  notEqual(caCert, null, "CA cert should now be found in the database");
-  ok(gCertDB.isCertTrusted(caCert, Ci.nsIX509Cert.CA_CERT,
-                           Ci.nsIX509CertDB.TRUSTED_EMAIL),
-     "CA cert should be trusted for e-mail");
+  // Attempt to export the certificate and key, but this time cancel the
+  // password request dialog. The export operation should also be canceled.
+  gPrompt.clickOk = false;
+  let output2 = do_get_tempdir();
+  output2.append("output2.p12");
+  ok(!output2.exists(), "output2 shouldn't exist before exporting PKCS12 file");
+  gPrompt.expectingAlert = true;
+  gPrompt.expectedAlertRegexp = /Failed to create the PKCS #12 backup file for unknown reasons\./;
+  throws(() => gCertDB.exportPKCS12File(output, 1, [cert]), /NS_ERROR_FAILURE/);
+  ok(!output2.exists(), "output2 shouldn't exist after failing to export");
 }
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -58,16 +58,18 @@ run-sequentially = hardcoded ports
 [test_cert_overrides_read_only.js]
 run-sequentially = hardcoded ports
 [test_cert_override_bits_mismatches.js]
 run-sequentially = hardcoded ports
 [test_cert_sha1.js]
 [test_cert_signatures.js]
 [test_cert_trust.js]
 [test_cert_version.js]
+[test_certDB_export_pkcs12.js]
+[test_certDB_export_pkcs12_with_master_password.js]
 [test_certDB_import.js]
 [test_certDB_import_pkcs12.js]
 [test_certDB_import_with_master_password.js]
 [test_certviewer_invalid_oids.js]
 skip-if = toolkit == 'android'
 [test_constructX509FromBase64.js]
 [test_content_signing.js]
 [test_ct.js]