--- 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]