mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-14 13:55:43 +00:00
bug 401240 - part 2/2 - reimplement PKCS#12 import/export without goto r=fkiefer
MozReview-Commit-ID: JUMmTPrEYND --HG-- rename : security/manager/ssl/tests/unit/test_certDB_import_with_master_password.js => security/manager/ssl/tests/unit/test_certDB_export_pkcs12.js rename : security/manager/ssl/tests/unit/test_certDB_import_with_master_password.js => security/manager/ssl/tests/unit/test_certDB_export_pkcs12_with_master_password.js extra : rebase_source : 3bf01e722f496b4dba634ed5d0345ce1fe0ceea7
This commit is contained in:
parent
18117a994a
commit
d8f748b996
@ -273,6 +273,7 @@ function backupCerts() {
|
||||
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);
|
||||
|
@ -356,6 +356,13 @@ MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniqueSGNDigestInfo,
|
||||
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
|
||||
|
@ -2645,6 +2645,21 @@ setPassword(PK11SlotInfo* slot, nsIInterfaceRequestor* ctx)
|
||||
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 {
|
||||
|
||||
@ -2680,7 +2695,7 @@ InitializeCipherSuite()
|
||||
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
|
||||
|
@ -1,20 +1,17 @@
|
||||
/* -*- 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:
|
||||
@ -27,30 +24,12 @@ 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
|
||||
|
@ -26,143 +26,128 @@
|
||||
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;
|
||||
// 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.
|
||||
SECStatus srv = SEC_PKCS12DecoderVerify(dcx.get());
|
||||
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;
|
||||
}
|
||||
// validate bags
|
||||
srv = SEC_PKCS12DecoderValidateBags(dcx.get(), nicknameCollision);
|
||||
if (srv != SECSuccess) {
|
||||
handleImportError(PR_GetError(), aWantRetry, unicodePw.len);
|
||||
return NS_OK;
|
||||
}
|
||||
// import cert and key
|
||||
srv = SEC_PKCS12DecoderImportBags(dcx.get());
|
||||
if (srv != SECSuccess) {
|
||||
handleImportError(PR_GetError(), aWantRetry, unicodePw.len);
|
||||
}
|
||||
// finish the decoder
|
||||
if (dcx)
|
||||
SEC_PKCS12DecoderFinish(dcx);
|
||||
SECITEM_ZfreeItem(&unicodePw, false);
|
||||
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;
|
||||
}
|
||||
@ -174,266 +159,216 @@ isExtractable(SECKEYPrivateKey* privKey)
|
||||
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, &mTmpFile);
|
||||
if (NS_FAILED(rv) || !this->mTmpFile)
|
||||
goto finish;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
@ -479,60 +414,40 @@ nsPKCS12Blob::nickname_collision(SECItem* oldNick, PRBool* cancel, void* wincx)
|
||||
}
|
||||
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;
|
||||
@ -544,15 +459,11 @@ nsPKCS12Blob::handleError(int myerr)
|
||||
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;
|
||||
@ -570,8 +481,9 @@ nsPKCS12Blob::handleError(int myerr)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!msgID)
|
||||
if (!msgID) {
|
||||
msgID = "PKCS12UnknownErr";
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
|
||||
if (!wwatch) {
|
||||
|
@ -1,3 +1,5 @@
|
||||
/* -*- 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/. */
|
||||
@ -14,16 +16,12 @@
|
||||
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);
|
||||
@ -32,50 +30,51 @@ public:
|
||||
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.
|
||||
// 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
|
||||
};
|
||||
|
||||
enum RetryReason
|
||||
{
|
||||
rr_do_not_retry,
|
||||
rr_bad_password,
|
||||
rr_auto_retry_empty_password_flavors
|
||||
};
|
||||
enum ImportMode
|
||||
{
|
||||
im_standard_prompt,
|
||||
im_try_zero_length_secitem
|
||||
};
|
||||
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
|
||||
|
115
security/manager/ssl/tests/unit/test_certDB_export_pkcs12.js
Normal file
115
security/manager/ssl/tests/unit/test_certDB_export_pkcs12.js
Normal file
@ -0,0 +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 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 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);
|
||||
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]);
|
||||
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);
|
||||
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.
|
||||
}
|
@ -0,0 +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 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 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");
|
||||
},
|
||||
|
||||
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;
|
||||
return this.clickOk;
|
||||
},
|
||||
};
|
||||
|
||||
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();
|
||||
|
||||
// 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();
|
||||
|
||||
// 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();
|
||||
|
||||
// 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");
|
||||
}
|
@ -63,6 +63,8 @@ run-sequentially = hardcoded ports
|
||||
[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]
|
||||
|
Loading…
Reference in New Issue
Block a user