summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/nsPKCS12Blob.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /security/manager/ssl/nsPKCS12Blob.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/manager/ssl/nsPKCS12Blob.cpp')
-rw-r--r--security/manager/ssl/nsPKCS12Blob.cpp357
1 files changed, 357 insertions, 0 deletions
diff --git a/security/manager/ssl/nsPKCS12Blob.cpp b/security/manager/ssl/nsPKCS12Blob.cpp
new file mode 100644
index 0000000000..2814626cfc
--- /dev/null
+++ b/security/manager/ssl/nsPKCS12Blob.cpp
@@ -0,0 +1,357 @@
+/* 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/Logging.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;
+}