summaryrefslogtreecommitdiffstats
path: root/security/nss/lib/mozpkix/test-lib
diff options
context:
space:
mode:
Diffstat (limited to 'security/nss/lib/mozpkix/test-lib')
-rw-r--r--security/nss/lib/mozpkix/test-lib/pkixtestalg.cpp211
-rw-r--r--security/nss/lib/mozpkix/test-lib/pkixtestnss.cpp374
-rw-r--r--security/nss/lib/mozpkix/test-lib/pkixtestutil.cpp1188
3 files changed, 1773 insertions, 0 deletions
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 <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& 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 <limits>
+
+#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<uint8_t*>(encryptedPrivateKey.data()),
+ static_cast<unsigned int>(encryptedPrivateKey.length())
+ };
+ SECItem encryptionAlgorithmItem = {
+ siBuffer,
+ const_cast<uint8_t*>(encryptionAlgorithm.data()),
+ static_cast<unsigned int>(encryptionAlgorithm.length())
+ };
+ SECItem encryptionParamsItem = {
+ siBuffer,
+ const_cast<uint8_t*>(encryptionParams.data()),
+ static_cast<unsigned int>(encryptionParams.length())
+ };
+ SECKEYEncryptedPrivateKeyInfo encryptedPrivateKeyInfo = {
+ nullptr,
+ { encryptionAlgorithmItem, encryptionParamsItem },
+ encryptedPrivateKeyInfoItem
+ };
+ SECItem passwordItem = { siBuffer, nullptr, 0 };
+ SECItem publicValueItem = {
+ siBuffer,
+ const_cast<uint8_t*>(subjectPublicKey.data()),
+ static_cast<unsigned int>(subjectPublicKey.length())
+ };
+ SECKEYPrivateKey* privateKey;
+ // This should always be an RSA key (we'll have aborted above if we're not
+ // doing an RSA signature).
+ if (PK11_ImportEncryptedPrivateKeyInfoAndReturnKey(
+ slot.get(), &encryptedPrivateKeyInfo, &passwordItem, nullptr,
+ &publicValueItem, false, false, rsaKey, KU_ALL, &privateKey,
+ nullptr) != SECSuccess) {
+ return MapPRErrorCodeToResult(PR_GetError());
+ }
+ ScopedSECKEYPrivateKey scopedPrivateKey(privateKey);
+ SECItem signatureItem;
+ if (SEC_SignData(&signatureItem, tbs.data(),
+ static_cast<int>(tbs.length()),
+ scopedPrivateKey.get(), oidTag) != SECSuccess) {
+ return MapPRErrorCodeToResult(PR_GetError());
+ }
+ signature.assign(signatureItem.data, signatureItem.len);
+ SECITEM_FreeItem(&signatureItem, false);
+ return Success;
+ }
+
+ TestKeyPair* Clone() const override
+ {
+ return new (std::nothrow) NSSTestKeyPair(publicKeyAlg,
+ subjectPublicKey,
+ encryptedPrivateKey,
+ encryptionAlgorithm,
+ encryptionParams);
+ }
+
+private:
+ const ByteString encryptedPrivateKey;
+ const ByteString encryptionAlgorithm;
+ const ByteString encryptionParams;
+};
+
+} // namespace
+
+// This private function is also used by Gecko's PSM test framework
+// (OCSPCommon.cpp).
+TestKeyPair* CreateTestKeyPair(const TestPublicKeyAlgorithm publicKeyAlg,
+ const ScopedSECKEYPublicKey& publicKey,
+ const ScopedSECKEYPrivateKey& privateKey)
+{
+ ScopedCERTSubjectPublicKeyInfo
+ spki(SECKEY_CreateSubjectPublicKeyInfo(publicKey.get()));
+ if (!spki) {
+ return nullptr;
+ }
+ SECItem spkDER = spki->subjectPublicKey;
+ DER_ConvertBitString(&spkDER); // bits to bytes
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ if (!slot) {
+ return nullptr;
+ }
+ // Because NSSTestKeyPair isn't tracked by XPCOM and won't otherwise be aware
+ // of shutdown, we don't have a way to release NSS resources at the
+ // appropriate time. To work around this, NSSTestKeyPair doesn't hold on to
+ // NSS resources. Instead, we export the generated private key part as an
+ // encrypted blob (with an empty password and fairly lame encryption). When we
+ // need to use it (e.g. to sign something), we decrypt it and create a
+ // temporary key object.
+ SECItem passwordItem = { siBuffer, nullptr, 0 };
+ ScopedSECKEYEncryptedPrivateKeyInfo encryptedPrivateKey(
+ PK11_ExportEncryptedPrivKeyInfo(
+ slot.get(), SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC,
+ &passwordItem, privateKey.get(), 1, nullptr));
+ if (!encryptedPrivateKey) {
+ return nullptr;
+ }
+
+ return new (std::nothrow) NSSTestKeyPair(
+ publicKeyAlg,
+ ByteString(spkDER.data, spkDER.len),
+ ByteString(encryptedPrivateKey->encryptedData.data,
+ encryptedPrivateKey->encryptedData.len),
+ ByteString(encryptedPrivateKey->algorithm.algorithm.data,
+ encryptedPrivateKey->algorithm.algorithm.len),
+ ByteString(encryptedPrivateKey->algorithm.parameters.data,
+ encryptedPrivateKey->algorithm.parameters.len));
+}
+
+namespace {
+
+TestKeyPair*
+GenerateKeyPairInner()
+{
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ if (!slot) {
+ abort();
+ }
+ PK11RSAGenParams params;
+ params.keySizeInBits = 2048;
+ params.pe = 65537;
+
+ // Bug 1012786: PK11_GenerateKeyPair can fail if there is insufficient
+ // entropy to generate a random key. Attempting to add some entropy and
+ // retrying appears to solve this issue.
+ for (uint32_t retries = 0; retries < 10; retries++) {
+ SECKEYPublicKey* publicKeyTemp = nullptr;
+ ScopedSECKEYPrivateKey
+ privateKey(PK11_GenerateKeyPair(slot.get(), CKM_RSA_PKCS_KEY_PAIR_GEN,
+ &params, &publicKeyTemp, false, true,
+ nullptr));
+ ScopedSECKEYPublicKey publicKey(publicKeyTemp);
+ if (privateKey) {
+ return CreateTestKeyPair(RSA_PKCS1(), publicKey, privateKey);
+ }
+
+ assert(!publicKeyTemp);
+
+ if (PR_GetError() != SEC_ERROR_PKCS11_FUNCTION_FAILED) {
+ break;
+ }
+
+ // Since these keys are only for testing, we don't need them to be good,
+ // random keys.
+ // https://xkcd.com/221/
+ static const uint8_t RANDOM_NUMBER[] = { 4, 4, 4, 4, 4, 4, 4, 4 };
+ if (PK11_RandomUpdate(
+ const_cast<void*>(reinterpret_cast<const void*>(RANDOM_NUMBER)),
+ sizeof(RANDOM_NUMBER)) != SECSuccess) {
+ break;
+ }
+ }
+
+ abort();
+}
+
+} // namespace
+
+TestKeyPair*
+GenerateKeyPair()
+{
+ InitNSSIfNeeded();
+ return GenerateKeyPairInner();
+}
+
+TestKeyPair*
+CloneReusedKeyPair()
+{
+ static PRCallOnceType initCallOnce;
+ if (PR_CallOnce(&initCallOnce, InitReusedKeyPair) != PR_SUCCESS) {
+ abort();
+ }
+ assert(reusedKeyPair);
+ return reusedKeyPair->Clone();
+}
+
+TestKeyPair*
+GenerateDSSKeyPair()
+{
+ InitNSSIfNeeded();
+
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ if (!slot) {
+ return nullptr;
+ }
+
+ ByteString p(DSS_P());
+ ByteString q(DSS_Q());
+ ByteString g(DSS_G());
+
+ static const PQGParams PARAMS = {
+ nullptr,
+ { siBuffer,
+ const_cast<uint8_t*>(p.data()),
+ static_cast<unsigned int>(p.length())
+ },
+ { siBuffer,
+ const_cast<uint8_t*>(q.data()),
+ static_cast<unsigned int>(q.length())
+ },
+ { siBuffer,
+ const_cast<uint8_t*>(g.data()),
+ static_cast<unsigned int>(g.length())
+ }
+ };
+
+ SECKEYPublicKey* publicKeyTemp = nullptr;
+ ScopedSECKEYPrivateKey
+ privateKey(PK11_GenerateKeyPair(slot.get(), CKM_DSA_KEY_PAIR_GEN,
+ const_cast<PQGParams*>(&PARAMS),
+ &publicKeyTemp, false, true, nullptr));
+ if (!privateKey) {
+ return nullptr;
+ }
+ ScopedSECKEYPublicKey publicKey(publicKeyTemp);
+ return CreateTestKeyPair(DSS(), publicKey, privateKey);
+}
+
+Result
+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 <cerrno>
+#include <cstdio>
+#include <limits>
+#include <new>
+#include <sstream>
+#include <cstdlib>
+
+#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<FILE, ScopedMaybeDeleteFile> 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& 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_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
+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<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(_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<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 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<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, 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<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& 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