From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- security/nss/lib/mozpkix/test-lib/pkixtestalg.cpp | 211 ++++ security/nss/lib/mozpkix/test-lib/pkixtestnss.cpp | 374 ++++++ security/nss/lib/mozpkix/test-lib/pkixtestutil.cpp | 1188 ++++++++++++++++++++ 3 files changed, 1773 insertions(+) create mode 100644 security/nss/lib/mozpkix/test-lib/pkixtestalg.cpp create mode 100644 security/nss/lib/mozpkix/test-lib/pkixtestnss.cpp create mode 100644 security/nss/lib/mozpkix/test-lib/pkixtestutil.cpp (limited to 'security/nss/lib/mozpkix/test-lib') diff --git a/security/nss/lib/mozpkix/test-lib/pkixtestalg.cpp b/security/nss/lib/mozpkix/test-lib/pkixtestalg.cpp new file mode 100644 index 0000000000..304641e2ff --- /dev/null +++ b/security/nss/lib/mozpkix/test-lib/pkixtestalg.cpp @@ -0,0 +1,211 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* 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/. + */ +/* Copyright 2015 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mozpkix/test/pkixtestutil.h" + +#include "mozpkix/pkixder.h" +#include "mozpkix/nss_scoped_ptrs.h" + +// python DottedOIDToCode.py --prefixdefine PREFIX_1_2_840_10040 1.2.840.10040 +#define PREFIX_1_2_840_10040 0x2a, 0x86, 0x48, 0xce, 0x38 + +// python DottedOIDToCode.py --prefixdefine PREFIX_1_2_840_10045 1.2.840.10045 +#define PREFIX_1_2_840_10045 0x2a, 0x86, 0x48, 0xce, 0x3d + +// python DottedOIDToCode.py --prefixdefine PREFIX_1_2_840_113549 1.2.840.113549 +#define PREFIX_1_2_840_113549 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d + +namespace mozilla { namespace pkix { namespace test { + +namespace { + +enum class NULLParam { NO, YES }; + +template +ByteString +OID(const uint8_t (&rawValue)[SIZE]) +{ + return TLV(der::OIDTag, ByteString(rawValue, SIZE)); +} + +template +ByteString +SimpleAlgID(const uint8_t (&rawValue)[SIZE], + NULLParam nullParam = NULLParam::NO) +{ + ByteString sequenceValue(OID(rawValue)); + if (nullParam == NULLParam::YES) { + sequenceValue.append(TLV(der::NULLTag, ByteString())); + } + return TLV(der::SEQUENCE, sequenceValue); +} + +template +ByteString +DERInteger(const uint8_t (&rawValue)[SIZE]) +{ + ByteString value(rawValue, SIZE); + if (value[0] & 0x80u) { + // Prefix with a leading zero to disambiguate this from a negative value. + value.insert(value.begin(), 0x00); + } + return TLV(der::INTEGER, value); +} + +// Generated with "openssl dsaparam -C -noout 2048" and reformatted. +// openssl 1.0 or later must be used so that a 256-bit Q value is +// generated. +static const uint8_t DSS_P_RAW[] = +{ + 0xB3,0xCD,0x29,0x44,0xF0,0x25,0xA7,0x73,0xFC,0x86,0x70,0xA2, + 0x69,0x5A,0x97,0x3F,0xBD,0x1C,0x6F,0xAA,0x4A,0x40,0x42,0x8E, + 0xCF,0xAE,0x62,0x12,0xED,0xB4,0xFD,0x05,0xC2,0xAE,0xB1,0x8C, + 0xFC,0xBE,0x38,0x90,0xBB,0x7C,0xFF,0x16,0xF4,0xED,0xCE,0x72, + 0x12,0x93,0x83,0xF0,0xA4,0xA1,0x71,0xDC,0x4B,0xF0,0x4E,0x3A, + 0x2B,0xFA,0x17,0xB7,0xB3,0x2A,0xCC,0x2C,0xD3,0xC8,0x21,0x49, + 0x7A,0x83,0x71,0x8B,0x3D,0x62,0x96,0xDC,0xAD,0xA8,0x03,0xBE, + 0x1D,0x33,0x11,0xF3,0xEB,0xD8,0x1B,0x8D,0xDB,0x62,0x79,0x83, + 0xF8,0x67,0x4E,0x62,0x21,0x2C,0x81,0x59,0xE8,0x73,0xD7,0xAF, + 0xB9,0x63,0x60,0xEA,0xAE,0xEC,0x68,0x6A,0xB4,0xB0,0x65,0xBA, + 0xA3,0x4C,0x09,0x99,0x29,0x6A,0x2E,0x2B,0xFC,0x6D,0x51,0xCA, + 0x30,0xA2,0x2F,0x7A,0x65,0x76,0xA7,0x55,0x13,0x11,0xA0,0x02, + 0xA2,0x59,0x4B,0xCE,0xA7,0x05,0xF6,0x07,0x35,0x9B,0x41,0xD7, + 0x11,0x5A,0x18,0x57,0xA7,0x78,0x88,0xC3,0xA8,0xE3,0x39,0xF5, + 0x47,0x3D,0x2E,0x18,0x54,0xB0,0xF0,0xBF,0x65,0x3F,0x77,0xC7, + 0x11,0xB8,0x0D,0x52,0xAD,0xC8,0xE8,0x6D,0xF6,0x7E,0x88,0x65, + 0x84,0x2B,0xF7,0xEF,0x8E,0xB5,0x7C,0xBD,0x2E,0x0D,0xF3,0xC6, + 0xDD,0x0B,0xB4,0xF2,0x23,0x1F,0xDA,0x55,0x05,0xF5,0xDC,0x53, + 0xA6,0x83,0xDA,0x5C,0xEF,0x29,0x02,0x78,0x68,0xD0,0xA4,0x39, + 0x09,0x7F,0xFA,0x49,0x18,0xD0,0xB5,0x19,0x35,0x31,0x8E,0xDE, + 0x43,0x35,0xA3,0xB9,0x6D,0xC1,0x70,0xC6,0x0D,0x18,0x24,0xEB, + 0x1E,0x4D,0x52,0xB7, +}; + +static const uint8_t DSS_Q_RAW[] = +{ + 0x8D,0x6B,0x86,0x89,0x9C,0x8D,0x30,0x91,0xCC,0x6E,0x34,0xF1, + 0xE8,0x9C,0x8A,0x5C,0xD6,0xAB,0x01,0x1E,0xC4,0xDB,0xFD,0x07, + 0xEB,0x5F,0x4E,0xE8,0xFA,0xFC,0x98,0x2D, +}; + +static const uint8_t DSS_G_RAW[] = +{ + 0x0E,0x2C,0x34,0xB2,0xE1,0x66,0x49,0xB6,0x9A,0x7D,0x67,0x3E, + 0xEE,0x98,0x35,0x18,0x28,0x35,0xFC,0x05,0x36,0x3B,0x94,0xE6, + 0x1E,0x1C,0x5B,0x05,0x3E,0x86,0x1B,0xE3,0xED,0xD2,0xE1,0xF3, + 0xF7,0xF7,0x60,0x6D,0x7D,0xA1,0xAF,0x9A,0xD1,0xDF,0xA2,0x9C, + 0xFC,0xA2,0xEB,0x90,0x8B,0x1C,0x82,0x92,0x45,0x7B,0x30,0x2A, + 0xFD,0x7A,0xE6,0x68,0x8F,0xEC,0x89,0x3A,0x9A,0xAD,0xFE,0x25, + 0x5E,0x51,0xC5,0x29,0x45,0x7F,0xAC,0xDE,0xFC,0xB4,0x1B,0x3A, + 0xDA,0xC7,0x21,0x68,0x87,0x27,0x8D,0x7B,0xB2,0xBB,0x41,0x60, + 0x46,0x42,0x5B,0x6B,0xE8,0x80,0xD2,0xE4,0xA3,0x30,0x8F,0xD5, + 0x71,0x07,0x8A,0x7B,0x32,0x56,0x84,0x41,0x1C,0xDF,0x69,0xE9, + 0xFD,0xBA,0x48,0xE0,0x43,0xA0,0x38,0x92,0x12,0xF3,0x52,0xA5, + 0x40,0x87,0xCB,0x34,0xBB,0x3E,0x25,0x29,0x3C,0xC6,0xA5,0x17, + 0xFD,0x58,0x47,0x89,0xDB,0x9B,0xB9,0xCF,0xE9,0xA8,0xF2,0xEC, + 0x55,0x76,0xF5,0xF1,0x9C,0x6E,0x0A,0x3F,0x16,0x5F,0x49,0x31, + 0x31,0x1C,0x43,0xA2,0x83,0xDA,0xDD,0x7F,0x1C,0xEA,0x05,0x36, + 0x7B,0xED,0x09,0xFB,0x6F,0x8A,0x2B,0x55,0xB9,0xBC,0x4A,0x8C, + 0x28,0xC1,0x4D,0x13,0x6E,0x47,0xF4,0xAD,0x79,0x00,0xE9,0x5A, + 0xB6,0xC7,0x73,0x28,0xA9,0x89,0xAD,0xE8,0x6E,0xC6,0x54,0xA5, + 0x56,0x2D,0xAA,0x81,0x83,0x9E,0xC1,0x13,0x79,0xA4,0x12,0xE0, + 0x76,0x1F,0x25,0x43,0xB6,0xDE,0x56,0xF7,0x52,0xCC,0x07,0xB8, + 0x37,0xE2,0x8C,0xC5,0x56,0x8C,0xDD,0x63,0xF5,0xB6,0xA3,0x46, + 0x62,0xF6,0x35,0x76, +}; + +} // namespace + +TestSignatureAlgorithm::TestSignatureAlgorithm( + const TestPublicKeyAlgorithm& aPublicKeyAlg, + TestDigestAlgorithmID aDigestAlg, + const ByteString& aAlgorithmIdentifier, + bool aAccepted) + : publicKeyAlg(aPublicKeyAlg) + , digestAlg(aDigestAlg) + , algorithmIdentifier(aAlgorithmIdentifier) + , accepted(aAccepted) +{ +} + +ByteString DSS_P() { return ByteString(DSS_P_RAW, sizeof(DSS_P_RAW)); } +ByteString DSS_Q() { return ByteString(DSS_Q_RAW, sizeof(DSS_Q_RAW)); } +ByteString DSS_G() { return ByteString(DSS_G_RAW, sizeof(DSS_G_RAW)); } + +TestPublicKeyAlgorithm +DSS() +{ + static const uint8_t oidValue[] = { PREFIX_1_2_840_10040, 4, 1 }; + + // RFC 3279 Section-2.3.2 + return TestPublicKeyAlgorithm( + TLV(der::SEQUENCE, + OID(oidValue) + + TLV(der::SEQUENCE, + DERInteger(DSS_P_RAW) + + DERInteger(DSS_Q_RAW) + + DERInteger(DSS_G_RAW)))); +} + +// RFC 3279 Section 2.3.1 +TestPublicKeyAlgorithm +RSA_PKCS1() +{ + static const uint8_t rsaEncryption[] = { PREFIX_1_2_840_113549, 1, 1, 1 }; + return TestPublicKeyAlgorithm(SimpleAlgID(rsaEncryption, NULLParam::YES)); +} + +// RFC 3279 Section 2.2.1 +TestSignatureAlgorithm md2WithRSAEncryption() +{ + static const uint8_t oidValue[] = { PREFIX_1_2_840_113549, 1, 1, 2 }; + return TestSignatureAlgorithm(RSA_PKCS1(), TestDigestAlgorithmID::MD2, + SimpleAlgID(oidValue), false); +} + +// RFC 3279 Section 2.2.1 +TestSignatureAlgorithm md5WithRSAEncryption() +{ + static const uint8_t oidValue[] = { PREFIX_1_2_840_113549, 1, 1, 4 }; + return TestSignatureAlgorithm(RSA_PKCS1(), TestDigestAlgorithmID::MD5, + SimpleAlgID(oidValue), false); +} + +// RFC 3279 Section 2.2.1 +TestSignatureAlgorithm sha1WithRSAEncryption() +{ + static const uint8_t oidValue[] = { PREFIX_1_2_840_113549, 1, 1, 5 }; + return TestSignatureAlgorithm(RSA_PKCS1(), TestDigestAlgorithmID::SHA1, + SimpleAlgID(oidValue), true); +} + +// RFC 4055 Section 5 +TestSignatureAlgorithm sha256WithRSAEncryption() +{ + static const uint8_t oidValue[] = { PREFIX_1_2_840_113549, 1, 1, 11 }; + return TestSignatureAlgorithm(RSA_PKCS1(), TestDigestAlgorithmID::SHA256, + SimpleAlgID(oidValue), true); +} + +} } } // namespace mozilla::pkix diff --git a/security/nss/lib/mozpkix/test-lib/pkixtestnss.cpp b/security/nss/lib/mozpkix/test-lib/pkixtestnss.cpp new file mode 100644 index 0000000000..e8e082ab68 --- /dev/null +++ b/security/nss/lib/mozpkix/test-lib/pkixtestnss.cpp @@ -0,0 +1,374 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* 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/. + */ +/* Copyright 2013 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mozpkix/test/pkixtestutil.h" +#include "mozpkix/test/pkixtestnss.h" + +#include + +#include "cryptohi.h" +#include "keyhi.h" +#include "nss.h" +#include "pk11pqg.h" +#include "pk11pub.h" +#include "mozpkix/nss_scoped_ptrs.h" +#include "mozpkix/pkixnss.h" +#include "mozpkix/pkixder.h" +#include "mozpkix/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(encryptedPrivateKey.data()), + static_cast(encryptedPrivateKey.length()) + }; + SECItem encryptionAlgorithmItem = { + siBuffer, + const_cast(encryptionAlgorithm.data()), + static_cast(encryptionAlgorithm.length()) + }; + SECItem encryptionParamsItem = { + siBuffer, + const_cast(encryptionParams.data()), + static_cast(encryptionParams.length()) + }; + SECKEYEncryptedPrivateKeyInfo encryptedPrivateKeyInfo = { + nullptr, + { encryptionAlgorithmItem, encryptionParamsItem }, + encryptedPrivateKeyInfoItem + }; + SECItem passwordItem = { siBuffer, nullptr, 0 }; + SECItem publicValueItem = { + siBuffer, + const_cast(subjectPublicKey.data()), + static_cast(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(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(reinterpret_cast(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(p.data()), + static_cast(p.length()) + }, + { siBuffer, + const_cast(q.data()), + static_cast(q.length()) + }, + { siBuffer, + const_cast(g.data()), + static_cast(g.length()) + } + }; + + SECKEYPublicKey* publicKeyTemp = nullptr; + ScopedSECKEYPrivateKey + privateKey(PK11_GenerateKeyPair(slot.get(), CKM_DSA_KEY_PAIR_GEN, + const_cast(&PARAMS), + &publicKeyTemp, false, true, nullptr)); + if (!privateKey) { + return nullptr; + } + ScopedSECKEYPublicKey publicKey(publicKeyTemp); + return CreateTestKeyPair(DSS(), publicKey, privateKey); +} + +Result +TestVerifyECDSASignedData(Input data, DigestAlgorithm digestAlgorithm, + Input signature, Input subjectPublicKeyInfo) +{ + InitNSSIfNeeded(); + return VerifyECDSASignedDataNSS(data, digestAlgorithm, signature, + subjectPublicKeyInfo, nullptr); +} + +Result +TestVerifyRSAPKCS1SignedData(Input data, DigestAlgorithm digestAlgorithm, + Input signature, Input subjectPublicKeyInfo) +{ + InitNSSIfNeeded(); + return VerifyRSAPKCS1SignedDataNSS(data, digestAlgorithm, signature, + subjectPublicKeyInfo, nullptr); +} + +Result +TestVerifyRSAPSSSignedData(Input data, DigestAlgorithm digestAlgorithm, + Input signature, Input subjectPublicKeyInfo) +{ + InitNSSIfNeeded(); + return VerifyRSAPSSSignedDataNSS(data, digestAlgorithm, signature, + 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 diff --git a/security/nss/lib/mozpkix/test-lib/pkixtestutil.cpp b/security/nss/lib/mozpkix/test-lib/pkixtestutil.cpp new file mode 100644 index 0000000000..306f7a0e4b --- /dev/null +++ b/security/nss/lib/mozpkix/test-lib/pkixtestutil.cpp @@ -0,0 +1,1188 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* 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/. + */ +/* Copyright 2013 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mozpkix/test/pkixtestutil.h" + +#include +#include +#include +#include +#include +#include + +#include "mozpkix/pkixder.h" +#include "mozpkix/pkixutil.h" + +using namespace std; + +namespace mozilla { namespace pkix { namespace test { + +namespace { + +struct ScopedMaybeDeleteFile { + void operator()(FILE* f) { + if (f) { + (void)fclose(f); + } + } +}; +typedef std::unique_ptr ScopedFILE; + +FILE* +OpenFile(const string& dir, const string& filename, const string& mode) +{ + string path = dir + '/' + filename; + + ScopedFILE file; +#ifdef _MSC_VER + { + FILE* rawFile; + errno_t error = fopen_s(&rawFile, path.c_str(), mode.c_str()); + if (error) { + // TODO: map error to NSPR error code + rawFile = nullptr; + } + file.reset(rawFile); + } +#else + file.reset(fopen(path.c_str(), mode.c_str())); +#endif + return file.release(); +} + +} // namespace + +bool +InputEqualsByteString(Input input, const ByteString& bs) +{ + Input bsInput; + if (bsInput.Init(bs.data(), bs.length()) != Success) { + // Init can only fail if it is given a bad pointer or if the input is too + // long, which won't ever happen. Plus, if it does, it is ok to call abort + // since this is only test code. + abort(); + } + return InputsAreEqual(input, bsInput); +} + +ByteString +InputToByteString(Input input) +{ + ByteString result; + Reader reader(input); + for (;;) { + uint8_t b; + if (reader.Read(b) != Success) { + return result; + } + result.push_back(b); + } +} + +Result +TamperOnce(/*in/out*/ ByteString& item, const ByteString& from, + const ByteString& to) +{ + if (from.length() < 8) { + return Result::FATAL_ERROR_INVALID_ARGS; + } + if (from.length() != to.length()) { + return Result::FATAL_ERROR_INVALID_ARGS; + } + size_t pos = item.find(from); + if (pos == string::npos) { + return Result::FATAL_ERROR_INVALID_ARGS; // No matches. + } + if (item.find(from, pos + from.length()) != string::npos) { + return Result::FATAL_ERROR_INVALID_ARGS; // More than once match. + } + item.replace(pos, from.length(), to); + return Success; +} + +// Given a tag and a value, generates a DER-encoded tag-length-value item. +ByteString +TLV(uint8_t tag, size_t length, const ByteString& value) +{ + ByteString result; + result.push_back(tag); + + if (value.length() < 128) { + result.push_back(static_cast(length)); + } else if (value.length() < 256) { + result.push_back(0x81u); + result.push_back(static_cast(length)); + } else if (value.length() < 65536) { + result.push_back(0x82u); + result.push_back(static_cast(length / 256)); + result.push_back(static_cast(length % 256)); + } else { + // It is MUCH more convenient for TLV to be infallible than for it to have + // "proper" error handling. + abort(); + } + result.append(value); + return result; +} + +OCSPResponseExtension::OCSPResponseExtension() + : id() + , critical(false) + , value() + , next(nullptr) +{ +} + +OCSPResponseContext::OCSPResponseContext(const CertID& aCertID, time_t time) + : certID(aCertID) + , certIDHashAlgorithm(DigestAlgorithm::sha1) + , certIDHashAlgorithmEncoded(ByteString()) + , responseStatus(successful) + , skipResponseBytes(false) + , producedAt(time) + , singleExtensions(nullptr) + , responseExtensions(nullptr) + , includeEmptyExtensions(false) + , signatureAlgorithm(sha256WithRSAEncryption()) + , badSignature(false) + , certs(nullptr) + + , certStatus(good) + , revocationTime(0) + , thisUpdate(time) + , nextUpdate(time + static_cast(Time::ONE_DAY_IN_SECONDS)) + , includeNextUpdate(true) +{ +} + +static ByteString ResponseBytes(OCSPResponseContext& context); +static ByteString BasicOCSPResponse(OCSPResponseContext& context); +static ByteString ResponseData(OCSPResponseContext& context); +static ByteString ResponderID(OCSPResponseContext& context); +static ByteString KeyHash(const ByteString& subjectPublicKeyInfo); +static ByteString SingleResponse(OCSPResponseContext& context); +static ByteString CertID(OCSPResponseContext& context); +static ByteString CertStatus(OCSPResponseContext& context); + +static ByteString +HASH(const ByteString& toHash, DigestAlgorithm digestAlgorithm) +{ + uint8_t digestBuf[MAX_DIGEST_SIZE_IN_BYTES]; + Input input; + if (input.Init(toHash.data(), toHash.length()) != Success) { + abort(); + } + size_t digestLen = DigestAlgorithmToSizeInBytes(digestAlgorithm); + assert(digestLen <= sizeof(digestBuf)); + Result rv = TestDigestBuf(input, digestAlgorithm, digestBuf, digestLen); + if (rv != Success) { + abort(); + } + return ByteString(digestBuf, digestLen); +} + +static ByteString +HashedOctetString(const ByteString& bytes, DigestAlgorithm digestAlgorithm) +{ + ByteString digest(HASH(bytes, digestAlgorithm)); + if (ENCODING_FAILED(digest)) { + return ByteString(); + } + return TLV(der::OCTET_STRING, digest); +} + +static ByteString +BitString(const ByteString& rawBytes, bool corrupt) +{ + ByteString prefixed; + // We have to add a byte at the beginning indicating no unused bits. + // TODO: add ability to have bit strings of bit length not divisible by 8, + // resulting in unused bits in the bitstring encoding + prefixed.push_back(0); + prefixed.append(rawBytes); + if (corrupt) { + assert(prefixed.length() > 8); + prefixed[8]++; + } + return TLV(der::BIT_STRING, prefixed); +} + +ByteString +Boolean(bool value) +{ + ByteString encodedValue; + encodedValue.push_back(value ? 0xffu : 0x00u); + return TLV(der::BOOLEAN, encodedValue); +} + +ByteString +Integer(long value) +{ + if (value < 0 || value > 127) { + // TODO: add encoding of larger values + // It is MUCH more convenient for Integer to be infallible than for it to + // have "proper" error handling. + abort(); + } + + ByteString encodedValue; + encodedValue.push_back(static_cast(value)); + return TLV(der::INTEGER, encodedValue); +} + +enum TimeEncoding { UTCTime = 0, GeneralizedTime = 1 }; + +// Windows doesn't provide gmtime_r, but it provides something very similar. +#if defined(_WINDOWS) && (!defined(_POSIX_C_SOURCE) || !defined(_POSIX_THREAD_SAFE_FUNCTIONS)) +static tm* +gmtime_r(const time_t* t, /*out*/ tm* exploded) +{ + if (gmtime_s(exploded, t) != 0) { + return nullptr; + } + return exploded; +} +#endif + +// http://tools.ietf.org/html/rfc5280#section-4.1.2.5 +// UTCTime: YYMMDDHHMMSSZ (years 1950-2049 only) +// GeneralizedTime: YYYYMMDDHHMMSSZ +// +// This assumes that time/time_t are POSIX-compliant in that time() returns +// the number of seconds since the Unix epoch. +static ByteString +TimeToEncodedTime(time_t time, TimeEncoding encoding) +{ + assert(encoding == UTCTime || encoding == GeneralizedTime); + + tm exploded; + if (!gmtime_r(&time, &exploded)) { + return ByteString(); + } + + if (exploded.tm_sec >= 60) { + // round down for leap seconds + exploded.tm_sec = 59; + } + + // exploded.tm_year is the year offset by 1900. + int year = exploded.tm_year + 1900; + + if (encoding == UTCTime && (year < 1950 || year >= 2050)) { + return ByteString(); + } + + ByteString value; + + if (encoding == GeneralizedTime) { + value.push_back(static_cast('0' + (year / 1000))); + value.push_back(static_cast('0' + ((year % 1000) / 100))); + } + + value.push_back(static_cast('0' + ((year % 100) / 10))); + value.push_back(static_cast('0' + (year % 10))); + value.push_back(static_cast('0' + ((exploded.tm_mon + 1) / 10))); + value.push_back(static_cast('0' + ((exploded.tm_mon + 1) % 10))); + value.push_back(static_cast('0' + (exploded.tm_mday / 10))); + value.push_back(static_cast('0' + (exploded.tm_mday % 10))); + value.push_back(static_cast('0' + (exploded.tm_hour / 10))); + value.push_back(static_cast('0' + (exploded.tm_hour % 10))); + value.push_back(static_cast('0' + (exploded.tm_min / 10))); + value.push_back(static_cast('0' + (exploded.tm_min % 10))); + value.push_back(static_cast('0' + (exploded.tm_sec / 10))); + value.push_back(static_cast('0' + (exploded.tm_sec % 10))); + value.push_back('Z'); + + return TLV(encoding == GeneralizedTime ? der::GENERALIZED_TIME : der::UTCTime, + value); +} + +static ByteString +TimeToGeneralizedTime(time_t time) +{ + return TimeToEncodedTime(time, GeneralizedTime); +} + +// http://tools.ietf.org/html/rfc5280#section-4.1.2.5: "CAs conforming to this +// profile MUST always encode certificate validity dates through the year 2049 +// as UTCTime; certificate validity dates in 2050 or later MUST be encoded as +// GeneralizedTime." (This is a special case of the rule that we must always +// use the shortest possible encoding.) +static ByteString +TimeToTimeChoice(time_t time) +{ + tm exploded; + if (!gmtime_r(&time, &exploded)) { + return ByteString(); + } + TimeEncoding encoding = (exploded.tm_year + 1900 >= 1950 && + exploded.tm_year + 1900 < 2050) + ? UTCTime + : GeneralizedTime; + + return TimeToEncodedTime(time, encoding); +} + +Time +YMDHMS(uint16_t year, uint16_t month, uint16_t day, + uint16_t hour, uint16_t minutes, uint16_t seconds) +{ + assert(year <= 9999); + assert(month >= 1); + assert(month <= 12); + assert(day >= 1); + assert(hour < 24); + assert(minutes < 60); + assert(seconds < 60); + + uint64_t days = DaysBeforeYear(year); + + { + static const int16_t DAYS_IN_MONTH[] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + }; + + int16_t i = 1; + for (;;) { + int16_t daysInMonth = DAYS_IN_MONTH[i - 1]; + if (i == 2 && + ((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)))) { + // Add leap day + ++daysInMonth; + } + if (i == month) { + assert(day <= daysInMonth); + break; + } + days += daysInMonth; + ++i; + } + } + + days += (day - 1); + + uint64_t totalSeconds = days * Time::ONE_DAY_IN_SECONDS; + totalSeconds += hour * 60 * 60; + totalSeconds += minutes * 60; + totalSeconds += seconds; + return TimeFromElapsedSecondsAD(totalSeconds); +} + +static ByteString +SignedData(const ByteString& tbsData, + const TestKeyPair& keyPair, + const TestSignatureAlgorithm& signatureAlgorithm, + bool corrupt, /*optional*/ const ByteString* certs) +{ + ByteString signature; + if (keyPair.SignData(tbsData, signatureAlgorithm, signature) != Success) { + return ByteString(); + } + + // TODO: add ability to have signatures of bit length not divisible by 8, + // resulting in unused bits in the bitstring encoding + ByteString signatureNested(BitString(signature, corrupt)); + if (ENCODING_FAILED(signatureNested)) { + return ByteString(); + } + + ByteString certsNested; + if (certs) { + ByteString certsSequenceValue; + while (!(*certs).empty()) { + certsSequenceValue.append(*certs); + ++certs; + } + ByteString certsSequence(TLV(der::SEQUENCE, certsSequenceValue)); + certsNested = TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0, + certsSequence); + } + + ByteString value; + value.append(tbsData); + value.append(signatureAlgorithm.algorithmIdentifier); + value.append(signatureNested); + value.append(certsNested); + return TLV(der::SEQUENCE, value); +} + +// Extension ::= SEQUENCE { +// extnID OBJECT IDENTIFIER, +// critical BOOLEAN DEFAULT FALSE, +// extnValue OCTET STRING +// -- contains the DER encoding of an ASN.1 value +// -- corresponding to the extension type identified +// -- by extnID +// } +static ByteString +Extension(Input extnID, Critical critical, const ByteString& extnValueBytes) +{ + ByteString encoded; + + encoded.append(ByteString(extnID.UnsafeGetData(), extnID.GetLength())); + + if (critical == Critical::Yes) { + encoded.append(Boolean(true)); + } + + ByteString extnValueSequence(TLV(der::SEQUENCE, extnValueBytes)); + ByteString extnValue(TLV(der::OCTET_STRING, extnValueSequence)); + encoded.append(extnValue); + return TLV(der::SEQUENCE, encoded); +} + +static ByteString +EmptyExtension(Input extnID, Critical critical) +{ + ByteString encoded(extnID.UnsafeGetData(), extnID.GetLength()); + + if (critical == Critical::Yes) { + encoded.append(Boolean(true)); + } + + ByteString extnValue(TLV(der::OCTET_STRING, ByteString())); + encoded.append(extnValue); + return TLV(der::SEQUENCE, encoded); +} + +std::string +GetEnv(const char* name) +{ + std::string result; + +#ifndef _MSC_VER + // XXX: Not thread safe. + const char* value = getenv(name); + if (value) { + result = value; + } +#else + char* value = nullptr; + size_t valueLength = 0; + if (_dupenv_s(&value, &valueLength, name) != 0) { + abort(); + } + if (value) { + result = value; + free(value); + } +#endif + return result; +} + +void +MaybeLogOutput(const ByteString& result, const char* suffix) +{ + assert(suffix); + + // This allows us to more easily debug the generated output, by creating a + // file in the directory given by MOZILLA_PKIX_TEST_LOG_DIR for each + // NOT THREAD-SAFE!!! + std::string logPath(GetEnv("MOZILLA_PKIX_TEST_LOG_DIR")); + if (!logPath.empty()) { + static int counter = 0; + + std::ostringstream counterStream; + counterStream << counter; + if (!counterStream) { + assert(false); + return; + } + string filename = counterStream.str() + '-' + suffix + ".der"; + + ++counter; + ScopedFILE file(OpenFile(logPath, filename, "wb")); + if (file) { + (void) fwrite(result.data(), result.length(), 1, file.get()); + } + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Certificates + +static ByteString TBSCertificate(long version, const ByteString& serialNumber, + const ByteString& signature, + const ByteString& issuer, + time_t notBefore, time_t notAfter, + const ByteString& subject, + const ByteString& subjectPublicKeyInfo, + /*optional*/ const ByteString* extensions); + +// Certificate ::= SEQUENCE { +// tbsCertificate TBSCertificate, +// signatureAlgorithm AlgorithmIdentifier, +// signatureValue BIT STRING } +ByteString +CreateEncodedCertificate(long version, + const TestSignatureAlgorithm& signature, + const ByteString& serialNumber, + const ByteString& issuerNameDER, + time_t notBefore, time_t notAfter, + const ByteString& subjectNameDER, + const TestKeyPair& subjectKeyPair, + /*optional*/ const ByteString* extensions, + const TestKeyPair& issuerKeyPair, + const TestSignatureAlgorithm& signatureAlgorithm) +{ + ByteString tbsCertificate(TBSCertificate(version, serialNumber, + signature.algorithmIdentifier, + issuerNameDER, notBefore, + notAfter, subjectNameDER, + subjectKeyPair.subjectPublicKeyInfo, + extensions)); + if (ENCODING_FAILED(tbsCertificate)) { + return ByteString(); + } + + ByteString result(SignedData(tbsCertificate, issuerKeyPair, + signatureAlgorithm, false, nullptr)); + if (ENCODING_FAILED(result)) { + return ByteString(); + } + + MaybeLogOutput(result, "cert"); + + return result; +} + +// TBSCertificate ::= SEQUENCE { +// version [0] Version DEFAULT v1, +// serialNumber CertificateSerialNumber, +// signature AlgorithmIdentifier, +// issuer Name, +// validity Validity, +// subject Name, +// subjectPublicKeyInfo SubjectPublicKeyInfo, +// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, +// -- If present, version MUST be v2 or v3 +// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, +// -- If present, version MUST be v2 or v3 +// extensions [3] Extensions OPTIONAL +// -- If present, version MUST be v3 -- } +static ByteString +TBSCertificate(long versionValue, + const ByteString& serialNumber, const ByteString& signature, + const ByteString& issuer, time_t notBeforeTime, + time_t notAfterTime, const ByteString& subject, + const ByteString& subjectPublicKeyInfo, + /*optional*/ const ByteString* extensions) +{ + ByteString value; + + if (versionValue != static_cast(der::Version::v1)) { + ByteString versionInteger(Integer(versionValue)); + ByteString version(TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0, + versionInteger)); + value.append(version); + } + + value.append(serialNumber); + value.append(signature); + value.append(issuer); + + // Validity ::= SEQUENCE { + // notBefore Time, + // notAfter Time } + ByteString validity; + { + ByteString notBefore(TimeToTimeChoice(notBeforeTime)); + if (ENCODING_FAILED(notBefore)) { + return ByteString(); + } + ByteString notAfter(TimeToTimeChoice(notAfterTime)); + if (ENCODING_FAILED(notAfter)) { + return ByteString(); + } + ByteString validityValue; + validityValue.append(notBefore); + validityValue.append(notAfter); + validity = TLV(der::SEQUENCE, validityValue); + if (ENCODING_FAILED(validity)) { + return ByteString(); + } + } + value.append(validity); + + value.append(subject); + + value.append(subjectPublicKeyInfo); + + if (extensions) { + ByteString extensionsValue; + while (!(*extensions).empty()) { + extensionsValue.append(*extensions); + ++extensions; + } + ByteString extensionsSequence(TLV(der::SEQUENCE, extensionsValue)); + if (ENCODING_FAILED(extensionsSequence)) { + return ByteString(); + } + ByteString extensionsWrapped( + TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 3, extensionsSequence)); + if (ENCODING_FAILED(extensionsWrapped)) { + return ByteString(); + } + value.append(extensionsWrapped); + } + + return TLV(der::SEQUENCE, value); +} + +// AttributeTypeAndValue ::= SEQUENCE { +// type AttributeType, +// value AttributeValue } +// +// AttributeType ::= OBJECT IDENTIFIER +// +// AttributeValue ::= ANY -- DEFINED BY AttributeType +// +// DirectoryString ::= CHOICE { +// teletexString TeletexString (SIZE (1..MAX)), +// printableString PrintableString (SIZE (1..MAX)), +// universalString UniversalString (SIZE (1..MAX)), +// utf8String UTF8String (SIZE (1..MAX)), +// bmpString BMPString (SIZE (1..MAX)) } +template +static ByteString +AVA(const uint8_t (&type)[N], uint8_t directoryStringType, + const ByteString& value) +{ + ByteString wrappedValue(TLV(directoryStringType, value)); + ByteString ava; + ava.append(type, N); + ava.append(wrappedValue); + return TLV(der::SEQUENCE, ava); +} + +ByteString +CN(const ByteString& value, uint8_t encodingTag) +{ + // id-at OBJECT IDENTIFIER ::= { joint-iso-ccitt(2) ds(5) 4 } + // id-at-commonName AttributeType ::= { id-at 3 } + // python DottedOIDToCode.py --tlv id-at-commonName 2.5.4.3 + static const uint8_t tlv_id_at_commonName[] = { + 0x06, 0x03, 0x55, 0x04, 0x03 + }; + return AVA(tlv_id_at_commonName, encodingTag, value); +} + +ByteString +OU(const ByteString& value, uint8_t encodingTag) +{ + // id-at OBJECT IDENTIFIER ::= { joint-iso-ccitt(2) ds(5) 4 } + // id-at-organizationalUnitName AttributeType ::= { id-at 11 } + // python DottedOIDToCode.py --tlv id-at-organizationalUnitName 2.5.4.11 + static const uint8_t tlv_id_at_organizationalUnitName[] = { + 0x06, 0x03, 0x55, 0x04, 0x0b + }; + + return AVA(tlv_id_at_organizationalUnitName, encodingTag, value); +} + +ByteString +emailAddress(const ByteString& value) +{ + // id-emailAddress AttributeType ::= { pkcs-9 1 } + // python DottedOIDToCode.py --tlv id-emailAddress 1.2.840.113549.1.9.1 + static const uint8_t tlv_id_emailAddress[] = { + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01 + }; + + return AVA(tlv_id_emailAddress, der::IA5String, value); +} + +// RelativeDistinguishedName ::= +// SET SIZE (1..MAX) OF AttributeTypeAndValue +// +ByteString +RDN(const ByteString& avas) +{ + return TLV(der::SET, avas); +} + +// Name ::= CHOICE { -- only one possibility for now -- +// rdnSequence RDNSequence } +// +// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName +// +ByteString +Name(const ByteString& rdns) +{ + return TLV(der::SEQUENCE, rdns); +} + +ByteString +CreateEncodedSerialNumber(long serialNumberValue) +{ + return Integer(serialNumberValue); +} + +// BasicConstraints ::= SEQUENCE { +// cA BOOLEAN DEFAULT FALSE, +// pathLenConstraint INTEGER (0..MAX) OPTIONAL } +ByteString +CreateEncodedBasicConstraints(bool isCA, + /*optional in*/ const long* pathLenConstraintValue, + Critical critical) +{ + ByteString value; + + if (isCA) { + ByteString cA(Boolean(true)); + value.append(cA); + } + + if (pathLenConstraintValue) { + ByteString pathLenConstraint(Integer(*pathLenConstraintValue)); + value.append(pathLenConstraint); + } + + // python DottedOIDToCode.py --tlv id-ce-basicConstraints 2.5.29.19 + static const uint8_t tlv_id_ce_basicConstraints[] = { + 0x06, 0x03, 0x55, 0x1d, 0x13 + }; + return Extension(Input(tlv_id_ce_basicConstraints), critical, value); +} + +// ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId +// KeyPurposeId ::= OBJECT IDENTIFIER +ByteString +CreateEncodedEKUExtension(Input ekuOID, Critical critical) +{ + ByteString value(ekuOID.UnsafeGetData(), ekuOID.GetLength()); + + // python DottedOIDToCode.py --tlv id-ce-extKeyUsage 2.5.29.37 + static const uint8_t tlv_id_ce_extKeyUsage[] = { + 0x06, 0x03, 0x55, 0x1d, 0x25 + }; + + return Extension(Input(tlv_id_ce_extKeyUsage), critical, value); +} + +// python DottedOIDToCode.py --tlv id-ce-subjectAltName 2.5.29.17 +static const uint8_t tlv_id_ce_subjectAltName[] = { + 0x06, 0x03, 0x55, 0x1d, 0x11 +}; + +ByteString +CreateEncodedSubjectAltName(const ByteString& names) +{ + return Extension(Input(tlv_id_ce_subjectAltName), Critical::No, names); +} + +ByteString +CreateEncodedEmptySubjectAltName() +{ + return EmptyExtension(Input(tlv_id_ce_subjectAltName), Critical::No); +} + +/////////////////////////////////////////////////////////////////////////////// +// OCSP responses + +ByteString +CreateEncodedOCSPResponse(OCSPResponseContext& context) +{ + if (!context.skipResponseBytes) { + if (!context.signerKeyPair) { + return ByteString(); + } + } + + // OCSPResponse ::= SEQUENCE { + // responseStatus OCSPResponseStatus, + // responseBytes [0] EXPLICIT ResponseBytes OPTIONAL } + + // OCSPResponseStatus ::= ENUMERATED { + // successful (0), -- Response has valid confirmations + // malformedRequest (1), -- Illegal confirmation request + // internalError (2), -- Internal error in issuer + // tryLater (3), -- Try again later + // -- (4) is not used + // sigRequired (5), -- Must sign the request + // unauthorized (6) -- Request unauthorized + // } + ByteString reponseStatusValue; + reponseStatusValue.push_back(context.responseStatus); + ByteString responseStatus(TLV(der::ENUMERATED, reponseStatusValue)); + + ByteString responseBytesNested; + if (!context.skipResponseBytes) { + ByteString responseBytes(ResponseBytes(context)); + if (ENCODING_FAILED(responseBytes)) { + return ByteString(); + } + + responseBytesNested = TLV(der::CONSTRUCTED | der::CONTEXT_SPECIFIC, + responseBytes); + } + + ByteString value; + value.append(responseStatus); + value.append(responseBytesNested); + ByteString result(TLV(der::SEQUENCE, value)); + + MaybeLogOutput(result, "ocsp"); + + return result; +} + +// ResponseBytes ::= SEQUENCE { +// responseType OBJECT IDENTIFIER, +// response OCTET STRING } +ByteString +ResponseBytes(OCSPResponseContext& context) +{ + // Includes tag and length + static const uint8_t id_pkix_ocsp_basic_encoded[] = { + 0x06, 0x09, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01 + }; + ByteString response(BasicOCSPResponse(context)); + if (ENCODING_FAILED(response)) { + return ByteString(); + } + ByteString responseNested = TLV(der::OCTET_STRING, response); + + ByteString value; + value.append(id_pkix_ocsp_basic_encoded, + sizeof(id_pkix_ocsp_basic_encoded)); + value.append(responseNested); + return TLV(der::SEQUENCE, value); +} + +// BasicOCSPResponse ::= SEQUENCE { +// tbsResponseData ResponseData, +// signatureAlgorithm AlgorithmIdentifier, +// signature BIT STRING, +// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL } +ByteString +BasicOCSPResponse(OCSPResponseContext& context) +{ + ByteString tbsResponseData(ResponseData(context)); + if (ENCODING_FAILED(tbsResponseData)) { + return ByteString(); + } + + return SignedData(tbsResponseData, *context.signerKeyPair, + context.signatureAlgorithm, context.badSignature, + context.certs); +} + +// Extension ::= SEQUENCE { +// id OBJECT IDENTIFIER, +// critical BOOLEAN DEFAULT FALSE +// value OCTET STRING +// } +static ByteString +OCSPExtension(OCSPResponseExtension& extension) +{ + ByteString encoded; + encoded.append(extension.id); + if (extension.critical) { + encoded.append(Boolean(true)); + } + ByteString value(TLV(der::OCTET_STRING, extension.value)); + encoded.append(value); + return TLV(der::SEQUENCE, encoded); +} + +// Extensions ::= [1] { +// SEQUENCE OF Extension +// } +static ByteString +OCSPExtensions(OCSPResponseExtension* extensions) +{ + ByteString value; + for (OCSPResponseExtension* extension = extensions; + extension; extension = extension->next) { + ByteString extensionEncoded(OCSPExtension(*extension)); + if (ENCODING_FAILED(extensionEncoded)) { + return ByteString(); + } + value.append(extensionEncoded); + } + ByteString sequence(TLV(der::SEQUENCE, value)); + return TLV(der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 1, sequence); +} + +// ResponseData ::= SEQUENCE { +// version [0] EXPLICIT Version DEFAULT v1, +// responderID ResponderID, +// producedAt GeneralizedTime, +// responses SEQUENCE OF SingleResponse, +// responseExtensions [1] EXPLICIT Extensions OPTIONAL } +ByteString +ResponseData(OCSPResponseContext& context) +{ + ByteString responderID(ResponderID(context)); + if (ENCODING_FAILED(responderID)) { + return ByteString(); + } + ByteString producedAtEncoded(TimeToGeneralizedTime(context.producedAt)); + if (ENCODING_FAILED(producedAtEncoded)) { + return ByteString(); + } + ByteString response(SingleResponse(context)); + if (ENCODING_FAILED(response)) { + return ByteString(); + } + ByteString responses(TLV(der::SEQUENCE, response)); + ByteString responseExtensions; + if (context.responseExtensions || context.includeEmptyExtensions) { + responseExtensions = OCSPExtensions(context.responseExtensions); + } + + ByteString value; + value.append(responderID); + value.append(producedAtEncoded); + value.append(responses); + value.append(responseExtensions); + return TLV(der::SEQUENCE, value); +} + +// ResponderID ::= CHOICE { +// byName [1] Name, +// byKey [2] KeyHash } +// } +ByteString +ResponderID(OCSPResponseContext& context) +{ + ByteString contents; + uint8_t responderIDType; + if (!context.signerNameDER.empty()) { + contents = context.signerNameDER; + responderIDType = 1; // byName + } else { + contents = KeyHash(context.signerKeyPair->subjectPublicKey); + if (ENCODING_FAILED(contents)) { + return ByteString(); + } + responderIDType = 2; // byKey + } + + // XXX: MSVC 2015 wrongly warns about signed/unsigned conversion without the + // static_cast. + uint8_t tag = static_cast(der::CONSTRUCTED | der::CONTEXT_SPECIFIC | + responderIDType); + return TLV(tag, contents); +} + +// KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key +// -- (i.e., the SHA-1 hash of the value of the +// -- BIT STRING subjectPublicKey [excluding +// -- the tag, length, and number of unused +// -- bits] in the responder's certificate) +ByteString +KeyHash(const ByteString& subjectPublicKey) +{ + return HashedOctetString(subjectPublicKey, DigestAlgorithm::sha1); +} + +// SingleResponse ::= SEQUENCE { +// certID CertID, +// certStatus CertStatus, +// thisUpdate GeneralizedTime, +// nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL, +// singleExtensions [1] EXPLICIT Extensions OPTIONAL } +ByteString +SingleResponse(OCSPResponseContext& context) +{ + ByteString certID(CertID(context)); + if (ENCODING_FAILED(certID)) { + return ByteString(); + } + ByteString certStatus(CertStatus(context)); + if (ENCODING_FAILED(certStatus)) { + return ByteString(); + } + ByteString thisUpdateEncoded(TimeToGeneralizedTime(context.thisUpdate)); + if (ENCODING_FAILED(thisUpdateEncoded)) { + return ByteString(); + } + ByteString nextUpdateEncodedNested; + if (context.includeNextUpdate) { + ByteString nextUpdateEncoded(TimeToGeneralizedTime(context.nextUpdate)); + if (ENCODING_FAILED(nextUpdateEncoded)) { + return ByteString(); + } + nextUpdateEncodedNested = TLV(der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 0, + nextUpdateEncoded); + } + ByteString singleExtensions; + if (context.singleExtensions || context.includeEmptyExtensions) { + singleExtensions = OCSPExtensions(context.singleExtensions); + } + + ByteString value; + value.append(certID); + value.append(certStatus); + value.append(thisUpdateEncoded); + value.append(nextUpdateEncodedNested); + value.append(singleExtensions); + return TLV(der::SEQUENCE, value); +} + +// CertID ::= SEQUENCE { +// hashAlgorithm AlgorithmIdentifier, +// issuerNameHash OCTET STRING, -- Hash of issuer's DN +// issuerKeyHash OCTET STRING, -- Hash of issuer's public key +// serialNumber CertificateSerialNumber } +ByteString +CertID(OCSPResponseContext& context) +{ + ByteString issuerName(context.certID.issuer.UnsafeGetData(), + context.certID.issuer.GetLength()); + ByteString issuerNameHash(HashedOctetString(issuerName, context.certIDHashAlgorithm)); + if (ENCODING_FAILED(issuerNameHash)) { + return ByteString(); + } + + ByteString issuerKeyHash; + { + // context.certID.issuerSubjectPublicKeyInfo is the entire + // SubjectPublicKeyInfo structure, but we need just the subjectPublicKey + // part. + Reader input(context.certID.issuerSubjectPublicKeyInfo); + Reader contents; + if (der::ExpectTagAndGetValue(input, der::SEQUENCE, contents) != Success) { + return ByteString(); + } + // Skip AlgorithmIdentifier + if (der::ExpectTagAndSkipValue(contents, der::SEQUENCE) != Success) { + return ByteString(); + } + Input subjectPublicKey; + if (der::BitStringWithNoUnusedBits(contents, subjectPublicKey) + != Success) { + return ByteString(); + } + issuerKeyHash = HashedOctetString(ByteString(subjectPublicKey.UnsafeGetData(), + subjectPublicKey.GetLength()), context.certIDHashAlgorithm); + if (ENCODING_FAILED(issuerKeyHash)) { + return ByteString(); + } + } + + ByteString serialNumberValue(context.certID.serialNumber.UnsafeGetData(), + context.certID.serialNumber.GetLength()); + ByteString serialNumber(TLV(der::INTEGER, serialNumberValue)); + + // python DottedOIDToCode.py --alg id-sha1 1.3.14.3.2.26 + static const uint8_t alg_id_sha1[] = { + 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a + }; + // python DottedOIDToCode.py --alg id-sha256 2.16.840.1.101.3.4.2.1 + static const uint8_t alg_id_sha256[] = { + 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 + }; + // python DottedOIDToCode.py --alg id-sha384 2.16.840.1.101.3.4.2.2 + static const uint8_t alg_id_sha384[] = { + 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02 + }; + // python DottedOIDToCode.py --alg id-sha512 2.16.840.1.101.3.4.2.3 + static const uint8_t alg_id_sha512[] = { + 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03 + }; + + ByteString value; + if (!context.certIDHashAlgorithmEncoded.empty()) { + value.append(context.certIDHashAlgorithmEncoded); + } else { + switch (context.certIDHashAlgorithm) { + case DigestAlgorithm::sha1: + value.append(alg_id_sha1, sizeof(alg_id_sha1)); + break; + case DigestAlgorithm::sha256: + value.append(alg_id_sha256, sizeof(alg_id_sha256)); + break; + case DigestAlgorithm::sha384: + value.append(alg_id_sha384, sizeof(alg_id_sha384)); + break; + case DigestAlgorithm::sha512: + value.append(alg_id_sha512, sizeof(alg_id_sha512)); + break; + MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM + } + } + value.append(issuerNameHash); + value.append(issuerKeyHash); + value.append(serialNumber); + return TLV(der::SEQUENCE, value); +} + +// CertStatus ::= CHOICE { +// good [0] IMPLICIT NULL, +// revoked [1] IMPLICIT RevokedInfo, +// unknown [2] IMPLICIT UnknownInfo } +// +// RevokedInfo ::= SEQUENCE { +// revocationTime GeneralizedTime, +// revocationReason [0] EXPLICIT CRLReason OPTIONAL } +// +// UnknownInfo ::= NULL +// +ByteString +CertStatus(OCSPResponseContext& context) +{ + switch (context.certStatus) { + // Both good and unknown are ultimately represented as NULL - the only + // difference is in the tag that identifies them. + case 0: + case 2: + { + // XXX: MSVC 2015 wrongly warns about signed/unsigned conversion without + // the static cast. + return TLV(static_cast(der::CONTEXT_SPECIFIC | + context.certStatus), ByteString()); + } + case 1: + { + ByteString revocationTime(TimeToGeneralizedTime(context.revocationTime)); + if (ENCODING_FAILED(revocationTime)) { + return ByteString(); + } + // TODO(bug 980536): add support for revocationReason + return TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1, revocationTime); + } + default: + assert(false); + // fall through + } + return ByteString(); +} + +static const ByteString NO_UNUSED_BITS(1, 0x00); + +// The SubjectPublicKeyInfo syntax is specified in RFC 5280 Section 4.1. +TestKeyPair::TestKeyPair(const TestPublicKeyAlgorithm& aPublicKeyAlg, + const ByteString& spk) + : publicKeyAlg(aPublicKeyAlg) + , subjectPublicKeyInfo(TLV(der::SEQUENCE, + aPublicKeyAlg.algorithmIdentifier + + TLV(der::BIT_STRING, NO_UNUSED_BITS + spk))) + , subjectPublicKey(spk) +{ +} + +} } } // namespace mozilla::pkix::test -- cgit v1.2.3