Bug 1468224 - remove dialogs from nsIX509CertDB PKCS12File methods. r?keeler draft
authorDipen Patel <bugzilla@pansara.org>
Thu, 02 Aug 2018 20:57:07 -0700
changeset 827679 a9c437c22ba47bf86f0e83000a10e6e6f0fd834a
parent 823567 69d756618c48147cc4fe860501ec45e9a4bead39
push id118561
push userbmo:bugzilla@pansara.org
push dateWed, 08 Aug 2018 17:29:18 +0000
reviewerskeeler
bugs1468224
milestone63.0a1
Bug 1468224 - remove dialogs from nsIX509CertDB PKCS12File methods. r?keeler - enhance nsIX509CertDB.importPKCS12File to accept a password and return error code. - enhance nsIX509CertDB.exportPKCS12File to accept a password and return error code. - remove password and error prompts being invoked in C++ layer to Javascript layer. - update unit tests - add unit test for importing certs with empty string password and no passwords. - remove unused code MozReview-Commit-ID: 23ypAzBarOp
security/manager/pki/resources/content/certManager.js
security/manager/ssl/nsIX509CertDB.idl
security/manager/ssl/nsNSSCertificateDB.cpp
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/test_certDB_import/cert_from_windows_emptypass.pfx
security/manager/ssl/tests/unit/test_certDB_import/cert_from_windows_nopass.pfx
security/manager/ssl/tests/unit/test_certDB_import_pkcs12.js
--- a/security/manager/pki/resources/content/certManager.js
+++ b/security/manager/pki/resources/content/certManager.js
@@ -4,25 +4,31 @@
 /* import-globals-from pippki.js */
 "use strict";
 
 const nsIFilePicker = Ci.nsIFilePicker;
 const nsFilePicker = "@mozilla.org/filepicker;1";
 const nsIX509CertDB = Ci.nsIX509CertDB;
 const nsX509CertDB = "@mozilla.org/security/x509certdb;1";
 const nsIX509Cert = Ci.nsIX509Cert;
+const nsStringBundle = "@mozilla.org/intl/stringbundle;1";
+const nsIStringBundleService = Ci.nsIStringBundleService;
 const nsICertTree = Ci.nsICertTree;
 const nsCertTree = "@mozilla.org/security/nsCertTree;1";
 
 const gCertFileTypes = "*.p7b; *.crt; *.cert; *.cer; *.pem; *.der";
 
 var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm", {});
 
 var key;
 
+var certdialogs = Cc[nsCertificateDialogs].getService(nsICertificateDialogs);
+var strbundle = Cc[nsStringBundle].getService(nsIStringBundleService)
+                  .createBundle("chrome://pipnss/locale/pipnss.properties");
+
 /**
  * List of certs currently selected in the active tab.
  * @type nsIX509Cert[]
  */
 var selected_certs = [];
 var selected_tree_items = [];
 var selected_index = [];
 var certdb;
@@ -200,16 +206,48 @@ function nothingOrContainerSelected(cert
         return true;
       }
     }
   }
 
   return false;
 }
 
+function promptError(aErrorCode) {
+  if (aErrorCode != Ci.nsIX509CertDB.Success) {
+    let msgName = "PKCS12UnknownErr";
+    switch (aErrorCode) {
+      case Ci.nsIX509CertDB.ERROR_PKCS12_NOSMARTCARD_EXPORT:
+        msgName = "PKCS12InfoNoSmartcardBackup";
+        break;
+      case Ci.nsIX509CertDB.ERROR_PKCS12_RESTORE_FAILED:
+        msgName = "PKCS12UnknownErrRestore";
+        break;
+      case Ci.nsIX509CertDB.ERROR_PKCS12_BACKUP_FAILED:
+        msgName = "PKCS12UnknownErrBackup";
+        break;
+      case Ci.nsIX509CertDB.ERROR_PKCS12_CERT_COLLISION:
+      case Ci.nsIX509CertDB.ERROR_PKCS12_DUPLICATE_DATA:
+        msgName = "PKCS12DupData";
+        break;
+      case Ci.nsIX509CertDB.ERROR_BAD_PASSWORD:
+        msgName = "PK11BadPassword";
+        break;
+      case Ci.nsIX509CertDB.ERROR_DECODE_ERROR:
+        msgName = "PKCS12DecodeErr";
+        break;
+      default:
+        break;
+    }
+    let message = strbundle.GetStringFromName(msgName);
+    let prompter = Services.ww.getNewPrompter(window);
+    prompter.alert(null, message);
+  }
+}
+
 /**
  * Enables or disables buttons corresponding to a cert tree depending on what
  * is selected in the cert tree.
  *
  * @param {nsICertTree} certTree
  * @param {Array} idList A list of string identifiers for button elements to
  *    enable or disable.
  */
@@ -271,17 +309,22 @@ function backupCerts() {
           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);
