/* 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/. */ #include "nsPKCS12Blob.h" #include "mozilla/Assertions.h" #include "mozilla/Casting.h" #include "mozilla/Preferences.h" #include "mozilla/Unused.h" #include "mozpkix/pkixtypes.h" #include "nsIFile.h" #include "nsIInputStream.h" #include "nsIX509CertDB.h" #include "nsNetUtil.h" #include "nsNSSCertHelper.h" #include "nsNSSCertificate.h" #include "nsNSSHelper.h" #include "nsReadableUtils.h" #include "nsTArray.h" #include "nsThreadUtils.h" #include "p12plcy.h" #include "ScopedNSSTypes.h" #include "secerr.h" using namespace mozilla; extern LazyLogModule gPIPNSSLog; #define PIP_PKCS12_BUFFER_SIZE 2048 #define PIP_PKCS12_NOSMARTCARD_EXPORT 4 #define PIP_PKCS12_RESTORE_FAILED 5 #define PIP_PKCS12_BACKUP_FAILED 6 #define PIP_PKCS12_NSS_ERROR 7 nsPKCS12Blob::nsPKCS12Blob() : mUIContext(new PipUIContext()) {} // Given a file handle, read a PKCS#12 blob from that file, decode it, and // import the results into the internal database. nsresult nsPKCS12Blob::ImportFromFile(nsIFile* aFile, const nsAString& aPassword, uint32_t& aError) { uint32_t passwordBufferLength; UniquePtr passwordBuffer; UniquePK11SlotInfo slot(PK11_GetInternalKeySlot()); if (!slot) { return NS_ERROR_FAILURE; } passwordBuffer = stringToBigEndianBytes(aPassword, passwordBufferLength); // initialize the decoder SECItem unicodePw = {siBuffer, passwordBuffer.get(), passwordBufferLength}; UniqueSEC_PKCS12DecoderContext dcx( SEC_PKCS12DecoderStart(&unicodePw, slot.get(), nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)); if (!dcx) { return NS_ERROR_FAILURE; } // read input aFile and feed it to the decoder PRErrorCode nssError; nsresult rv = inputToDecoder(dcx, aFile, nssError); if (NS_FAILED(rv)) { return rv; } if (nssError != 0) { aError = handlePRErrorCode(nssError); return NS_OK; } // verify the blob SECStatus srv = SEC_PKCS12DecoderVerify(dcx.get()); if (srv != SECSuccess) { aError = handlePRErrorCode(PR_GetError()); return NS_OK; } // validate bags srv = SEC_PKCS12DecoderValidateBags(dcx.get(), nicknameCollision); if (srv != SECSuccess) { aError = handlePRErrorCode(PR_GetError()); return NS_OK; } // import cert and key srv = SEC_PKCS12DecoderImportBags(dcx.get()); if (srv != SECSuccess) { aError = handlePRErrorCode(PR_GetError()); return NS_OK; } aError = nsIX509CertDB::Success; return NS_OK; } static bool isExtractable(UniqueSECKEYPrivateKey& privKey) { ScopedAutoSECItem 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; } // 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* aFile, const nsTArray>& aCerts, const nsAString& aPassword, uint32_t& aError) { nsCString passwordUtf8 = NS_ConvertUTF16toUTF8(aPassword); uint32_t passwordBufferLength = passwordUtf8.Length(); aError = nsIX509CertDB::Success; // The conversion to UCS2 is executed by sec_pkcs12_encode_password when // necessary (for some older PKCS12 algorithms). The NSS 3.31 and newer // expects password to be in the utf8 encoding to support modern encoders. UniquePtr passwordBuffer( reinterpret_cast(ToNewCString(passwordUtf8))); if (!passwordBuffer.get()) { return NS_OK; } UniqueSEC_PKCS12ExportContext ecx( SEC_PKCS12CreateExportContext(nullptr, nullptr, nullptr, nullptr)); if (!ecx) { aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED; return NS_OK; } // add password integrity SECItem unicodePw = {siBuffer, passwordBuffer.get(), passwordBufferLength}; SECStatus srv = SEC_PKCS12AddPasswordIntegrity(ecx.get(), &unicodePw, SEC_OID_SHA1); if (srv != SECSuccess) { aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED; return NS_OK; } for (auto& cert : aCerts) { UniqueCERTCertificate nssCert(cert->GetCert()); if (!nssCert) { aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED; return NS_OK; } // We can probably only successfully export certs that are on the internal // token. Most, if not all, smart card vendors won't let you extract the // private key (in any way shape or form) from the card. So let's punt if // the cert is not in the internal db. if (nssCert->slot && !PK11_IsInternal(nssCert->slot)) { // We aren't the internal token, see if the key is extractable. UniqueSECKEYPrivateKey privKey( PK11_FindKeyByDERCert(nssCert->slot, nssCert.get(), mUIContext)); if (privKey && !isExtractable(privKey)) { // This is informative. If a serious error occurs later it will // override it later and return. aError = nsIX509CertDB::ERROR_PKCS12_NOSMARTCARD_EXPORT; continue; } } // certSafe and keySafe are owned by ecx. SEC_PKCS12SafeInfo* certSafe; SEC_PKCS12SafeInfo* keySafe = SEC_PKCS12CreateUnencryptedSafe(ecx.get()); bool useModernCrypto = Preferences::GetBool( "security.pki.use_modern_crypto_with_pkcs12", false); // We use SEC_OID_AES_128_CBC for the password and SEC_OID_AES_256_CBC // for the certificate because it's a default for openssl an pk12util // command. if (!SEC_PKCS12IsEncryptionAllowed() || PK11_IsFIPS()) { certSafe = keySafe; } else { SECOidTag privAlg = useModernCrypto ? SEC_OID_AES_128_CBC : SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC; certSafe = SEC_PKCS12CreatePasswordPrivSafe(ecx.get(), &unicodePw, privAlg); } if (!certSafe || !keySafe) { aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED; return NS_OK; } // add the cert and key to the blob SECOidTag algorithm = useModernCrypto ? SEC_OID_AES_256_CBC : SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC; srv = SEC_PKCS12AddCertAndKey(ecx.get(), certSafe, nullptr, nssCert.get(), CERT_GetDefaultCertDB(), keySafe, nullptr, true, &unicodePw, algorithm); if (srv != SECSuccess) { aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED; return NS_OK; } } UniquePRFileDesc prFile; PRFileDesc* rawPRFile; nsresult rv = aFile->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 0664, &rawPRFile); if (NS_FAILED(rv) || !rawPRFile) { aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED; return NS_OK; } prFile.reset(rawPRFile); // encode and write srv = SEC_PKCS12Encode(ecx.get(), writeExportFile, prFile.get()); if (srv != SECSuccess) { aError = nsIX509CertDB::ERROR_PKCS12_BACKUP_FAILED; } return NS_OK; } // For the NSS PKCS#12 library, must convert PRUnichars (shorts) to a buffer of // octets. Must handle byte order correctly. UniquePtr nsPKCS12Blob::stringToBigEndianBytes( const nsAString& uni, uint32_t& bytesLength) { if (uni.IsVoid()) { bytesLength = 0; return nullptr; } uint32_t wideLength = uni.Length() + 1; // +1 for the null terminator. bytesLength = wideLength * 2; auto buffer = MakeUnique(bytesLength); // We have to use a cast here because on Windows, uni.get() returns // char16ptr_t instead of char16_t*. mozilla::NativeEndian::copyAndSwapToBigEndian( buffer.get(), static_cast(uni.BeginReading()), wideLength); return buffer; } // Given a decoder, read bytes from file and input them to the decoder. nsresult nsPKCS12Blob::inputToDecoder(UniqueSEC_PKCS12DecoderContext& dcx, nsIFile* file, PRErrorCode& nssError) { nssError = 0; nsCOMPtr fileStream; 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 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) { break; } } return NS_OK; } // What to do when the nickname collides with one already in the db. SECItem* 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; } NS_ConvertUTF16toUTF8 nickFromPropC(nickFromProp); // The user is trying to import a PKCS#12 file that doesn't have the // attribute we use to set the nickname. So in order to reduce the // number of interactions we require with the user, we'll build a nickname // for the user. The nickname isn't prominently displayed in the UI, // so it's OK if we generate one on our own here. // XXX If the NSS API were smarter and actually passed a pointer to // the CERTCertificate* we're importing we could actually just // call default_nickname (which is what the issuance code path // does) and come up with a reasonable nickname. Alas, the NSS // API limits our ability to produce a useful nickname without // bugging the user. :( while (1) { // If we've gotten this far, that means there isn't a certificate // in the database that has the same subject name as the cert we're // trying to import. So we need to come up with a "nickname" to // satisfy the NSS requirement or fail in trying to import. // Basically we use a default nickname from a properties file and // see if a certificate exists with that nickname. If there isn't, then // create update the count by one and append the string '#1' Or // whatever the count currently is, and look for a cert with // that nickname. Keep updating the count until we find a nickname // without a corresponding cert. // XXX If a user imports *many* certs without the 'friendly name' // attribute, then this may take a long time. :( nickname = nickFromPropC; if (count > 1) { nickname.AppendPrintf(" #%d", count); } UniqueCERTCertificate cert( CERT_FindCertByNickname(CERT_GetDefaultCertDB(), nickname.get())); if (!cert) { break; } count++; } 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; return newNick.release(); } // write bytes to the exported PKCS#12 file void nsPKCS12Blob::writeExportFile(void* arg, const char* buf, unsigned long len) { PRFileDesc* file = static_cast(arg); MOZ_RELEASE_ASSERT(file); PR_Write(file, buf, len); } // Translate PRErrorCode to nsIX509CertDB error uint32_t nsPKCS12Blob::handlePRErrorCode(PRErrorCode aPrerr) { MOZ_ASSERT(aPrerr != 0); uint32_t error = nsIX509CertDB::ERROR_UNKNOWN; switch (aPrerr) { case SEC_ERROR_PKCS12_CERT_COLLISION: error = nsIX509CertDB::ERROR_PKCS12_DUPLICATE_DATA; break; // INVALID_ARGS is returned on bad password when importing cert // exported from firefox or generated by openssl case SEC_ERROR_INVALID_ARGS: case SEC_ERROR_BAD_PASSWORD: error = nsIX509CertDB::ERROR_BAD_PASSWORD; break; case SEC_ERROR_BAD_DER: case SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE: case SEC_ERROR_PKCS12_INVALID_MAC: error = nsIX509CertDB::ERROR_DECODE_ERROR; break; case SEC_ERROR_PKCS12_DUPLICATE_DATA: error = nsIX509CertDB::ERROR_PKCS12_DUPLICATE_DATA; break; } return error; }