summaryrefslogtreecommitdiff
path: root/system/security/pkix/test/lib/pkixtestnss.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'system/security/pkix/test/lib/pkixtestnss.cpp')
-rw-r--r--system/security/pkix/test/lib/pkixtestnss.cpp347
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,
+ &params, &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