+      let password = {};
+      if (certdialogs.setPKCS12FilePassword(window, password)) {
+        let errorCode = certdb.exportPKCS12File(fp.file, selected_certs.length,
+                                                selected_certs, password.value);
+        promptError(errorCode);
+      }
     }
   });
 }
 
 function backupAllCerts() {
   // Select all rows, then call doBackup()
   userTreeView.selection.selectAll();
   backupCerts();
@@ -337,17 +380,28 @@ function restoreCerts() {
       let interfaceRequestor = {
         getInterface() {
           return prompter;
         }
       };
       certdb.importUserCertificate(dataArray, dataArray.length, interfaceRequestor);
     } else {
       // Otherwise, assume it's a PKCS12 file and import it that way.
-      certdb.importPKCS12File(fp.file);
+      let password = {};
+      let errorCode = Ci.nsIX509CertDB.ERROR_BAD_PASSWORD;
+      while (errorCode == Ci.nsIX509CertDB.ERROR_BAD_PASSWORD &&
+             certdialogs.getPKCS12FilePassword(window, password)) {
+        errorCode = certdb.importPKCS12File(fp.file, password.value);
+        if (errorCode == Ci.nsIX509CertDB.ERROR_BAD_PASSWORD &&
+            password.value.length == 0) {
+          // It didn't like empty string password, try no password.
+          errorCode = certdb.importPKCS12File(fp.file, null);
+        }
+        promptError(errorCode);
+      }
     }
 
     var certcache = certdb.getCerts();
     userTreeView.loadCertsFromCache(certcache, nsIX509Cert.USER_CERT);
     userTreeView.selection.clearSelection();
     caTreeView.loadCertsFromCache(certcache, nsIX509Cert.CA_CERT);
     caTreeView.selection.clearSelection();
     enableBackupAllButton();
--- a/security/manager/ssl/nsIX509CertDB.idl
+++ b/security/manager/ssl/nsIX509CertDB.idl
@@ -165,36 +165,52 @@ interface nsIX509CertDB : nsISupports {
    *               to be imported.
    *  @param aType Describes the type of certificate that is going to
    *               be imported. See type constants in nsIX509Cert.
    */
   [must_use]
   void importCertsFromFile(in nsIFile aFile,
                            in unsigned long aType);
 
+  const uint32_t Success = 0;
+  const uint32_t ERROR_UNKNOWN = 1;
+  const uint32_t ERROR_PKCS12_NOSMARTCARD_EXPORT = 2;
+  const uint32_t ERROR_PKCS12_RESTORE_FAILED = 3;
+  const uint32_t ERROR_PKCS12_BACKUP_FAILED = 4;
+  const uint32_t ERROR_PKCS12_CERT_COLLISION = 5;
+  const uint32_t ERROR_BAD_PASSWORD = 6;
+  const uint32_t ERROR_DECODE_ERROR = 7;
+  const uint32_t ERROR_PKCS12_DUPLICATE_DATA = 8;
+
   /**
    *  Import a PKCS#12 file containing cert(s) and key(s) into the database.
    *
    *  @param aFile Identifies a file that contains the data to be imported.
+   *  @param password The password used to protect the file.
+   *  @return Success or the specific error code on failure.  The return
+   *          values are defined in this file.
    */
   [must_use]
-  void importPKCS12File(in nsIFile aFile);
+  uint32_t importPKCS12File(in nsIFile aFile, in AString aPassword);
 
   /**
    *  Export a set of certs and keys from the database to a PKCS#12 file.
    *
    *  @param aFile Identifies a file that will be filled with the data to be
    *               exported.
    *  @param count The number of certificates to be exported.
    *  @param aCerts The array of all certificates to be exported.
+   *  @param password The password used to protect the file.
+   *  @return Success or the specific error code on failure
    */
   [must_use]
-  void exportPKCS12File(in nsIFile aFile,
-                        in unsigned long count,
-                        [array, size_is(count)] in nsIX509Cert aCerts);
+  uint32_t exportPKCS12File(in nsIFile aFile,
+                            in unsigned long count,
+                            [array, size_is(count)] in nsIX509Cert aCerts,
+                            in AString aPassword);
 
   /*
    *  Decode a raw data presentation and instantiate an object in memory.
    *
    *  @param base64 The raw representation of a certificate,
    *                encoded as Base 64.
    *  @return The new certificate object.
    */
--- a/security/manager/ssl/nsNSSCertificateDB.cpp
+++ b/security/manager/ssl/nsNSSCertificateDB.cpp
@@ -834,57 +834,59 @@ nsNSSCertificateDB::ImportCertsFromFile(
       MOZ_ASSERT(false, "Unsupported type should have been filtered out");
       break;
   }
 
   return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
-nsNSSCertificateDB::ImportPKCS12File(nsIFile* aFile)
+nsNSSCertificateDB::ImportPKCS12File(nsIFile* aFile, const nsAString& aPassword,
+                                     uint32_t* aError)
 {
   if (!NS_IsMainThread()) {
     return NS_ERROR_NOT_SAME_THREAD;
   }
   nsresult rv = BlockUntilLoadableRootsLoaded();
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   NS_ENSURE_ARG(aFile);
   nsPKCS12Blob blob;
-  rv = blob.ImportFromFile(aFile);
-
+  rv = blob.ImportFromFile(aFile, aPassword, *aError);
   nsCOMPtr<nsIObserverService> observerService =
     mozilla::services::GetObserverService();
   if (NS_SUCCEEDED(rv) && observerService) {
     observerService->NotifyObservers(nullptr, "psm:user-certificate-added", nullptr);
   }
 
   return rv;
 }
 
 NS_IMETHODIMP
-nsNSSCertificateDB::ExportPKCS12File(nsIFile* aFile, uint32_t count,
-                                     nsIX509Cert** certs)
+nsNSSCertificateDB::ExportPKCS12File(nsIFile* aFile, uint32_t aCount,
+                                     nsIX509Cert** aCerts,
+                                     const nsAString& aPassword,
+                                     uint32_t* aError)
 {
   if (!NS_IsMainThread()) {
     return NS_ERROR_NOT_SAME_THREAD;
   }
   nsresult rv = BlockUntilLoadableRootsLoaded();
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   NS_ENSURE_ARG(aFile);
-  if (count == 0) {
+  if (aCount == 0) {
     return NS_OK;
   }
   nsPKCS12Blob blob;
-  return blob.ExportToFile(aFile, certs, count);
+  return blob.ExportToFile(aFile, aCerts, aCount, aPassword, *aError);
 }
 
 NS_IMETHODIMP
 nsNSSCertificateDB::ConstructX509FromBase64(const nsACString& base64,
                                     /*out*/ nsIX509Cert** _retval)
 {
   if (!_retval) {
     return NS_ERROR_INVALID_POINTER;
--- a/security/manager/ssl/nsPKCS12Blob.cpp
+++ b/security/manager/ssl/nsPKCS12Blob.cpp
@@ -8,16 +8,17 @@
 #include "mozilla/Assertions.h"
 #include "mozilla/Casting.h"
 #include "mozilla/Unused.h"
 #include "nsICertificateDialogs.h"
 #include "nsIFile.h"
 #include "nsIInputStream.h"
 #include "nsIPrompt.h"
 #include "nsIWindowWatcher.h"
+#include "nsIX509CertDB.h"
 #include "nsNSSCertHelper.h"
 #include "nsNSSCertificate.h"
 #include "nsNSSHelper.h"
 #include "nsNetUtil.h"
 #include "nsReadableUtils.h"
 #include "nsThreadUtils.h"
 #include "p12plcy.h"
 #include "pkix/pkixtypes.h"
@@ -35,115 +36,65 @@ extern LazyLogModule gPIPNSSLog;
 nsPKCS12Blob::nsPKCS12Blob()
   : mUIContext(new PipUIContext())
 {
 }
 
 // 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;
-  RetryReason wantRetry;
-  do {
-    rv = ImportFromFileHelper(file, ImportMode::StandardPrompt, wantRetry);
-
-    if (NS_SUCCEEDED(rv) && wantRetry == RetryReason::AutoRetryEmptyPassword) {
-      rv = ImportFromFileHelper(file, ImportMode::TryZeroLengthSecitem,
-                                wantRetry);
-    }
-  } while (NS_SUCCEEDED(rv) && (wantRetry != RetryReason::DoNotRetry));
-
-  return rv;
-}
-
-void
-nsPKCS12Blob::handleImportError(PRErrorCode nssError, RetryReason& retryReason,
-                                uint32_t passwordLengthInBytes)
+nsPKCS12Blob::ImportFromFile(nsIFile* aFile, const nsAString& aPassword, uint32_t& aError)
 {
-  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);
-  }
-}
-
-// 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;
+  uint32_t passwordBufferLength;
+  UniquePtr<uint8_t[]> passwordBuffer;
 
   UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
   if (!slot) {
     return NS_ERROR_FAILURE;
   }
 
-  uint32_t passwordBufferLength;
-  UniquePtr<uint8_t[]> passwordBuffer;
-  if (aImportMode == ImportMode::TryZeroLengthSecitem) {
-    passwordBufferLength = 0;
-    passwordBuffer = nullptr;
-  } else {
-    // get file password (unicode)
-    nsresult rv = getPKCS12FilePassword(passwordBufferLength, passwordBuffer);
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-    if (!passwordBuffer) {
-      return NS_OK;
-    }
-  }
+  passwordBuffer = stringToBigEndianBytes(aPassword, passwordBufferLength);
 
   // initialize the decoder
   SECItem unicodePw = { siBuffer, passwordBuffer.get(), passwordBufferLength };
   UniqueSEC_PKCS12DecoderContext dcx(
     SEC_PKCS12DecoderStart(&unicodePw, slot.get(), nullptr, nullptr, nullptr,
                            nullptr, nullptr, nullptr));
   if (!dcx) {
     return NS_ERROR_FAILURE;
   }
-  // read input file and feed it to the decoder
+  // read input aFile and feed it to the decoder
   PRErrorCode nssError;
-  nsresult rv = inputToDecoder(dcx, file, nssError);
+  nsresult rv = inputToDecoder(dcx, aFile, nssError);
   if (NS_FAILED(rv)) {
     return rv;
   }
   if (nssError != 0) {
-    handleImportError(nssError, aWantRetry, unicodePw.len);
+    aError = handlePRErrorCode(nssError);
     return NS_OK;
   }
   // verify the blob
   SECStatus srv = SEC_PKCS12DecoderVerify(dcx.get());
   if (srv != SECSuccess) {
-    handleImportError(PR_GetError(), aWantRetry, unicodePw.len);
+    aError = handlePRErrorCode(PR_GetError());
     return NS_OK;
   }
   // validate bags
   srv = SEC_PKCS12DecoderValidateBags(dcx.get(), nicknameCollision);
   if (srv != SECSuccess) {
-    handleImportError(PR_GetError(), aWantRetry, unicodePw.len);
+    aError = handlePRErrorCode(PR_GetError());
     return NS_OK;
   }
   // import cert and key
   srv = SEC_PKCS12DecoderImportBags(dcx.get());
   if (srv != SECSuccess) {
-    handleImportError(PR_GetError(), aWantRetry, unicodePw.len);
+    aError = handlePRErrorCode(PR_GetError());
+    return NS_OK;
   }
+  aError = nsIX509CertDB::Success;
   return NS_OK;
 }
 
 static bool
 isExtractable(UniqueSECKEYPrivateKey& privKey)
 {
   ScopedAutoSECItem value;
   SECStatus rv = PK11_ReadRawAttribute(
@@ -157,186 +108,135 @@ isExtractable(UniqueSECKEYPrivateKey& pr
     isExtractable = !!(*(CK_BBOOL*)value.data);
   }
   return isExtractable;
 }
 
 // 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)
+nsPKCS12Blob::ExportToFile(nsIFile* aFile, nsIX509Cert** aCerts, int aNumCerts,
+                           const nsAString& aPassword, uint32_t& aError)
 {
-  bool informedUserNoSmartcardBackup = false;
 
   // get file password (unicode)
   uint32_t passwordBufferLength;
   UniquePtr<uint8_t[]> passwordBuffer;
-  nsresult rv = newPKCS12FilePassword(passwordBufferLength, passwordBuffer);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
+  passwordBuffer = stringToBigEndianBytes(aPassword, passwordBufferLength);
+  aError = nsIX509CertDB::Success;
   if (!passwordBuffer) {
     return NS_OK;
   }
   UniqueSEC_PKCS12ExportContext ecx(
     SEC_PKCS12CreateExportContext(nullptr, nullptr, nullptr, nullptr));
   if (!ecx) {
-    handleError(PIP_PKCS12_BACKUP_FAILED, PR_GetError());
-    return NS_ERROR_FAILURE;
+    aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED;
+    return NS_OK;
   }
   // add password integrity
   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;
+    aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED;
+    return NS_OK;
   }
-  for (int i = 0; i < numCerts; i++) {
-    UniqueCERTCertificate nssCert(certs[i]->GetCert());
+  for (int i = 0; i < aNumCerts; i++) {
+    UniqueCERTCertificate nssCert(aCerts[i]->GetCert());
     if (!nssCert) {
-      handleError(PIP_PKCS12_BACKUP_FAILED, PR_GetError());
-      return NS_ERROR_FAILURE;
+      aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED;
+      return NS_OK;
     }
     // 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.
       UniqueSECKEYPrivateKey privKey(
         PK11_FindKeyByDERCert(nssCert->slot, nssCert.get(), mUIContext));
       if (privKey && !isExtractable(privKey)) {
-        if (!informedUserNoSmartcardBackup) {
-          informedUserNoSmartcardBackup = true;
-          handleError(PIP_PKCS12_NOSMARTCARD_EXPORT, PR_GetError());
-        }
+        // This is informative.  If a serious error occurs later it will
+        // override it later and return.
+        aError = nsIX509CertDB::ERROR_PKCS12_NOSMARTCARD_EXPORT;
         continue;
       }
     }
 
     // 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.get(), &unicodePw,
         SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC);
     }
     if (!certSafe || !keySafe) {
-      handleError(PIP_PKCS12_BACKUP_FAILED, PR_GetError());
-      return NS_ERROR_FAILURE;
+      aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED;
+      return NS_OK;
     }
     // add the cert and key to the blob
     srv = SEC_PKCS12AddCertAndKey(
       ecx.get(),
       certSafe,
       nullptr,
       nssCert.get(),
       CERT_GetDefaultCertDB(),
       keySafe,
       nullptr,
       true,
       &unicodePw,
       SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC);
     if (srv != SECSuccess) {
-      handleError(PIP_PKCS12_BACKUP_FAILED, PR_GetError());
-      return NS_ERROR_FAILURE;
+      aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED;
+      return NS_OK;
     }
   }
 
   UniquePRFileDesc prFile;
   PRFileDesc* rawPRFile;
