bug 401240 - part 2/2 - reimplement PKCS#12 import/export without goto r?fkiefer draft
authorDavid Keeler <dkeeler@mozilla.com>
Fri, 11 May 2018 11:37:50 -0700
changeset 795835 92ee9bb7c64c7d7f42726c65ddcdb691de190526
parent 795834 d9e743fd05bcfb41cf7950c164ce5a2c6293aabb
push id110093
push userbmo:dkeeler@mozilla.com
push dateWed, 16 May 2018 17:15:04 +0000
reviewersfkiefer
bugs401240
milestone62.0a1
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]