diff options
Diffstat (limited to 'system/security/pkix/test/lib/pkixtestnss.cpp')
-rw-r--r-- | system/security/pkix/test/lib/pkixtestnss.cpp | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/system/security/pkix/test/lib/pkixtestnss.cpp b/system/security/pkix/test/lib/pkixtestnss.cpp new file mode 100644 index 000000000..001fdfbc4 --- /dev/null +++ b/system/security/pkix/test/lib/pkixtestnss.cpp @@ -0,0 +1,347 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "pkix/test/pkixtestutil.h" +#include "pkix/test/pkixtestnss.h" + +#include <limits> + +#include "cryptohi.h" +#include "keyhi.h" +#include "nss.h" +#include "pk11pqg.h" +#include "pk11pub.h" +#include "nss_scoped_ptrs.h" +#include "pkix/pkixnss.h" +#include "pkix/pkixder.h" +#include "pkix/pkixutil.h" +#include "prinit.h" +#include "secerr.h" +#include "secitem.h" + +namespace mozilla { namespace pkix { namespace test { + +namespace { + +TestKeyPair* GenerateKeyPairInner(); + +void +InitNSSIfNeeded() +{ + if (NSS_NoDB_Init(nullptr) != SECSuccess) { + abort(); + } +} + +static ScopedTestKeyPair reusedKeyPair; + +PRStatus +InitReusedKeyPair() +{ + InitNSSIfNeeded(); + reusedKeyPair.reset(GenerateKeyPairInner()); + return reusedKeyPair ? PR_SUCCESS : PR_FAILURE; +} + +class NSSTestKeyPair final : public TestKeyPair +{ +public: + NSSTestKeyPair(const TestPublicKeyAlgorithm& aPublicKeyAlg, + const ByteString& spk, + const ByteString& aEncryptedPrivateKey, + const ByteString& aEncryptionAlgorithm, + const ByteString& aEncryptionParams) + : TestKeyPair(aPublicKeyAlg, spk) + , encryptedPrivateKey(aEncryptedPrivateKey) + , encryptionAlgorithm(aEncryptionAlgorithm) + , encryptionParams(aEncryptionParams) + { + } + + Result SignData(const ByteString& tbs, + const TestSignatureAlgorithm& signatureAlgorithm, + /*out*/ ByteString& signature) const override + { + SECOidTag oidTag; + if (signatureAlgorithm.publicKeyAlg == RSA_PKCS1()) { + switch (signatureAlgorithm.digestAlg) { + case TestDigestAlgorithmID::MD2: + oidTag = SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION; + break; + case TestDigestAlgorithmID::MD5: + oidTag = SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION; + break; + case TestDigestAlgorithmID::SHA1: + oidTag = SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION; + break; + case TestDigestAlgorithmID::SHA224: + oidTag = SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION; + break; + case TestDigestAlgorithmID::SHA256: + oidTag = SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION; + break; + case TestDigestAlgorithmID::SHA384: + oidTag = SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION; + break; + case TestDigestAlgorithmID::SHA512: + oidTag = SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION; + break; + MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM + } + } else { + abort(); + } + + ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); + if (!slot) { + return MapPRErrorCodeToResult(PR_GetError()); + } + SECItem encryptedPrivateKeyInfoItem = { + siBuffer, + const_cast<uint8_t*>(encryptedPrivateKey.data()), + static_cast<unsigned int>(encryptedPrivateKey.length()) + }; + SECItem encryptionAlgorithmItem = { + siBuffer, + const_cast<uint8_t*>(encryptionAlgorithm.data()), + static_cast<unsigned int>(encryptionAlgorithm.length()) + }; + SECItem encryptionParamsItem = { + siBuffer, + const_cast<uint8_t*>(encryptionParams.data()), + static_cast<unsigned int>(encryptionParams.length()) + }; + SECKEYEncryptedPrivateKeyInfo encryptedPrivateKeyInfo = { + nullptr, + { encryptionAlgorithmItem, encryptionParamsItem }, + encryptedPrivateKeyInfoItem + }; + SECItem passwordItem = { siBuffer, nullptr, 0 }; + SECItem publicValueItem = { + siBuffer, + const_cast<uint8_t*>(subjectPublicKey.data()), + static_cast<unsigned int>(subjectPublicKey.length()) + }; + SECKEYPrivateKey* privateKey; + // This should always be an RSA key (we'll have aborted above if we're not + // doing an RSA signature). + if (PK11_ImportEncryptedPrivateKeyInfoAndReturnKey( + slot.get(), &encryptedPrivateKeyInfo, &passwordItem, nullptr, + &publicValueItem, false, false, rsaKey, KU_ALL, &privateKey, + nullptr) != SECSuccess) { + return MapPRErrorCodeToResult(PR_GetError()); + } + ScopedSECKEYPrivateKey scopedPrivateKey(privateKey); + SECItem signatureItem; + if (SEC_SignData(&signatureItem, tbs.data(), + static_cast<int>(tbs.length()), + scopedPrivateKey.get(), oidTag) != SECSuccess) { + return MapPRErrorCodeToResult(PR_GetError()); + } + signature.assign(signatureItem.data, signatureItem.len); + SECITEM_FreeItem(&signatureItem, false); + return Success; + } + + TestKeyPair* Clone() const override + { + return new (std::nothrow) NSSTestKeyPair(publicKeyAlg, + subjectPublicKey, + encryptedPrivateKey, + encryptionAlgorithm, + encryptionParams); + } + +private: + const ByteString encryptedPrivateKey; + const ByteString encryptionAlgorithm; + const ByteString encryptionParams; +}; + +} // namespace + +// This private function is also used by Gecko's PSM test framework +// (OCSPCommon.cpp). +TestKeyPair* CreateTestKeyPair(const TestPublicKeyAlgorithm publicKeyAlg, + const ScopedSECKEYPublicKey& publicKey, + const ScopedSECKEYPrivateKey& privateKey) +{ + ScopedCERTSubjectPublicKeyInfo + spki(SECKEY_CreateSubjectPublicKeyInfo(publicKey.get())); + if (!spki) { + return nullptr; + } + SECItem spkDER = spki->subjectPublicKey; + DER_ConvertBitString(&spkDER); // bits to bytes + ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); + if (!slot) { + return nullptr; + } + // Because NSSTestKeyPair isn't tracked by XPCOM and won't otherwise be aware + // of shutdown, we don't have a way to release NSS resources at the + // appropriate time. To work around this, NSSTestKeyPair doesn't hold on to + // NSS resources. Instead, we export the generated private key part as an + // encrypted blob (with an empty password and fairly lame encryption). When we + // need to use it (e.g. to sign something), we decrypt it and create a + // temporary key object. + SECItem passwordItem = { siBuffer, nullptr, 0 }; + ScopedSECKEYEncryptedPrivateKeyInfo encryptedPrivateKey( + PK11_ExportEncryptedPrivKeyInfo( + slot.get(), SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC, + &passwordItem, privateKey.get(), 1, nullptr)); + if (!encryptedPrivateKey) { + return nullptr; + } + + return new (std::nothrow) NSSTestKeyPair( + publicKeyAlg, + ByteString(spkDER.data, spkDER.len), + ByteString(encryptedPrivateKey->encryptedData.data, + encryptedPrivateKey->encryptedData.len), + ByteString(encryptedPrivateKey->algorithm.algorithm.data, + encryptedPrivateKey->algorithm.algorithm.len), + ByteString(encryptedPrivateKey->algorithm.parameters.data, + encryptedPrivateKey->algorithm.parameters.len)); +} + +namespace { + +TestKeyPair* +GenerateKeyPairInner() +{ + ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); + if (!slot) { + abort(); + } + PK11RSAGenParams params; + params.keySizeInBits = 2048; + params.pe = 65537; + + // Bug 1012786: PK11_GenerateKeyPair can fail if there is insufficient + // entropy to generate a random key. Attempting to add some entropy and + // retrying appears to solve this issue. + for (uint32_t retries = 0; retries < 10; retries++) { + SECKEYPublicKey* publicKeyTemp = nullptr; + ScopedSECKEYPrivateKey + privateKey(PK11_GenerateKeyPair(slot.get(), CKM_RSA_PKCS_KEY_PAIR_GEN, + ¶ms, &publicKeyTemp, false, true, + nullptr)); + ScopedSECKEYPublicKey publicKey(publicKeyTemp); + if (privateKey) { + return CreateTestKeyPair(RSA_PKCS1(), publicKey, privateKey); + } + + assert(!publicKeyTemp); + + if (PR_GetError() != SEC_ERROR_PKCS11_FUNCTION_FAILED) { + break; + } + + // Since these keys are only for testing, we don't need them to be good, + // random keys. + // https://xkcd.com/221/ + static const uint8_t RANDOM_NUMBER[] = { 4, 4, 4, 4, 4, 4, 4, 4 }; + if (PK11_RandomUpdate( + const_cast<void*>(reinterpret_cast<const void*>(RANDOM_NUMBER)), + sizeof(RANDOM_NUMBER)) != SECSuccess) { + break; + } + } + + abort(); +} + +} // namespace + +TestKeyPair* +GenerateKeyPair() +{ + InitNSSIfNeeded(); + return GenerateKeyPairInner(); +} + +TestKeyPair* +CloneReusedKeyPair() +{ + static PRCallOnceType initCallOnce; + if (PR_CallOnce(&initCallOnce, InitReusedKeyPair) != PR_SUCCESS) { + abort(); + } + assert(reusedKeyPair); + return reusedKeyPair->Clone(); +} + +TestKeyPair* +GenerateDSSKeyPair() +{ + InitNSSIfNeeded(); + + ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); + if (!slot) { + return nullptr; + } + + ByteString p(DSS_P()); + ByteString q(DSS_Q()); + ByteString g(DSS_G()); + + static const PQGParams PARAMS = { + nullptr, + { siBuffer, + const_cast<uint8_t*>(p.data()), + static_cast<unsigned int>(p.length()) + }, + { siBuffer, + const_cast<uint8_t*>(q.data()), + static_cast<unsigned int>(q.length()) + }, + { siBuffer, + const_cast<uint8_t*>(g.data()), + static_cast<unsigned int>(g.length()) + } + }; + + SECKEYPublicKey* publicKeyTemp = nullptr; + ScopedSECKEYPrivateKey + privateKey(PK11_GenerateKeyPair(slot.get(), CKM_DSA_KEY_PAIR_GEN, + const_cast<PQGParams*>(&PARAMS), + &publicKeyTemp, false, true, nullptr)); + if (!privateKey) { + return nullptr; + } + ScopedSECKEYPublicKey publicKey(publicKeyTemp); + return CreateTestKeyPair(DSS(), publicKey, privateKey); +} + +Result +TestVerifyECDSASignedDigest(const SignedDigest& signedDigest, + Input subjectPublicKeyInfo) +{ + InitNSSIfNeeded(); + return VerifyECDSASignedDigestNSS(signedDigest, subjectPublicKeyInfo, + nullptr); +} + +Result +TestVerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest, + Input subjectPublicKeyInfo) +{ + InitNSSIfNeeded(); + return VerifyRSAPKCS1SignedDigestNSS(signedDigest, subjectPublicKeyInfo, + nullptr); +} + +Result +TestDigestBuf(Input item, + DigestAlgorithm digestAlg, + /*out*/ uint8_t* digestBuf, + size_t digestBufLen) +{ + InitNSSIfNeeded(); + return DigestBufNSS(item, digestAlg, digestBuf, digestBufLen); +} + +} } } // namespace mozilla::pkix::test |