-  rv = file->OpenNSPRFileDesc(
-    PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0664, &rawPRFile);
+  nsresult rv = aFile->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;
+    aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED;
+    return NS_OK;
   }
   prFile.reset(rawPRFile);
   // encode and write
   srv = SEC_PKCS12Encode(ecx.get(), writeExportFile, prFile.get());
   if (srv != SECSuccess) {
-    handleError(PIP_PKCS12_BACKUP_FAILED, PR_GetError());
-    return NS_ERROR_FAILURE;
+    aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED;
   }
   return NS_OK;
 }
 
 // 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)
+nsPKCS12Blob::stringToBigEndianBytes(const nsAString& uni, uint32_t& bytesLength)
 {
+  if (uni.IsVoid()) {
+    bytesLength = 0;
+    return nullptr;
+  }
+
   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(
-    buffer.get(), static_cast<const char16_t*>(uni.get()), wideLength);
+    buffer.get(), static_cast<const char16_t*>(uni.BeginReading()), wideLength);
 
   return buffer;
 }
 
-// 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(uint32_t& passwordBufferLength,
-                                    UniquePtr<uint8_t[]>& passwordBuffer)
-{
-  nsAutoString password;
-  nsCOMPtr<nsICertificateDialogs> certDialogs;
-  nsresult rv = ::getNSSDialogs(getter_AddRefs(certDialogs),
-                                NS_GET_IID(nsICertificateDialogs),
-                                NS_CERTIFICATEDIALOGS_CONTRACTID);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-  bool pressedOK = false;
-  rv = certDialogs->SetPKCS12FilePassword(mUIContext, password, &pressedOK);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-  if (!pressedOK) {
-    return NS_OK;
-  }
-  passwordBuffer = stringToBigEndianBytes(password, passwordBufferLength);
-  return NS_OK;
-}
-
-// 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(uint32_t& passwordBufferLength,
-                                    UniquePtr<uint8_t[]>& passwordBuffer)
-{
-  nsCOMPtr<nsICertificateDialogs> certDialogs;
-  nsresult rv = ::getNSSDialogs(getter_AddRefs(certDialogs),
-                                NS_GET_IID(nsICertificateDialogs),
-                                NS_CERTIFICATEDIALOGS_CONTRACTID);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-  nsAutoString password;
-  bool pressedOK = false;
-  rv = certDialogs->GetPKCS12FilePassword(mUIContext, password, &pressedOK);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-  if (!pressedOK) {
-    return NS_OK;
-  }
-  passwordBuffer = stringToBigEndianBytes(password, passwordBufferLength);
-  return NS_OK;
-}
-
 // Given a decoder, read bytes from file and input them to the decoder.
 nsresult
 nsPKCS12Blob::inputToDecoder(UniqueSEC_PKCS12DecoderContext& dcx, nsIFile* file,
                              PRErrorCode& nssError)
 {
   nssError = 0;
 
   nsCOMPtr<nsIInputStream> fileStream;
@@ -429,74 +329,35 @@ nsPKCS12Blob::nicknameCollision(SECItem*
 void
 nsPKCS12Blob::writeExportFile(void* arg, const char* buf, unsigned long len)
 {
   PRFileDesc* file = static_cast<PRFileDesc*>(arg);
   MOZ_RELEASE_ASSERT(file);
   PR_Write(file, buf, len);
 }
 
-void
-nsPKCS12Blob::handleError(int myerr, PRErrorCode prerr)
+// Translate PRErrorCode to nsIX509CertDB error
+uint32_t
+nsPKCS12Blob::handlePRErrorCode(PRErrorCode aPrerr)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-  if (!NS_IsMainThread()) {
-    return;
-  }
-
-  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_NOSMARTCARD_EXPORT:
-      msgID = "PKCS12InfoNoSmartcardBackup";
-      break;
-    case PIP_PKCS12_RESTORE_FAILED:
-      msgID = "PKCS12UnknownErrRestore";
+  MOZ_ASSERT(aPrerr != 0);
+  uint32_t error = nsIX509CertDB::ERROR_UNKNOWN;
+  switch (aPrerr) {
+    case SEC_ERROR_PKCS12_CERT_COLLISION:
+      error = nsIX509CertDB::ERROR_PKCS12_DUPLICATE_DATA;
       break;
-    case PIP_PKCS12_BACKUP_FAILED:
-      msgID = "PKCS12UnknownErrBackup";
+    // INVALID_ARGS is returned on bad password when importing cert
+    // exported from firefox or generated by openssl
+    case SEC_ERROR_INVALID_ARGS:
+    case SEC_ERROR_BAD_PASSWORD:
+      error = nsIX509CertDB::ERROR_BAD_PASSWORD;
       break;
-    case PIP_PKCS12_NSS_ERROR:
-      switch (prerr) {
-        case 0:
-          break;
-        case SEC_ERROR_PKCS12_CERT_COLLISION:
-          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";
-          break;
-
-        case SEC_ERROR_PKCS12_DUPLICATE_DATA:
-          msgID = "PKCS12DupData";
-          break;
-      }
+    case SEC_ERROR_BAD_DER:
+    case SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE:
+    case SEC_ERROR_PKCS12_INVALID_MAC:
+      error = nsIX509CertDB::ERROR_DECODE_ERROR;
+      break;
+    case SEC_ERROR_PKCS12_DUPLICATE_DATA:
+      error = nsIX509CertDB::ERROR_PKCS12_DUPLICATE_DATA;
       break;
   }
-
-  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;
-  }
-  nsAutoString message;
-  if (NS_FAILED(GetPIPNSSBundleString(msgID, message))) {
-    return;
-  }
-
-  Unused << prompter->Alert(nullptr, message.get());
+  return error;
 }
