diff options
Diffstat (limited to 'security/pkix/test/lib')
-rw-r--r-- | security/pkix/test/lib/moz.build | 39 | ||||
-rw-r--r-- | security/pkix/test/lib/pkixtestalg.cpp | 210 | ||||
-rw-r--r-- | security/pkix/test/lib/pkixtestnss.cpp | 309 | ||||
-rw-r--r-- | security/pkix/test/lib/pkixtestutil.cpp | 1153 | ||||
-rw-r--r-- | security/pkix/test/lib/pkixtestutil.h | 448 |
5 files changed, 2159 insertions, 0 deletions
diff --git a/security/pkix/test/lib/moz.build b/security/pkix/test/lib/moz.build new file mode 100644 index 000000000..c99aedf3a --- /dev/null +++ b/security/pkix/test/lib/moz.build @@ -0,0 +1,39 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# 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. + +SOURCES += [ + 'pkixtestalg.cpp', + 'pkixtestnss.cpp', + 'pkixtestutil.cpp', +] + +Library('pkixtestutil') + +LOCAL_INCLUDES += [ + '../../include', + '../../lib', +] + +FINAL_LIBRARY = 'xul-gtest' + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/security/pkix/test/lib/pkixtestalg.cpp b/security/pkix/test/lib/pkixtestalg.cpp new file mode 100644 index 000000000..a19fd26f3 --- /dev/null +++ b/security/pkix/test/lib/pkixtestalg.cpp @@ -0,0 +1,210 @@ +/* -*- 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 "pkixtestutil.h" + +#include "pkixder.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 <size_t SIZE> +ByteString +OID(const uint8_t (&rawValue)[SIZE]) +{ + return TLV(der::OIDTag, ByteString(rawValue, SIZE)); +} + +template <size_t SIZE> +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 <size_t SIZE> +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& publicKeyAlg, + TestDigestAlgorithmID digestAlg, + const ByteString& algorithmIdentifier, + bool accepted) + : publicKeyAlg(publicKeyAlg) + , digestAlg(digestAlg) + , algorithmIdentifier(algorithmIdentifier) + , accepted(accepted) +{ +} + +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/pkix/test/lib/pkixtestnss.cpp b/security/pkix/test/lib/pkixtestnss.cpp new file mode 100644 index 000000000..4d49b9e31 --- /dev/null +++ b/security/pkix/test/lib/pkixtestnss.cpp @@ -0,0 +1,309 @@ +/* -*- 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 "pkixtestutil.h" + +#include <limits> + +#include "cryptohi.h" +#include "keyhi.h" +#include "nss.h" +#include "pk11pqg.h" +#include "pk11pub.h" +#include "pkix/pkixnss.h" +#include "pkixder.h" +#include "pkixutil.h" +#include "prinit.h" +#include "secerr.h" +#include "secitem.h" + +namespace mozilla { namespace pkix { namespace test { + +namespace { + +typedef ScopedPtr<SECKEYPublicKey, SECKEY_DestroyPublicKey> + ScopedSECKEYPublicKey; +typedef ScopedPtr<SECKEYPrivateKey, SECKEY_DestroyPrivateKey> + ScopedSECKEYPrivateKey; + +inline void +SECITEM_FreeItem_true(SECItem* item) +{ + SECITEM_FreeItem(item, true); +} + +typedef mozilla::pkix::ScopedPtr<SECItem, SECITEM_FreeItem_true> ScopedSECItem; + +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 takes ownership of privateKey. + NSSTestKeyPair(const TestPublicKeyAlgorithm& publicKeyAlg, + const ByteString& spk, + SECKEYPrivateKey* privateKey) + : TestKeyPair(publicKeyAlg, spk) + , privateKey(privateKey) + { + } + + 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(); + } + + SECItem signatureItem; + if (SEC_SignData(&signatureItem, tbs.data(), + static_cast<int>(tbs.length()), + privateKey.get(), oidTag) != SECSuccess) { + return MapPRErrorCodeToResult(PR_GetError()); + } + signature.assign(signatureItem.data, signatureItem.len); + SECITEM_FreeItem(&signatureItem, false); + return Success; + } + + TestKeyPair* Clone() const override + { + ScopedSECKEYPrivateKey + privateKeyCopy(SECKEY_CopyPrivateKey(privateKey.get())); + if (!privateKeyCopy) { + return nullptr; + } + return new (std::nothrow) NSSTestKeyPair(publicKeyAlg, + subjectPublicKey, + privateKeyCopy.release()); + } + +private: + ScopedSECKEYPrivateKey privateKey; +}; + +} // namespace + +// This private function is also used by Gecko's PSM test framework +// (OCSPCommon.cpp). +// +// Ownership of privateKey is transfered. +TestKeyPair* CreateTestKeyPair(const TestPublicKeyAlgorithm publicKeyAlg, + const SECKEYPublicKey& publicKey, + SECKEYPrivateKey* privateKey) +{ + ScopedPtr<CERTSubjectPublicKeyInfo, SECKEY_DestroySubjectPublicKeyInfo> + spki(SECKEY_CreateSubjectPublicKeyInfo(&publicKey)); + if (!spki) { + return nullptr; + } + SECItem spkDER = spki->subjectPublicKey; + DER_ConvertBitString(&spkDER); // bits to bytes + return new (std::nothrow) NSSTestKeyPair(publicKeyAlg, + ByteString(spkDER.data, spkDER.len), + privateKey); +} + +namespace { + +TestKeyPair* +GenerateKeyPairInner() +{ + ScopedPtr<PK11SlotInfo, PK11_FreeSlot> slot(PK11_GetInternalSlot()); + if (!slot) { + abort(); + } + + // 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++) { + PK11RSAGenParams params; + params.keySizeInBits = 2048; + params.pe = 3; + 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.release()); + } + + 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((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(); + + ScopedPtr<PK11SlotInfo, PK11_FreeSlot> 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.release()); +} + +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 diff --git a/security/pkix/test/lib/pkixtestutil.cpp b/security/pkix/test/lib/pkixtestutil.cpp new file mode 100644 index 000000000..decdee09a --- /dev/null +++ b/security/pkix/test/lib/pkixtestutil.cpp @@ -0,0 +1,1153 @@ +/* -*- 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 "pkixtestutil.h" + +#include <cerrno> +#include <cstdio> +#include <limits> +#include <new> +#include <sstream> +#include <cstdlib> + +#include "pkixder.h" +#include "pkixutil.h" + +using namespace std; + +namespace mozilla { namespace pkix { namespace test { + +namespace { + +inline void +fclose_void(FILE* file) { + (void) fclose(file); +} + +typedef mozilla::pkix::ScopedPtr<FILE, fclose_void> 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<uint8_t>(length)); + } else if (value.length() < 256) { + result.push_back(0x81u); + result.push_back(static_cast<uint8_t>(length)); + } else if (value.length() < 65536) { + result.push_back(0x82u); + result.push_back(static_cast<uint8_t>(length / 256)); + result.push_back(static_cast<uint8_t>(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& certID, time_t time) + : certID(certID) + , 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_t>(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 +SHA1(const ByteString& toHash) +{ + uint8_t digestBuf[20]; + Input input; + if (input.Init(toHash.data(), toHash.length()) != Success) { + abort(); + } + Result rv = TestDigestBuf(input, DigestAlgorithm::sha1, digestBuf, + sizeof(digestBuf)); + if (rv != Success) { + abort(); + } + return ByteString(digestBuf, sizeof(digestBuf)); +} + +static ByteString +HashedOctetString(const ByteString& bytes) +{ + ByteString digest(SHA1(bytes)); + 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<uint8_t>(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(WIN32) && !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<uint8_t>('0' + (year / 1000))); + value.push_back(static_cast<uint8_t>('0' + ((year % 1000) / 100))); + } + + value.push_back(static_cast<uint8_t>('0' + ((year % 100) / 10))); + value.push_back(static_cast<uint8_t>('0' + (year % 10))); + value.push_back(static_cast<uint8_t>('0' + ((exploded.tm_mon + 1) / 10))); + value.push_back(static_cast<uint8_t>('0' + ((exploded.tm_mon + 1) % 10))); + value.push_back(static_cast<uint8_t>('0' + (exploded.tm_mday / 10))); + value.push_back(static_cast<uint8_t>('0' + (exploded.tm_mday % 10))); + value.push_back(static_cast<uint8_t>('0' + (exploded.tm_hour / 10))); + value.push_back(static_cast<uint8_t>('0' + (exploded.tm_hour % 10))); + value.push_back(static_cast<uint8_t>('0' + (exploded.tm_min / 10))); + value.push_back(static_cast<uint8_t>('0' + (exploded.tm_min % 10))); + value.push_back(static_cast<uint8_t>('0' + (exploded.tm_sec / 10))); + value.push_back(static_cast<uint8_t>('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<long>(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 <size_t N> +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*/ 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<uint8_t>(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); +} + +// 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)); + 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 = KeyHash(ByteString(subjectPublicKey.UnsafeGetData(), + subjectPublicKey.GetLength())); + 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 + }; + + ByteString value; + value.append(alg_id_sha1, sizeof(alg_id_sha1)); + 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<uint8_t>(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& publicKeyAlg, + const ByteString& spk) + : publicKeyAlg(publicKeyAlg) + , subjectPublicKeyInfo(TLV(der::SEQUENCE, + publicKeyAlg.algorithmIdentifier + + TLV(der::BIT_STRING, NO_UNUSED_BITS + spk))) + , subjectPublicKey(spk) +{ +} + +} } } // namespace mozilla::pkix::test diff --git a/security/pkix/test/lib/pkixtestutil.h b/security/pkix/test/lib/pkixtestutil.h new file mode 100644 index 000000000..b36f1f8ad --- /dev/null +++ b/security/pkix/test/lib/pkixtestutil.h @@ -0,0 +1,448 @@ +/* -*- 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. + */ + +#ifndef mozilla_pkix_test_pkixtestutils_h +#define mozilla_pkix_test_pkixtestutils_h + +#include <ctime> +#include <stdint.h> // Some Mozilla-supported compilers lack <cstdint> +#include <string> +#include <cstring> + +#include "pkix/pkixtypes.h" +#include "../../lib/ScopedPtr.h" + +namespace mozilla { namespace pkix { namespace test { + +typedef std::basic_string<uint8_t> ByteString; + +inline bool ENCODING_FAILED(const ByteString& bs) { return bs.empty(); } + +template <size_t L> +inline ByteString +BytesToByteString(const uint8_t (&bytes)[L]) +{ + return ByteString(bytes, L); +} + +// XXX: Ideally, we should define this instead: +// +// template <typename T, std::size_t N> +// constexpr inline std::size_t +// ArrayLength(T (&)[N]) +// { +// return N; +// } +// +// However, we don't because not all supported compilers support constexpr, +// and we need to calculate array lengths in static_assert sometimes. +// +// XXX: Evaluates its argument twice +#define MOZILLA_PKIX_ARRAY_LENGTH(x) (sizeof(x) / sizeof((x)[0])) + +bool InputEqualsByteString(Input input, const ByteString& bs); +ByteString InputToByteString(Input input); + +// python DottedOIDToCode.py --tlv id-kp-OCSPSigning 1.3.6.1.5.5.7.3.9 +static const uint8_t tlv_id_kp_OCSPSigning[] = { + 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x09 +}; + +// python DottedOIDToCode.py --tlv id-kp-serverAuth 1.3.6.1.5.5.7.3.1 +static const uint8_t tlv_id_kp_serverAuth[] = { + 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01 +}; + +enum class TestDigestAlgorithmID +{ + MD2, + MD5, + SHA1, + SHA224, + SHA256, + SHA384, + SHA512, +}; + +struct TestPublicKeyAlgorithm +{ + explicit TestPublicKeyAlgorithm(const ByteString& algorithmIdentifier) + : algorithmIdentifier(algorithmIdentifier) { } + bool operator==(const TestPublicKeyAlgorithm& other) const + { + return algorithmIdentifier == other.algorithmIdentifier; + } + ByteString algorithmIdentifier; +}; + +ByteString DSS_P(); +ByteString DSS_Q(); +ByteString DSS_G(); + +TestPublicKeyAlgorithm DSS(); +TestPublicKeyAlgorithm RSA_PKCS1(); + +struct TestSignatureAlgorithm +{ + TestSignatureAlgorithm(const TestPublicKeyAlgorithm& publicKeyAlg, + TestDigestAlgorithmID digestAlg, + const ByteString& algorithmIdentifier, + bool accepted); + + TestPublicKeyAlgorithm publicKeyAlg; + TestDigestAlgorithmID digestAlg; + ByteString algorithmIdentifier; + bool accepted; +}; + +TestSignatureAlgorithm md2WithRSAEncryption(); +TestSignatureAlgorithm md5WithRSAEncryption(); +TestSignatureAlgorithm sha1WithRSAEncryption(); +TestSignatureAlgorithm sha256WithRSAEncryption(); + +// e.g. YMDHMS(2016, 12, 31, 1, 23, 45) => 2016-12-31:01:23:45 (GMT) +mozilla::pkix::Time YMDHMS(uint16_t year, uint16_t month, uint16_t day, + uint16_t hour, uint16_t minutes, uint16_t seconds); + +ByteString TLV(uint8_t tag, size_t length, const ByteString& value); + +inline ByteString +TLV(uint8_t tag, const ByteString& value) +{ + return TLV(tag, value.length(), value); +} + +// Although we can't enforce it without relying on Cuser-defined literals, +// which aren't supported by all of our compilers yet, you should only pass +// string literals as the last parameter to the following two functions. + +template <size_t N> +inline ByteString +TLV(uint8_t tag, const char(&value)[N]) +{ + static_assert(N > 0, "cannot have string literal of size 0"); + assert(value[N - 1] == 0); + return TLV(tag, ByteString(reinterpret_cast<const uint8_t*>(&value), N - 1)); +} + +template <size_t N> +inline ByteString +TLV(uint8_t tag, size_t length, const char(&value)[N]) +{ + static_assert(N > 0, "cannot have string literal of size 0"); + assert(value[N - 1] == 0); + return TLV(tag, length, + ByteString(reinterpret_cast<const uint8_t*>(&value), N - 1)); +} + +ByteString Boolean(bool value); +ByteString Integer(long value); + +ByteString CN(const ByteString&, uint8_t encodingTag = 0x0c /*UTF8String*/); + +inline ByteString +CN(const char* value, uint8_t encodingTag = 0x0c /*UTF8String*/) +{ + return CN(ByteString(reinterpret_cast<const uint8_t*>(value), + std::strlen(value)), encodingTag); +} + +ByteString OU(const ByteString&, uint8_t encodingTag = 0x0c /*UTF8String*/); + +inline ByteString +OU(const char* value, uint8_t encodingTag = 0x0c /*UTF8String*/) +{ + return OU(ByteString(reinterpret_cast<const uint8_t*>(value), + std::strlen(value)), encodingTag); +} + +ByteString emailAddress(const ByteString&); + +inline ByteString +emailAddress(const char* value) +{ + return emailAddress(ByteString(reinterpret_cast<const uint8_t*>(value), + std::strlen(value))); +} + +// RelativeDistinguishedName ::= +// SET SIZE (1..MAX) OF AttributeTypeAndValue +// +ByteString RDN(const ByteString& avas); + +// Name ::= CHOICE { -- only one possibility for now -- +// rdnSequence RDNSequence } +// +// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName +// +ByteString Name(const ByteString& rdns); + +inline ByteString +CNToDERName(const ByteString& cn) +{ + return Name(RDN(CN(cn))); +} + +inline ByteString +CNToDERName(const char* cn) +{ + return Name(RDN(CN(cn))); +} + +// GeneralName ::= CHOICE { +// otherName [0] OtherName, +// rfc822Name [1] IA5String, +// dNSName [2] IA5String, +// x400Address [3] ORAddress, +// directoryName [4] Name, +// ediPartyName [5] EDIPartyName, +// uniformResourceIdentifier [6] IA5String, +// iPAddress [7] OCTET STRING, +// registeredID [8] OBJECT IDENTIFIER } + +inline ByteString +RFC822Name(const ByteString& name) +{ + // (2 << 6) means "context-specific", 1 is the GeneralName tag. + return TLV((2 << 6) | 1, name); +} + +template <size_t L> +inline ByteString +RFC822Name(const char (&bytes)[L]) +{ + return RFC822Name(ByteString(reinterpret_cast<const uint8_t*>(&bytes), + L - 1)); +} + +inline ByteString +DNSName(const ByteString& name) +{ + // (2 << 6) means "context-specific", 2 is the GeneralName tag. + return TLV((2 << 6) | 2, name); +} + +template <size_t L> +inline ByteString +DNSName(const char (&bytes)[L]) +{ + return DNSName(ByteString(reinterpret_cast<const uint8_t*>(&bytes), + L - 1)); +} + +inline ByteString +DirectoryName(const ByteString& name) +{ + // (2 << 6) means "context-specific", (1 << 5) means "constructed", and 4 is + // the DirectoryName tag. + return TLV((2 << 6) | (1 << 5) | 4, name); +} + +inline ByteString +IPAddress() +{ + // (2 << 6) means "context-specific", 7 is the GeneralName tag. + return TLV((2 << 6) | 7, ByteString()); +} + +template <size_t L> +inline ByteString +IPAddress(const uint8_t (&bytes)[L]) +{ + // (2 << 6) means "context-specific", 7 is the GeneralName tag. + return TLV((2 << 6) | 7, ByteString(bytes, L)); +} + +// Names should be zero or more GeneralNames, like DNSName and IPAddress return, +// concatenated together. +// +// CreatedEncodedSubjectAltName(ByteString()) results in a SAN with an empty +// sequence. CreateEmptyEncodedSubjectName() results in a SAN without any +// sequence. +ByteString CreateEncodedSubjectAltName(const ByteString& names); +ByteString CreateEncodedEmptySubjectAltName(); + +class TestKeyPair +{ +public: + virtual ~TestKeyPair() { } + + const TestPublicKeyAlgorithm publicKeyAlg; + + // The DER encoding of the entire SubjectPublicKeyInfo structure. This is + // what is encoded in certificates. + const ByteString subjectPublicKeyInfo; + + // The DER encoding of subjectPublicKeyInfo.subjectPublicKey. This is what is + // hashed to create CertIDs for OCSP. + const ByteString subjectPublicKey; + + virtual Result SignData(const ByteString& tbs, + const TestSignatureAlgorithm& signatureAlgorithm, + /*out*/ ByteString& signature) const = 0; + + virtual TestKeyPair* Clone() const = 0; +protected: + TestKeyPair(const TestPublicKeyAlgorithm& publicKeyAlg, const ByteString& spk); + TestKeyPair(const TestKeyPair&) = delete; + void operator=(const TestKeyPair&) = delete; +}; + +TestKeyPair* CloneReusedKeyPair(); +TestKeyPair* GenerateKeyPair(); +TestKeyPair* GenerateDSSKeyPair(); +inline void DeleteTestKeyPair(TestKeyPair* keyPair) { delete keyPair; } +typedef ScopedPtr<TestKeyPair, DeleteTestKeyPair> ScopedTestKeyPair; + +Result TestVerifyECDSASignedDigest(const SignedDigest& signedDigest, + Input subjectPublicKeyInfo); +Result TestVerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest, + Input subjectPublicKeyInfo); +Result TestDigestBuf(Input item, DigestAlgorithm digestAlg, + /*out*/ uint8_t* digestBuf, size_t digestBufLen); + +// Replace one substring in item with another of the same length, but only if +// the substring was found exactly once. The "same length" restriction is +// useful for avoiding invalidating lengths encoded within the item. The +// "only once" restriction is helpful for avoiding making accidental changes. +// +// The string to search for must be 8 or more bytes long so that it is +// extremely unlikely that there will ever be any false positive matches +// in digital signatures, keys, hashes, etc. +Result TamperOnce(/*in/out*/ ByteString& item, const ByteString& from, + const ByteString& to); + +/////////////////////////////////////////////////////////////////////////////// +// Encode Certificates + +enum Version { v1 = 0, v2 = 1, v3 = 2 }; + +// signature is assumed to be the DER encoding of an AlgorithmIdentifer. It is +// put into the signature field of the TBSCertificate. In most cases, it will +// be the same as signatureAlgorithm, which is the algorithm actually used +// to sign the certificate. +// serialNumber is assumed to be the DER encoding of an INTEGER. +// +// If extensions is null, then no extensions will be encoded. Otherwise, +// extensions must point to an array of ByteStrings, terminated with an empty +// ByteString. (If the first item of the array is empty then an empty +// Extensions sequence will be encoded.) +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 CreateEncodedSerialNumber(long value); + +enum class Critical { No = 0, Yes = 1 }; + +ByteString CreateEncodedBasicConstraints(bool isCA, + /*optional*/ long* pathLenConstraint, + Critical critical); + +// Creates a DER-encoded extKeyUsage extension with one EKU OID. +ByteString CreateEncodedEKUExtension(Input eku, Critical critical); + +/////////////////////////////////////////////////////////////////////////////// +// Encode OCSP responses + +class OCSPResponseExtension final +{ +public: + OCSPResponseExtension(); + + ByteString id; + bool critical; + ByteString value; + OCSPResponseExtension* next; +}; + +class OCSPResponseContext final +{ +public: + OCSPResponseContext(const CertID& certID, std::time_t time); + + const CertID& certID; + // TODO(bug 980538): add a way to specify what certificates are included. + + // The fields below are in the order that they appear in an OCSP response. + + enum OCSPResponseStatus + { + successful = 0, + malformedRequest = 1, + internalError = 2, + tryLater = 3, + // 4 is not used + sigRequired = 5, + unauthorized = 6, + }; + uint8_t responseStatus; // an OCSPResponseStatus or an invalid value + bool skipResponseBytes; // If true, don't include responseBytes + + // responderID + ByteString signerNameDER; // If set, responderID will use the byName + // form; otherwise responderID will use the + // byKeyHash form. + + std::time_t producedAt; + + // SingleResponse extensions (for the certID given in the constructor). + OCSPResponseExtension* singleExtensions; + // ResponseData extensions. + OCSPResponseExtension* responseExtensions; + bool includeEmptyExtensions; // If true, include the extension wrapper + // regardless of if there are any actual + // extensions. + ScopedTestKeyPair signerKeyPair; + TestSignatureAlgorithm signatureAlgorithm; + bool badSignature; // If true, alter the signature to fail verification + const ByteString* certs; // optional; array terminated by an empty string + + // The following fields are on a per-SingleResponse basis. In the future we + // may support including multiple SingleResponses per response. + enum CertStatus + { + good = 0, + revoked = 1, + unknown = 2, + }; + uint8_t certStatus; // CertStatus or an invalid value + std::time_t revocationTime; // For certStatus == revoked + std::time_t thisUpdate; + std::time_t nextUpdate; + bool includeNextUpdate; +}; + +ByteString CreateEncodedOCSPResponse(OCSPResponseContext& context); + +} } } // namespace mozilla::pkix::test + +#endif // mozilla_pkix_test_pkixtestutils_h |