/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the Netscape security libraries. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 2000 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Ian McGreer * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "net/third_party/mozilla_security_manager/nsPKCS12Blob.h" #include #include #include #include #include "base/lazy_instance.h" #include "base/logging.h" #include "base/strings/string_util.h" #include "crypto/nss_util_internal.h" #include "net/base/net_errors.h" namespace mozilla_security_manager { namespace { // unicodeToItem // // For the NSS PKCS#12 library, must convert PRUnichars (shorts) to // a buffer of octets. Must handle byte order correctly. // TODO: Is there a Mozilla way to do this? In the string lib? void unicodeToItem(const PRUnichar *uni, SECItem *item) { int len = 0; while (uni[len++] != 0); SECITEM_AllocItem(NULL, item, sizeof(PRUnichar) * len); #ifdef IS_LITTLE_ENDIAN int i = 0; for (i=0; idata[2*i ] = (unsigned char )(uni[i] << 8); item->data[2*i+1] = (unsigned char )(uni[i]); } #else memcpy(item->data, uni, item->len); #endif } // write_export_data // write bytes to the exported PKCS#12 data buffer void write_export_data(void* arg, const char* buf, unsigned long len) { std::string* dest = reinterpret_cast(arg); dest->append(buf, len); } // nickname_collision // what to do when the nickname collides with one already in the db. // Based on P12U_NicknameCollisionCallback from nss/cmd/pk12util/pk12util.c SECItem* PR_CALLBACK nickname_collision(SECItem *old_nick, PRBool *cancel, void *wincx) { char *nick = NULL; SECItem *ret_nick = NULL; CERTCertificate* cert = (CERTCertificate*)wincx; if (!cancel || !cert) { // pk12util calls this error user cancelled? return NULL; } if (!old_nick) VLOG(1) << "no nickname for cert in PKCS12 file."; nick = CERT_MakeCANickname(cert); if (!nick) { return NULL; } if(old_nick && old_nick->data && old_nick->len && PORT_Strlen(nick) == old_nick->len && !PORT_Strncmp((char *)old_nick->data, nick, old_nick->len)) { PORT_Free(nick); PORT_SetError(SEC_ERROR_IO); return NULL; } VLOG(1) << "using nickname " << nick; ret_nick = PORT_ZNew(SECItem); if(ret_nick == NULL) { PORT_Free(nick); return NULL; } ret_nick->data = (unsigned char *)nick; ret_nick->len = PORT_Strlen(nick); return ret_nick; } // 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) { CHECK_GE(maxOutBufLen, inBufLen); // do a no-op, since I've already got Unicode. Hah! *outBufLen = inBufLen; memcpy(outBuf, inBuf, inBufLen); return PR_TRUE; } // Based on nsPKCS12Blob::ImportFromFileHelper. int nsPKCS12Blob_ImportHelper(const char* pkcs12_data, size_t pkcs12_len, const base::string16& password, bool is_extractable, bool try_zero_length_secitem, PK11SlotInfo* slot, net::ScopedCERTCertificateList* imported_certs) { DCHECK(pkcs12_data); DCHECK(slot); int import_result = net::ERR_PKCS12_IMPORT_FAILED; SECStatus srv = SECSuccess; SEC_PKCS12DecoderContext *dcx = NULL; SECItem unicodePw; SECItem attribute_value; CK_BBOOL attribute_data = CK_FALSE; const SEC_PKCS12DecoderItem* decoder_item = NULL; unicodePw.type = siBuffer; unicodePw.len = 0; unicodePw.data = NULL; if (!try_zero_length_secitem) { unicodeToItem(password.c_str(), &unicodePw); } // Initialize the decoder dcx = SEC_PKCS12DecoderStart(&unicodePw, slot, // wincx NULL, // dOpen, dClose, dRead, dWrite, dArg: NULL // specifies default impl using memory buffer. NULL, NULL, NULL, NULL, NULL); if (!dcx) { srv = SECFailure; goto finish; } // feed input to the decoder srv = SEC_PKCS12DecoderUpdate(dcx, (unsigned char*)pkcs12_data, pkcs12_len); if (srv) goto finish; // verify the blob srv = SEC_PKCS12DecoderVerify(dcx); if (srv) goto finish; // validate bags srv = SEC_PKCS12DecoderValidateBags(dcx, nickname_collision); if (srv) goto finish; // import certificate and key srv = SEC_PKCS12DecoderImportBags(dcx); if (srv) goto finish; attribute_value.data = &attribute_data; attribute_value.len = sizeof(attribute_data); srv = SEC_PKCS12DecoderIterateInit(dcx); if (srv) goto finish; if (imported_certs) imported_certs->clear(); // Collect the list of decoded certificates, and mark private keys // non-extractable if needed. while (SEC_PKCS12DecoderIterateNext(dcx, &decoder_item) == SECSuccess) { if (decoder_item->type != SEC_OID_PKCS12_V1_CERT_BAG_ID) continue; net::ScopedCERTCertificate cert( PK11_FindCertFromDERCertItem(slot, decoder_item->der, NULL)); // wincx if (!cert) { LOG(ERROR) << "Could not grab a handle to the certificate in the slot " << "from the corresponding PKCS#12 DER certificate."; continue; } // Once we have determined that the imported certificate has an // associated private key too, only then can we mark the key as // non-extractable. // Iterate through all the imported PKCS12 items and mark any accompanying // private keys as non-extractable. if (decoder_item->hasKey && !is_extractable) { SECKEYPrivateKey* privKey = PK11_FindPrivateKeyFromCert(slot, cert.get(), NULL); // wincx if (privKey) { // Mark the private key as non-extractable. srv = PK11_WriteRawAttribute(PK11_TypePrivKey, privKey, CKA_EXTRACTABLE, &attribute_value); SECKEY_DestroyPrivateKey(privKey); if (srv) { LOG(ERROR) << "Could not set CKA_EXTRACTABLE attribute on private " << "key."; break; } } } // Add the cert to the list if (imported_certs) imported_certs->push_back(std::move(cert)); if (srv) goto finish; } import_result = net::OK; 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. if (srv != SECSuccess) { int error = PORT_GetError(); LOG(ERROR) << "PKCS#12 import failed with error " << error; switch (error) { case SEC_ERROR_BAD_PASSWORD: case SEC_ERROR_PKCS12_PRIVACY_PASSWORD_INCORRECT: import_result = net::ERR_PKCS12_IMPORT_BAD_PASSWORD; break; case SEC_ERROR_PKCS12_INVALID_MAC: import_result = net::ERR_PKCS12_IMPORT_INVALID_MAC; break; case SEC_ERROR_BAD_DER: case SEC_ERROR_PKCS12_DECODING_PFX: case SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE: import_result = net::ERR_PKCS12_IMPORT_INVALID_FILE; break; case SEC_ERROR_PKCS12_UNSUPPORTED_MAC_ALGORITHM: case SEC_ERROR_PKCS12_UNSUPPORTED_TRANSPORT_MODE: case SEC_ERROR_PKCS12_UNSUPPORTED_PBE_ALGORITHM: case SEC_ERROR_PKCS12_UNSUPPORTED_VERSION: import_result = net::ERR_PKCS12_IMPORT_UNSUPPORTED; break; default: import_result = net::ERR_PKCS12_IMPORT_FAILED; break; } } // Finish the decoder if (dcx) SEC_PKCS12DecoderFinish(dcx); SECITEM_ZfreeItem(&unicodePw, PR_FALSE); return import_result; } // Attempt to read the CKA_EXTRACTABLE attribute on a private key inside // a token. On success, store the attribute in |extractable| and return // SECSuccess. SECStatus isExtractable(SECKEYPrivateKey *privKey, PRBool *extractable) { SECItem value; SECStatus rv; rv=PK11_ReadRawAttribute(PK11_TypePrivKey, privKey, CKA_EXTRACTABLE, &value); if (rv != SECSuccess) return rv; if ((value.len == 1) && (value.data != NULL)) *extractable = !!(*(CK_BBOOL*)value.data); else rv = SECFailure; SECITEM_FreeItem(&value, PR_FALSE); return rv; } class PKCS12InitSingleton { public: // From the PKCS#12 section of nsNSSComponent::InitializeNSS in // nsNSSComponent.cpp. PKCS12InitSingleton() { // 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); // Set no-op ascii-ucs2 conversion function to work around weird NSS // interface. Thankfully, PKCS12 appears to be the only thing in NSS that // uses PORT_UCS2_ASCIIConversion, so this doesn't break anything else. PORT_SetUCS2_ASCIIConversionFunction(pip_ucs2_ascii_conversion_fn); } }; // Leaky so it can be initialized on worker threads and because there is no // cleanup necessary. static base::LazyInstance::Leaky g_pkcs12_init_singleton = LAZY_INSTANCE_INITIALIZER; } // namespace void EnsurePKCS12Init() { g_pkcs12_init_singleton.Get(); } // Based on nsPKCS12Blob::ImportFromFile. int nsPKCS12Blob_Import(PK11SlotInfo* slot, const char* pkcs12_data, size_t pkcs12_len, const base::string16& password, bool is_extractable, net::ScopedCERTCertificateList* imported_certs) { int rv = nsPKCS12Blob_ImportHelper(pkcs12_data, pkcs12_len, password, is_extractable, false, slot, imported_certs); // 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. if ((rv == net::ERR_PKCS12_IMPORT_BAD_PASSWORD || rv == net::ERR_PKCS12_IMPORT_INVALID_MAC) && password.empty()) { rv = nsPKCS12Blob_ImportHelper(pkcs12_data, pkcs12_len, password, is_extractable, true, slot, imported_certs); } return rv; } // Based on 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. // // TODO: handle slots correctly // mirror "slotToUse" behavior from PSM 1.x // verify the cert array to start off with? // set appropriate error codes int nsPKCS12Blob_Export(std::string* output, const net::ScopedCERTCertificateList& certs, const base::string16& password) { int return_count = 0; SECStatus srv = SECSuccess; SEC_PKCS12ExportContext *ecx = NULL; SEC_PKCS12SafeInfo *certSafe = NULL, *keySafe = NULL; SECItem unicodePw; unicodePw.type = siBuffer; unicodePw.len = 0; unicodePw.data = NULL; int numCertsExported = 0; // get file password (unicode) unicodeToItem(password.c_str(), &unicodePw); // what about slotToUse in psm 1.x ??? // create export context ecx = SEC_PKCS12CreateExportContext(NULL, NULL, NULL /*slot*/, NULL); if (!ecx) { srv = SECFailure; goto finish; } // add password integrity srv = SEC_PKCS12AddPasswordIntegrity(ecx, &unicodePw, SEC_OID_SHA1); if (srv) goto finish; for (size_t i=0; islot) { SECKEYPrivateKey *privKey=PK11_FindKeyByDERCert(nssCert->slot, nssCert, NULL); // wincx if (privKey) { PRBool privKeyIsExtractable = PR_FALSE; SECStatus rv = isExtractable(privKey, &privKeyIsExtractable); SECKEY_DestroyPrivateKey(privKey); if (rv == SECSuccess && !privKeyIsExtractable) { LOG(ERROR) << "Private key is not extractable"; continue; } } } // XXX this is why, to verify the slot is the same // PK11_FindObjectForCert(nssCert, NULL, slot); // create the cert and key safes keySafe = SEC_PKCS12CreateUnencryptedSafe(ecx); 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); } if (!certSafe || !keySafe) { LOG(ERROR) << "!certSafe || !keySafe " << certSafe << " " << keySafe; srv = SECFailure; goto finish; } // add the cert and key to the blob srv = SEC_PKCS12AddCertAndKey(ecx, certSafe, NULL, nssCert, CERT_GetDefaultCertDB(), keySafe, NULL, PR_TRUE, &unicodePw, SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC); if (srv) goto finish; ++numCertsExported; } if (!numCertsExported) goto finish; // encode and write srv = SEC_PKCS12Encode(ecx, write_export_data, output); if (srv) goto finish; return_count = numCertsExported; finish: if (srv) LOG(ERROR) << "PKCS#12 export failed with error " << PORT_GetError(); if (ecx) SEC_PKCS12DestroyExportContext(ecx); SECITEM_ZfreeItem(&unicodePw, PR_FALSE); return return_count; } } // namespace mozilla_security_manager