--- a/security/manager/ssl/nsPKCS12Blob.h
+++ b/security/manager/ssl/nsPKCS12Blob.h
@@ -19,63 +19,32 @@ class nsIX509Cert;
 // Class for importing/exporting PKCS#12 blobs
 class nsPKCS12Blob
 {
 public:
   nsPKCS12Blob();
   ~nsPKCS12Blob() {}
 
   // PKCS#12 Import
-  nsresult ImportFromFile(nsIFile* file);
+  nsresult ImportFromFile(nsIFile* file, const nsAString& password,
+                          uint32_t& error);
 
   // PKCS#12 Export
-  nsresult ExportToFile(nsIFile* file, nsIX509Cert** certs, int numCerts);
+  nsresult ExportToFile(nsIFile* file, nsIX509Cert** certs, int numCerts,
+                        const nsAString& password, uint32_t& error);
 
 private:
   nsCOMPtr<nsIInterfaceRequestor> mUIContext;
 
   // local helper functions
-  nsresult getPKCS12FilePassword(uint32_t& passwordBufferLength,
-                                 mozilla::UniquePtr<uint8_t[]>& passwordBuffer);
-  nsresult newPKCS12FilePassword(uint32_t& passwordBufferLength,
-                                 mozilla::UniquePtr<uint8_t[]>& passwordBuffer);
   nsresult inputToDecoder(mozilla::UniqueSEC_PKCS12DecoderContext& dcx,
                           nsIFile* file,
                           PRErrorCode& nssError);
-  mozilla::UniquePtr<uint8_t[]> stringToBigEndianBytes(const nsString& uni,
+  mozilla::UniquePtr<uint8_t[]> stringToBigEndianBytes(const nsAString& 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 class RetryReason
-  {
-    DoNotRetry,
-    BadPassword,
-    AutoRetryEmptyPassword,
-  };
-  enum class ImportMode
-  {
-    StandardPrompt,
-    TryZeroLengthSecitem
-  };
-
-  void handleImportError(PRErrorCode nssError, RetryReason& retryReason,
-                         uint32_t passwordLengthInBytes);
-
-  nsresult ImportFromFileHelper(nsIFile* file,
-                                ImportMode aImportMode,
-                                RetryReason& aWantRetry);
+  uint32_t handlePRErrorCode(PRErrorCode prerr);
 
   static SECItem* nicknameCollision(SECItem* oldNick, PRBool* cancel,
                                     void* wincx);
   static void writeExportFile(void* arg, const char* buf, unsigned long len);
 };
 
 #endif // nsPKCS12Blob_h
--- a/security/manager/ssl/tests/unit/test_certDB_export_pkcs12.js
+++ b/security/manager/ssl/tests/unit/test_certDB_export_pkcs12.js
@@ -11,105 +11,48 @@ do_get_profile();
 const gCertDB = Cc["@mozilla.org/security/x509certdb;1"]
                   .getService(Ci.nsIX509CertDB);
 
 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 gPasswordToUse = TEST_CERT_PASSWORD;
-
-// Mock implementation of nsICertificateDialogs.
-const gCertificateDialogs = {
-  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;
-  },
-  getPKCS12FilePassword: (ctx, password) => {
-    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 gPrompt = {
-  clickOk: true,
-
-  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 != gPrompt| due to
-  // how objects get wrapped when going across xpcom boundaries.
-  alert(title, text) {
-    ok(false, "Not expecting alert to be called.");
-  },
-
-  promptPassword(dialogTitle, text, password, checkMsg, checkValue) {
-    ok(false, "Not expecting a password prompt.");
-    return false;
-  },
-};
-
-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);
-  });
-
   // 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);
