diff options
Diffstat (limited to '')
-rw-r--r-- | security/manager/ssl/nsPKCS12Blob.cpp | 356 |
1 files changed, 356 insertions, 0 deletions
diff --git a/security/manager/ssl/nsPKCS12Blob.cpp b/security/manager/ssl/nsPKCS12Blob.cpp new file mode 100644 index 0000000000..440e969f99 --- /dev/null +++ b/security/manager/ssl/nsPKCS12Blob.cpp @@ -0,0 +1,356 @@ +/* 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<uint8_t[]> 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<RefPtr<nsIX509Cert>>& 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<unsigned char[]> passwordBuffer( + reinterpret_cast<unsigned char*>(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<uint8_t[]> 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<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( + buffer.get(), static_cast<const char16_t*>(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<nsIInputStream> 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<PRFileDesc*>(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; +} |