michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: /* $Id: nsPKCS12Blob.cpp,v 1.49 2007/09/05 07:13:46 jwalden%mit.edu Exp $ */ michael@0: michael@0: #include "nsPKCS12Blob.h" michael@0: michael@0: #include "pkix/pkixtypes.h" michael@0: michael@0: #include "prmem.h" michael@0: #include "prprf.h" michael@0: michael@0: #include "nsIFile.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsIDirectoryService.h" michael@0: #include "nsThreadUtils.h" michael@0: michael@0: #include "nsNSSComponent.h" michael@0: #include "nsNSSHelper.h" michael@0: #include "nsString.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsXPIDLString.h" michael@0: #include "nsDirectoryServiceDefs.h" michael@0: #include "nsNSSHelper.h" michael@0: #include "nsNSSCertificate.h" michael@0: #include "nsKeygenHandler.h" //For GetSlotWithMechanism michael@0: #include "nsPK11TokenDB.h" michael@0: #include "nsICertificateDialogs.h" michael@0: #include "nsNSSShutDown.h" michael@0: #include "nsCRT.h" michael@0: michael@0: #include "secerr.h" michael@0: michael@0: #ifdef PR_LOGGING michael@0: extern PRLogModuleInfo* gPIPNSSLog; michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: michael@0: #define PIP_PKCS12_TMPFILENAME NS_LITERAL_CSTRING(".pip_p12tmp") michael@0: #define PIP_PKCS12_BUFFER_SIZE 2048 michael@0: #define PIP_PKCS12_RESTORE_OK 1 michael@0: #define PIP_PKCS12_BACKUP_OK 2 michael@0: #define PIP_PKCS12_USER_CANCELED 3 michael@0: #define PIP_PKCS12_NOSMARTCARD_EXPORT 4 michael@0: #define PIP_PKCS12_RESTORE_FAILED 5 michael@0: #define PIP_PKCS12_BACKUP_FAILED 6 michael@0: #define PIP_PKCS12_NSS_ERROR 7 michael@0: michael@0: // constructor michael@0: nsPKCS12Blob::nsPKCS12Blob():mCertArray(0), michael@0: mTmpFile(nullptr), michael@0: mDigest(nullptr), michael@0: mDigestIterator(nullptr), michael@0: mTokenSet(false) michael@0: { michael@0: mUIContext = new PipUIContext(); michael@0: } michael@0: michael@0: // destructor michael@0: nsPKCS12Blob::~nsPKCS12Blob() michael@0: { michael@0: delete mDigestIterator; michael@0: delete mDigest; michael@0: } michael@0: michael@0: // nsPKCS12Blob::SetToken michael@0: // michael@0: // Set the token to use for import/export michael@0: nsresult michael@0: nsPKCS12Blob::SetToken(nsIPK11Token *token) michael@0: { michael@0: nsNSSShutDownPreventionLock locker; michael@0: nsresult rv = NS_OK; michael@0: if (token) { michael@0: mToken = token; michael@0: } else { michael@0: PK11SlotInfo *slot; michael@0: rv = GetSlotWithMechanism(CKM_RSA_PKCS, mUIContext,&slot); michael@0: if (NS_FAILED(rv)) { michael@0: mToken = 0; michael@0: } else { michael@0: mToken = new nsPK11Token(slot); michael@0: PK11_FreeSlot(slot); michael@0: } michael@0: } michael@0: mTokenSet = true; michael@0: return rv; michael@0: } michael@0: michael@0: // nsPKCS12Blob::ImportFromFile michael@0: // michael@0: // Given a file handle, read a PKCS#12 blob from that file, decode it, michael@0: // and import the results into the token. michael@0: nsresult michael@0: nsPKCS12Blob::ImportFromFile(nsIFile *file) michael@0: { michael@0: nsNSSShutDownPreventionLock locker; michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (!mToken) { michael@0: if (!mTokenSet) { michael@0: rv = SetToken(nullptr); // Ask the user to pick a slot michael@0: if (NS_FAILED(rv)) { michael@0: handleError(PIP_PKCS12_USER_CANCELED); michael@0: return rv; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!mToken) { michael@0: handleError(PIP_PKCS12_RESTORE_FAILED); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: // init slot michael@0: rv = mToken->Login(true); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: RetryReason wantRetry; michael@0: michael@0: do { michael@0: rv = ImportFromFileHelper(file, im_standard_prompt, wantRetry); michael@0: michael@0: if (NS_SUCCEEDED(rv) && wantRetry == rr_auto_retry_empty_password_flavors) michael@0: { michael@0: rv = ImportFromFileHelper(file, im_try_zero_length_secitem, wantRetry); michael@0: } michael@0: } michael@0: while (NS_SUCCEEDED(rv) && (wantRetry != rr_do_not_retry)); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsPKCS12Blob::ImportFromFileHelper(nsIFile *file, michael@0: nsPKCS12Blob::ImportMode aImportMode, michael@0: nsPKCS12Blob::RetryReason &aWantRetry) michael@0: { michael@0: nsNSSShutDownPreventionLock locker; michael@0: nsresult rv = NS_OK; michael@0: SECStatus srv = SECSuccess; michael@0: SEC_PKCS12DecoderContext *dcx = nullptr; michael@0: SECItem unicodePw; michael@0: michael@0: PK11SlotInfo *slot=nullptr; michael@0: nsXPIDLString tokenName; michael@0: unicodePw.data = nullptr; michael@0: michael@0: aWantRetry = rr_do_not_retry; michael@0: michael@0: if (aImportMode == im_try_zero_length_secitem) michael@0: { michael@0: unicodePw.len = 0; michael@0: } michael@0: else michael@0: { michael@0: // get file password (unicode) michael@0: rv = getPKCS12FilePassword(&unicodePw); michael@0: if (NS_FAILED(rv)) goto finish; michael@0: if (!unicodePw.data) { michael@0: handleError(PIP_PKCS12_USER_CANCELED); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: mToken->GetTokenName(getter_Copies(tokenName)); michael@0: { michael@0: NS_ConvertUTF16toUTF8 tokenNameCString(tokenName); michael@0: slot = PK11_FindSlotByName(tokenNameCString.get()); michael@0: } michael@0: if (!slot) { michael@0: srv = SECFailure; michael@0: goto finish; michael@0: } michael@0: michael@0: // initialize the decoder michael@0: dcx = SEC_PKCS12DecoderStart(&unicodePw, slot, nullptr, michael@0: digest_open, digest_close, michael@0: digest_read, digest_write, michael@0: this); michael@0: if (!dcx) { michael@0: srv = SECFailure; michael@0: goto finish; michael@0: } michael@0: // read input file and feed it to the decoder michael@0: rv = inputToDecoder(dcx, file); michael@0: if (NS_FAILED(rv)) { michael@0: if (NS_ERROR_ABORT == rv) { michael@0: // inputToDecoder indicated a NSS error michael@0: srv = SECFailure; michael@0: } michael@0: goto finish; michael@0: } michael@0: // verify the blob michael@0: srv = SEC_PKCS12DecoderVerify(dcx); michael@0: if (srv) goto finish; michael@0: // validate bags michael@0: srv = SEC_PKCS12DecoderValidateBags(dcx, nickname_collision); michael@0: if (srv) goto finish; michael@0: // import cert and key michael@0: srv = SEC_PKCS12DecoderImportBags(dcx); michael@0: if (srv) goto finish; michael@0: // Later - check to see if this should become default email cert michael@0: handleError(PIP_PKCS12_RESTORE_OK); michael@0: finish: michael@0: // If srv != SECSuccess, NSS probably set a specific error code. michael@0: // We should use that error code instead of inventing a new one michael@0: // for every error possible. michael@0: if (srv != SECSuccess) { michael@0: if (SEC_ERROR_BAD_PASSWORD == PORT_GetError()) { michael@0: if (unicodePw.len == sizeof(char16_t)) michael@0: { michael@0: // no password chars available, michael@0: // unicodeToItem allocated space for the trailing zero character only. michael@0: aWantRetry = rr_auto_retry_empty_password_flavors; michael@0: } michael@0: else michael@0: { michael@0: aWantRetry = rr_bad_password; michael@0: handleError(PIP_PKCS12_NSS_ERROR); michael@0: } michael@0: } michael@0: else michael@0: { michael@0: handleError(PIP_PKCS12_NSS_ERROR); michael@0: } michael@0: } else if (NS_FAILED(rv)) { michael@0: handleError(PIP_PKCS12_RESTORE_FAILED); michael@0: } michael@0: if (slot) michael@0: PK11_FreeSlot(slot); michael@0: // finish the decoder michael@0: if (dcx) michael@0: SEC_PKCS12DecoderFinish(dcx); michael@0: SECITEM_ZfreeItem(&unicodePw, false); michael@0: return NS_OK; michael@0: } michael@0: michael@0: static bool michael@0: isExtractable(SECKEYPrivateKey *privKey) michael@0: { michael@0: SECItem value; michael@0: bool isExtractable = false; michael@0: SECStatus rv; michael@0: michael@0: rv=PK11_ReadRawAttribute(PK11_TypePrivKey, privKey, CKA_EXTRACTABLE, &value); michael@0: if (rv != SECSuccess) { michael@0: return false; michael@0: } michael@0: if ((value.len == 1) && value.data) { michael@0: isExtractable = !!(*(CK_BBOOL*)value.data); michael@0: } michael@0: SECITEM_FreeItem(&value, false); michael@0: return isExtractable; michael@0: } michael@0: michael@0: // nsPKCS12Blob::ExportToFile michael@0: // michael@0: // Having already loaded the certs, form them into a blob (loading the keys michael@0: // also), encode the blob, and stuff it into the file. michael@0: // michael@0: // TODO: handle slots correctly michael@0: // mirror "slotToUse" behavior from PSM 1.x michael@0: // verify the cert array to start off with? michael@0: // open output file as nsIFileStream object? michael@0: // set appropriate error codes michael@0: nsresult michael@0: nsPKCS12Blob::ExportToFile(nsIFile *file, michael@0: nsIX509Cert **certs, int numCerts) michael@0: { michael@0: nsNSSShutDownPreventionLock locker; michael@0: nsresult rv; michael@0: SECStatus srv = SECSuccess; michael@0: SEC_PKCS12ExportContext *ecx = nullptr; michael@0: SEC_PKCS12SafeInfo *certSafe = nullptr, *keySafe = nullptr; michael@0: SECItem unicodePw; michael@0: nsAutoString filePath; michael@0: int i; michael@0: nsCOMPtr localFileRef; michael@0: NS_ASSERTION(mToken, "Need to set the token before exporting"); michael@0: // init slot michael@0: michael@0: bool InformedUserNoSmartcardBackup = false; michael@0: int numCertsExported = 0; michael@0: michael@0: rv = mToken->Login(true); michael@0: if (NS_FAILED(rv)) goto finish; michael@0: // get file password (unicode) michael@0: unicodePw.data = nullptr; michael@0: rv = newPKCS12FilePassword(&unicodePw); michael@0: if (NS_FAILED(rv)) goto finish; michael@0: if (!unicodePw.data) { michael@0: handleError(PIP_PKCS12_USER_CANCELED); michael@0: return NS_OK; michael@0: } michael@0: // what about slotToUse in psm 1.x ??? michael@0: // create export context michael@0: ecx = SEC_PKCS12CreateExportContext(nullptr, nullptr, nullptr /*slot*/, nullptr); michael@0: if (!ecx) { michael@0: srv = SECFailure; michael@0: goto finish; michael@0: } michael@0: // add password integrity michael@0: srv = SEC_PKCS12AddPasswordIntegrity(ecx, &unicodePw, SEC_OID_SHA1); michael@0: if (srv) goto finish; michael@0: for (i=0; iGetCert()); michael@0: if (!nssCert) { michael@0: rv = NS_ERROR_FAILURE; michael@0: goto finish; michael@0: } michael@0: // We can only successfully export certs that are on michael@0: // internal token. Most, if not all, smart card vendors michael@0: // won't let you extract the private key (in any way michael@0: // shape or form) from the card. So let's punt if michael@0: // the cert is not in the internal db. michael@0: if (nssCert->slot && !PK11_IsInternal(nssCert->slot)) { michael@0: // we aren't the internal token, see if the key is extractable. michael@0: SECKEYPrivateKey *privKey=PK11_FindKeyByDERCert(nssCert->slot, michael@0: nssCert.get(), this); michael@0: michael@0: if (privKey) { michael@0: bool privKeyIsExtractable = isExtractable(privKey); michael@0: michael@0: SECKEY_DestroyPrivateKey(privKey); michael@0: michael@0: if (!privKeyIsExtractable) { michael@0: if (!InformedUserNoSmartcardBackup) { michael@0: InformedUserNoSmartcardBackup = true; michael@0: handleError(PIP_PKCS12_NOSMARTCARD_EXPORT); michael@0: } michael@0: continue; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // XXX this is why, to verify the slot is the same michael@0: // PK11_FindObjectForCert(nssCert, nullptr, slot); michael@0: // create the cert and key safes michael@0: keySafe = SEC_PKCS12CreateUnencryptedSafe(ecx); michael@0: if (!SEC_PKCS12IsEncryptionAllowed() || PK11_IsFIPS()) { michael@0: certSafe = keySafe; michael@0: } else { michael@0: certSafe = SEC_PKCS12CreatePasswordPrivSafe(ecx, &unicodePw, michael@0: SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC); michael@0: } michael@0: if (!certSafe || !keySafe) { michael@0: rv = NS_ERROR_FAILURE; michael@0: goto finish; michael@0: } michael@0: // add the cert and key to the blob michael@0: srv = SEC_PKCS12AddCertAndKey(ecx, certSafe, nullptr, nssCert.get(), michael@0: CERT_GetDefaultCertDB(), // XXX michael@0: keySafe, nullptr, true, &unicodePw, michael@0: SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC); michael@0: if (srv) goto finish; michael@0: // cert was dup'ed, so release it michael@0: ++numCertsExported; michael@0: } michael@0: michael@0: if (!numCertsExported) goto finish; michael@0: michael@0: // prepare the instance to write to an export file michael@0: this->mTmpFile = nullptr; michael@0: file->GetPath(filePath); michael@0: // Use the nsCOMPtr var localFileRef so that michael@0: // the reference to the nsIFile we create gets released as soon as michael@0: // we're out of scope, ie when this function exits. michael@0: if (filePath.RFind(".p12", true, -1, 4) < 0) { michael@0: // We're going to add the .p12 extension to the file name just like michael@0: // Communicator used to. We create a new nsIFile and initialize michael@0: // it with the new patch. michael@0: filePath.AppendLiteral(".p12"); michael@0: localFileRef = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) goto finish; michael@0: localFileRef->InitWithPath(filePath); michael@0: file = localFileRef; michael@0: } michael@0: rv = file->OpenNSPRFileDesc(PR_RDWR|PR_CREATE_FILE|PR_TRUNCATE, 0664, michael@0: &mTmpFile); michael@0: if (NS_FAILED(rv) || !this->mTmpFile) goto finish; michael@0: // encode and write michael@0: srv = SEC_PKCS12Encode(ecx, write_export_file, this); michael@0: if (srv) goto finish; michael@0: handleError(PIP_PKCS12_BACKUP_OK); michael@0: finish: michael@0: if (NS_FAILED(rv) || srv != SECSuccess) { michael@0: handleError(PIP_PKCS12_BACKUP_FAILED); michael@0: } michael@0: if (ecx) michael@0: SEC_PKCS12DestroyExportContext(ecx); michael@0: if (this->mTmpFile) { michael@0: PR_Close(this->mTmpFile); michael@0: this->mTmpFile = nullptr; michael@0: } michael@0: SECITEM_ZfreeItem(&unicodePw, false); michael@0: return rv; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////// michael@0: // michael@0: // private members michael@0: // michael@0: /////////////////////////////////////////////////////////////////////// michael@0: michael@0: // unicodeToItem michael@0: // michael@0: // For the NSS PKCS#12 library, must convert PRUnichars (shorts) to michael@0: // a buffer of octets. Must handle byte order correctly. michael@0: // TODO: Is there a mozilla way to do this? In the string lib? michael@0: void michael@0: nsPKCS12Blob::unicodeToItem(const char16_t *uni, SECItem *item) michael@0: { michael@0: int len = 0; michael@0: while (uni[len++] != 0); michael@0: SECITEM_AllocItem(nullptr, item, sizeof(char16_t) * len); michael@0: #ifdef IS_LITTLE_ENDIAN michael@0: int i = 0; michael@0: for (i=0; idata[2*i ] = (unsigned char )(uni[i] << 8); michael@0: item->data[2*i+1] = (unsigned char )(uni[i]); michael@0: } michael@0: #else michael@0: memcpy(item->data, uni, item->len); michael@0: #endif michael@0: } michael@0: michael@0: // newPKCS12FilePassword michael@0: // michael@0: // Launch a dialog requesting the user for a new PKCS#12 file passowrd. michael@0: // Handle user canceled by returning null password (caller must catch). michael@0: nsresult michael@0: nsPKCS12Blob::newPKCS12FilePassword(SECItem *unicodePw) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: nsAutoString password; michael@0: nsCOMPtr certDialogs; michael@0: rv = ::getNSSDialogs(getter_AddRefs(certDialogs), michael@0: NS_GET_IID(nsICertificateDialogs), michael@0: NS_CERTIFICATEDIALOGS_CONTRACTID); michael@0: if (NS_FAILED(rv)) return rv; michael@0: bool pressedOK; michael@0: { michael@0: nsPSMUITracker tracker; michael@0: if (tracker.isUIForbidden()) { michael@0: rv = NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: else { michael@0: rv = certDialogs->SetPKCS12FilePassword(mUIContext, password, &pressedOK); michael@0: } michael@0: } michael@0: if (NS_FAILED(rv) || !pressedOK) return rv; michael@0: unicodeToItem(password.get(), unicodePw); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // getPKCS12FilePassword michael@0: // michael@0: // Launch a dialog requesting the user for the password to a PKCS#12 file. michael@0: // Handle user canceled by returning null password (caller must catch). michael@0: nsresult michael@0: nsPKCS12Blob::getPKCS12FilePassword(SECItem *unicodePw) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: nsAutoString password; michael@0: nsCOMPtr certDialogs; michael@0: rv = ::getNSSDialogs(getter_AddRefs(certDialogs), michael@0: NS_GET_IID(nsICertificateDialogs), michael@0: NS_CERTIFICATEDIALOGS_CONTRACTID); michael@0: if (NS_FAILED(rv)) return rv; michael@0: bool pressedOK; michael@0: { michael@0: nsPSMUITracker tracker; michael@0: if (tracker.isUIForbidden()) { michael@0: rv = NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: else { michael@0: rv = certDialogs->GetPKCS12FilePassword(mUIContext, password, &pressedOK); michael@0: } michael@0: } michael@0: if (NS_FAILED(rv) || !pressedOK) return rv; michael@0: unicodeToItem(password.get(), unicodePw); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // inputToDecoder michael@0: // michael@0: // Given a decoder, read bytes from file and input them to the decoder. michael@0: nsresult michael@0: nsPKCS12Blob::inputToDecoder(SEC_PKCS12DecoderContext *dcx, nsIFile *file) michael@0: { michael@0: nsNSSShutDownPreventionLock locker; michael@0: nsresult rv; michael@0: SECStatus srv; michael@0: uint32_t amount; michael@0: char buf[PIP_PKCS12_BUFFER_SIZE]; michael@0: michael@0: nsCOMPtr fileStream; michael@0: rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), file); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: while (true) { michael@0: rv = fileStream->Read(buf, PIP_PKCS12_BUFFER_SIZE, &amount); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: // feed the file data into the decoder michael@0: srv = SEC_PKCS12DecoderUpdate(dcx, michael@0: (unsigned char*) buf, michael@0: amount); michael@0: if (srv) { michael@0: // don't allow the close call to overwrite our precious error code michael@0: int pr_err = PORT_GetError(); michael@0: PORT_SetError(pr_err); michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: if (amount < PIP_PKCS12_BUFFER_SIZE) michael@0: break; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // michael@0: // C callback methods michael@0: // michael@0: michael@0: // digest_open michael@0: // prepare a memory buffer for reading/writing digests michael@0: SECStatus michael@0: nsPKCS12Blob::digest_open(void *arg, PRBool reading) michael@0: { michael@0: nsPKCS12Blob *cx = reinterpret_cast(arg); michael@0: NS_ENSURE_TRUE(cx, SECFailure); michael@0: michael@0: if (reading) { michael@0: NS_ENSURE_TRUE(cx->mDigest, SECFailure); michael@0: michael@0: delete cx->mDigestIterator; michael@0: cx->mDigestIterator = new nsCString::const_iterator; michael@0: michael@0: if (!cx->mDigestIterator) { michael@0: PORT_SetError(SEC_ERROR_NO_MEMORY); michael@0: return SECFailure; michael@0: } michael@0: michael@0: cx->mDigest->BeginReading(*cx->mDigestIterator); michael@0: } michael@0: else { michael@0: delete cx->mDigest; michael@0: cx->mDigest = new nsCString; michael@0: michael@0: if (!cx->mDigest) { michael@0: PORT_SetError(SEC_ERROR_NO_MEMORY); michael@0: return SECFailure; michael@0: } michael@0: } michael@0: michael@0: return SECSuccess; michael@0: } michael@0: michael@0: // digest_close michael@0: // destroy a possibly active iterator michael@0: // remove the data buffer if requested michael@0: SECStatus michael@0: nsPKCS12Blob::digest_close(void *arg, PRBool remove_it) michael@0: { michael@0: nsPKCS12Blob *cx = reinterpret_cast(arg); michael@0: NS_ENSURE_TRUE(cx, SECFailure); michael@0: michael@0: delete cx->mDigestIterator; michael@0: cx->mDigestIterator = nullptr; michael@0: michael@0: if (remove_it) { michael@0: delete cx->mDigest; michael@0: cx->mDigest = nullptr; michael@0: } michael@0: michael@0: return SECSuccess; michael@0: } michael@0: michael@0: // digest_read michael@0: // read bytes from the memory buffer michael@0: int michael@0: nsPKCS12Blob::digest_read(void *arg, unsigned char *buf, unsigned long len) michael@0: { michael@0: nsPKCS12Blob *cx = reinterpret_cast(arg); michael@0: NS_ENSURE_TRUE(cx, SECFailure); michael@0: NS_ENSURE_TRUE(cx->mDigest, SECFailure); michael@0: michael@0: // iterator object must exist when digest has been opened in read mode michael@0: NS_ENSURE_TRUE(cx->mDigestIterator, SECFailure); michael@0: michael@0: unsigned long available = cx->mDigestIterator->size_forward(); michael@0: michael@0: if (len > available) michael@0: len = available; michael@0: michael@0: memcpy(buf, cx->mDigestIterator->get(), len); michael@0: cx->mDigestIterator->advance(len); michael@0: michael@0: return len; michael@0: } michael@0: michael@0: // digest_write michael@0: // append bytes to the memory buffer michael@0: int michael@0: nsPKCS12Blob::digest_write(void *arg, unsigned char *buf, unsigned long len) michael@0: { michael@0: nsPKCS12Blob *cx = reinterpret_cast(arg); michael@0: NS_ENSURE_TRUE(cx, SECFailure); michael@0: NS_ENSURE_TRUE(cx->mDigest, SECFailure); michael@0: michael@0: // make sure we are in write mode, read iterator has not yet been allocated michael@0: NS_ENSURE_FALSE(cx->mDigestIterator, SECFailure); michael@0: michael@0: cx->mDigest->Append(reinterpret_cast(buf), michael@0: static_cast(len)); michael@0: michael@0: return len; michael@0: } michael@0: michael@0: // nickname_collision michael@0: // what to do when the nickname collides with one already in the db. michael@0: // TODO: not handled, throw a dialog allowing the nick to be changed? michael@0: SECItem * michael@0: nsPKCS12Blob::nickname_collision(SECItem *oldNick, PRBool *cancel, void *wincx) michael@0: { michael@0: static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); michael@0: michael@0: nsNSSShutDownPreventionLock locker; michael@0: *cancel = false; michael@0: nsresult rv; michael@0: nsCOMPtr nssComponent(do_GetService(kNSSComponentCID, &rv)); michael@0: if (NS_FAILED(rv)) return nullptr; michael@0: int count = 1; michael@0: nsCString nickname; michael@0: nsAutoString nickFromProp; michael@0: nssComponent->GetPIPNSSBundleString("P12DefaultNickname", nickFromProp); michael@0: NS_ConvertUTF16toUTF8 nickFromPropC(nickFromProp); michael@0: // The user is trying to import a PKCS#12 file that doesn't have the michael@0: // attribute we use to set the nickname. So in order to reduce the michael@0: // number of interactions we require with the user, we'll build a nickname michael@0: // for the user. The nickname isn't prominently displayed in the UI, michael@0: // so it's OK if we generate one on our own here. michael@0: // XXX If the NSS API were smarter and actually passed a pointer to michael@0: // the CERTCertificate* we're importing we could actually just michael@0: // call default_nickname (which is what the issuance code path michael@0: // does) and come up with a reasonable nickname. Alas, the NSS michael@0: // API limits our ability to produce a useful nickname without michael@0: // bugging the user. :( michael@0: while (1) { michael@0: // If we've gotten this far, that means there isn't a certificate michael@0: // in the database that has the same subject name as the cert we're michael@0: // trying to import. So we need to come up with a "nickname" to michael@0: // satisfy the NSS requirement or fail in trying to import. michael@0: // Basically we use a default nickname from a properties file and michael@0: // see if a certificate exists with that nickname. If there isn't, then michael@0: // create update the count by one and append the string '#1' Or michael@0: // whatever the count currently is, and look for a cert with michael@0: // that nickname. Keep updating the count until we find a nickname michael@0: // without a corresponding cert. michael@0: // XXX If a user imports *many* certs without the 'friendly name' michael@0: // attribute, then this may take a long time. :( michael@0: if (count > 1) { michael@0: nickname.Adopt(PR_smprintf("%s #%d", nickFromPropC.get(), count)); michael@0: } else { michael@0: nickname = nickFromPropC; michael@0: } michael@0: CERTCertificate *cert = CERT_FindCertByNickname(CERT_GetDefaultCertDB(), michael@0: const_cast(nickname.get())); michael@0: if (!cert) { michael@0: break; michael@0: } michael@0: CERT_DestroyCertificate(cert); michael@0: count++; michael@0: } michael@0: SECItem *newNick = new SECItem; michael@0: if (!newNick) michael@0: return nullptr; michael@0: michael@0: newNick->type = siAsciiString; michael@0: newNick->data = (unsigned char*) strdup(nickname.get()); michael@0: newNick->len = strlen((char*)newNick->data); michael@0: return newNick; michael@0: } michael@0: michael@0: // write_export_file michael@0: // write bytes to the exported PKCS#12 file michael@0: void michael@0: nsPKCS12Blob::write_export_file(void *arg, const char *buf, unsigned long len) michael@0: { michael@0: nsPKCS12Blob *cx = (nsPKCS12Blob *)arg; michael@0: PR_Write(cx->mTmpFile, buf, len); michael@0: } michael@0: michael@0: // pip_ucs2_ascii_conversion_fn michael@0: // required to be set by NSS (to do PKCS#12), but since we've already got michael@0: // unicode make this a no-op. michael@0: PRBool michael@0: pip_ucs2_ascii_conversion_fn(PRBool toUnicode, michael@0: unsigned char *inBuf, michael@0: unsigned int inBufLen, michael@0: unsigned char *outBuf, michael@0: unsigned int maxOutBufLen, michael@0: unsigned int *outBufLen, michael@0: PRBool swapBytes) michael@0: { michael@0: // do a no-op, since I've already got unicode. Hah! michael@0: *outBufLen = inBufLen; michael@0: memcpy(outBuf, inBuf, inBufLen); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsPKCS12Blob::handleError(int myerr) michael@0: { michael@0: static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); michael@0: michael@0: if (!NS_IsMainThread()) { michael@0: NS_ERROR("nsPKCS12Blob::handleError called off the mai nthread."); michael@0: return; michael@0: } michael@0: michael@0: int prerr = PORT_GetError(); michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("PKCS12: NSS/NSPR error(%d)", prerr)); michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("PKCS12: I called(%d)", myerr)); michael@0: michael@0: const char * msgID = nullptr; michael@0: michael@0: switch (myerr) { michael@0: case PIP_PKCS12_RESTORE_OK: msgID = "SuccessfulP12Restore"; break; michael@0: case PIP_PKCS12_BACKUP_OK: msgID = "SuccessfulP12Backup"; break; michael@0: case PIP_PKCS12_USER_CANCELED: michael@0: return; /* Just ignore it for now */ michael@0: case PIP_PKCS12_NOSMARTCARD_EXPORT: msgID = "PKCS12InfoNoSmartcardBackup"; break; michael@0: case PIP_PKCS12_RESTORE_FAILED: msgID = "PKCS12UnknownErrRestore"; break; michael@0: case PIP_PKCS12_BACKUP_FAILED: msgID = "PKCS12UnknownErrBackup"; break; michael@0: case PIP_PKCS12_NSS_ERROR: michael@0: switch (prerr) { michael@0: // The following errors have the potential to be "handled", by asking michael@0: // the user (via a dialog) whether s/he wishes to continue michael@0: case 0: break; michael@0: case SEC_ERROR_PKCS12_CERT_COLLISION: michael@0: /* pop a dialog saying the cert is already in the database */ michael@0: /* ask to keep going? what happens if one collision but others ok? */ michael@0: // The following errors cannot be "handled", notify the user (via an alert) michael@0: // that the operation failed. michael@0: case SEC_ERROR_BAD_PASSWORD: msgID = "PK11BadPassword"; break; michael@0: michael@0: case SEC_ERROR_BAD_DER: michael@0: case SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE: michael@0: case SEC_ERROR_PKCS12_INVALID_MAC: michael@0: msgID = "PKCS12DecodeErr"; michael@0: break; michael@0: michael@0: case SEC_ERROR_PKCS12_DUPLICATE_DATA: msgID = "PKCS12DupData"; break; michael@0: } michael@0: break; michael@0: } michael@0: michael@0: if (!msgID) michael@0: msgID = "PKCS12UnknownErr"; michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr nssComponent = do_GetService(kNSSComponentCID, &rv); michael@0: if (NS_SUCCEEDED(rv)) michael@0: (void) nssComponent->ShowAlertFromStringBundle(msgID); michael@0: }