+  let errorCode = gCertDB.importPKCS12File(certFile, TEST_CERT_PASSWORD);
+  equal(errorCode, Ci.nsIX509CertDB.Success, "cert should be imported");
   cert = findCertByCommonName(CERT_COMMON_NAME);
   notEqual(cert, null, "cert should be found now");
 
   // 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]);
+  errorCode = gCertDB.exportPKCS12File(output, 1, [cert], TEST_CERT_PASSWORD);
+  equal(errorCode, Ci.nsIX509CertDB.Success, "cert should be exported");
   ok(output.exists(), "output should exist after exporting PKCS12 file");
 
   // We should be able to import the exported blob again using the new password.
-  gCertDB.importPKCS12File(output);
+  errorCode = gCertDB.importPKCS12File(output, TEST_CERT_PASSWORD);
+  equal(errorCode, Ci.nsIX509CertDB.Success, "cert should be imported");
   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.
 }
--- a/security/manager/ssl/tests/unit/test_certDB_export_pkcs12_with_master_password.js
+++ b/security/manager/ssl/tests/unit/test_certDB_export_pkcs12_with_master_password.js
@@ -10,55 +10,28 @@ do_get_profile();
 
 const gCertDB = Cc["@mozilla.org/security/x509certdb;1"]
                   .getService(Ci.nsIX509CertDB);
 
 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: () => {
-    // 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;
-  },
-  getPKCS12FilePassword: (ctx, password) => {
-    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 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 != 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");
+    ok(false, "not expecting alert() to be called");
   },
 
   promptPassword(dialogTitle, text, password, checkMsg, checkValue) {
     equal(text,
           "Please enter your master password.",
           "password prompt text should be as expected");
     equal(checkMsg, null, "checkMsg should be null");
     password.value = this.password;
@@ -78,61 +51,59 @@ function findCertByCommonName(commonName
     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();
 
   // 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);
+  let errorCode = gCertDB.importPKCS12File(certFile, TEST_CERT_PASSWORD);
+  equal(errorCode, Ci.nsIX509CertDB.Success, "cert should import");
   cert = findCertByCommonName(CERT_COMMON_NAME);
   notEqual(cert, null, "cert should be found now");
 
   // Log out so we're prompted for the password.
   token.logoutSimple();
 
   // 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]);
+  errorCode = gCertDB.exportPKCS12File(output, 1, [cert], TEST_CERT_PASSWORD);
+  equal(errorCode, Ci.nsIX509CertDB.Success, "cert should export");
   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();
 
   // 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/);
+  errorCode = gCertDB.exportPKCS12File(output, 1, [cert], TEST_CERT_PASSWORD);
+  equal(errorCode, Ci.nsIX509CertDB.ERROR_PKCS12_BACKUP_FAILED, "cert should not export");
+
   ok(!output2.exists(), "output2 shouldn't exist after failing to export");
 }
new file mode 100644
index 0000000000000000000000000000000000000000..879d424b85fe096f066bcd76c01f4f4571b8bbca
GIT binary patch
literal 2068
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
new file mode 100644
index 0000000000000000000000000000000000000000..7dcd6681212314f9cef827d6d85fbf9069082db9
GIT binary patch
literal 2068
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
--- a/security/manager/ssl/tests/unit/test_certDB_import_pkcs12.js
+++ b/security/manager/ssl/tests/unit/test_certDB_import_pkcs12.js
@@ -6,97 +6,77 @@
 // Tests import PKCS12 file by nsIX509CertDB.
 
 do_get_profile();
 
 const gCertDB = Cc["@mozilla.org/security/x509certdb;1"]
                   .getService(Ci.nsIX509CertDB);
 
 const PKCS12_FILE = "test_certDB_import/cert_from_windows.pfx";
+const PKCS12_FILE_EMPTY_PASS =
+        "test_certDB_import/cert_from_windows_emptypass.pfx";
+const PKCS12_FILE_NO_PASS = "test_certDB_import/cert_from_windows_nopass.pfx";
 const CERT_COMMON_NAME = "test_cert_from_windows";
 const TEST_CERT_PASSWORD = "黒い";
 
 // Has getPKCS12FilePassword been called since we last reset this?
 let gGetPKCS12FilePasswordCalled = false;
 let gCurrentTestcase = null;
 
 let gTestcases = [
   // Test that importing a PKCS12 file with the wrong password fails.
   {
     name: "import using incorrect password",
     filename: PKCS12_FILE,
-    // cancel after the first failed password prompt
-    multiplePasswordPromptsMeans: "cancel",
-    expectingAlert: true,
-    expectedAlertRegexp: /^The password entered was incorrect\.$/,
     passwordToUse: "this is the wrong password",
     successExpected: false,
+    errorCode: Ci.nsIX509CertDB.ERROR_BAD_PASSWORD,
+    checkCertExist: true,
   },
   // Test that importing something that isn't a PKCS12 file fails.
   {
     name: "import non-PKCS12 file",
     filename: "test_certDB_import_pkcs12.js",
-    // cancel after the first failed password prompt
-    multiplePasswordPromptsMeans: "cancel",
-    expectingAlert: true,
-    expectedAlertRegexp: /^Failed to decode the file\./,
     passwordToUse: TEST_CERT_PASSWORD,
     successExpected: false,
+    errorCode: Ci.nsIX509CertDB.ERROR_DECODE_ERROR,
+    checkCertExist: true,
   },
   // Test that importing a PKCS12 file with the correct password succeeds.
   // This needs to be last because currently there isn't a way to delete the
   // imported certificate (and thus reset the test state) that doesn't depend on
   // the garbage collector running.
   {
     name: "import PKCS12 file",
     filename: PKCS12_FILE,
-    // If we see more than one password prompt, this is a test failure.
-    multiplePasswordPromptsMeans: "test failure",
-    expectingAlert: false,
-    expectedAlertRegexp: null,
     passwordToUse: TEST_CERT_PASSWORD,
     successExpected: true,
+    errorCode: Ci.nsIX509CertDB.Success,
+    checkCertExist: true,
+  },
+  // Same cert file protected with empty string password
+  {
+    name: "import PKCS12 file empty password",
+    filename: PKCS12_FILE_EMPTY_PASS,
+    passwordToUse: "",
+    successExpected: true,
+    errorCode: Ci.nsIX509CertDB.Success,
+    checkCertExist: false,
+  },
+  // Same cert file protected with no password
+  {
+    name: "import PKCS12 file no password",
+    filename: PKCS12_FILE_NO_PASS,
+    passwordToUse: null,
+    successExpected: true,
+    errorCode: Ci.nsIX509CertDB.Success,
+    checkCertExist: false,
   },
 ];
 
-// Mock implementation of nsICertificateDialogs.
-const gCertificateDialogs = {
-  QueryInterface: ChromeUtils.generateQI([Ci.nsICertificateDialogs]),
-
-  getPKCS12FilePassword: (ctx, password) => {
-    if (gGetPKCS12FilePasswordCalled) {
-      if (gCurrentTestcase.multiplePasswordPromptsMeans == "cancel") {
-        return false;
-      }
-      ok(false, "getPKCS12FilePassword should be called only once");
-    }
-
-    password.value = gCurrentTestcase.passwordToUse;
-    info("getPKCS12FilePassword() called");
-    gGetPKCS12FilePasswordCalled = true;
-    return true;
-  },
-};
-
-const gPrompt = {
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIPrompt]),
-  alert: (title, text) => {
-    info(`alert('${text}')`);
-    ok(gCurrentTestcase.expectingAlert,
-       "alert() should only be called if we're expecting it");
-    ok(gCurrentTestcase.expectedAlertRegexp.test(text),
-       "alert text should match expected message");
-  },
-};
-
-const gPromptFactory = {
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIPromptFactory]),
-  getPrompt: (aWindow, aIID) => gPrompt,
-};
-
 function doesCertExist(commonName) {
   let allCerts = gCertDB.getCerts();
   let enumerator = allCerts.getEnumerator();
   while (enumerator.hasMoreElements()) {
     let cert = enumerator.getNext().QueryInterface(Ci.nsIX509Cert);
     if (cert.isBuiltInRoot) {
       continue;
     }
@@ -105,40 +85,29 @@ function doesCertExist(commonName) {
     }
   }
 
   return false;
 }
 
 function runOneTestcase(testcase) {
   info(`running ${testcase.name}`);
-  ok(!doesCertExist(CERT_COMMON_NAME),
-     "cert should not be in the database before import");
+  if (testcase.checkCertExist) {
+    ok(!doesCertExist(CERT_COMMON_NAME),
+       "cert should not be in the database before import");
+  }
 
   // Import and check for failure.
   let certFile = do_get_file(testcase.filename);
   ok(certFile, `${testcase.filename} should exist`);
   gGetPKCS12FilePasswordCalled = false;
   gCurrentTestcase = testcase;
-  gCertDB.importPKCS12File(certFile);
-  ok(gGetPKCS12FilePasswordCalled,
-     "getPKCS12FilePassword should have been called");
-
+  let errorCode = gCertDB.importPKCS12File(certFile, testcase.passwordToUse);
+  equal(errorCode, testcase.errorCode, `verifying error code`);
   equal(doesCertExist(CERT_COMMON_NAME), testcase.successExpected,
         `cert should${testcase.successExpected ? "" : " not"} be found now`);
 }
 
 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);
-  });
-
   for (let testcase of gTestcases) {
     runOneTestcase(testcase);
   }
 }