summaryrefslogtreecommitdiffstats
path: root/security/ct/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /security/ct/tests
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/ct/tests')
-rw-r--r--security/ct/tests/gtest/BTSerializationTest.cpp140
-rw-r--r--security/ct/tests/gtest/BTSignedTreeHeadTest.cpp266
-rw-r--r--security/ct/tests/gtest/BTVerificationTest.cpp257
-rw-r--r--security/ct/tests/gtest/CTDiversityPolicyTest.cpp15
-rw-r--r--security/ct/tests/gtest/CTLogVerifierTest.cpp118
-rw-r--r--security/ct/tests/gtest/CTObjectsExtractorTest.cpp82
-rw-r--r--security/ct/tests/gtest/CTPolicyEnforcerTest.cpp387
-rw-r--r--security/ct/tests/gtest/CTSerializationTest.cpp216
-rw-r--r--security/ct/tests/gtest/CTTestUtils.cpp820
-rw-r--r--security/ct/tests/gtest/CTTestUtils.h137
-rw-r--r--security/ct/tests/gtest/MultiLogCTVerifierTest.cpp254
-rwxr-xr-xsecurity/ct/tests/gtest/createSTHTestData.py133
-rw-r--r--security/ct/tests/gtest/ec-signer-rsa-spki-sth.inc.tbs8
-rw-r--r--security/ct/tests/gtest/missing-extensions-sth.inc.tbs8
-rw-r--r--security/ct/tests/gtest/missing-log-id-sth.inc.tbs8
-rw-r--r--security/ct/tests/gtest/missing-root-hash-sth.inc.tbs7
-rw-r--r--security/ct/tests/gtest/missing-timestamp-sth.inc.tbs8
-rw-r--r--security/ct/tests/gtest/missing-tree-size-sth.inc.tbs8
-rw-r--r--security/ct/tests/gtest/moz.build56
-rw-r--r--security/ct/tests/gtest/rsa-signer-ec-spki-sth.inc.tbs8
-rw-r--r--security/ct/tests/gtest/rsa-signer-rsa-spki-sth.inc.tbs8
-rw-r--r--security/ct/tests/gtest/signature-covers-log-id-sth.inc.tbs8
-rw-r--r--security/ct/tests/gtest/truncated-extension-sth.inc.tbs11
-rw-r--r--security/ct/tests/gtest/truncated-log-id-sth.inc.tbs8
-rw-r--r--security/ct/tests/gtest/truncated-root-hash-sth.inc.tbs8
-rw-r--r--security/ct/tests/gtest/truncated-timestamp-sth.inc.tbs8
-rw-r--r--security/ct/tests/gtest/truncated-tree-size-sth.inc.tbs8
-rw-r--r--security/ct/tests/gtest/valid-secp521r1-sha512-sth.inc.tbs10
-rw-r--r--security/ct/tests/gtest/valid-sth.inc.tbs8
-rw-r--r--security/ct/tests/gtest/valid-with-extension-sth.inc.tbs11
-rw-r--r--security/ct/tests/gtest/wrong-signing-key-sth.inc.tbs8
-rw-r--r--security/ct/tests/gtest/wrong-spki-sth.inc.tbs8
32 files changed, 3040 insertions, 0 deletions
diff --git a/security/ct/tests/gtest/BTSerializationTest.cpp b/security/ct/tests/gtest/BTSerializationTest.cpp
new file mode 100644
index 0000000000..75c16faf42
--- /dev/null
+++ b/security/ct/tests/gtest/BTSerializationTest.cpp
@@ -0,0 +1,140 @@
+/* -*- 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 Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "BTVerifier.h"
+#include "CTTestUtils.h"
+#include "gtest/gtest.h"
+
+namespace mozilla {
+namespace ct {
+
+using namespace pkix;
+
+class BTSerializationTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ mTestInclusionProof = GetTestInclusionProof();
+ mTestInclusionProofUnexpectedData = GetTestInclusionProofUnexpectedData();
+ mTestInclusionProofInvalidHashSize = GetTestInclusionProofInvalidHashSize();
+ mTestInclusionProofInvalidHash = GetTestInclusionProofInvalidHash();
+ mTestInclusionProofMissingLogId = GetTestInclusionProofMissingLogId();
+ mTestInclusionProofNullPathLength = GetTestInclusionProofNullPathLength();
+ mTestInclusionProofPathLengthTooSmall =
+ GetTestInclusionProofPathLengthTooSmall();
+ mTestInclusionProofPathLengthTooLarge =
+ GetTestInclusionProofPathLengthTooLarge();
+ mTestInclusionProofNullTreeSize = GetTestInclusionProofNullTreeSize();
+ mTestInclusionProofLeafIndexOutOfBounds =
+ GetTestInclusionProofLeafIndexOutOfBounds();
+ mTestInclusionProofExtraData = GetTestInclusionProofExtraData();
+ }
+
+ protected:
+ Buffer mTestInclusionProof;
+ Buffer mTestInclusionProofUnexpectedData;
+ Buffer mTestInclusionProofInvalidHashSize;
+ Buffer mTestInclusionProofInvalidHash;
+ Buffer mTestInclusionProofMissingLogId;
+ Buffer mTestInclusionProofNullPathLength;
+ Buffer mTestInclusionProofPathLengthTooSmall;
+ Buffer mTestInclusionProofPathLengthTooLarge;
+ Buffer mTestInclusionProofNullTreeSize;
+ Buffer mTestInclusionProofLeafIndexOutOfBounds;
+ Buffer mTestInclusionProofExtraData;
+};
+
+TEST_F(BTSerializationTest, DecodesInclusionProof) {
+ const uint64_t expectedTreeSize = 4;
+ const uint64_t expectedLeafIndex = 2;
+ const uint64_t expectedInclusionPathElements = 2;
+
+ Buffer expectedLogId = {0x01, 0x00};
+
+ Input encodedProofInput = InputForBuffer(mTestInclusionProof);
+ InclusionProofDataV2 ipr;
+ ASSERT_EQ(Success, DecodeInclusionProof(encodedProofInput, ipr));
+ EXPECT_EQ(expectedLogId, ipr.logId);
+ EXPECT_EQ(expectedTreeSize, ipr.treeSize);
+ EXPECT_EQ(expectedLeafIndex, ipr.leafIndex);
+ EXPECT_EQ(expectedInclusionPathElements, ipr.inclusionPath.size());
+ EXPECT_EQ(GetTestNodeHash0(), ipr.inclusionPath[0]);
+ EXPECT_EQ(GetTestNodeHash1(), ipr.inclusionPath[1]);
+}
+
+TEST_F(BTSerializationTest, FailsDecodingInclusionProofUnexpectedData) {
+ Input encodedProofInput = InputForBuffer(mTestInclusionProofUnexpectedData);
+ InclusionProofDataV2 ipr;
+ ASSERT_EQ(pkix::Result::ERROR_BAD_DER,
+ DecodeInclusionProof(encodedProofInput, ipr));
+}
+
+TEST_F(BTSerializationTest, FailsDecodingInvalidHashSize) {
+ Input encodedProofInput = InputForBuffer(mTestInclusionProofInvalidHashSize);
+ InclusionProofDataV2 ipr;
+ ASSERT_EQ(pkix::Result::ERROR_BAD_DER,
+ DecodeInclusionProof(encodedProofInput, ipr));
+}
+
+TEST_F(BTSerializationTest, FailsDecodingInvalidHash) {
+ Input encodedProofInput = InputForBuffer(mTestInclusionProofInvalidHash);
+ InclusionProofDataV2 ipr;
+ ASSERT_EQ(pkix::Result::ERROR_BAD_DER,
+ DecodeInclusionProof(encodedProofInput, ipr));
+}
+
+TEST_F(BTSerializationTest, FailsDecodingMissingLogId) {
+ Input encodedProofInput = InputForBuffer(mTestInclusionProofMissingLogId);
+ InclusionProofDataV2 ipr;
+ ASSERT_EQ(pkix::Result::ERROR_BAD_DER,
+ DecodeInclusionProof(encodedProofInput, ipr));
+}
+
+TEST_F(BTSerializationTest, FailsDecodingNullPathLength) {
+ Input encodedProofInput = InputForBuffer(mTestInclusionProofNullPathLength);
+ InclusionProofDataV2 ipr;
+ ASSERT_EQ(pkix::Result::ERROR_BAD_DER,
+ DecodeInclusionProof(encodedProofInput, ipr));
+}
+
+TEST_F(BTSerializationTest, FailsDecodingPathLengthTooSmall) {
+ Input encodedProofInput =
+ InputForBuffer(mTestInclusionProofPathLengthTooSmall);
+ InclusionProofDataV2 ipr;
+ ASSERT_EQ(pkix::Result::ERROR_BAD_DER,
+ DecodeInclusionProof(encodedProofInput, ipr));
+}
+
+TEST_F(BTSerializationTest, FailsDecodingPathLengthTooLarge) {
+ Input encodedProofInput =
+ InputForBuffer(mTestInclusionProofPathLengthTooLarge);
+ InclusionProofDataV2 ipr;
+ ASSERT_EQ(pkix::Result::ERROR_BAD_DER,
+ DecodeInclusionProof(encodedProofInput, ipr));
+}
+
+TEST_F(BTSerializationTest, FailsDecodingNullTreeSize) {
+ Input encodedProofInput = InputForBuffer(mTestInclusionProofNullTreeSize);
+ InclusionProofDataV2 ipr;
+ ASSERT_EQ(pkix::Result::ERROR_BAD_DER,
+ DecodeInclusionProof(encodedProofInput, ipr));
+}
+
+TEST_F(BTSerializationTest, FailsDecodingLeafIndexOutOfBounds) {
+ Input encodedProofInput =
+ InputForBuffer(mTestInclusionProofLeafIndexOutOfBounds);
+ InclusionProofDataV2 ipr;
+ ASSERT_EQ(pkix::Result::ERROR_BAD_DER,
+ DecodeInclusionProof(encodedProofInput, ipr));
+}
+
+TEST_F(BTSerializationTest, FailsDecodingExtraData) {
+ Input encodedProofInput = InputForBuffer(mTestInclusionProofExtraData);
+ InclusionProofDataV2 ipr;
+ ASSERT_EQ(pkix::Result::ERROR_BAD_DER,
+ DecodeInclusionProof(encodedProofInput, ipr));
+}
+} // namespace ct
+} // namespace mozilla
diff --git a/security/ct/tests/gtest/BTSignedTreeHeadTest.cpp b/security/ct/tests/gtest/BTSignedTreeHeadTest.cpp
new file mode 100644
index 0000000000..580d6a84f3
--- /dev/null
+++ b/security/ct/tests/gtest/BTSignedTreeHeadTest.cpp
@@ -0,0 +1,266 @@
+/* -*- 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 Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "BTVerifier.h"
+#include "CTTestUtils.h"
+#include "gtest/gtest.h"
+
+#include "nss.h"
+
+namespace mozilla {
+namespace ct {
+
+using namespace pkix;
+
+struct BTSignedTreeHeadTestParams {
+ const char* mSubjectPublicKeyInfoHex;
+ pkix::DigestAlgorithm mDigestAlgorithm;
+ pkix::der::PublicKeyAlgorithm mPublicKeyAlgorithm;
+ const char* mSignedTreeHeadHex;
+
+ pkix::Result mExpectedSignedTreeHeadResult;
+ uint64_t mExpectedTimestamp;
+ uint64_t mExpectedTreeSize;
+ const char* mExpectedRootHashHex;
+};
+
+class BTSignedTreeHeadTest
+ : public ::testing::Test,
+ public ::testing::WithParamInterface<BTSignedTreeHeadTestParams> {
+ void SetUp() override {
+ if (!NSS_IsInitialized()) {
+ if (NSS_NoDB_Init(nullptr) != SECSuccess) {
+ abort();
+ }
+ }
+ }
+};
+
+namespace ValidSTH {
+#include "valid-sth.inc"
+}
+namespace ValidWithExtensionSTH {
+#include "valid-with-extension-sth.inc"
+}
+namespace ValidSecp521r1SHA512STH {
+#include "valid-secp521r1-sha512-sth.inc"
+}
+namespace SignatureCoversLogIDSTH {
+#include "signature-covers-log-id-sth.inc"
+}
+namespace WrongSPKISTH {
+#include "wrong-spki-sth.inc"
+}
+namespace WrongSigningKeySTH {
+#include "wrong-signing-key-sth.inc"
+}
+namespace MissingLogIDSTH {
+#include "missing-log-id-sth.inc"
+}
+namespace MissingTimestampSTH {
+#include "missing-timestamp-sth.inc"
+}
+namespace MissingTreeSizeSTH {
+#include "missing-tree-size-sth.inc"
+}
+namespace MissingRootHashSTH {
+#include "missing-root-hash-sth.inc"
+}
+namespace MissingExtensionsSTH {
+#include "missing-extensions-sth.inc"
+}
+namespace TruncatedLogIDSTH {
+#include "truncated-log-id-sth.inc"
+}
+namespace TruncatedTimestampSTH {
+#include "truncated-timestamp-sth.inc"
+}
+namespace TruncatedTreeSizeSTH {
+#include "truncated-tree-size-sth.inc"
+}
+namespace TruncatedRootHashSTH {
+#include "truncated-root-hash-sth.inc"
+}
+namespace TruncatedExtensionSTH {
+#include "truncated-extension-sth.inc"
+}
+namespace RSASignerRSASPKISTH {
+#include "rsa-signer-rsa-spki-sth.inc"
+}
+namespace RSASignerECSPKISTH {
+#include "rsa-signer-ec-spki-sth.inc"
+}
+namespace ECSignerRSASPKISTH {
+#include "ec-signer-rsa-spki-sth.inc"
+}
+
+static const char* kValidRootHashHex =
+ "d1a0d3947db4ae8305f2ac32985957e02659b2ea3c10da52a48d2526e9af3bbc";
+
+static const char* kValidRootHashSHA512Hex =
+ "374d794a95cdcfd8b35993185fef9ba368f160d8daf432d08ba9f1ed1e5abe6c"
+ "c69291e0fa2fe0006a52570ef18c19def4e617c33ce52ef0a6e5fbe318cb0387";
+
+static const BTSignedTreeHeadTestParams BT_SIGNED_TREE_HEAD_TEST_PARAMS[] = {
+ {ValidSTH::kSPKIHex, pkix::DigestAlgorithm::sha256,
+ pkix::der::PublicKeyAlgorithm::ECDSA, ValidSTH::kSTHHex, Success,
+ 1541189938000, 7, kValidRootHashHex},
+ {ValidSTH::kSPKIHex, pkix::DigestAlgorithm::sha512,
+ pkix::der::PublicKeyAlgorithm::ECDSA, ValidSTH::kSTHHex,
+ Result::ERROR_BAD_SIGNATURE, 0, 0, nullptr},
+ {ValidSTH::kSPKIHex, pkix::DigestAlgorithm::sha256,
+ pkix::der::PublicKeyAlgorithm::RSA_PKCS1, ValidSTH::kSTHHex,
+ Result::FATAL_ERROR_INVALID_ARGS, 0, 0, nullptr},
+ {ValidWithExtensionSTH::kSPKIHex, pkix::DigestAlgorithm::sha256,
+ pkix::der::PublicKeyAlgorithm::ECDSA, ValidWithExtensionSTH::kSTHHex,
+ Success, 1541189938000, 7, kValidRootHashHex},
+ {ValidSecp521r1SHA512STH::kSPKIHex, pkix::DigestAlgorithm::sha512,
+ pkix::der::PublicKeyAlgorithm::ECDSA, ValidSecp521r1SHA512STH::kSTHHex,
+ Success, 1542136309473, 731393445, kValidRootHashSHA512Hex},
+ {ValidSecp521r1SHA512STH::kSPKIHex, pkix::DigestAlgorithm::sha256,
+ pkix::der::PublicKeyAlgorithm::ECDSA, ValidSecp521r1SHA512STH::kSTHHex,
+ Result::ERROR_BAD_SIGNATURE, 0, 0, nullptr},
+ {ValidSTH::kSPKIHex, pkix::DigestAlgorithm::sha512,
+ pkix::der::PublicKeyAlgorithm::ECDSA, ValidSecp521r1SHA512STH::kSTHHex,
+ Result::ERROR_BAD_SIGNATURE, 0, 0, nullptr},
+ {SignatureCoversLogIDSTH::kSPKIHex, pkix::DigestAlgorithm::sha256,
+ pkix::der::PublicKeyAlgorithm::ECDSA, SignatureCoversLogIDSTH::kSTHHex,
+ Result::ERROR_BAD_SIGNATURE, 0, 0, nullptr},
+ {WrongSPKISTH::kSPKIHex, pkix::DigestAlgorithm::sha256,
+ pkix::der::PublicKeyAlgorithm::ECDSA, WrongSPKISTH::kSTHHex,
+ Result::ERROR_BAD_SIGNATURE, 0, 0, nullptr},
+ {WrongSigningKeySTH::kSPKIHex, pkix::DigestAlgorithm::sha256,
+ pkix::der::PublicKeyAlgorithm::ECDSA, WrongSigningKeySTH::kSTHHex,
+ Result::ERROR_BAD_SIGNATURE, 0, 0, nullptr},
+ {MissingLogIDSTH::kSPKIHex, pkix::DigestAlgorithm::sha256,
+ pkix::der::PublicKeyAlgorithm::ECDSA, MissingLogIDSTH::kSTHHex,
+ Result::ERROR_BAD_DER, 0, 0, nullptr},
+ {MissingTimestampSTH::kSPKIHex, pkix::DigestAlgorithm::sha256,
+ pkix::der::PublicKeyAlgorithm::ECDSA, MissingTimestampSTH::kSTHHex,
+ Result::ERROR_BAD_DER, 0, 0, nullptr},
+ {MissingTreeSizeSTH::kSPKIHex, pkix::DigestAlgorithm::sha256,
+ pkix::der::PublicKeyAlgorithm::ECDSA, MissingTreeSizeSTH::kSTHHex,
+ Result::ERROR_BAD_DER, 0, 0, nullptr},
+ {MissingRootHashSTH::kSPKIHex, pkix::DigestAlgorithm::sha256,
+ pkix::der::PublicKeyAlgorithm::ECDSA, MissingRootHashSTH::kSTHHex,
+ Result::ERROR_BAD_DER, 0, 0, nullptr},
+ {MissingExtensionsSTH::kSPKIHex, pkix::DigestAlgorithm::sha256,
+ pkix::der::PublicKeyAlgorithm::ECDSA, MissingExtensionsSTH::kSTHHex,
+ Result::ERROR_BAD_DER, 0, 0, nullptr},
+ {TruncatedLogIDSTH::kSPKIHex, pkix::DigestAlgorithm::sha256,
+ pkix::der::PublicKeyAlgorithm::ECDSA, TruncatedLogIDSTH::kSTHHex,
+ Result::ERROR_BAD_DER, 0, 0, nullptr},
+ {TruncatedTimestampSTH::kSPKIHex, pkix::DigestAlgorithm::sha256,
+ pkix::der::PublicKeyAlgorithm::ECDSA, TruncatedTimestampSTH::kSTHHex,
+ Result::ERROR_BAD_DER, 0, 0, nullptr},
+ {TruncatedTreeSizeSTH::kSPKIHex, pkix::DigestAlgorithm::sha256,
+ pkix::der::PublicKeyAlgorithm::ECDSA, TruncatedTreeSizeSTH::kSTHHex,
+ Result::ERROR_BAD_DER, 0, 0, nullptr},
+ {TruncatedRootHashSTH::kSPKIHex, pkix::DigestAlgorithm::sha256,
+ pkix::der::PublicKeyAlgorithm::ECDSA, TruncatedRootHashSTH::kSTHHex,
+ Result::ERROR_BAD_DER, 0, 0, nullptr},
+ {TruncatedExtensionSTH::kSPKIHex, pkix::DigestAlgorithm::sha256,
+ pkix::der::PublicKeyAlgorithm::ECDSA, TruncatedExtensionSTH::kSTHHex,
+ Result::ERROR_BAD_DER, 0, 0, nullptr},
+ {RSASignerRSASPKISTH::kSPKIHex, pkix::DigestAlgorithm::sha256,
+ pkix::der::PublicKeyAlgorithm::ECDSA, RSASignerRSASPKISTH::kSTHHex,
+ Result::ERROR_BAD_SIGNATURE, 0, 0, nullptr},
+ {RSASignerECSPKISTH::kSPKIHex, pkix::DigestAlgorithm::sha256,
+ pkix::der::PublicKeyAlgorithm::ECDSA, RSASignerECSPKISTH::kSTHHex,
+ Result::ERROR_BAD_SIGNATURE, 0, 0, nullptr},
+ {ECSignerRSASPKISTH::kSPKIHex, pkix::DigestAlgorithm::sha256,
+ pkix::der::PublicKeyAlgorithm::ECDSA, ECSignerRSASPKISTH::kSTHHex,
+ Result::ERROR_INVALID_KEY, 0, 0, nullptr},
+};
+
+TEST_P(BTSignedTreeHeadTest, BTSignedTreeHeadSimpleTest) {
+ const BTSignedTreeHeadTestParams& params(GetParam());
+
+ Buffer subjectPublicKeyInfoBuffer(
+ HexToBytes(params.mSubjectPublicKeyInfoHex));
+ Input subjectPublicKeyInfoInput = InputForBuffer(subjectPublicKeyInfoBuffer);
+
+ Buffer signedTreeHeadBuffer(HexToBytes(params.mSignedTreeHeadHex));
+ Input signedTreeHeadInput = InputForBuffer(signedTreeHeadBuffer);
+
+ SignedTreeHeadDataV2 sth;
+ EXPECT_EQ(params.mExpectedSignedTreeHeadResult,
+ DecodeAndVerifySignedTreeHead(
+ subjectPublicKeyInfoInput, params.mDigestAlgorithm,
+ params.mPublicKeyAlgorithm, signedTreeHeadInput, sth));
+
+ if (params.mExpectedSignedTreeHeadResult == Success) {
+ EXPECT_EQ(params.mExpectedTimestamp, sth.timestamp);
+ EXPECT_EQ(params.mExpectedTreeSize, sth.treeSize);
+ EXPECT_EQ(HexToBytes(params.mExpectedRootHashHex), sth.rootHash);
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(BTSignedTreeHeadTest, BTSignedTreeHeadTest,
+ testing::ValuesIn(BT_SIGNED_TREE_HEAD_TEST_PARAMS));
+
+TEST_F(BTSignedTreeHeadTest, BTSignedTreeHeadTamperedSignatureTest) {
+ Buffer subjectPublicKeyInfoBuffer(HexToBytes(ValidSTH::kSPKIHex));
+ Input subjectPublicKeyInfoInput = InputForBuffer(subjectPublicKeyInfoBuffer);
+
+ Buffer signedTreeHeadBuffer(HexToBytes(ValidSTH::kSTHHex));
+ ASSERT_TRUE(signedTreeHeadBuffer.size() > 15);
+ signedTreeHeadBuffer[signedTreeHeadBuffer.size() - 15] ^= 0xff;
+ Input signedTreeHeadInput = InputForBuffer(signedTreeHeadBuffer);
+
+ SignedTreeHeadDataV2 sth;
+ EXPECT_EQ(Result::ERROR_BAD_SIGNATURE,
+ DecodeAndVerifySignedTreeHead(subjectPublicKeyInfoInput,
+ pkix::DigestAlgorithm::sha256,
+ pkix::der::PublicKeyAlgorithm::ECDSA,
+ signedTreeHeadInput, sth));
+}
+
+TEST_F(BTSignedTreeHeadTest, BTSignedTreeHeadTruncatedSignatureTest) {
+ Buffer subjectPublicKeyInfoBuffer(HexToBytes(ValidSTH::kSPKIHex));
+ Input subjectPublicKeyInfoInput = InputForBuffer(subjectPublicKeyInfoBuffer);
+
+ Buffer signedTreeHeadBuffer(HexToBytes(ValidSTH::kSTHHex));
+ ASSERT_TRUE(signedTreeHeadBuffer.size() > 17);
+ signedTreeHeadBuffer.resize(signedTreeHeadBuffer.size() - 17);
+ Input signedTreeHeadInput = InputForBuffer(signedTreeHeadBuffer);
+
+ SignedTreeHeadDataV2 sth;
+ EXPECT_EQ(Result::ERROR_BAD_DER,
+ DecodeAndVerifySignedTreeHead(subjectPublicKeyInfoInput,
+ pkix::DigestAlgorithm::sha256,
+ pkix::der::PublicKeyAlgorithm::ECDSA,
+ signedTreeHeadInput, sth));
+}
+
+TEST_F(BTSignedTreeHeadTest, BTSignedTreeHeadMissingSignatureTest) {
+ Buffer subjectPublicKeyInfoBuffer(HexToBytes(ValidSTH::kSPKIHex));
+ Input subjectPublicKeyInfoInput = InputForBuffer(subjectPublicKeyInfoBuffer);
+
+ Buffer signedTreeHeadBuffer = {
+ 0x02, 0x00, 0x00,
+ // 1541189938000 milliseconds since the epoch
+ 0x00, 0x00, 0x01, 0x66, 0xd6, 0x14, 0x2b, 0x50, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x07, // 7 total nodes
+ 0x20, // 32 byte hash
+ 0xd1, 0xa0, 0xd3, 0x94, 0x7d, 0xb4, 0xae, 0x83, 0x05, 0xf2, 0xac, 0x32,
+ 0x98, 0x59, 0x57, 0xe0, 0x26, 0x59, 0xb2, 0xea, 0x3c, 0x10, 0xda, 0x52,
+ 0xa4, 0x8d, 0x25, 0x26, 0xe9, 0xaf, 0x3b, 0xbc, 0x00,
+ 0x00, // no extensions
+ // missing signature
+ };
+ Input signedTreeHeadInput = InputForBuffer(signedTreeHeadBuffer);
+
+ SignedTreeHeadDataV2 sth;
+ EXPECT_EQ(Result::ERROR_BAD_DER,
+ DecodeAndVerifySignedTreeHead(subjectPublicKeyInfoInput,
+ pkix::DigestAlgorithm::sha256,
+ pkix::der::PublicKeyAlgorithm::ECDSA,
+ signedTreeHeadInput, sth));
+}
+
+} // namespace ct
+} // namespace mozilla
diff --git a/security/ct/tests/gtest/BTVerificationTest.cpp b/security/ct/tests/gtest/BTVerificationTest.cpp
new file mode 100644
index 0000000000..41d8a11d2c
--- /dev/null
+++ b/security/ct/tests/gtest/BTVerificationTest.cpp
@@ -0,0 +1,257 @@
+/* -*- 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 Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "BTVerifier.h"
+#include "CTTestUtils.h"
+#include "gtest/gtest.h"
+
+#include "nss.h"
+
+namespace mozilla {
+namespace ct {
+
+using namespace pkix;
+
+struct BTVerificationTestParams {
+ const char* mInclusionProofHex;
+ const char* mExpectedRootHashHex;
+ const char* mInputHex;
+ pkix::DigestAlgorithm mDigestAlgorithm;
+ pkix::Result mExpectedVerificationResult;
+};
+
+class BTVerificationTest
+ : public ::testing::Test,
+ public ::testing::WithParamInterface<BTVerificationTestParams> {
+ void SetUp() override {
+ if (!NSS_IsInitialized()) {
+ if (NSS_NoDB_Init(nullptr) != SECSuccess) {
+ abort();
+ }
+ }
+ }
+};
+
+// This comes from testing/mozharness/test/test_mozilla_merkle.py
+static const char* kValidInclusionProofHex =
+ "020000"
+ "0000000000000007" // 7 total nodes
+ "0000000000000005" // leaf index is 5
+ "0063" // 99 (in base 10) bytes follow
+ "20" // 32 byte hash
+ "2ff3bdac9bec3580b82da8a357746f15919414d9cbe517e2dd96910c9814c30c"
+ "20"
+ "bb13dfb7b202a95f241ea1715c8549dc048d9936ec747028002f7c795de72fcf"
+ "20"
+ "380d0dc6fd7d4f37859a12dbfc7171b3cce29ab0688c6cffd2b15f3e0b21af49";
+
+static const char* kExpectedRootHashHex =
+ "d1a0d3947db4ae8305f2ac32985957e02659b2ea3c10da52a48d2526e9af3bbc";
+
+static const char* kInputHex = "81aae91cf90be172eedd1c75c349bf9e";
+static const char* kAlteredInputHex = "81aae91c0000000000000000c349bf9e";
+
+// Same as kValidInclusionProofHex, but missing the last hash (the proof is
+// still structurally valid, so it decodes successfully).
+static const char* kShortInclusionProofHex =
+ "020000"
+ "0000000000000007" // 7 total nodes
+ "0000000000000005" // leaf index is 5
+ "0042" // 66 (in base 10) bytes follow
+ "20" // 32 byte hash
+ "2ff3bdac9bec3580b82da8a357746f15919414d9cbe517e2dd96910c9814c30c"
+ "20"
+ "bb13dfb7b202a95f241ea1715c8549dc048d9936ec747028002f7c795de72fcf";
+
+// Same as kValidInclusionProofHex, but has too many hashes (the proof is
+// still structurally valid, so it decodes successfully).
+static const char* kLongInclusionProofHex =
+ "020000"
+ "0000000000000007" // 7 total nodes
+ "0000000000000005" // leaf index is 5
+ "0084" // 132 (in base 10) bytes follow
+ "20" // 32 byte hash
+ "2ff3bdac9bec3580b82da8a357746f15919414d9cbe517e2dd96910c9814c30c"
+ "20"
+ "bb13dfb7b202a95f241ea1715c8549dc048d9936ec747028002f7c795de72fcf"
+ "20"
+ "380d0dc6fd7d4f37859a12dbfc7171b3cce29ab0688c6cffd2b15f3e0b21af49"
+ "20"
+ "abbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabba";
+
+static const char* kIncorrectRootHashHex =
+ "beebeebeebeebeebeebeebeebeebeebeebeebeebeebeebeebeebeebeebeebeee";
+
+static const char* kWrongTreeSizeInclusionProofHex =
+ "020000"
+ "0000000000000009" // 9 total nodes (this is wrong)
+ "0000000000000005" // leaf index is 5
+ "0063" // 99 (in base 10) bytes follow
+ "20" // 32 byte hash
+ "2ff3bdac9bec3580b82da8a357746f15919414d9cbe517e2dd96910c9814c30c"
+ "20"
+ "bb13dfb7b202a95f241ea1715c8549dc048d9936ec747028002f7c795de72fcf"
+ "20"
+ "380d0dc6fd7d4f37859a12dbfc7171b3cce29ab0688c6cffd2b15f3e0b21af49";
+
+static const char* kWrongLeafIndexInclusionProofHex =
+ "020000"
+ "0000000000000007" // 7 total nodes
+ "0000000000000002" // leaf index is 2 (this is wrong)
+ "0063" // 99 (in base 10) bytes follow
+ "20" // 32 byte hash
+ "2ff3bdac9bec3580b82da8a357746f15919414d9cbe517e2dd96910c9814c30c"
+ "20"
+ "bb13dfb7b202a95f241ea1715c8549dc048d9936ec747028002f7c795de72fcf"
+ "20"
+ "380d0dc6fd7d4f37859a12dbfc7171b3cce29ab0688c6cffd2b15f3e0b21af49";
+
+// This is a longer proof taken from the Firefox 62.0.2 release.
+static const char* kValidInclusionProof2Hex =
+ "020000"
+ "0000000000000cb3"
+ "00000000000000e2"
+ "018c"
+ "20"
+ "6d3d060c72c28cd1967a6ce4e87a470c266981ee8beb6e5956745b5839120338"
+ "20"
+ "cd028053c51f52447cd2b1ffea7761be546b72832d776db155008dc3071231eb"
+ "20"
+ "36167b32f7ba4980257c79ed65d133d8c53efbfd7addaa391a121953c78b2b98"
+ "20"
+ "f2b4abf09140b05058a74e25f4008157655e73fa4aeece9b3f2a40dbbae95c16"
+ "20"
+ "a2be4813b2ca0e7bb755ed5459789c25a83465fb3f2808590c04c64cdbb404ef"
+ "20"
+ "43e4341b74ea11f075f35761b867cb256e9d42c4c1253d6b5c958174daa800da"
+ "20"
+ "1c925ec8d2d4014c0b84d7bed8065d9e7b326e3e4eaebc90d7a6a66431249851"
+ "20"
+ "bd275e9e4e48c3725f6ef68c2661d4d6ae415129cfb0ea0881177f5347d683ae"
+ "20"
+ "01e84996a7cc1719b19a4a7e48941657b125bcd0dfd03d183daff87de6a480c3"
+ "20"
+ "664b6962909692980661be55e8bdadc48350b2461d37372457e8ff97824ae8da"
+ "20"
+ "39f7ab1500a58643fccc9e07ed409be4c707c5536b95c57f6716207656464019"
+ "20"
+ "d68c856664d660456d4c85498c2cc2abfb955a63bd1fb23f3e25850da80153f6";
+
+static const char* kExpectedRootHash2Hex =
+ "0d489b16a64b80035a8d08ce55549c89c4b67be64a0456382cc5be1e51ddae1a";
+
+// This the hex encoding of the SHA-256 hash of
+// linux-x86_64/en-US/firefox-62.0.2.tar.bz2, encoded (again) in hex (because
+// that's how generate-checksums.py currently builds the binary transparency
+// tree).
+static const char* kInput2Hex =
+ "3231623461306361626538663030306261663236373235643530313462386434"
+ "3865353561333832336538653665656131343638313062313031623034396536";
+
+// This is the hex encoding of an inclusion proof for
+// update/win32/en-GB/firefox-61.0-62.0.2.partial.mar using SHA-512.
+static const char* kValidInclusionProofSHA512Hex =
+ "020000"
+ "0000000000000cb3"
+ "00000000000007ac"
+ "030c"
+ "40"
+ "b39fbe359108724ec1daed2a8c88511c6b94a74563bfaaf25b774286ab03cab9"
+ "b2e64bbd4eeb02003b7397cd814b5d18a160e14ad08eca04559abe146201e027"
+ "40"
+ "de119745a41884ed482a41638cc1979d89e1bd0ca295e0c412e7ee4b5c2e042d"
+ "a1f4aa9c7f1e58b78fac10400b207197383177401976ef6baa80d482c102a94f"
+ "40"
+ "d0fe203b0e95cbe48e2e663538aba382718c85ae167d8c4ccf326519f81b3860"
+ "55058f6a49943b8ed228af88b3414ec00e939ad8dc736f502ca90d63494eddcd"
+ "40"
+ "ae825352bbe12845fb1d3be27375c27dd462ebd6dc6d7bd0b5929df97aaccf1a"
+ "9eedb304ae1e9742b1e2a2ba8b22408d2d52e3bb3d547632d5f03cfe112572d2"
+ "40"
+ "1ab0f7f7d674c691cf8e51d4409d3402d94927231b82f7f71f16828dd9e36152"
+ "1a69f7e571df06aea16c83df88dc74042444ae8771375a7118477275395b570d"
+ "40"
+ "9c6b94c5be456c68518753cf626daf11b30ce981e18b76c036664dcafeba19b7"
+ "3f5ee20ab35aa38b11c92e2c8f103d1b19113b7171c615ce0b9c5ff520242d8c"
+ "40"
+ "d395ef3cd63ae617a96bd6c5c361d5f6aaccff1ed90599115394310dfa0c528d"
+ "611998d325f374c01a827ee900d72ed66d299845438bba565ba666c588b232a0"
+ "40"
+ "ce3548561f5ec7a94a6c7a2adc604f5e32869d0727cee80f12c95cab2a996965"
+ "6ad61a02b35e0fb2d0031ea496381f1413d33b8cbfdf092accd903881e083ee0"
+ "40"
+ "a4247b649fee9017d9c9e729c4763719b7155056013abc488de0079dc7bddce6"
+ "57a93f2596967cfb123d812a901bc4ffe0338cbecf30adc1a821e7261f88e3cd"
+ "40"
+ "7d2056ea6645b0306373fa2e26fbc4a4ccd4c82cb7d605d2372fa297654cd3a0"
+ "b151f6b0fffe7472661de79c7ce7873cb75e05cad5ea40173be540e16d51414d"
+ "40"
+ "eed2c98b3d8b8035777af74a0d5dd38963f84c09e6f42b6ca4e290674c175035"
+ "b9bc85bb4304fcf3d042c444f951b3b134563c6936a1b176679e35e94c42416b"
+ "40"
+ "6167cccd9d03909f95bb389936766340234b5b3e35dfa8c81d330db9055d7b7d"
+ "bfaf262f1821b4a68f43af62c53987550b4174eaab8357f9bd83f2715c98d441";
+
+// This is the hex encoding of the expected root hash of the SHA-512 tree.
+static const char* kExpectedRootHashSHA512Hex =
+ "6f20812da2980393767e98dd7f79d89aa41e029c790d3908739c02c01d9c6525"
+ "8bb3e24ab608c95b063259620751ce258930d48929cbe543b166c33c4de65e60";
+
+// This the hex encoding of the SHA-512 hash of
+// update/win32/en-GB/firefox-61.0-62.0.2.partial.mar, encoded (again) in hex.
+static const char* kInputSHA512Hex =
+ "6237656563626164353363386432346230383763666437323333303162326437"
+ "6234326136323536663462636336356134623231636437376133613830613734"
+ "3865396533623666306432333839666234623763396563356638643833373534"
+ "6239313635313165333661373238653863303236376564653761666561316638";
+
+static const BTVerificationTestParams BT_VERIFICATION_TEST_PARAMS[] = {
+ {kValidInclusionProofHex, kExpectedRootHashHex, kInputHex,
+ pkix::DigestAlgorithm::sha256, Success},
+ {kValidInclusionProofHex, kExpectedRootHashHex, kAlteredInputHex,
+ pkix::DigestAlgorithm::sha256, pkix::Result::ERROR_BAD_SIGNATURE},
+ {kShortInclusionProofHex, kExpectedRootHashHex, kInputHex,
+ pkix::DigestAlgorithm::sha256, pkix::Result::ERROR_BAD_SIGNATURE},
+ {kLongInclusionProofHex, kExpectedRootHashHex, kInputHex,
+ pkix::DigestAlgorithm::sha256, pkix::Result::ERROR_BAD_SIGNATURE},
+ {kValidInclusionProofHex, kIncorrectRootHashHex, kInputHex,
+ pkix::DigestAlgorithm::sha256, pkix::Result::ERROR_BAD_SIGNATURE},
+ {kWrongTreeSizeInclusionProofHex, kExpectedRootHashHex, kInputHex,
+ pkix::DigestAlgorithm::sha256, pkix::Result::ERROR_BAD_SIGNATURE},
+ {kWrongLeafIndexInclusionProofHex, kExpectedRootHashHex, kInputHex,
+ pkix::DigestAlgorithm::sha256, pkix::Result::ERROR_BAD_SIGNATURE},
+ {kValidInclusionProofHex, kExpectedRootHashHex, kInputHex,
+ pkix::DigestAlgorithm::sha512, pkix::Result::ERROR_BAD_SIGNATURE},
+ {kValidInclusionProof2Hex, kExpectedRootHash2Hex, kInput2Hex,
+ pkix::DigestAlgorithm::sha256, Success},
+ {kValidInclusionProofSHA512Hex, kExpectedRootHashSHA512Hex, kInputSHA512Hex,
+ pkix::DigestAlgorithm::sha512, Success},
+ {kValidInclusionProofSHA512Hex, kExpectedRootHashSHA512Hex, kInputSHA512Hex,
+ pkix::DigestAlgorithm::sha256, pkix::Result::ERROR_BAD_SIGNATURE},
+};
+
+TEST_P(BTVerificationTest, BTVerificationSimpleTest) {
+ const BTVerificationTestParams& params(GetParam());
+
+ Buffer encodedProofBuffer(HexToBytes(params.mInclusionProofHex));
+ Input encodedProof = InputForBuffer(encodedProofBuffer);
+ InclusionProofDataV2 ipr;
+ ASSERT_EQ(Success, DecodeInclusionProof(encodedProof, ipr));
+
+ Buffer leafEntryBuffer(HexToBytes(params.mInputHex));
+ Input leafEntry = InputForBuffer(leafEntryBuffer);
+ Buffer expectedRootHashBuffer(HexToBytes(params.mExpectedRootHashHex));
+ Input expectedRootHash = InputForBuffer(expectedRootHashBuffer);
+ ASSERT_EQ(params.mExpectedVerificationResult,
+ VerifyInclusionProof(ipr, leafEntry, expectedRootHash,
+ params.mDigestAlgorithm));
+}
+
+INSTANTIATE_TEST_SUITE_P(BTVerificationTest, BTVerificationTest,
+ testing::ValuesIn(BT_VERIFICATION_TEST_PARAMS));
+
+} // namespace ct
+} // namespace mozilla
diff --git a/security/ct/tests/gtest/CTDiversityPolicyTest.cpp b/security/ct/tests/gtest/CTDiversityPolicyTest.cpp
new file mode 100644
index 0000000000..bf999acea4
--- /dev/null
+++ b/security/ct/tests/gtest/CTDiversityPolicyTest.cpp
@@ -0,0 +1,15 @@
+/* -*- 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 Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CTDiversityPolicy.h"
+
+namespace mozilla {
+namespace ct {
+
+// TBD, PENDING FINALIZATION OF MOZILLA CT POLICY.
+
+}
+} // namespace mozilla
diff --git a/security/ct/tests/gtest/CTLogVerifierTest.cpp b/security/ct/tests/gtest/CTLogVerifierTest.cpp
new file mode 100644
index 0000000000..1b7374a836
--- /dev/null
+++ b/security/ct/tests/gtest/CTLogVerifierTest.cpp
@@ -0,0 +1,118 @@
+/* -*- 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 Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CTLogVerifier.h"
+#include "CTTestUtils.h"
+#include "nss.h"
+
+#include "gtest/gtest.h"
+
+namespace mozilla {
+namespace ct {
+
+using namespace pkix;
+
+class CTLogVerifierTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ // Does nothing if NSS is already initialized.
+ if (NSS_NoDB_Init(nullptr) != SECSuccess) {
+ abort();
+ }
+
+ ASSERT_EQ(Success,
+ mLog.Init(InputForBuffer(GetTestPublicKey()), -1 /*operator id*/,
+ CTLogStatus::Included, 0 /*disqualification time*/));
+ ASSERT_EQ(GetTestPublicKeyId(), mLog.keyId());
+ }
+
+ protected:
+ CTLogVerifier mLog;
+};
+
+TEST_F(CTLogVerifierTest, VerifiesCertSCT) {
+ LogEntry certEntry;
+ GetX509CertLogEntry(certEntry);
+
+ SignedCertificateTimestamp certSct;
+ GetX509CertSCT(certSct);
+
+ EXPECT_EQ(Success, mLog.Verify(certEntry, certSct));
+}
+
+TEST_F(CTLogVerifierTest, VerifiesPrecertSCT) {
+ LogEntry precertEntry;
+ GetPrecertLogEntry(precertEntry);
+
+ SignedCertificateTimestamp precertSct;
+ GetPrecertSCT(precertSct);
+
+ EXPECT_EQ(Success, mLog.Verify(precertEntry, precertSct));
+}
+
+TEST_F(CTLogVerifierTest, FailsInvalidTimestamp) {
+ LogEntry certEntry;
+ GetX509CertLogEntry(certEntry);
+
+ SignedCertificateTimestamp certSct;
+ GetX509CertSCT(certSct);
+
+ // Mangle the timestamp, so that it should fail signature validation.
+ certSct.timestamp = 0;
+
+ EXPECT_EQ(pkix::Result::ERROR_BAD_SIGNATURE, mLog.Verify(certEntry, certSct));
+}
+
+TEST_F(CTLogVerifierTest, FailsInvalidSignature) {
+ LogEntry certEntry;
+ GetX509CertLogEntry(certEntry);
+
+ // Mangle the value of the signature, making the underlying signature
+ // verification code return ERROR_BAD_SIGNATURE.
+ SignedCertificateTimestamp certSct;
+ GetX509CertSCT(certSct);
+ certSct.signature.signatureData[20] ^= '\xFF';
+ EXPECT_EQ(pkix::Result::ERROR_BAD_SIGNATURE, mLog.Verify(certEntry, certSct));
+
+ // Mangle the encoding of the signature, making the underlying implementation
+ // return ERROR_BAD_DER. We still expect the verifier to return
+ // ERROR_BAD_SIGNATURE.
+ SignedCertificateTimestamp certSct2;
+ GetX509CertSCT(certSct2);
+ certSct2.signature.signatureData[0] ^= '\xFF';
+ EXPECT_EQ(pkix::Result::ERROR_BAD_SIGNATURE,
+ mLog.Verify(certEntry, certSct2));
+}
+
+TEST_F(CTLogVerifierTest, FailsInvalidLogID) {
+ LogEntry certEntry;
+ GetX509CertLogEntry(certEntry);
+
+ SignedCertificateTimestamp certSct;
+ GetX509CertSCT(certSct);
+
+ // Mangle the log ID, which should cause it to match a different log before
+ // attempting signature validation.
+ certSct.logId.push_back('\x0');
+
+ EXPECT_EQ(pkix::Result::FATAL_ERROR_INVALID_ARGS,
+ mLog.Verify(certEntry, certSct));
+}
+
+// Test that excess data after the public key is rejected.
+TEST_F(CTLogVerifierTest, ExcessDataInPublicKey) {
+ Buffer key = GetTestPublicKey();
+ std::string extra = "extra";
+ key.insert(key.end(), extra.begin(), extra.end());
+
+ CTLogVerifier log;
+ EXPECT_NE(Success,
+ log.Init(InputForBuffer(key), -1 /*operator id*/,
+ CTLogStatus::Included, 0 /*disqualification time*/));
+}
+
+} // namespace ct
+} // namespace mozilla
diff --git a/security/ct/tests/gtest/CTObjectsExtractorTest.cpp b/security/ct/tests/gtest/CTObjectsExtractorTest.cpp
new file mode 100644
index 0000000000..20b454b98c
--- /dev/null
+++ b/security/ct/tests/gtest/CTObjectsExtractorTest.cpp
@@ -0,0 +1,82 @@
+/* -*- 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 Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CTLogVerifier.h"
+#include "CTObjectsExtractor.h"
+#include "CTSerialization.h"
+#include "CTTestUtils.h"
+#include "gtest/gtest.h"
+#include "nss.h"
+
+namespace mozilla {
+namespace ct {
+
+using namespace pkix;
+
+class CTObjectsExtractorTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ // Does nothing if NSS is already initialized.
+ if (NSS_NoDB_Init(nullptr) != SECSuccess) {
+ abort();
+ }
+
+ mTestCert = GetDEREncodedX509Cert();
+ mEmbeddedCert = GetDEREncodedTestEmbeddedCert();
+ mCaCert = GetDEREncodedCACert();
+ mCaCertSPKI = ExtractCertSPKI(mCaCert);
+
+ Buffer logPublicKey = GetTestPublicKey();
+ ASSERT_EQ(Success,
+ mLog.Init(InputForBuffer(logPublicKey), -1 /*operator id*/,
+ CTLogStatus::Included, 0 /*disqualification time*/));
+ }
+
+ protected:
+ Buffer mTestCert;
+ Buffer mEmbeddedCert;
+ Buffer mCaCert;
+ Buffer mCaCertSPKI;
+ CTLogVerifier mLog;
+};
+
+TEST_F(CTObjectsExtractorTest, ExtractPrecert) {
+ LogEntry entry;
+ ASSERT_EQ(Success, GetPrecertLogEntry(InputForBuffer(mEmbeddedCert),
+ InputForBuffer(mCaCertSPKI), entry));
+
+ EXPECT_EQ(LogEntry::Type::Precert, entry.type);
+ // Should have empty leaf cert for this log entry type.
+ EXPECT_TRUE(entry.leafCertificate.empty());
+ EXPECT_EQ(GetDefaultIssuerKeyHash(), entry.issuerKeyHash);
+ EXPECT_EQ(GetDEREncodedTestTbsCert(), entry.tbsCertificate);
+}
+
+TEST_F(CTObjectsExtractorTest, ExtractOrdinaryX509Cert) {
+ LogEntry entry;
+ GetX509LogEntry(InputForBuffer(mTestCert), entry);
+
+ EXPECT_EQ(LogEntry::Type::X509, entry.type);
+ // Should have empty tbsCertificate / issuerKeyHash for this log entry type.
+ EXPECT_TRUE(entry.tbsCertificate.empty());
+ EXPECT_TRUE(entry.issuerKeyHash.empty());
+ // Length of leafCertificate should be 718, see the CT Serialization tests.
+ EXPECT_EQ(718U, entry.leafCertificate.size());
+}
+
+// Test that an externally-provided SCT verifies over the LogEntry
+// of a regular X.509 Certificate
+TEST_F(CTObjectsExtractorTest, ComplementarySCTVerifies) {
+ SignedCertificateTimestamp sct;
+ GetX509CertSCT(sct);
+
+ LogEntry entry;
+ GetX509LogEntry(InputForBuffer(mTestCert), entry);
+ EXPECT_EQ(Success, mLog.Verify(entry, sct));
+}
+
+} // namespace ct
+} // namespace mozilla
diff --git a/security/ct/tests/gtest/CTPolicyEnforcerTest.cpp b/security/ct/tests/gtest/CTPolicyEnforcerTest.cpp
new file mode 100644
index 0000000000..7bc5b296a5
--- /dev/null
+++ b/security/ct/tests/gtest/CTPolicyEnforcerTest.cpp
@@ -0,0 +1,387 @@
+/* -*- 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 Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CTPolicyEnforcer.h"
+
+#include <algorithm>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "CTLogVerifier.h"
+#include "CTVerifyResult.h"
+#include "SignedCertificateTimestamp.h"
+#include "mozpkix/Time.h"
+#include "gtest/gtest.h"
+#include "hasht.h"
+#include "prtime.h"
+
+// Implemented in CertVerifier.cpp.
+extern mozilla::pkix::Result GetCertLifetimeInFullMonths(
+ mozilla::pkix::Time certNotBefore, mozilla::pkix::Time certNotAfter,
+ size_t& months);
+
+namespace mozilla {
+namespace ct {
+
+using namespace mozilla::pkix;
+
+class CTPolicyEnforcerTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ OPERATORS_1_AND_2.push_back(OPERATOR_1);
+ OPERATORS_1_AND_2.push_back(OPERATOR_2);
+ }
+
+ void GetLogId(Buffer& logId, size_t logNo) {
+ logId.resize(SHA256_LENGTH);
+ std::fill(logId.begin(), logId.end(), 0);
+ // Just raw-copy |logId| into the output buffer.
+ assert(sizeof(logNo) <= logId.size());
+ memcpy(logId.data(), &logNo, sizeof(logNo));
+ }
+
+ void AddSct(VerifiedSCTList& verifiedScts, size_t logNo,
+ CTLogOperatorId operatorId, VerifiedSCT::Origin origin,
+ uint64_t timestamp,
+ VerifiedSCT::Status status = VerifiedSCT::Status::Valid) {
+ VerifiedSCT verifiedSct;
+ verifiedSct.status = status;
+ verifiedSct.origin = origin;
+ verifiedSct.logOperatorId = operatorId;
+ verifiedSct.logDisqualificationTime =
+ status == VerifiedSCT::Status::ValidFromDisqualifiedLog
+ ? DISQUALIFIED_AT
+ : UINT64_MAX;
+ verifiedSct.sct.version = SignedCertificateTimestamp::Version::V1;
+ verifiedSct.sct.timestamp = timestamp;
+ Buffer logId;
+ GetLogId(logId, logNo);
+ verifiedSct.sct.logId = std::move(logId);
+ verifiedScts.push_back(std::move(verifiedSct));
+ }
+
+ void AddMultipleScts(
+ VerifiedSCTList& verifiedScts, size_t logsCount, uint8_t operatorsCount,
+ VerifiedSCT::Origin origin, uint64_t timestamp,
+ VerifiedSCT::Status status = VerifiedSCT::Status::Valid) {
+ for (size_t logNo = 0; logNo < logsCount; logNo++) {
+ CTLogOperatorId operatorId = logNo % operatorsCount;
+ AddSct(verifiedScts, logNo, operatorId, origin, timestamp, status);
+ }
+ }
+
+ void CheckCompliance(const VerifiedSCTList& verifiedSct,
+ size_t certLifetimeInCalendarMonths,
+ const CTLogOperatorList& dependentLogOperators,
+ CTPolicyCompliance expectedCompliance) {
+ CTPolicyCompliance compliance;
+ mPolicyEnforcer.CheckCompliance(verifiedSct, certLifetimeInCalendarMonths,
+ dependentLogOperators, compliance);
+ EXPECT_EQ(expectedCompliance, compliance);
+ }
+
+ protected:
+ CTPolicyEnforcer mPolicyEnforcer;
+
+ const size_t LOG_1 = 1;
+ const size_t LOG_2 = 2;
+ const size_t LOG_3 = 3;
+ const size_t LOG_4 = 4;
+ const size_t LOG_5 = 5;
+
+ const CTLogOperatorId OPERATOR_1 = 1;
+ const CTLogOperatorId OPERATOR_2 = 2;
+ const CTLogOperatorId OPERATOR_3 = 3;
+
+ CTLogOperatorList NO_OPERATORS;
+ CTLogOperatorList OPERATORS_1_AND_2;
+
+ const VerifiedSCT::Origin ORIGIN_EMBEDDED = VerifiedSCT::Origin::Embedded;
+ const VerifiedSCT::Origin ORIGIN_TLS = VerifiedSCT::Origin::TLSExtension;
+ const VerifiedSCT::Origin ORIGIN_OCSP = VerifiedSCT::Origin::OCSPResponse;
+
+ // 4 years of cert lifetime requires 5 SCTs for the embedded case.
+ const size_t DEFAULT_MONTHS = 4 * 12L;
+
+ // Date.parse("2015-08-15T00:00:00Z")
+ const uint64_t TIMESTAMP_1 = 1439596800000L;
+
+ // Date.parse("2016-04-15T00:00:00Z")
+ const uint64_t DISQUALIFIED_AT = 1460678400000L;
+
+ // Date.parse("2016-04-01T00:00:00Z")
+ const uint64_t BEFORE_DISQUALIFIED = 1459468800000L;
+
+ // Date.parse("2016-04-16T00:00:00Z")
+ const uint64_t AFTER_DISQUALIFIED = 1460764800000L;
+};
+
+TEST_F(CTPolicyEnforcerTest, ConformsToCTPolicyWithNonEmbeddedSCTs) {
+ VerifiedSCTList scts;
+
+ AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
+ AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, TIMESTAMP_1);
+
+ CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
+ CTPolicyCompliance::Compliant);
+}
+
+TEST_F(CTPolicyEnforcerTest, DoesNotConformNotEnoughDiverseNonEmbeddedSCTs) {
+ VerifiedSCTList scts;
+
+ AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
+ AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, TIMESTAMP_1);
+
+ CheckCompliance(scts, DEFAULT_MONTHS, OPERATORS_1_AND_2,
+ CTPolicyCompliance::NotDiverseScts);
+}
+
+TEST_F(CTPolicyEnforcerTest, ConformsToCTPolicyWithEmbeddedSCTs) {
+ VerifiedSCTList scts;
+
+ // 5 embedded SCTs required for DEFAULT_MONTHS.
+ AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+ AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+ AddSct(scts, LOG_3, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+ AddSct(scts, LOG_4, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+ AddSct(scts, LOG_5, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1);
+
+ CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
+ CTPolicyCompliance::Compliant);
+}
+
+TEST_F(CTPolicyEnforcerTest, DoesNotConformNotEnoughDiverseEmbeddedSCTs) {
+ VerifiedSCTList scts;
+
+ // 5 embedded SCTs required for DEFAULT_MONTHS.
+ AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+ AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+ AddSct(scts, LOG_3, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+ AddSct(scts, LOG_4, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+ AddSct(scts, LOG_5, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1);
+
+ CheckCompliance(scts, DEFAULT_MONTHS, OPERATORS_1_AND_2,
+ CTPolicyCompliance::NotDiverseScts);
+}
+
+TEST_F(CTPolicyEnforcerTest, ConformsToCTPolicyWithPooledNonEmbeddedSCTs) {
+ VerifiedSCTList scts;
+
+ AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_OCSP, TIMESTAMP_1);
+ AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, TIMESTAMP_1);
+
+ CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
+ CTPolicyCompliance::Compliant);
+}
+
+TEST_F(CTPolicyEnforcerTest, ConformsToCTPolicyWithPooledEmbeddedSCTs) {
+ VerifiedSCTList scts;
+
+ AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+ AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_OCSP, TIMESTAMP_1);
+
+ CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
+ CTPolicyCompliance::Compliant);
+}
+
+TEST_F(CTPolicyEnforcerTest, DoesNotConformToCTPolicyNotEnoughSCTs) {
+ VerifiedSCTList scts;
+
+ AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+ AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1);
+
+ CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
+ CTPolicyCompliance::NotEnoughScts);
+}
+
+TEST_F(CTPolicyEnforcerTest, DoesNotConformToCTPolicyNotEnoughFreshSCTs) {
+ VerifiedSCTList scts;
+
+ // The results should be the same before and after disqualification,
+ // regardless of the delivery method.
+
+ // SCT from before disqualification.
+ scts.clear();
+ AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
+ AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, BEFORE_DISQUALIFIED,
+ VerifiedSCT::Status::ValidFromDisqualifiedLog);
+ CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
+ CTPolicyCompliance::NotEnoughScts);
+ // SCT from after disqualification.
+ scts.clear();
+ AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
+ AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, AFTER_DISQUALIFIED,
+ VerifiedSCT::Status::ValidFromDisqualifiedLog);
+ CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
+ CTPolicyCompliance::NotEnoughScts);
+
+ // Embedded SCT from before disqualification.
+ scts.clear();
+ AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
+ AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_EMBEDDED, BEFORE_DISQUALIFIED,
+ VerifiedSCT::Status::ValidFromDisqualifiedLog);
+ CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
+ CTPolicyCompliance::NotEnoughScts);
+
+ // Embedded SCT from after disqualification.
+ scts.clear();
+ AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
+ AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED,
+ VerifiedSCT::Status::ValidFromDisqualifiedLog);
+ CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
+ CTPolicyCompliance::NotEnoughScts);
+}
+
+TEST_F(CTPolicyEnforcerTest,
+ ConformsWithDisqualifiedLogBeforeDisqualificationDate) {
+ VerifiedSCTList scts;
+
+ // 5 embedded SCTs required for DEFAULT_MONTHS.
+ AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+ AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+ AddSct(scts, LOG_3, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+ AddSct(scts, LOG_4, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+ AddSct(scts, LOG_5, OPERATOR_2, ORIGIN_EMBEDDED, BEFORE_DISQUALIFIED,
+ VerifiedSCT::Status::ValidFromDisqualifiedLog);
+
+ CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
+ CTPolicyCompliance::Compliant);
+}
+
+TEST_F(CTPolicyEnforcerTest,
+ DoesNotConformWithDisqualifiedLogAfterDisqualificationDate) {
+ VerifiedSCTList scts;
+
+ // 5 embedded SCTs required for DEFAULT_MONTHS.
+ AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+ AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+ AddSct(scts, LOG_3, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+ AddSct(scts, LOG_4, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+ AddSct(scts, LOG_5, OPERATOR_2, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED,
+ VerifiedSCT::Status::ValidFromDisqualifiedLog);
+
+ CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
+ CTPolicyCompliance::NotEnoughScts);
+}
+
+TEST_F(CTPolicyEnforcerTest,
+ DoesNotConformWithIssuanceDateAfterDisqualificationDate) {
+ VerifiedSCTList scts;
+
+ // 5 embedded SCTs required for DEFAULT_MONTHS.
+ AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED,
+ VerifiedSCT::Status::ValidFromDisqualifiedLog);
+ AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED);
+ AddSct(scts, LOG_3, OPERATOR_1, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED);
+ AddSct(scts, LOG_4, OPERATOR_1, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED);
+ AddSct(scts, LOG_5, OPERATOR_2, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED);
+
+ CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
+ CTPolicyCompliance::NotEnoughScts);
+}
+
+TEST_F(CTPolicyEnforcerTest,
+ DoesNotConformToCTPolicyNotEnoughUniqueEmbeddedLogs) {
+ VerifiedSCTList scts;
+
+ // Operator #1
+ AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+ // Operator #2, different logs
+ AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1);
+ AddSct(scts, LOG_3, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1);
+ // Operator #3, same log
+ AddSct(scts, LOG_4, OPERATOR_3, ORIGIN_EMBEDDED, TIMESTAMP_1);
+ AddSct(scts, LOG_4, OPERATOR_3, ORIGIN_EMBEDDED, TIMESTAMP_1);
+
+ // 5 embedded SCTs required. However, only 4 are from distinct logs.
+ CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
+ CTPolicyCompliance::NotEnoughScts);
+}
+
+TEST_F(CTPolicyEnforcerTest,
+ ConformsToPolicyExactNumberOfSCTsForValidityPeriod) {
+ // Test multiple validity periods.
+ const struct TestData {
+ size_t certLifetimeInCalendarMonths;
+ size_t sctsRequired;
+ } kTestData[] = {{3, 2}, {12 + 2, 2}, {12 + 3, 3},
+ {2 * 12 + 2, 3}, {2 * 12 + 3, 4}, {3 * 12 + 2, 4},
+ {3 * 12 + 4, 5}};
+
+ for (size_t i = 0; i < MOZILLA_CT_ARRAY_LENGTH(kTestData); ++i) {
+ SCOPED_TRACE(i);
+
+ size_t months = kTestData[i].certLifetimeInCalendarMonths;
+ size_t sctsRequired = kTestData[i].sctsRequired;
+
+ // Less SCTs than required is not enough.
+ for (size_t sctsAvailable = 0; sctsAvailable < sctsRequired;
+ ++sctsAvailable) {
+ VerifiedSCTList scts;
+ AddMultipleScts(scts, sctsAvailable, 1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+
+ CTPolicyCompliance compliance;
+ mPolicyEnforcer.CheckCompliance(scts, months, NO_OPERATORS, compliance);
+ EXPECT_EQ(CTPolicyCompliance::NotEnoughScts, compliance)
+ << "i=" << i << " sctsRequired=" << sctsRequired
+ << " sctsAvailable=" << sctsAvailable;
+ }
+
+ // Add exactly the required number of SCTs (from 2 operators).
+ VerifiedSCTList scts;
+ AddMultipleScts(scts, sctsRequired, 2, ORIGIN_EMBEDDED, TIMESTAMP_1);
+
+ CTPolicyCompliance compliance;
+ mPolicyEnforcer.CheckCompliance(scts, months, NO_OPERATORS, compliance);
+ EXPECT_EQ(CTPolicyCompliance::Compliant, compliance) << "i=" << i;
+ }
+}
+
+TEST_F(CTPolicyEnforcerTest, TestEdgeCasesOfGetCertLifetimeInFullMonths) {
+ const struct TestData {
+ uint64_t notBefore;
+ uint64_t notAfter;
+ size_t expectedMonths;
+ } kTestData[] = {
+ { // 1 second less than 1 month
+ 1424863500000000, // Date.parse("2015-02-25T11:25:00Z") * 1000
+ 1427196299000000, // Date.parse("2015-03-24T11:24:59Z") * 1000
+ 0},
+ { // exactly 1 month
+ 1424863500000000, // Date.parse("2015-02-25T11:25:00Z") * 1000
+ 1427282700000000, // Date.parse("2015-03-25T11:25:00Z") * 1000
+ 1},
+ { // 1 year, 1 month
+ 1427282700000000, // Date.parse("2015-03-25T11:25:00Z") * 1000
+ 1461583500000000, // Date.parse("2016-04-25T11:25:00Z") * 1000
+ 13},
+ {// 1 year, 1 month, first day of notBefore month, last of notAfter
+ 1425209100000000, // Date.parse("2015-03-01T11:25:00Z") * 1000
+ 1462015500000000, // Date.parse("2016-04-30T11:25:00Z") * 1000
+ 13},
+ {// 1 year, adjacent months, last day of notBefore month, first of
+ // notAfter
+ 1427801100000000, // Date.parse("2015-03-31T11:25:00Z") * 1000
+ 1459509900000000, // Date.parse("2016-04-01T11:25:00Z") * 1000
+ 12}};
+
+ for (size_t i = 0; i < MOZILLA_CT_ARRAY_LENGTH(kTestData); ++i) {
+ SCOPED_TRACE(i);
+
+ size_t months;
+ ASSERT_EQ(Success,
+ GetCertLifetimeInFullMonths(mozilla::pkix::TimeFromEpochInSeconds(
+ kTestData[i].notBefore / 1000000),
+ mozilla::pkix::TimeFromEpochInSeconds(
+ kTestData[i].notAfter / 1000000),
+ months))
+ << "i=" << i;
+ EXPECT_EQ(kTestData[i].expectedMonths, months) << "i=" << i;
+ }
+}
+
+} // namespace ct
+} // namespace mozilla
diff --git a/security/ct/tests/gtest/CTSerializationTest.cpp b/security/ct/tests/gtest/CTSerializationTest.cpp
new file mode 100644
index 0000000000..983759cf71
--- /dev/null
+++ b/security/ct/tests/gtest/CTSerializationTest.cpp
@@ -0,0 +1,216 @@
+/* -*- 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 Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CTSerialization.h"
+#include "CTTestUtils.h"
+#include "gtest/gtest.h"
+
+namespace mozilla {
+namespace ct {
+
+using namespace pkix;
+
+class CTSerializationTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ mTestDigitallySigned = GetTestDigitallySigned();
+ mTestSignatureData = GetTestDigitallySignedData();
+ }
+
+ protected:
+ Buffer mTestDigitallySigned;
+ Buffer mTestSignatureData;
+};
+
+TEST_F(CTSerializationTest, DecodesDigitallySigned) {
+ Input digitallySigned = InputForBuffer(mTestDigitallySigned);
+ Reader digitallySignedReader(digitallySigned);
+
+ DigitallySigned parsed;
+ ASSERT_EQ(Success, DecodeDigitallySigned(digitallySignedReader, parsed));
+ EXPECT_TRUE(digitallySignedReader.AtEnd());
+
+ EXPECT_EQ(DigitallySigned::HashAlgorithm::SHA256, parsed.hashAlgorithm);
+ EXPECT_EQ(DigitallySigned::SignatureAlgorithm::ECDSA,
+ parsed.signatureAlgorithm);
+ EXPECT_EQ(mTestSignatureData, parsed.signatureData);
+}
+
+TEST_F(CTSerializationTest, FailsToDecodePartialDigitallySigned) {
+ Input partial;
+ ASSERT_EQ(Success, partial.Init(mTestDigitallySigned.data(),
+ mTestDigitallySigned.size() - 5));
+ Reader partialReader(partial);
+
+ DigitallySigned parsed;
+
+ EXPECT_NE(Success, DecodeDigitallySigned(partialReader, parsed));
+}
+
+TEST_F(CTSerializationTest, EncodesDigitallySigned) {
+ DigitallySigned digitallySigned;
+ digitallySigned.hashAlgorithm = DigitallySigned::HashAlgorithm::SHA256;
+ digitallySigned.signatureAlgorithm =
+ DigitallySigned::SignatureAlgorithm::ECDSA;
+ digitallySigned.signatureData = mTestSignatureData;
+
+ Buffer encoded;
+
+ ASSERT_EQ(Success, EncodeDigitallySigned(digitallySigned, encoded));
+ EXPECT_EQ(mTestDigitallySigned, encoded);
+}
+
+TEST_F(CTSerializationTest, EncodesLogEntryForX509Cert) {
+ LogEntry entry;
+ GetX509CertLogEntry(entry);
+
+ Buffer encoded;
+ ASSERT_EQ(Success, EncodeLogEntry(entry, encoded));
+ EXPECT_EQ((718U + 5U), encoded.size());
+ // First two bytes are log entry type. Next, length:
+ // Length is 718 which is 512 + 206, which is { 0, ..., 2, 206 }.
+ Buffer expectedPrefix = {0, 0, 0, 2, 206};
+ Buffer encodedPrefix;
+ encodedPrefix.assign(encoded.begin(), encoded.begin() + 5);
+ EXPECT_EQ(expectedPrefix, encodedPrefix);
+}
+
+TEST_F(CTSerializationTest, EncodesLogEntryForPrecert) {
+ LogEntry entry;
+ GetPrecertLogEntry(entry);
+
+ Buffer encoded;
+ ASSERT_EQ(Success, EncodeLogEntry(entry, encoded));
+ // log entry type + issuer key + length + tbsCertificate
+ EXPECT_EQ((2U + 32U + 3U + entry.tbsCertificate.size()), encoded.size());
+
+ // First two bytes are log entry type.
+ Buffer expectedPrefix = {0, 1};
+ Buffer encodedPrefix;
+ encodedPrefix.assign(encoded.begin(), encoded.begin() + 2);
+ EXPECT_EQ(expectedPrefix, encodedPrefix);
+
+ // Next is the issuer key (32 bytes).
+ Buffer encodedKeyHash;
+ encodedKeyHash.assign(encoded.begin() + 2, encoded.begin() + 2 + 32);
+ EXPECT_EQ(GetDefaultIssuerKeyHash(), encodedKeyHash);
+}
+
+TEST_F(CTSerializationTest, EncodesV1SCTSignedData) {
+ uint64_t timestamp = UINT64_C(0x139fe353cf5);
+ const uint8_t DUMMY_BYTES[] = {0x61, 0x62, 0x63}; // abc
+ Input dummyEntry(DUMMY_BYTES);
+ Input emptyExtensions;
+ Buffer encoded;
+ ASSERT_EQ(Success, EncodeV1SCTSignedData(timestamp, dummyEntry,
+ emptyExtensions, encoded));
+ EXPECT_EQ((size_t)15, encoded.size());
+
+ Buffer expectedBuffer = {
+ 0x00, // version
+ 0x00, // signature type
+ 0x00, 0x00, 0x01, 0x39, 0xFE, 0x35, 0x3C, 0xF5, // timestamp
+ 0x61, 0x62, 0x63, // log signature
+ 0x00, 0x00 // extensions (empty)
+ };
+ EXPECT_EQ(expectedBuffer, encoded);
+}
+
+TEST_F(CTSerializationTest, DecodesSCTList) {
+ // Two items in the list: "abc", "def"
+ const uint8_t ENCODED[] = {0x00, 0x0a, 0x00, 0x03, 0x61, 0x62,
+ 0x63, 0x00, 0x03, 0x64, 0x65, 0x66};
+ const uint8_t DECODED_1[] = {0x61, 0x62, 0x63};
+ const uint8_t DECODED_2[] = {0x64, 0x65, 0x66};
+
+ Reader listReader;
+ ASSERT_EQ(Success, DecodeSCTList(Input(ENCODED), listReader));
+
+ Input decoded1;
+ ASSERT_EQ(Success, ReadSCTListItem(listReader, decoded1));
+
+ Input decoded2;
+ ASSERT_EQ(Success, ReadSCTListItem(listReader, decoded2));
+
+ EXPECT_TRUE(listReader.AtEnd());
+ EXPECT_TRUE(InputsAreEqual(decoded1, Input(DECODED_1)));
+ EXPECT_TRUE(InputsAreEqual(decoded2, Input(DECODED_2)));
+}
+
+TEST_F(CTSerializationTest, FailsDecodingInvalidSCTList) {
+ // A list with one item that's too short (the second one)
+ const uint8_t ENCODED[] = {0x00, 0x0a, 0x00, 0x03, 0x61, 0x62,
+ 0x63, 0x00, 0x05, 0x64, 0x65, 0x66};
+
+ Reader listReader;
+ ASSERT_EQ(Success, DecodeSCTList(Input(ENCODED), listReader));
+ Input decoded1;
+ EXPECT_EQ(Success, ReadSCTListItem(listReader, decoded1));
+ Input decoded2;
+ EXPECT_NE(Success, ReadSCTListItem(listReader, decoded2));
+}
+
+TEST_F(CTSerializationTest, EncodesSCTList) {
+ const uint8_t SCT_1[] = {0x61, 0x62, 0x63};
+ const uint8_t SCT_2[] = {0x64, 0x65, 0x66};
+
+ std::vector<Input> list;
+ list.push_back(Input(SCT_1));
+ list.push_back(Input(SCT_2));
+
+ Buffer encodedList;
+ ASSERT_EQ(Success, EncodeSCTList(list, encodedList));
+
+ Reader listReader;
+ ASSERT_EQ(Success, DecodeSCTList(InputForBuffer(encodedList), listReader));
+
+ Input decoded1;
+ ASSERT_EQ(Success, ReadSCTListItem(listReader, decoded1));
+ EXPECT_TRUE(InputsAreEqual(decoded1, Input(SCT_1)));
+
+ Input decoded2;
+ ASSERT_EQ(Success, ReadSCTListItem(listReader, decoded2));
+ EXPECT_TRUE(InputsAreEqual(decoded2, Input(SCT_2)));
+
+ EXPECT_TRUE(listReader.AtEnd());
+}
+
+TEST_F(CTSerializationTest, DecodesSignedCertificateTimestamp) {
+ Buffer encodedSctBuffer = GetTestSignedCertificateTimestamp();
+ Input encodedSctInput = InputForBuffer(encodedSctBuffer);
+ Reader encodedSctReader(encodedSctInput);
+
+ SignedCertificateTimestamp sct;
+ ASSERT_EQ(Success, DecodeSignedCertificateTimestamp(encodedSctReader, sct));
+ EXPECT_EQ(SignedCertificateTimestamp::Version::V1, sct.version);
+ EXPECT_EQ(GetTestPublicKeyId(), sct.logId);
+ const uint64_t expectedTime = 1365181456089;
+ EXPECT_EQ(expectedTime, sct.timestamp);
+ const size_t expectedSignatureLength = 71;
+ EXPECT_EQ(expectedSignatureLength, sct.signature.signatureData.size());
+ EXPECT_TRUE(sct.extensions.empty());
+}
+
+TEST_F(CTSerializationTest, FailsDecodingInvalidSignedCertificateTimestamp) {
+ SignedCertificateTimestamp sct;
+
+ // Invalid version
+ const uint8_t INVALID_VERSION_BYTES[] = {0x02, 0x00};
+ Input invalidVersionSctInput(INVALID_VERSION_BYTES);
+ Reader invalidVersionSctReader(invalidVersionSctInput);
+ EXPECT_EQ(pkix::Result::ERROR_BAD_DER,
+ DecodeSignedCertificateTimestamp(invalidVersionSctReader, sct));
+
+ // Valid version, invalid length (missing data)
+ const uint8_t INVALID_LENGTH_BYTES[] = {0x00, 0x0a, 0x0b, 0x0c};
+ Input invalidLengthSctInput(INVALID_LENGTH_BYTES);
+ Reader invalidLengthSctReader(invalidLengthSctInput);
+ EXPECT_EQ(pkix::Result::ERROR_BAD_DER,
+ DecodeSignedCertificateTimestamp(invalidLengthSctReader, sct));
+}
+
+} // namespace ct
+} // namespace mozilla
diff --git a/security/ct/tests/gtest/CTTestUtils.cpp b/security/ct/tests/gtest/CTTestUtils.cpp
new file mode 100644
index 0000000000..786c7c190d
--- /dev/null
+++ b/security/ct/tests/gtest/CTTestUtils.cpp
@@ -0,0 +1,820 @@
+/* -*- 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 Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CTTestUtils.h"
+
+#include <stdint.h>
+#include <iomanip>
+
+#include "BTTypes.h"
+#include "CTSerialization.h"
+#include "gtest/gtest.h"
+#include "mozpkix/Input.h"
+#include "mozpkix/pkix.h"
+#include "mozpkix/pkixnss.h"
+#include "mozpkix/pkixtypes.h"
+#include "mozpkix/Result.h"
+#include "mozpkix/pkixcheck.h"
+#include "mozpkix/pkixutil.h"
+#include "SignedCertificateTimestamp.h"
+
+namespace mozilla {
+namespace ct {
+
+using namespace mozilla::pkix;
+
+// The following test vectors are from the CT test data repository at
+// https://github.com/google/certificate-transparency/tree/master/test/testdata
+
+// test-cert.pem
+const char kDefaultDerCert[] =
+ "308202ca30820233a003020102020106300d06092a864886f70d01010505003055310b3009"
+ "06035504061302474231243022060355040a131b4365727469666963617465205472616e73"
+ "706172656e6379204341310e300c0603550408130557616c65733110300e06035504071307"
+ "4572772057656e301e170d3132303630313030303030305a170d3232303630313030303030"
+ "305a3052310b30090603550406130247423121301f060355040a1318436572746966696361"
+ "7465205472616e73706172656e6379310e300c0603550408130557616c65733110300e0603"
+ "55040713074572772057656e30819f300d06092a864886f70d010101050003818d00308189"
+ "02818100b1fa37936111f8792da2081c3fe41925008531dc7f2c657bd9e1de4704160b4c9f"
+ "19d54ada4470404c1c51341b8f1f7538dddd28d9aca48369fc5646ddcc7617f8168aae5b41"
+ "d43331fca2dadfc804d57208949061f9eef902ca47ce88c644e000f06eeeccabdc9dd2f68a"
+ "22ccb09dc76e0dbc73527765b1a37a8c676253dcc10203010001a381ac3081a9301d060355"
+ "1d0e041604146a0d982a3b62c44b6d2ef4e9bb7a01aa9cb798e2307d0603551d2304763074"
+ "80145f9d880dc873e654d4f80dd8e6b0c124b447c355a159a4573055310b30090603550406"
+ "1302474231243022060355040a131b4365727469666963617465205472616e73706172656e"
+ "6379204341310e300c0603550408130557616c65733110300e060355040713074572772057"
+ "656e82010030090603551d1304023000300d06092a864886f70d010105050003818100171c"
+ "d84aac414a9a030f22aac8f688b081b2709b848b4e5511406cd707fed028597a9faefc2eee"
+ "2978d633aaac14ed3235197da87e0f71b8875f1ac9e78b281749ddedd007e3ecf50645f8cb"
+ "f667256cd6a1647b5e13203bb8582de7d6696f656d1c60b95f456b7fcf338571908f1c6972"
+ "7d24c4fccd249295795814d1dac0e6";
+
+// key hash of test-cert.pem's issuer (ca-cert.pem)
+const char kDefaultIssuerKeyHash[] =
+ "02adddca08b8bf9861f035940c940156d8350fdff899a6239c6bd77255b8f8fc";
+
+const char kDefaultDerTbsCert[] =
+ "30820233a003020102020107300d06092a864886f70d01010505003055310b300906035504"
+ "061302474231243022060355040a131b4365727469666963617465205472616e7370617265"
+ "6e6379204341310e300c0603550408130557616c65733110300e0603550407130745727720"
+ "57656e301e170d3132303630313030303030305a170d3232303630313030303030305a3052"
+ "310b30090603550406130247423121301f060355040a131843657274696669636174652054"
+ "72616e73706172656e6379310e300c0603550408130557616c65733110300e060355040713"
+ "074572772057656e30819f300d06092a864886f70d010101050003818d0030818902818100"
+ "beef98e7c26877ae385f75325a0c1d329bedf18faaf4d796bf047eb7e1ce15c95ba2f80ee4"
+ "58bd7db86f8a4b252191a79bd700c38e9c0389b45cd4dc9a120ab21e0cb41cd0e72805a410"
+ "cd9c5bdb5d4927726daf1710f60187377ea25b1a1e39eed0b88119dc154dc68f7da8e30caf"
+ "158a33e6c9509f4a05b01409ff5dd87eb50203010001a381ac3081a9301d0603551d0e0416"
+ "04142031541af25c05ffd8658b6843794f5e9036f7b4307d0603551d230476307480145f9d"
+ "880dc873e654d4f80dd8e6b0c124b447c355a159a4573055310b3009060355040613024742"
+ "31243022060355040a131b4365727469666963617465205472616e73706172656e63792043"
+ "41310e300c0603550408130557616c65733110300e060355040713074572772057656e8201"
+ "0030090603551d1304023000";
+
+// DigitallySigned of test-cert.proof
+const char kTestDigitallySigned[] =
+ "0403004730450220606e10ae5c2d5a1b0aed49dc4937f48de71a4e9784e9c208dfbfe9ef53"
+ "6cf7f2022100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc45689a2c0187ef5"
+ "a5";
+
+// test-cert.proof
+const char kTestSignedCertificateTimestamp[] =
+ "00df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d7640000013d"
+ "db27ded900000403004730450220606e10ae5c2d5a1b0aed49dc4937f48de71a4e9784e9c2"
+ "08dfbfe9ef536cf7f2022100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc456"
+ "89a2c0187ef5a5";
+
+// ct-server-key-public.pem
+const char kEcP256PublicKey[] =
+ "3059301306072a8648ce3d020106082a8648ce3d0301070342000499783cb14533c0161a5a"
+ "b45bf95d08a29cd0ea8dd4c84274e2be59ad15c676960cf0afa1074a57ac644b23479e5b3f"
+ "b7b245eb4b420ef370210371a944beaceb";
+
+// key id (sha256) of ct-server-key-public.pem
+const char kTestKeyId[] =
+ "df1c2ec11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d764";
+
+// signature field of DigitallySigned from test-cert.proof
+const char kTestSCTSignatureData[] =
+ "30450220606e10ae5c2d5a1b0aed49dc4937f48de71a4e9784e9c208dfbfe9ef536cf7f202"
+ "2100beb29c72d7d06d61d06bdb38a069469aa86fe12e18bb7cc45689a2c0187ef5a5";
+
+// signature field of DigitallySigned from test-embedded-pre-cert.proof
+const char kTestSCTPrecertSignatureData[] =
+ "30450220482f6751af35dba65436be1fd6640f3dbf9a41429495924530288fa3e5e23e0602"
+ "2100e4edc0db3ac572b1e2f5e8ab6a680653987dcf41027dfeffa105519d89edbf08";
+
+// test-embedded-cert.pem
+const char kTestEmbeddedCertData[] =
+ "30820359308202c2a003020102020107300d06092a864886f70d01010505"
+ "003055310b300906035504061302474231243022060355040a131b436572"
+ "7469666963617465205472616e73706172656e6379204341310e300c0603"
+ "550408130557616c65733110300e060355040713074572772057656e301e"
+ "170d3132303630313030303030305a170d3232303630313030303030305a"
+ "3052310b30090603550406130247423121301f060355040a131843657274"
+ "69666963617465205472616e73706172656e6379310e300c060355040813"
+ "0557616c65733110300e060355040713074572772057656e30819f300d06"
+ "092a864886f70d010101050003818d0030818902818100beef98e7c26877"
+ "ae385f75325a0c1d329bedf18faaf4d796bf047eb7e1ce15c95ba2f80ee4"
+ "58bd7db86f8a4b252191a79bd700c38e9c0389b45cd4dc9a120ab21e0cb4"
+ "1cd0e72805a410cd9c5bdb5d4927726daf1710f60187377ea25b1a1e39ee"
+ "d0b88119dc154dc68f7da8e30caf158a33e6c9509f4a05b01409ff5dd87e"
+ "b50203010001a382013a30820136301d0603551d0e041604142031541af2"
+ "5c05ffd8658b6843794f5e9036f7b4307d0603551d230476307480145f9d"
+ "880dc873e654d4f80dd8e6b0c124b447c355a159a4573055310b30090603"
+ "5504061302474231243022060355040a131b436572746966696361746520"
+ "5472616e73706172656e6379204341310e300c0603550408130557616c65"
+ "733110300e060355040713074572772057656e82010030090603551d1304"
+ "02300030818a060a2b06010401d679020402047c047a0078007600df1c2e"
+ "c11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d76400"
+ "00013ddb27df9300000403004730450220482f6751af35dba65436be1fd6"
+ "640f3dbf9a41429495924530288fa3e5e23e06022100e4edc0db3ac572b1"
+ "e2f5e8ab6a680653987dcf41027dfeffa105519d89edbf08300d06092a86"
+ "4886f70d0101050500038181008a0c4bef099d479279afa0a28e689f91e1"
+ "c4421be2d269a2ea6ca4e8215ddeddca1504a11e7c87c4b77e80f0e97903"
+ "5268f27ca20e166804ae556f316981f96a394ab7abfd3e255ac0044513fe"
+ "76570c6795abe4703133d303f89f3afa6bbcfc517319dfd95b934241211f"
+ "634035c3d078307a68c6075a2e20c89f36b8910ca0";
+
+const char kTestTbsCertData[] =
+ "30820233a003020102020107300d06092a864886f70d0101050500305531"
+ "0b300906035504061302474231243022060355040a131b43657274696669"
+ "63617465205472616e73706172656e6379204341310e300c060355040813"
+ "0557616c65733110300e060355040713074572772057656e301e170d3132"
+ "303630313030303030305a170d3232303630313030303030305a3052310b"
+ "30090603550406130247423121301f060355040a13184365727469666963"
+ "617465205472616e73706172656e6379310e300c0603550408130557616c"
+ "65733110300e060355040713074572772057656e30819f300d06092a8648"
+ "86f70d010101050003818d0030818902818100beef98e7c26877ae385f75"
+ "325a0c1d329bedf18faaf4d796bf047eb7e1ce15c95ba2f80ee458bd7db8"
+ "6f8a4b252191a79bd700c38e9c0389b45cd4dc9a120ab21e0cb41cd0e728"
+ "05a410cd9c5bdb5d4927726daf1710f60187377ea25b1a1e39eed0b88119"
+ "dc154dc68f7da8e30caf158a33e6c9509f4a05b01409ff5dd87eb5020301"
+ "0001a381ac3081a9301d0603551d0e041604142031541af25c05ffd8658b"
+ "6843794f5e9036f7b4307d0603551d230476307480145f9d880dc873e654"
+ "d4f80dd8e6b0c124b447c355a159a4573055310b30090603550406130247"
+ "4231243022060355040a131b4365727469666963617465205472616e7370"
+ "6172656e6379204341310e300c0603550408130557616c65733110300e06"
+ "0355040713074572772057656e82010030090603551d1304023000";
+
+// test-embedded-with-preca-cert.pem
+const char kTestEmbeddedWithPreCaCertData[] =
+ "30820359308202c2a003020102020108300d06092a864886f70d01010505"
+ "003055310b300906035504061302474231243022060355040a131b436572"
+ "7469666963617465205472616e73706172656e6379204341310e300c0603"
+ "550408130557616c65733110300e060355040713074572772057656e301e"
+ "170d3132303630313030303030305a170d3232303630313030303030305a"
+ "3052310b30090603550406130247423121301f060355040a131843657274"
+ "69666963617465205472616e73706172656e6379310e300c060355040813"
+ "0557616c65733110300e060355040713074572772057656e30819f300d06"
+ "092a864886f70d010101050003818d0030818902818100afaeeacac51ab7"
+ "cebdf9eacae7dd175295e193955a17989aef8d97ab7cdff7761093c0b823"
+ "d2a4e3a51a17b86f28162b66a2538935ebecdc1036233da2dd6531b0c63b"
+ "cc68761ebdc854037b77399246b870a7b72b14c9b1667de09a9640ed9f3f"
+ "3c725d950b4d26559869fe7f1e919a66eb76d35c0117c6bcd0d8cfd21028"
+ "b10203010001a382013a30820136301d0603551d0e04160414612c64efac"
+ "79b728397c9d93e6df86465fa76a88307d0603551d230476307480145f9d"
+ "880dc873e654d4f80dd8e6b0c124b447c355a159a4573055310b30090603"
+ "5504061302474231243022060355040a131b436572746966696361746520"
+ "5472616e73706172656e6379204341310e300c0603550408130557616c65"
+ "733110300e060355040713074572772057656e82010030090603551d1304"
+ "02300030818a060a2b06010401d679020402047c047a0078007600df1c2e"
+ "c11500945247a96168325ddc5c7959e8f7c6d388fc002e0bbd3f74d76400"
+ "00013ddb27e05b000004030047304502207aa79604c47480f3727b084f90"
+ "b3989f79091885e00484431a2a297cbf3a355c022100b49fd8120b0d644c"
+ "d7e75269b4da6317a9356cb950224fc11cc296b2e39b2386300d06092a86"
+ "4886f70d010105050003818100a3a86c41ad0088a25aedc4e7b529a2ddbf"
+ "9e187ffb362157e9302d961b73b43cba0ae1e230d9e45049b7e8c924792e"
+ "bbe7d175baa87b170dfad8ee788984599d05257994084e2e0e796fca5836"
+ "881c3e053553e06ab230f919089b914e4a8e2da45f8a87f2c81a25a61f04"
+ "fe1cace60155653827d41fad9f0658f287d058192c";
+
+// ca-cert.pem
+const char kCaCertData[] =
+ "308202d030820239a003020102020100300d06092a864886f70d01010505"
+ "003055310b300906035504061302474231243022060355040a131b436572"
+ "7469666963617465205472616e73706172656e6379204341310e300c0603"
+ "550408130557616c65733110300e060355040713074572772057656e301e"
+ "170d3132303630313030303030305a170d3232303630313030303030305a"
+ "3055310b300906035504061302474231243022060355040a131b43657274"
+ "69666963617465205472616e73706172656e6379204341310e300c060355"
+ "0408130557616c65733110300e060355040713074572772057656e30819f"
+ "300d06092a864886f70d010101050003818d0030818902818100d58a6853"
+ "6210a27119936e778321181c2a4013c6d07b8c76eb9157d3d0fb4b3b516e"
+ "cecbd1c98d91c52f743fab635d55099cd13abaf31ae541442451a74c7816"
+ "f2243cf848cf2831cce67ba04a5a23819f3cba37e624d9c3bdb299b839dd"
+ "fe2631d2cb3a84fc7bb2b5c52fcfc14fff406f5cd44669cbb2f7cfdf86fb"
+ "6ab9d1b10203010001a381af3081ac301d0603551d0e041604145f9d880d"
+ "c873e654d4f80dd8e6b0c124b447c355307d0603551d230476307480145f"
+ "9d880dc873e654d4f80dd8e6b0c124b447c355a159a4573055310b300906"
+ "035504061302474231243022060355040a131b4365727469666963617465"
+ "205472616e73706172656e6379204341310e300c0603550408130557616c"
+ "65733110300e060355040713074572772057656e820100300c0603551d13"
+ "040530030101ff300d06092a864886f70d0101050500038181000608cc4a"
+ "6d64f2205e146c04b276f92b0efa94a5daf23afc3806606d3990d0a1ea23"
+ "3d40295769463b046661e7fa1d179915209aea2e0a775176411227d7c003"
+ "07c7470e61584fd7334224727f51d690bc47a9df354db0f6eb25955de189"
+ "3c4dd5202b24a2f3e440d274b54e1bd376269ca96289b76ecaa41090e14f"
+ "3b0a942e";
+
+// intermediate-cert.pem
+const char kIntermediateCertData[] =
+ "308202dd30820246a003020102020109300d06092a864886f70d01010505"
+ "003055310b300906035504061302474231243022060355040a131b436572"
+ "7469666963617465205472616e73706172656e6379204341310e300c0603"
+ "550408130557616c65733110300e060355040713074572772057656e301e"
+ "170d3132303630313030303030305a170d3232303630313030303030305a"
+ "3062310b30090603550406130247423131302f060355040a132843657274"
+ "69666963617465205472616e73706172656e637920496e7465726d656469"
+ "617465204341310e300c0603550408130557616c65733110300e06035504"
+ "0713074572772057656e30819f300d06092a864886f70d01010105000381"
+ "8d0030818902818100d76a678d116f522e55ff821c90642508b7074b14d7"
+ "71159064f7927efdedb87135a1365ee7de18cbd5ce865f860c78f433b4d0"
+ "d3d3407702e7a3ef542b1dfe9bbaa7cdf94dc5975fc729f86f105f381b24"
+ "3535cf9c800f5ca780c1d3c84400ee65d16ee9cf52db8adffe50f5c49335"
+ "0b2190bf50d5bc36f3cac5a8daae92cd8b0203010001a381af3081ac301d"
+ "0603551d0e04160414965508050278479e8773764131bc143a47e229ab30"
+ "7d0603551d230476307480145f9d880dc873e654d4f80dd8e6b0c124b447"
+ "c355a159a4573055310b300906035504061302474231243022060355040a"
+ "131b4365727469666963617465205472616e73706172656e637920434131"
+ "0e300c0603550408130557616c65733110300e0603550407130745727720"
+ "57656e820100300c0603551d13040530030101ff300d06092a864886f70d"
+ "0101050500038181002206dab1c66b71dce095c3f6aa2ef72cf7761be7ab"
+ "d7fc39c31a4cfe1bd96d6734ca82f22dde5a0c8bbbdd825d7b6f3e7612ad"
+ "8db300a7e21169886023262284c3aa5d2191efda10bf9235d37b3a2a340d"
+ "59419b94a48566f3fac3cd8b53d5a4e98270ead297b07210f9ce4a2138b1"
+ "8811143b93fa4e7a87dd37e1385f2c2908";
+
+// test-embedded-with-intermediate-cert.pem
+const char kTestEmbeddedWithIntermediateCertData[] =
+ "30820366308202cfa003020102020102300d06092a864886f70d01010505"
+ "003062310b30090603550406130247423131302f060355040a1328436572"
+ "7469666963617465205472616e73706172656e637920496e7465726d6564"
+ "69617465204341310e300c0603550408130557616c65733110300e060355"
+ "040713074572772057656e301e170d3132303630313030303030305a170d"
+ "3232303630313030303030305a3052310b30090603550406130247423121"
+ "301f060355040a13184365727469666963617465205472616e7370617265"
+ "6e6379310e300c0603550408130557616c65733110300e06035504071307"
+ "4572772057656e30819f300d06092a864886f70d010101050003818d0030"
+ "818902818100bb272b26e5deb5459d4acca027e8f12a4d839ac3730a6a10"
+ "9ff7e25498ddbd3f1895d08ba41f8de34967a3a086ce13a90dd5adbb5418"
+ "4bdc08e1ac7826adb8dc9c717bfd7da5b41b4db1736e00f1dac3cec9819c"
+ "cb1a28ba120b020a820e940dd61f95b5432a4bc05d0818f18ce2154eb38d"
+ "2fa7d22d72b976e560db0c7fc77f0203010001a382013a30820136301d06"
+ "03551d0e04160414b1b148e658e703f5f7f3105f20b3c384d7eff1bf307d"
+ "0603551d23047630748014965508050278479e8773764131bc143a47e229"
+ "aba159a4573055310b300906035504061302474231243022060355040a13"
+ "1b4365727469666963617465205472616e73706172656e6379204341310e"
+ "300c0603550408130557616c65733110300e060355040713074572772057"
+ "656e82010930090603551d130402300030818a060a2b06010401d6790204"
+ "02047c047a0078007600df1c2ec11500945247a96168325ddc5c7959e8f7"
+ "c6d388fc002e0bbd3f74d7640000013ddb27e2a400000403004730450221"
+ "00a6d34517f3392d9ec5d257adf1c597dc45bd4cd3b73856c616a9fb99e5"
+ "ae75a802205e26c8d1c7e222fe8cda29baeb04a834ee97d34fd81718f1aa"
+ "e0cd66f4b8a93f300d06092a864886f70d0101050500038181000f95a5b4"
+ "e128a914b1e88be8b32964221b58f4558433d020a8e246cca65a40bcbf5f"
+ "2d48933ebc99be6927ca756472fb0bdc7f505f41f462f2bc19d0b299c990"
+ "918df8820f3d31db37979e8bad563b17f00ae67b0f8731c106c943a73bf5"
+ "36af168afe21ef4adfcae19a3cc074899992bf506bc5ce1decaaf07ffeeb"
+ "c805c039";
+
+// test-embedded-with-intermediate-preca-cert.pem
+const char kTestEmbeddedWithIntermediatePreCaCertData[] =
+ "30820366308202cfa003020102020103300d06092a864886f70d01010505"
+ "003062310b30090603550406130247423131302f060355040a1328436572"
+ "7469666963617465205472616e73706172656e637920496e7465726d6564"
+ "69617465204341310e300c0603550408130557616c65733110300e060355"
+ "040713074572772057656e301e170d3132303630313030303030305a170d"
+ "3232303630313030303030305a3052310b30090603550406130247423121"
+ "301f060355040a13184365727469666963617465205472616e7370617265"
+ "6e6379310e300c0603550408130557616c65733110300e06035504071307"
+ "4572772057656e30819f300d06092a864886f70d010101050003818d0030"
+ "818902818100d4497056cdfc65e1342cc3df6e654b8af0104702acd2275c"
+ "7d3fb1fc438a89b212110d6419bcc13ae47d64bba241e6706b9ed627f8b3"
+ "4a0d7dff1c44b96287c54bea9d10dc017bceb64f7b6aff3c35a474afec40"
+ "38ab3640b0cd1fb0582ec03b179a2776c8c435d14ab4882d59d7b724fa37"
+ "7ca6db08392173f9c6056b3abadf0203010001a382013a30820136301d06"
+ "03551d0e0416041432da5518d87f1d26ea2767973c0bef286e786a4a307d"
+ "0603551d23047630748014965508050278479e8773764131bc143a47e229"
+ "aba159a4573055310b300906035504061302474231243022060355040a13"
+ "1b4365727469666963617465205472616e73706172656e6379204341310e"
+ "300c0603550408130557616c65733110300e060355040713074572772057"
+ "656e82010930090603551d130402300030818a060a2b06010401d6790204"
+ "02047c047a0078007600df1c2ec11500945247a96168325ddc5c7959e8f7"
+ "c6d388fc002e0bbd3f74d7640000013ddb27e3be00000403004730450221"
+ "00d9f61a07fee021e3159f3ca2f570d833ff01374b2096cba5658c5e16fb"
+ "43eb3002200b76fe475138d8cf76833831304dabf043eb1213c96e13ff4f"
+ "a37f7cd3c8dc1f300d06092a864886f70d01010505000381810088ee4e9e"
+ "5eed6b112cc764b151ed929400e9406789c15fbbcfcdab2f10b400234139"
+ "e6ce65c1e51b47bf7c8950f80bccd57168567954ed35b0ce9346065a5eae"
+ "5bf95d41da8e27cee9eeac688f4bd343f9c2888327abd8b9f68dcb1e3050"
+ "041d31bda8e2dd6d39b3664de5ce0870f5fc7e6a00d6ed00528458d953d2"
+ "37586d73";
+
+// Given the ordered set of data [ 0x00, 0x01, 0x02, deadbeef ],
+// the 'inclusion proof' of the leaf of index '2' (for '0x02') is created from
+// the Merkle Tree generated for that set of data.
+// A Merkle inclusion proof for a leaf in a Merkle Tree is the shortest list
+// of additional nodes in the Merkle Tree required to compute the Merkle Tree
+// Hash (also called 'Merkle Tree head') for that tree.
+// This follows the structure defined in RFC 6962-bis.
+//
+// https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-24#section-2.1
+
+const char kTestInclusionProof[] =
+ "020100" // logId
+ "0000000000000004" // tree size
+ "0000000000000002" // leaf index
+ "0042" // inclusion path length
+ "2048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed32172"
+ "9" // node
+ // hash
+ // 0
+ "20a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953"
+ "a"; // node
+ // hash
+ // 1
+
+const char kTestNodeHash0[] =
+ "48c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed321729";
+
+const char kTestNodeHash1[] =
+ "a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953a";
+
+const char kTestInclusionProofUnexpectedData[] = "12345678";
+
+const char kTestInclusionProofInvalidHashSize[] =
+ "020100" // logId
+ "0000000000000004" // treesize
+ "0000000000000002" // leafindex
+ "0042" // inclusion path length
+ "3048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed32172"
+ "9" // invalid hash size
+ "20a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953"
+ "a"; // node hash 1
+
+const char kTestInclusionProofInvalidHash[] =
+ "020100" // logId
+ "0000000000000004" // treesize
+ "0000000000000002" // leafindex
+ "0042" // inclusion path length
+ "2048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed32172"
+ "9" // node
+ // hash
+ // 0
+ "20a20bf9a7cc2dc8a08f5f415a71b19f6ac427"; // truncated node hash 1
+
+const char kTestInclusionProofMissingLogId[] =
+ "0000000000000004" // treesize
+ "0000000000000002" // leafindex
+ "0042"
+ "2048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed32172"
+ "9" // node
+ // hash
+ // 0
+ "20a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953"
+ "a"; // node
+ // hash
+ // 1
+
+const char kTestInclusionProofNullPathLength[] =
+ "020100"
+ "0000000000000004" // treesize
+ "0000000000000002" // leafindex
+ "0000"
+ "2048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed32172"
+ "9" // node
+ // hash
+ // 0
+ "20a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953"
+ "a"; // node
+ // hash
+ // 1
+
+const char kTestInclusionProofPathLengthTooSmall[] =
+ "020100"
+ "0000000000000004" // treesize
+ "0000000000000002" // leafindex
+ "0036"
+ "2048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed32172"
+ "9" // node
+ // hash
+ // 0
+ "20a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953"
+ "a"; // node
+ // hash
+ // 1
+
+const char kTestInclusionProofPathLengthTooLarge[] =
+ "020100"
+ "0000000000000004" // treesize
+ "0000000000000002" // leafindex
+ "0080"
+ "2048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed32172"
+ "9" // node
+ // hash
+ // 0
+ "20a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953"
+ "a"; // node
+ // hash
+ // 1
+
+const char kTestInclusionProofNullTreeSize[] =
+ "020100"
+ "0000000000000000" // treesize
+ "0000000000000002" // leafindex
+ "0042"
+ "2048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed32172"
+ "9" // node
+ // hash
+ // 0
+ "20a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953"
+ "a"; // node
+ // hash
+ // 1
+
+const char kTestInclusionProofLeafIndexOutOfBounds[] =
+ "020100"
+ "0000000000000004" // treesize
+ "0000000000000004" // leafindex
+ "0042"
+ "2048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed32172"
+ "9" // node
+ // hash
+ // 0
+ "20a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953"
+ "a"; // node
+ // hash
+ // 1
+
+const char kTestInclusionProofExtraData[] =
+ "020100" // logId
+ "0000000000000004" // tree size
+ "0000000000000002" // leaf index
+ "0042" // inclusion path length
+ "2048c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed32172"
+ "9" // node
+ // hash
+ // 0
+ "20a20bf9a7cc2dc8a08f5f415a71b19f6ac427bab54d24eec868b5d3103449953"
+ "a" // node
+ // hash
+ // 1
+ "123456"; // extra data after the proof
+
+static uint8_t CharToByte(char c) {
+ if (c >= '0' && c <= '9') {
+ return c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ return c - 'a' + 10;
+ } else if (c >= 'A' && c <= 'F') {
+ return c - 'A' + 10;
+ }
+ abort();
+}
+
+Buffer HexToBytes(const char* hexData) {
+ size_t hexLen = strlen(hexData);
+ if (!(hexLen > 0 && (hexLen % 2 == 0))) {
+ abort();
+ }
+ size_t resultLen = hexLen / 2;
+ Buffer result;
+ result.reserve(resultLen);
+ for (size_t i = 0; i < resultLen; ++i) {
+ uint8_t hi = CharToByte(hexData[i * 2]);
+ uint8_t lo = CharToByte(hexData[i * 2 + 1]);
+ result.push_back((hi << 4) | lo);
+ }
+ return result;
+}
+
+void GetX509CertLogEntry(LogEntry& entry) {
+ entry.Reset();
+ entry.type = ct::LogEntry::Type::X509;
+ entry.leafCertificate = HexToBytes(kDefaultDerCert);
+}
+
+Buffer GetDEREncodedX509Cert() { return HexToBytes(kDefaultDerCert); }
+
+void GetPrecertLogEntry(LogEntry& entry) {
+ entry.Reset();
+ entry.type = ct::LogEntry::Type::Precert;
+ entry.issuerKeyHash = HexToBytes(kDefaultIssuerKeyHash);
+ entry.tbsCertificate = HexToBytes(kDefaultDerTbsCert);
+}
+
+Buffer GetTestDigitallySigned() { return HexToBytes(kTestDigitallySigned); }
+
+Buffer GetTestDigitallySignedData() {
+ Buffer encoded = GetTestDigitallySigned();
+ // The encoded buffer contains the signature data itself from the 4th byte.
+ // The first bytes are:
+ // 1 byte of hash algorithm
+ // 1 byte of signature algorithm
+ // 2 bytes - prefix containing length of the signature data.
+ Buffer result;
+ result.assign(encoded.begin() + 4, encoded.end());
+ return result;
+}
+
+Buffer GetTestSignedCertificateTimestamp() {
+ return HexToBytes(kTestSignedCertificateTimestamp);
+}
+
+Buffer GetTestInclusionProof() { return HexToBytes(kTestInclusionProof); }
+
+Buffer GetTestInclusionProofUnexpectedData() {
+ return HexToBytes(kTestInclusionProofUnexpectedData);
+}
+
+Buffer GetTestInclusionProofInvalidHashSize() {
+ return HexToBytes(kTestInclusionProofInvalidHashSize);
+}
+
+Buffer GetTestInclusionProofInvalidHash() {
+ return HexToBytes(kTestInclusionProofInvalidHash);
+}
+
+Buffer GetTestInclusionProofMissingLogId() {
+ return HexToBytes(kTestInclusionProofMissingLogId);
+}
+
+Buffer GetTestInclusionProofNullPathLength() {
+ return HexToBytes(kTestInclusionProofNullPathLength);
+}
+
+Buffer GetTestInclusionProofPathLengthTooSmall() {
+ return HexToBytes(kTestInclusionProofPathLengthTooSmall);
+}
+
+Buffer GetTestInclusionProofPathLengthTooLarge() {
+ return HexToBytes(kTestInclusionProofPathLengthTooLarge);
+}
+
+Buffer GetTestInclusionProofNullTreeSize() {
+ return HexToBytes(kTestInclusionProofNullTreeSize);
+}
+
+Buffer GetTestInclusionProofLeafIndexOutOfBounds() {
+ return HexToBytes(kTestInclusionProofLeafIndexOutOfBounds);
+}
+
+Buffer GetTestInclusionProofExtraData() {
+ return HexToBytes(kTestInclusionProofExtraData);
+}
+
+Buffer GetTestNodeHash0() { return HexToBytes(kTestNodeHash0); }
+
+Buffer GetTestNodeHash1() { return HexToBytes(kTestNodeHash1); }
+
+Buffer GetTestPublicKey() { return HexToBytes(kEcP256PublicKey); }
+
+Buffer GetTestPublicKeyId() { return HexToBytes(kTestKeyId); }
+
+void GetX509CertSCT(SignedCertificateTimestamp& sct) {
+ sct.version = ct::SignedCertificateTimestamp::Version::V1;
+ sct.logId = HexToBytes(kTestKeyId);
+ // Time the log issued a SCT for this certificate, which is
+ // Fri Apr 5 10:04:16.089 2013
+ sct.timestamp = INT64_C(1365181456089);
+ sct.extensions.clear();
+
+ sct.signature.hashAlgorithm = ct::DigitallySigned::HashAlgorithm::SHA256;
+ sct.signature.signatureAlgorithm =
+ ct::DigitallySigned::SignatureAlgorithm::ECDSA;
+ sct.signature.signatureData = HexToBytes(kTestSCTSignatureData);
+}
+
+void GetPrecertSCT(SignedCertificateTimestamp& sct) {
+ sct.version = ct::SignedCertificateTimestamp::Version::V1;
+ sct.logId = HexToBytes(kTestKeyId);
+ // Time the log issued a SCT for this Precertificate, which is
+ // Fri Apr 5 10:04:16.275 2013
+ sct.timestamp = INT64_C(1365181456275);
+ sct.extensions.clear();
+
+ sct.signature.hashAlgorithm = ct::DigitallySigned::HashAlgorithm::SHA256;
+ sct.signature.signatureAlgorithm =
+ ct::DigitallySigned::SignatureAlgorithm::ECDSA;
+ sct.signature.signatureData = HexToBytes(kTestSCTPrecertSignatureData);
+}
+
+Buffer GetDefaultIssuerKeyHash() { return HexToBytes(kDefaultIssuerKeyHash); }
+
+Buffer GetDEREncodedTestEmbeddedCert() {
+ return HexToBytes(kTestEmbeddedCertData);
+}
+
+Buffer GetDEREncodedTestTbsCert() { return HexToBytes(kTestTbsCertData); }
+
+Buffer GetDEREncodedTestEmbeddedWithPreCACert() {
+ return HexToBytes(kTestEmbeddedWithPreCaCertData);
+}
+
+Buffer GetDEREncodedCACert() { return HexToBytes(kCaCertData); }
+
+Buffer GetDEREncodedIntermediateCert() {
+ return HexToBytes(kIntermediateCertData);
+}
+
+Buffer GetDEREncodedTestEmbeddedWithIntermediateCert() {
+ return HexToBytes(kTestEmbeddedWithIntermediateCertData);
+}
+
+Buffer GetDEREncodedTestEmbeddedWithIntermediatePreCACert() {
+ return HexToBytes(kTestEmbeddedWithIntermediatePreCaCertData);
+}
+
+Buffer ExtractCertSPKI(Input cert) {
+ BackCert backCert(cert, EndEntityOrCA::MustBeEndEntity, nullptr);
+ if (backCert.Init() != Success) {
+ abort();
+ }
+
+ Input spkiInput = backCert.GetSubjectPublicKeyInfo();
+ Buffer spki;
+ InputToBuffer(spkiInput, spki);
+ return spki;
+}
+
+Buffer ExtractCertSPKI(const Buffer& cert) {
+ return ExtractCertSPKI(InputForBuffer(cert));
+}
+
+void ExtractEmbeddedSCTList(Input cert, Buffer& result) {
+ result.clear();
+ BackCert backCert(cert, EndEntityOrCA::MustBeEndEntity, nullptr);
+ ASSERT_EQ(Success, backCert.Init());
+ const Input* scts = backCert.GetSignedCertificateTimestamps();
+ if (scts) {
+ Input sctList;
+ ASSERT_EQ(Success, ExtractSignedCertificateTimestampListFromExtension(
+ *scts, sctList));
+ InputToBuffer(sctList, result);
+ }
+}
+
+void ExtractEmbeddedSCTList(const Buffer& cert, Buffer& result) {
+ ExtractEmbeddedSCTList(InputForBuffer(cert), result);
+}
+
+class OCSPExtensionTrustDomain : public TrustDomain {
+ public:
+ pkix::Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input,
+ TrustLevel&) override {
+ ADD_FAILURE();
+ return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
+ }
+
+ pkix::Result FindIssuer(Input, IssuerChecker&, Time) override {
+ ADD_FAILURE();
+ return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
+ }
+
+ pkix::Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+ const Input*, const Input*,
+ const Input*) override {
+ ADD_FAILURE();
+ return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
+ }
+
+ pkix::Result IsChainValid(const DERArray&, Time,
+ const CertPolicyId&) override {
+ ADD_FAILURE();
+ return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
+ }
+
+ pkix::Result DigestBuf(Input item, DigestAlgorithm digestAlg,
+ /*out*/ uint8_t* digestBuf,
+ size_t digestBufLen) override {
+ return DigestBufNSS(item, digestAlg, digestBuf, digestBufLen);
+ }
+
+ pkix::Result CheckSignatureDigestAlgorithm(DigestAlgorithm, EndEntityOrCA,
+ Time) override {
+ ADD_FAILURE();
+ return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
+ }
+
+ pkix::Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override {
+ ADD_FAILURE();
+ return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
+ }
+
+ pkix::Result VerifyECDSASignedData(Input data,
+ DigestAlgorithm digestAlgorithm,
+ Input signature,
+ Input subjectPublicKeyInfo) override {
+ return VerifyECDSASignedDataNSS(data, digestAlgorithm, signature,
+ subjectPublicKeyInfo, nullptr);
+ }
+
+ pkix::Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA,
+ unsigned int) override {
+ ADD_FAILURE();
+ return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
+ }
+
+ pkix::Result VerifyRSAPKCS1SignedData(Input data,
+ DigestAlgorithm digestAlgorithm,
+ Input signature,
+ Input subjectPublicKeyInfo) override {
+ return VerifyRSAPKCS1SignedDataNSS(data, digestAlgorithm, signature,
+ subjectPublicKeyInfo, nullptr);
+ }
+
+ pkix::Result VerifyRSAPSSSignedData(Input data,
+ DigestAlgorithm digestAlgorithm,
+ Input signature,
+ Input subjectPublicKeyInfo) override {
+ return VerifyRSAPSSSignedDataNSS(data, digestAlgorithm, signature,
+ subjectPublicKeyInfo, nullptr);
+ }
+
+ pkix::Result CheckValidityIsAcceptable(Time, Time, EndEntityOrCA,
+ KeyPurposeId) override {
+ ADD_FAILURE();
+ return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
+ }
+
+ pkix::Result NetscapeStepUpMatchesServerAuth(Time, bool&) override {
+ ADD_FAILURE();
+ return pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
+ }
+
+ void NoteAuxiliaryExtension(AuxiliaryExtension extension,
+ Input data) override {
+ if (extension != AuxiliaryExtension::SCTListFromOCSPResponse) {
+ ADD_FAILURE();
+ return;
+ }
+ InputToBuffer(data, signedCertificateTimestamps);
+ }
+
+ Buffer signedCertificateTimestamps;
+};
+
+void ExtractSCTListFromOCSPResponse(Input cert, Input issuerSPKI,
+ Input encodedResponse, Time time,
+ Buffer& result) {
+ result.clear();
+
+ BackCert backCert(cert, EndEntityOrCA::MustBeEndEntity, nullptr);
+ ASSERT_EQ(Success, backCert.Init());
+
+ CertID certID(backCert.GetIssuer(), issuerSPKI, backCert.GetSerialNumber());
+
+ bool expired;
+ OCSPExtensionTrustDomain trustDomain;
+ pkix::Result rv =
+ VerifyEncodedOCSPResponse(trustDomain, certID, time, /*time*/
+ 1000, /*maxLifetimeInDays*/
+ encodedResponse, expired);
+ ASSERT_EQ(Success, rv);
+
+ result = std::move(trustDomain.signedCertificateTimestamps);
+}
+
+Input InputForBuffer(const Buffer& buffer) {
+ Input input;
+ if (input.Init(buffer.data(), buffer.size()) != Success) {
+ abort();
+ }
+ return input;
+}
+
+Input InputForSECItem(const SECItem& item) {
+ Input input;
+ if (input.Init(item.data, item.len) != Success) {
+ abort();
+ }
+ return input;
+}
+
+} // namespace ct
+} // namespace mozilla
+
+namespace mozilla {
+
+std::ostream& operator<<(std::ostream& stream, const ct::Buffer& buffer) {
+ if (buffer.empty()) {
+ stream << "EMPTY";
+ } else {
+ for (size_t i = 0; i < buffer.size(); ++i) {
+ if (i >= 1000) {
+ stream << "...";
+ break;
+ }
+ stream << std::hex << std::setw(2) << std::setfill('0')
+ << static_cast<unsigned>(buffer[i]);
+ }
+ }
+ stream << std::dec;
+ return stream;
+}
+
+} // namespace mozilla
diff --git a/security/ct/tests/gtest/CTTestUtils.h b/security/ct/tests/gtest/CTTestUtils.h
new file mode 100644
index 0000000000..3679d4411e
--- /dev/null
+++ b/security/ct/tests/gtest/CTTestUtils.h
@@ -0,0 +1,137 @@
+/* -*- 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 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/. */
+
+#ifndef CTTestUtils_h
+#define CTTestUtils_h
+
+#include <iostream>
+
+#include "mozpkix/Input.h"
+#include "mozpkix/Time.h"
+#include "seccomon.h"
+#include "SignedCertificateTimestamp.h"
+
+namespace mozilla {
+namespace ct {
+
+Buffer HexToBytes(const char* hexData);
+
+// Note: unless specified otherwise, all test data is taken from
+// Certificate Transparency test data repository at
+// https://github.com/google/certificate-transparency/tree/master/test/testdata
+
+// Fills |entry| with test data for an X.509 entry.
+void GetX509CertLogEntry(LogEntry& entry);
+
+// Returns a DER-encoded X509 cert. The SCT provided by
+// GetX509CertSCT is signed over this certificate.
+Buffer GetDEREncodedX509Cert();
+
+// Fills |entry| with test data for a Precertificate entry.
+void GetPrecertLogEntry(LogEntry& entry);
+
+// Returns the binary representation of a test DigitallySigned.
+Buffer GetTestDigitallySigned();
+
+// Returns the source data of the test DigitallySigned.
+Buffer GetTestDigitallySignedData();
+
+// Returns the binary representation of a test serialized SCT.
+Buffer GetTestSignedCertificateTimestamp();
+
+// Returns the binary representation of a test serialized InclusionProof.
+Buffer GetTestInclusionProof();
+Buffer GetTestInclusionProofUnexpectedData();
+Buffer GetTestInclusionProofInvalidHashSize();
+Buffer GetTestInclusionProofInvalidHash();
+Buffer GetTestInclusionProofMissingLogId();
+Buffer GetTestInclusionProofNullPathLength();
+Buffer GetTestInclusionProofPathLengthTooSmall();
+Buffer GetTestInclusionProofPathLengthTooLarge();
+Buffer GetTestInclusionProofNullTreeSize();
+Buffer GetTestInclusionProofLeafIndexOutOfBounds();
+Buffer GetTestInclusionProofExtraData();
+
+// Returns the binary representation of test serialized node hashs from an
+// inclusion proof.
+Buffer GetTestNodeHash0();
+Buffer GetTestNodeHash1();
+
+// Test log key.
+Buffer GetTestPublicKey();
+
+// ID of test log key.
+Buffer GetTestPublicKeyId();
+
+// SCT for the X509Certificate provided above.
+void GetX509CertSCT(SignedCertificateTimestamp& sct);
+
+// SCT for the Precertificate log entry provided above.
+void GetPrecertSCT(SignedCertificateTimestamp& sct);
+
+// Issuer key hash.
+Buffer GetDefaultIssuerKeyHash();
+
+// The SHA256 root hash for the sample STH.
+Buffer GetSampleSTHSHA256RootHash();
+
+// The tree head signature for the sample STH.
+Buffer GetSampleSTHTreeHeadSignature();
+
+// The same signature as GetSampleSTHTreeHeadSignature, decoded.
+void GetSampleSTHTreeHeadDecodedSignature(DigitallySigned& signature);
+
+// Certificate with embedded SCT in an X509v3 extension.
+Buffer GetDEREncodedTestEmbeddedCert();
+
+// For the above certificate, the corresponsing TBSCertificate without
+// the embedded SCT extension.
+Buffer GetDEREncodedTestTbsCert();
+
+// As above, but signed with an intermediate CA certificate containing
+// the CT extended key usage OID 1.3.6.1.4.1.11129.2.4.4 for issuing precerts
+// (i.e. signed with a "precert CA certificate").
+Buffer GetDEREncodedTestEmbeddedWithPreCACert();
+
+// The issuer of the above certificates (self-signed root CA certificate).
+Buffer GetDEREncodedCACert();
+
+// An intermediate CA certificate issued by the above CA.
+Buffer GetDEREncodedIntermediateCert();
+
+// Certificate with embedded SCT signed by the intermediate certificate above.
+Buffer GetDEREncodedTestEmbeddedWithIntermediateCert();
+
+// As above, but signed by the precert CA certificate.
+Buffer GetDEREncodedTestEmbeddedWithIntermediatePreCACert();
+
+// Given a DER-encoded certificate, returns its SubjectPublicKeyInfo.
+Buffer ExtractCertSPKI(pkix::Input cert);
+Buffer ExtractCertSPKI(const Buffer& cert);
+
+// Extracts a SignedCertificateTimestampList from the provided leaf certificate
+// (kept in X.509v3 extension with OID 1.3.6.1.4.1.11129.2.4.2).
+void ExtractEmbeddedSCTList(pkix::Input cert, Buffer& result);
+void ExtractEmbeddedSCTList(const Buffer& cert, Buffer& result);
+
+// Extracts a SignedCertificateTimestampList that has been embedded within
+// an OCSP response as an extension with the OID 1.3.6.1.4.1.11129.2.4.5.
+// The OCSP response is verified, and the verification must succeed for the
+// extension to be extracted.
+void ExtractSCTListFromOCSPResponse(pkix::Input cert, pkix::Input issuerSPKI,
+ pkix::Input encodedResponse,
+ pkix::Time time, Buffer& result);
+
+// Returns Input for the data stored in the buffer, failing assertion on error.
+pkix::Input InputForBuffer(const Buffer& buffer);
+
+// Returns Input for the data stored in the item, failing assertion on error.
+pkix::Input InputForSECItem(const SECItem& item);
+
+} // namespace ct
+} // namespace mozilla
+
+#endif // CTTestUtils_h
diff --git a/security/ct/tests/gtest/MultiLogCTVerifierTest.cpp b/security/ct/tests/gtest/MultiLogCTVerifierTest.cpp
new file mode 100644
index 0000000000..4ac3f42f28
--- /dev/null
+++ b/security/ct/tests/gtest/MultiLogCTVerifierTest.cpp
@@ -0,0 +1,254 @@
+/* -*- 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 Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MultiLogCTVerifier.h"
+
+#include <stdint.h>
+
+#include "CTLogVerifier.h"
+#include "CTObjectsExtractor.h"
+#include "CTSerialization.h"
+#include "CTTestUtils.h"
+#include "gtest/gtest.h"
+#include "nss.h"
+
+namespace mozilla {
+namespace ct {
+
+using namespace mozilla::pkix;
+
+class MultiLogCTVerifierTest : public ::testing::Test {
+ public:
+ MultiLogCTVerifierTest() : mNow(Time::uninitialized), mLogOperatorID(123) {}
+
+ void SetUp() override {
+ // Does nothing if NSS is already initialized.
+ if (NSS_NoDB_Init(nullptr) != SECSuccess) {
+ abort();
+ }
+
+ CTLogVerifier log;
+ ASSERT_EQ(Success,
+ log.Init(InputForBuffer(GetTestPublicKey()), mLogOperatorID,
+ CTLogStatus::Included, 0 /*disqualification time*/));
+ mVerifier.AddLog(std::move(log));
+
+ mTestCert = GetDEREncodedX509Cert();
+ mEmbeddedCert = GetDEREncodedTestEmbeddedCert();
+ mCaCert = GetDEREncodedCACert();
+ mCaCertSPKI = ExtractCertSPKI(mCaCert);
+ mIntermediateCert = GetDEREncodedIntermediateCert();
+ mIntermediateCertSPKI = ExtractCertSPKI(mIntermediateCert);
+
+ // Set the current time making sure all test timestamps are in the past.
+ mNow =
+ TimeFromEpochInSeconds(1451606400u); // Date.parse("2016-01-01")/1000
+ }
+
+ void CheckForSingleValidSCTInResult(const CTVerifyResult& result,
+ VerifiedSCT::Origin origin) {
+ EXPECT_EQ(0U, result.decodingErrors);
+ ASSERT_EQ(1U, result.verifiedScts.size());
+ EXPECT_EQ(VerifiedSCT::Status::Valid, result.verifiedScts[0].status);
+ EXPECT_EQ(origin, result.verifiedScts[0].origin);
+ EXPECT_EQ(mLogOperatorID, result.verifiedScts[0].logOperatorId);
+ }
+
+ // Writes an SCTList containing a single |sct| into |output|.
+ void EncodeSCTListForTesting(Input sct, Buffer& output) {
+ std::vector<Input> list;
+ list.push_back(std::move(sct));
+ ASSERT_EQ(Success, EncodeSCTList(list, output));
+ }
+
+ void GetSCTListWithInvalidLogID(Buffer& result) {
+ result.clear();
+ Buffer sct(GetTestSignedCertificateTimestamp());
+ // Change a byte inside the Log ID part of the SCT so it does
+ // not match the log used in the tests.
+ sct[15] ^= '\xFF';
+ EncodeSCTListForTesting(InputForBuffer(sct), result);
+ }
+
+ void CheckPrecertVerification(const Buffer& cert, const Buffer& issuerSPKI) {
+ Buffer sctList;
+ ExtractEmbeddedSCTList(cert, sctList);
+ ASSERT_FALSE(sctList.empty());
+
+ CTVerifyResult result;
+ ASSERT_EQ(Success,
+ mVerifier.Verify(InputForBuffer(cert), InputForBuffer(issuerSPKI),
+ InputForBuffer(sctList), Input(), Input(), mNow,
+ result));
+ CheckForSingleValidSCTInResult(result, VerifiedSCT::Origin::Embedded);
+ }
+
+ protected:
+ MultiLogCTVerifier mVerifier;
+ Buffer mTestCert;
+ Buffer mEmbeddedCert;
+ Buffer mCaCert;
+ Buffer mCaCertSPKI;
+ Buffer mIntermediateCert;
+ Buffer mIntermediateCertSPKI;
+ Time mNow;
+ CTLogOperatorId mLogOperatorID;
+};
+
+// Test that an embedded SCT can be extracted and the extracted SCT contains
+// the expected data. This tests the ExtractEmbeddedSCTList function from
+// CTTestUtils.h that other tests here rely upon.
+TEST_F(MultiLogCTVerifierTest, ExtractEmbeddedSCT) {
+ SignedCertificateTimestamp sct;
+
+ // Extract the embedded SCT.
+
+ Buffer sctList;
+ ExtractEmbeddedSCTList(mEmbeddedCert, sctList);
+ ASSERT_FALSE(sctList.empty());
+
+ Reader sctReader;
+ ASSERT_EQ(Success, DecodeSCTList(InputForBuffer(sctList), sctReader));
+ Input sctItemInput;
+ ASSERT_EQ(Success, ReadSCTListItem(sctReader, sctItemInput));
+ EXPECT_TRUE(sctReader.AtEnd()); // we only expect one sct in the list
+
+ Reader sctItemReader(sctItemInput);
+ ASSERT_EQ(Success, DecodeSignedCertificateTimestamp(sctItemReader, sct));
+
+ // Make sure the SCT contains the expected data.
+
+ EXPECT_EQ(SignedCertificateTimestamp::Version::V1, sct.version);
+ EXPECT_EQ(GetTestPublicKeyId(), sct.logId);
+
+ uint64_t expectedTimestamp = 1365181456275;
+ EXPECT_EQ(expectedTimestamp, sct.timestamp);
+}
+
+TEST_F(MultiLogCTVerifierTest, VerifiesEmbeddedSCT) {
+ CheckPrecertVerification(mEmbeddedCert, mCaCertSPKI);
+}
+
+TEST_F(MultiLogCTVerifierTest, VerifiesEmbeddedSCTWithPreCA) {
+ CheckPrecertVerification(GetDEREncodedTestEmbeddedWithPreCACert(),
+ mCaCertSPKI);
+}
+
+TEST_F(MultiLogCTVerifierTest, VerifiesEmbeddedSCTWithIntermediate) {
+ CheckPrecertVerification(GetDEREncodedTestEmbeddedWithIntermediateCert(),
+ mIntermediateCertSPKI);
+}
+
+TEST_F(MultiLogCTVerifierTest, VerifiesEmbeddedSCTWithIntermediateAndPreCA) {
+ CheckPrecertVerification(GetDEREncodedTestEmbeddedWithIntermediatePreCACert(),
+ mIntermediateCertSPKI);
+}
+
+TEST_F(MultiLogCTVerifierTest, VerifiesSCTFromOCSP) {
+ Buffer sct(GetTestSignedCertificateTimestamp());
+ Buffer sctList;
+ EncodeSCTListForTesting(InputForBuffer(sct), sctList);
+
+ CTVerifyResult result;
+ ASSERT_EQ(Success,
+ mVerifier.Verify(InputForBuffer(mTestCert), Input(), Input(),
+ InputForBuffer(sctList), Input(), mNow, result));
+
+ CheckForSingleValidSCTInResult(result, VerifiedSCT::Origin::OCSPResponse);
+}
+
+TEST_F(MultiLogCTVerifierTest, VerifiesSCTFromTLS) {
+ Buffer sct(GetTestSignedCertificateTimestamp());
+ Buffer sctList;
+ EncodeSCTListForTesting(InputForBuffer(sct), sctList);
+
+ CTVerifyResult result;
+ ASSERT_EQ(Success,
+ mVerifier.Verify(InputForBuffer(mTestCert), Input(), Input(),
+ Input(), InputForBuffer(sctList), mNow, result));
+
+ CheckForSingleValidSCTInResult(result, VerifiedSCT::Origin::TLSExtension);
+}
+
+TEST_F(MultiLogCTVerifierTest, VerifiesSCTFromMultipleSources) {
+ Buffer sct(GetTestSignedCertificateTimestamp());
+ Buffer sctList;
+ EncodeSCTListForTesting(InputForBuffer(sct), sctList);
+
+ CTVerifyResult result;
+ ASSERT_EQ(Success, mVerifier.Verify(InputForBuffer(mTestCert), Input(),
+ Input(), InputForBuffer(sctList),
+ InputForBuffer(sctList), mNow, result));
+
+ // The result should contain verified SCTs from TLS and OCSP origins.
+ size_t embeddedCount = 0;
+ size_t tlsExtensionCount = 0;
+ size_t ocspResponseCount = 0;
+ for (const VerifiedSCT& verifiedSct : result.verifiedScts) {
+ EXPECT_EQ(VerifiedSCT::Status::Valid, verifiedSct.status);
+ switch (verifiedSct.origin) {
+ case VerifiedSCT::Origin::Embedded:
+ embeddedCount++;
+ break;
+ case VerifiedSCT::Origin::TLSExtension:
+ tlsExtensionCount++;
+ break;
+ case VerifiedSCT::Origin::OCSPResponse:
+ ocspResponseCount++;
+ break;
+ case VerifiedSCT::Origin::Unknown:
+ default:
+ ASSERT_TRUE(false);
+ }
+ }
+ EXPECT_EQ(embeddedCount, 0u);
+ EXPECT_TRUE(tlsExtensionCount > 0);
+ EXPECT_TRUE(ocspResponseCount > 0);
+}
+
+TEST_F(MultiLogCTVerifierTest, IdentifiesSCTFromUnknownLog) {
+ Buffer sctList;
+ GetSCTListWithInvalidLogID(sctList);
+
+ CTVerifyResult result;
+ ASSERT_EQ(Success,
+ mVerifier.Verify(InputForBuffer(mTestCert), Input(), Input(),
+ Input(), InputForBuffer(sctList), mNow, result));
+
+ EXPECT_EQ(0U, result.decodingErrors);
+ ASSERT_EQ(1U, result.verifiedScts.size());
+ EXPECT_EQ(VerifiedSCT::Status::UnknownLog, result.verifiedScts[0].status);
+}
+
+TEST_F(MultiLogCTVerifierTest, IdentifiesSCTFromDisqualifiedLog) {
+ MultiLogCTVerifier verifier;
+ CTLogVerifier log;
+ const uint64_t disqualificationTime = 12345u;
+ ASSERT_EQ(Success,
+ log.Init(InputForBuffer(GetTestPublicKey()), mLogOperatorID,
+ CTLogStatus::Disqualified, disqualificationTime));
+ verifier.AddLog(std::move(log));
+
+ Buffer sct(GetTestSignedCertificateTimestamp());
+ Buffer sctList;
+ EncodeSCTListForTesting(InputForBuffer(sct), sctList);
+
+ CTVerifyResult result;
+ ASSERT_EQ(Success,
+ verifier.Verify(InputForBuffer(mTestCert), Input(), Input(),
+ Input(), InputForBuffer(sctList), mNow, result));
+
+ EXPECT_EQ(0U, result.decodingErrors);
+ ASSERT_EQ(1U, result.verifiedScts.size());
+ EXPECT_EQ(VerifiedSCT::Status::ValidFromDisqualifiedLog,
+ result.verifiedScts[0].status);
+ EXPECT_EQ(disqualificationTime,
+ result.verifiedScts[0].logDisqualificationTime);
+ EXPECT_EQ(mLogOperatorID, result.verifiedScts[0].logOperatorId);
+}
+
+} // namespace ct
+} // namespace mozilla
diff --git a/security/ct/tests/gtest/createSTHTestData.py b/security/ct/tests/gtest/createSTHTestData.py
new file mode 100755
index 0000000000..ab61d4ba0e
--- /dev/null
+++ b/security/ct/tests/gtest/createSTHTestData.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+#
+# 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/.
+
+"""
+This utility is used by the build system to create test inputs for the
+signed tree head decoding and verification implementation. The format is
+generally lines of <key>:<value> pairs except for the to-be-signed
+section, which consists of one or more lines of hex bytes. Comments may
+appear at the end of lines and begin with '//'.
+The following keys are valid:
+signingKey: A pykey key identifier to use to sign the to-be-signed data.
+ Required.
+spki: A pykey key identifier to create an encoded SubjectPublicKeyInfo
+ to be included with the test data. The tests will use this spki to
+ validate the signature. Required.
+prefix: Hex bytes to include at the beginning of the signed tree head
+ data. This data is not covered by the signature (typically this
+ is used for the log_id field). Optional. Defaults to the empty
+ string.
+hash: The name of a hash algorithm to use when signing. Optional.
+ Defaults to 'sha256'.
+"""
+
+import binascii
+import os
+import sys
+
+from pyasn1.codec.der import encoder
+
+sys.path.append(
+ os.path.join(os.path.dirname(__file__), "..", "..", "..", "manager", "tools")
+)
+import pykey
+
+
+def sign(signingKey, hashAlgorithm, hexToSign):
+ """Given a pykey, the name of a hash function, and hex bytes to
+ sign, signs the data (as binary) and returns a hex string consisting
+ of the signature."""
+ # key.sign returns a hex string in the format "'<hex bytes>'H",
+ # so we have to strip off the "'"s and trailing 'H'
+ return signingKey.sign(binascii.unhexlify(hexToSign), "hash:%s" % hashAlgorithm)[
+ 1:-2
+ ]
+
+
+class Error(Exception):
+ """Base class for exceptions in this module."""
+
+ pass
+
+
+class UnknownParameterTypeError(Error):
+ """Base class for handling unexpected input in this module."""
+
+ def __init__(self, value):
+ super(Error, self).__init__()
+ self.value = value
+ self.category = "key"
+
+ def __str__(self):
+ return 'Unknown %s type "%s"' % (self.category, repr(self.value))
+
+
+class InputTooLongError(Error):
+ """Helper exception type for inputs that are too long."""
+
+ def __init__(self, length):
+ super(InputTooLongError, self).__init__()
+ self.length = length
+
+ def __str__(self):
+ return "Input too long: %s > 65535" % self.length
+
+
+def getTwoByteLenAsHex(callLenOnMe):
+ """Given something that len can be called on, returns a hex string
+ representing the two-byte length of the something, in network byte
+ order (the length must be less than or equal to 65535)."""
+ length = len(callLenOnMe)
+ if length > 65535:
+ raise InputTooLongError(length)
+ return bytes([length // 256, length % 256]).hex()
+
+
+def createSTH(configStream):
+ """Given a stream that will provide the specification for a signed
+ tree head (see the comment at the top of this file), creates the
+ corresponding signed tree head. Returns a string that can be
+ compiled as C/C++ that declares two const char*s kSTHHex and
+ kSPKIHex corresponding to the hex encoding of the signed tree head
+ and the hex encoding of the subject public key info from the
+ specification, respectively."""
+ toSign = ""
+ prefix = ""
+ hashAlgorithm = "sha256"
+ for line in configStream.readlines():
+ if ":" in line:
+ param = line.split(":")[0]
+ arg = line.split(":")[1].split("//")[0].strip()
+ if param == "signingKey":
+ signingKey = pykey.keyFromSpecification(arg)
+ elif param == "spki":
+ spki = pykey.keyFromSpecification(arg)
+ elif param == "prefix":
+ prefix = arg
+ elif param == "hash":
+ hashAlgorithm = arg
+ else:
+ raise UnknownParameterTypeError(param)
+ else:
+ toSign = toSign + line.split("//")[0].strip()
+ signature = sign(signingKey, hashAlgorithm, toSign)
+ lengthBytesHex = getTwoByteLenAsHex(binascii.unhexlify(signature))
+ sth = prefix + toSign + lengthBytesHex + signature
+ spkiHex = encoder.encode(spki.asSubjectPublicKeyInfo()).hex()
+ return 'const char* kSTHHex = "%s";\nconst char* kSPKIHex = "%s";\n' % (
+ sth,
+ spkiHex,
+ )
+
+
+def main(output, inputPath):
+ """Given a file-like output and the path to a signed tree head
+ specification (see the comment at the top of this file), reads the
+ specification, creates the signed tree head, and outputs test data
+ that can be included by a gtest corresponding to the
+ specification."""
+ with open(inputPath) as configStream:
+ output.write(createSTH(configStream))
diff --git a/security/ct/tests/gtest/ec-signer-rsa-spki-sth.inc.tbs b/security/ct/tests/gtest/ec-signer-rsa-spki-sth.inc.tbs
new file mode 100644
index 0000000000..a8593e27c4
--- /dev/null
+++ b/security/ct/tests/gtest/ec-signer-rsa-spki-sth.inc.tbs
@@ -0,0 +1,8 @@
+signingKey:secp256r1
+spki:default // this is the default 2048-bit RSA key
+prefix:020000
+00000166d6142b50 // 1541189938000 milliseconds since the epoch
+0000000000000007 // 7 total nodes
+20 // 32 byte hash
+ d1a0d3947db4ae8305f2ac32985957e02659b2ea3c10da52a48d2526e9af3bbc
+0000 // no extensions
diff --git a/security/ct/tests/gtest/missing-extensions-sth.inc.tbs b/security/ct/tests/gtest/missing-extensions-sth.inc.tbs
new file mode 100644
index 0000000000..98807b854d
--- /dev/null
+++ b/security/ct/tests/gtest/missing-extensions-sth.inc.tbs
@@ -0,0 +1,8 @@
+signingKey:secp256r1
+spki:secp256r1
+prefix:020000
+00000166d6142b50 // 1541189938000 milliseconds since the epoch
+0000000000000007 // 7 total nodes
+20 // 32 byte hash
+ d1a0d3947db4ae8305f2ac32985957e02659b2ea3c10da52a48d2526e9af3bbc
+// missing extensions
diff --git a/security/ct/tests/gtest/missing-log-id-sth.inc.tbs b/security/ct/tests/gtest/missing-log-id-sth.inc.tbs
new file mode 100644
index 0000000000..6c1bb21997
--- /dev/null
+++ b/security/ct/tests/gtest/missing-log-id-sth.inc.tbs
@@ -0,0 +1,8 @@
+signingKey:secp256r1
+spki:secp256r1
+// missing logid
+00000166d6142b50 // 1541189938000 milliseconds since the epoch
+0000000000000007 // 7 total nodes
+20 // 32 byte hash
+ d1a0d3947db4ae8305f2ac32985957e02659b2ea3c10da52a48d2526e9af3bbc
+0000 // no extensions
diff --git a/security/ct/tests/gtest/missing-root-hash-sth.inc.tbs b/security/ct/tests/gtest/missing-root-hash-sth.inc.tbs
new file mode 100644
index 0000000000..5accf70fe8
--- /dev/null
+++ b/security/ct/tests/gtest/missing-root-hash-sth.inc.tbs
@@ -0,0 +1,7 @@
+signingKey:secp256r1
+spki:secp256r1
+prefix:020000
+00000166d6142b50 // 1541189938000 milliseconds since the epoch
+0000000000000007 // 7 total nodes
+// missing root_hash
+0000 // no extensions
diff --git a/security/ct/tests/gtest/missing-timestamp-sth.inc.tbs b/security/ct/tests/gtest/missing-timestamp-sth.inc.tbs
new file mode 100644
index 0000000000..5e7ebb6cf2
--- /dev/null
+++ b/security/ct/tests/gtest/missing-timestamp-sth.inc.tbs
@@ -0,0 +1,8 @@
+signingKey:secp256r1
+spki:secp256r1
+prefix:020000
+// missing timestamp
+0000000000000007 // 7 total nodes
+20 // 32 byte hash
+ d1a0d3947db4ae8305f2ac32985957e02659b2ea3c10da52a48d2526e9af3bbc
+0000 // no extensions
diff --git a/security/ct/tests/gtest/missing-tree-size-sth.inc.tbs b/security/ct/tests/gtest/missing-tree-size-sth.inc.tbs
new file mode 100644
index 0000000000..128e6d64b1
--- /dev/null
+++ b/security/ct/tests/gtest/missing-tree-size-sth.inc.tbs
@@ -0,0 +1,8 @@
+signingKey:secp256r1
+spki:secp256r1
+prefix:020000
+00000166d6142b50 // 1541189938000 milliseconds since the epoch
+// missing tree_size
+20 // 32 byte hash
+ d1a0d3947db4ae8305f2ac32985957e02659b2ea3c10da52a48d2526e9af3bbc
+0000 // no extensions
diff --git a/security/ct/tests/gtest/moz.build b/security/ct/tests/gtest/moz.build
new file mode 100644
index 0000000000..4c0ed38557
--- /dev/null
+++ b/security/ct/tests/gtest/moz.build
@@ -0,0 +1,56 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+
+@template
+def STHTestFile(name):
+ if not CONFIG["COMPILE_ENVIRONMENT"]:
+ return
+
+ GeneratedFile(name, script="createSTHTestData.py", inputs=["%s.tbs" % name])
+
+
+STHTestFile("valid-sth.inc")
+STHTestFile("valid-with-extension-sth.inc")
+STHTestFile("valid-secp521r1-sha512-sth.inc")
+STHTestFile("signature-covers-log-id-sth.inc")
+STHTestFile("wrong-spki-sth.inc")
+STHTestFile("wrong-signing-key-sth.inc")
+STHTestFile("missing-log-id-sth.inc")
+STHTestFile("missing-timestamp-sth.inc")
+STHTestFile("missing-tree-size-sth.inc")
+STHTestFile("missing-root-hash-sth.inc")
+STHTestFile("missing-extensions-sth.inc")
+STHTestFile("truncated-log-id-sth.inc")
+STHTestFile("truncated-timestamp-sth.inc")
+STHTestFile("truncated-tree-size-sth.inc")
+STHTestFile("truncated-root-hash-sth.inc")
+STHTestFile("truncated-extension-sth.inc")
+STHTestFile("rsa-signer-rsa-spki-sth.inc")
+STHTestFile("rsa-signer-ec-spki-sth.inc")
+STHTestFile("ec-signer-rsa-spki-sth.inc")
+
+UNIFIED_SOURCES += [
+ "BTSerializationTest.cpp",
+ "BTSignedTreeHeadTest.cpp",
+ "BTVerificationTest.cpp",
+ "CTDiversityPolicyTest.cpp",
+ "CTLogVerifierTest.cpp",
+ "CTObjectsExtractorTest.cpp",
+ "CTPolicyEnforcerTest.cpp",
+ "CTSerializationTest.cpp",
+ "CTTestUtils.cpp",
+ "MultiLogCTVerifierTest.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "../..",
+]
+
+if not CONFIG["MOZ_DEBUG"]:
+ DEFINES["NDEBUG"] = True
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/security/ct/tests/gtest/rsa-signer-ec-spki-sth.inc.tbs b/security/ct/tests/gtest/rsa-signer-ec-spki-sth.inc.tbs
new file mode 100644
index 0000000000..92f1a47490
--- /dev/null
+++ b/security/ct/tests/gtest/rsa-signer-ec-spki-sth.inc.tbs
@@ -0,0 +1,8 @@
+signingKey:default // this is the default 2048-bit RSA key
+spki:secp256r1
+prefix:020000
+00000166d6142b50 // 1541189938000 milliseconds since the epoch
+0000000000000007 // 7 total nodes
+20 // 32 byte hash
+ d1a0d3947db4ae8305f2ac32985957e02659b2ea3c10da52a48d2526e9af3bbc
+0000 // no extensions
diff --git a/security/ct/tests/gtest/rsa-signer-rsa-spki-sth.inc.tbs b/security/ct/tests/gtest/rsa-signer-rsa-spki-sth.inc.tbs
new file mode 100644
index 0000000000..b412c87e12
--- /dev/null
+++ b/security/ct/tests/gtest/rsa-signer-rsa-spki-sth.inc.tbs
@@ -0,0 +1,8 @@
+signingKey:default // this is the default 2048-bit RSA key
+spki:default
+prefix:020000
+00000166d6142b50 // 1541189938000 milliseconds since the epoch
+0000000000000007 // 7 total nodes
+20 // 32 byte hash
+ d1a0d3947db4ae8305f2ac32985957e02659b2ea3c10da52a48d2526e9af3bbc
+0000 // no extensions
diff --git a/security/ct/tests/gtest/signature-covers-log-id-sth.inc.tbs b/security/ct/tests/gtest/signature-covers-log-id-sth.inc.tbs
new file mode 100644
index 0000000000..723ee7a978
--- /dev/null
+++ b/security/ct/tests/gtest/signature-covers-log-id-sth.inc.tbs
@@ -0,0 +1,8 @@
+signingKey:secp256r1
+spki:secp256r1
+020000 // This is the log id. It should not be covered by the signature, but it is in this case.
+00000166d6142b50 // 1541189938000 milliseconds since the epoch
+0000000000000007 // 7 total nodes
+20 // 32 byte hash
+ d1a0d3947db4ae8305f2ac32985957e02659b2ea3c10da52a48d2526e9af3bbc
+0000 // no extensions
diff --git a/security/ct/tests/gtest/truncated-extension-sth.inc.tbs b/security/ct/tests/gtest/truncated-extension-sth.inc.tbs
new file mode 100644
index 0000000000..0fe30a58da
--- /dev/null
+++ b/security/ct/tests/gtest/truncated-extension-sth.inc.tbs
@@ -0,0 +1,11 @@
+signingKey:secp256r1
+spki:secp256r1
+prefix:020000
+00000166d6142b50 // 1541189938000 milliseconds since the epoch
+0000000000000007 // 7 total nodes
+20 // 32 byte hash
+ d1a0d3947db4ae8305f2ac32985957e02659b2ea3c10da52a48d2526e9af3bbc
+0014 // 20 bytes of extensions
+ ffff // extension type ffff
+ 0010 // 16 bytes of data
+ ffffffffff // truncated
diff --git a/security/ct/tests/gtest/truncated-log-id-sth.inc.tbs b/security/ct/tests/gtest/truncated-log-id-sth.inc.tbs
new file mode 100644
index 0000000000..267976af44
--- /dev/null
+++ b/security/ct/tests/gtest/truncated-log-id-sth.inc.tbs
@@ -0,0 +1,8 @@
+signingKey:secp256r1
+spki:secp256r1
+prefix:0200 // truncated log_id
+00000166d6142b50 // 1541189938000 milliseconds since the epoch
+0000000000000007 // 7 total nodes
+20 // 32 byte hash
+ d1a0d3947db4ae8305f2ac32985957e02659b2ea3c10da52a48d2526e9af3bbc
+0000 // no extensions
diff --git a/security/ct/tests/gtest/truncated-root-hash-sth.inc.tbs b/security/ct/tests/gtest/truncated-root-hash-sth.inc.tbs
new file mode 100644
index 0000000000..295b069880
--- /dev/null
+++ b/security/ct/tests/gtest/truncated-root-hash-sth.inc.tbs
@@ -0,0 +1,8 @@
+signingKey:secp256r1
+spki:secp256r1
+prefix:020000
+00000166d6142b50 // 1541189938000 milliseconds since the epoch
+0000000000000007 // 7 total nodes
+20 // 32 byte hash
+ d1a0d3947db4ae8305f2ac32985957e0 // truncated
+0000 // no extensions
diff --git a/security/ct/tests/gtest/truncated-timestamp-sth.inc.tbs b/security/ct/tests/gtest/truncated-timestamp-sth.inc.tbs
new file mode 100644
index 0000000000..247bd4dddc
--- /dev/null
+++ b/security/ct/tests/gtest/truncated-timestamp-sth.inc.tbs
@@ -0,0 +1,8 @@
+signingKey:secp256r1
+spki:secp256r1
+prefix:020000
+00000166d6 // truncated timestamp
+0000000000000007 // 7 total nodes
+20 // 32 byte hash
+ d1a0d3947db4ae8305f2ac32985957e02659b2ea3c10da52a48d2526e9af3bbc
+0000 // no extensions
diff --git a/security/ct/tests/gtest/truncated-tree-size-sth.inc.tbs b/security/ct/tests/gtest/truncated-tree-size-sth.inc.tbs
new file mode 100644
index 0000000000..9e0a942827
--- /dev/null
+++ b/security/ct/tests/gtest/truncated-tree-size-sth.inc.tbs
@@ -0,0 +1,8 @@
+signingKey:secp256r1
+spki:secp256r1
+prefix:020000
+00000166d6142b50 // 1541189938000 milliseconds since the epoch
+000000 // truncate tree_size
+20 // 32 byte hash
+ d1a0d3947db4ae8305f2ac32985957e02659b2ea3c10da52a48d2526e9af3bbc
+0000 // no extensions
diff --git a/security/ct/tests/gtest/valid-secp521r1-sha512-sth.inc.tbs b/security/ct/tests/gtest/valid-secp521r1-sha512-sth.inc.tbs
new file mode 100644
index 0000000000..674adfec56
--- /dev/null
+++ b/security/ct/tests/gtest/valid-secp521r1-sha512-sth.inc.tbs
@@ -0,0 +1,10 @@
+signingKey:secp521r1
+spki:secp521r1
+hash:sha512
+prefix:020000
+000001670e7ca6e1 // 1542136309473 milliseconds since the epoch
+000000002b982da5 // 731393445 total nodes
+40 // 64 byte hash
+ 374d794a95cdcfd8b35993185fef9ba368f160d8daf432d08ba9f1ed1e5abe6c
+ c69291e0fa2fe0006a52570ef18c19def4e617c33ce52ef0a6e5fbe318cb0387
+0000 // no extensions
diff --git a/security/ct/tests/gtest/valid-sth.inc.tbs b/security/ct/tests/gtest/valid-sth.inc.tbs
new file mode 100644
index 0000000000..b1bb592e93
--- /dev/null
+++ b/security/ct/tests/gtest/valid-sth.inc.tbs
@@ -0,0 +1,8 @@
+signingKey:secp256r1
+spki:secp256r1
+prefix:020000
+00000166d6142b50 // 1541189938000 milliseconds since the epoch
+0000000000000007 // 7 total nodes
+20 // 32 byte hash
+ d1a0d3947db4ae8305f2ac32985957e02659b2ea3c10da52a48d2526e9af3bbc
+0000 // no extensions
diff --git a/security/ct/tests/gtest/valid-with-extension-sth.inc.tbs b/security/ct/tests/gtest/valid-with-extension-sth.inc.tbs
new file mode 100644
index 0000000000..62612de301
--- /dev/null
+++ b/security/ct/tests/gtest/valid-with-extension-sth.inc.tbs
@@ -0,0 +1,11 @@
+signingKey:secp256r1
+spki:secp256r1
+prefix:020000
+00000166d6142b50 // 1541189938000 milliseconds since the epoch
+0000000000000007 // 7 total nodes
+20 // 32 byte hash
+ d1a0d3947db4ae8305f2ac32985957e02659b2ea3c10da52a48d2526e9af3bbc
+0014 // 20 bytes of extensions
+ ffff // extension type ffff
+ 0010 // 16 bytes of data
+ ffffffffffffffffffffffffffffffff
diff --git a/security/ct/tests/gtest/wrong-signing-key-sth.inc.tbs b/security/ct/tests/gtest/wrong-signing-key-sth.inc.tbs
new file mode 100644
index 0000000000..b8a93bb39e
--- /dev/null
+++ b/security/ct/tests/gtest/wrong-signing-key-sth.inc.tbs
@@ -0,0 +1,8 @@
+signingKey:secp384r1
+spki:secp256r1
+prefix:020000
+00000166d6142b50 // 1541189938000 milliseconds since the epoch
+0000000000000007 // 7 total nodes
+20 // 32 byte hash
+ d1a0d3947db4ae8305f2ac32985957e02659b2ea3c10da52a48d2526e9af3bbc
+0000 // no extensions
diff --git a/security/ct/tests/gtest/wrong-spki-sth.inc.tbs b/security/ct/tests/gtest/wrong-spki-sth.inc.tbs
new file mode 100644
index 0000000000..c02282c904
--- /dev/null
+++ b/security/ct/tests/gtest/wrong-spki-sth.inc.tbs
@@ -0,0 +1,8 @@
+signingKey:secp256r1
+spki:secp384r1
+prefix:020000
+00000166d6142b50 // 1541189938000 milliseconds since the epoch
+0000000000000007 // 7 total nodes
+20 // 32 byte hash
+ d1a0d3947db4ae8305f2ac32985957e02659b2ea3c10da52a48d2526e9af3bbc
+0000 // no extensions