summaryrefslogtreecommitdiffstats
path: root/security/pkix/test
diff options
context:
space:
mode:
Diffstat (limited to 'security/pkix/test')
-rw-r--r--security/pkix/test/gtest/README.txt61
-rw-r--r--security/pkix/test/gtest/moz.build66
-rw-r--r--security/pkix/test/gtest/pkixbuild_tests.cpp584
-rw-r--r--security/pkix/test/gtest/pkixcert_extension_tests.cpp270
-rw-r--r--security/pkix/test/gtest/pkixcert_signature_algorithm_tests.cpp247
-rw-r--r--security/pkix/test/gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp711
-rw-r--r--security/pkix/test/gtest/pkixcheck_CheckIssuer_tests.cpp62
-rw-r--r--security/pkix/test/gtest/pkixcheck_CheckKeyUsage_tests.cpp284
-rw-r--r--security/pkix/test/gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp360
-rw-r--r--security/pkix/test/gtest/pkixcheck_CheckValidity_tests.cpp127
-rw-r--r--security/pkix/test/gtest/pkixcheck_ParseValidity_tests.cpp83
-rw-r--r--security/pkix/test/gtest/pkixcheck_TLSFeaturesSatisfiedInternal_tests.cpp114
-rw-r--r--security/pkix/test/gtest/pkixder_input_tests.cpp920
-rw-r--r--security/pkix/test/gtest/pkixder_pki_types_tests.cpp479
-rw-r--r--security/pkix/test/gtest/pkixder_universal_types_tests.cpp1220
-rw-r--r--security/pkix/test/gtest/pkixgtest.cpp46
-rw-r--r--security/pkix/test/gtest/pkixgtest.h255
-rw-r--r--security/pkix/test/gtest/pkixnames_tests.cpp2811
-rw-r--r--security/pkix/test/gtest/pkixocsp_CreateEncodedOCSPRequest_tests.cpp145
-rw-r--r--security/pkix/test/gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp1046
-rw-r--r--security/pkix/test/lib/moz.build39
-rw-r--r--security/pkix/test/lib/pkixtestalg.cpp210
-rw-r--r--security/pkix/test/lib/pkixtestnss.cpp309
-rw-r--r--security/pkix/test/lib/pkixtestutil.cpp1153
-rw-r--r--security/pkix/test/lib/pkixtestutil.h448
25 files changed, 12050 insertions, 0 deletions
diff --git a/security/pkix/test/gtest/README.txt b/security/pkix/test/gtest/README.txt
new file mode 100644
index 000000000..5d3484a21
--- /dev/null
+++ b/security/pkix/test/gtest/README.txt
@@ -0,0 +1,61 @@
+-------------
+Running Tests
+-------------
+
+Because of the rules below, you can run all the unit tests in this directory,
+and only these tests, with:
+
+ mach gtest "pkix*"
+
+You can run just the tests for functions defined in filename pkixfoo.cpp with:
+
+ mach gtest "pkixfoo*"
+
+If you run "mach gtest" then you'll end up running every gtest in Gecko.
+
+
+
+------------
+Naming Files
+------------
+
+Name files containing tests according to one of the following patterns:
+
+ * <filename>_tests.cpp
+ * <filename>_<Function>_tests.cpp
+ * <filename>_<category>_tests.cpp
+
+ <filename> is the name of the file containing the definitions of the
+ function(s) being tested by every test.
+ <Function> is the name of the function that is being tested by every
+ test.
+ <category> describes the group of related functions that are being
+ tested by every test.
+
+
+
+------------------------------------------------
+Always Use a Fixture Class: TEST_F(), not TEST()
+------------------------------------------------
+
+Many tests don't technically need a fixture, and so TEST() could technically
+be used to define the test. However, when you use TEST_F() instead of TEST(),
+the compiler will not allow you to make any typos in the test case name, but
+if you use TEST() then the name of the test case is not checked.
+
+See https://code.google.com/p/googletest/wiki/Primer#Test_Fixtures:_Using_the_Same_Data_Configuration_for_Multiple_Te
+to learn more about test fixtures.
+
+---------------
+Naming Fixtures
+---------------
+
+When all tests in a file use the same fixture, use the base name of the file
+without the "_tests" suffix as the name of the fixture class; e.g. tests in
+"pkixocsp.cpp" should use a fixture "class pkixocsp" by default.
+
+Sometimes tests in a file need separate fixtures. In this case, name the
+fixture class according to the pattern <fixture_base>_<fixture_suffix>, where
+<fixture_base> is the base name of the file without the "_tests" suffix, and
+<fixture_suffix> is a descriptive name for the fixture class, e.g.
+"class pkixocsp_DelegatedResponder".
diff --git a/security/pkix/test/gtest/moz.build b/security/pkix/test/gtest/moz.build
new file mode 100644
index 000000000..50d7ae966
--- /dev/null
+++ b/security/pkix/test/gtest/moz.build
@@ -0,0 +1,66 @@
+# -*- 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/.
+
+SOURCES += [
+ 'pkixbuild_tests.cpp',
+ 'pkixcert_extension_tests.cpp',
+ 'pkixcert_signature_algorithm_tests.cpp',
+ 'pkixcheck_CheckExtendedKeyUsage_tests.cpp',
+ 'pkixcheck_CheckIssuer_tests.cpp',
+ 'pkixcheck_CheckKeyUsage_tests.cpp',
+ 'pkixcheck_CheckSignatureAlgorithm_tests.cpp',
+ 'pkixcheck_CheckValidity_tests.cpp',
+ 'pkixcheck_ParseValidity_tests.cpp',
+ 'pkixcheck_TLSFeaturesSatisfiedInternal_tests.cpp',
+
+ # The naming conventions are described in ./README.txt.
+
+ 'pkixder_input_tests.cpp',
+ 'pkixder_pki_types_tests.cpp',
+ 'pkixder_universal_types_tests.cpp',
+ 'pkixgtest.cpp',
+ 'pkixnames_tests.cpp',
+ 'pkixocsp_CreateEncodedOCSPRequest_tests.cpp',
+ 'pkixocsp_VerifyEncodedOCSPResponse.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '../../include',
+ '../../lib',
+ '../lib',
+]
+
+FINAL_LIBRARY = 'xul-gtest'
+
+include('../../warnings.mozbuild')
+
+# These warnings are disabled in order to minimize the amount of boilerplate
+# required to implement tests, and/or because they originate in the GTest
+# framework in a way we cannot otherwise work around.
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += [
+ '-Wno-error=shadow',
+ '-Wno-old-style-cast',
+ ]
+ if CONFIG['CLANG_CXX']:
+ CXXFLAGS += [
+ '-Wno-exit-time-destructors',
+ '-Wno-global-constructors',
+ '-Wno-used-but-marked-unused',
+ ]
+elif CONFIG['_MSC_VER']:
+ CXXFLAGS += [
+ '-wd4350', # behavior change: 'std::_Wrap_alloc<std::allocator<_Ty>>::...
+ '-wd4275', # non dll-interface class used as base for dll-interface class
+ '-wd4548', # Expression before comma has no effect
+ '-wd4625', # copy constructor could not be generated.
+ '-wd4626', # assugment operator could not be generated.
+ '-wd4640', # construction of local static object is not thread safe.
+
+ # This is intended as a temporary hack to support building with VS2015.
+ # declaration of '*' hides class member
+ '-wd4458',
+ ]
diff --git a/security/pkix/test/gtest/pkixbuild_tests.cpp b/security/pkix/test/gtest/pkixbuild_tests.cpp
new file mode 100644
index 000000000..6ddd88823
--- /dev/null
+++ b/security/pkix/test/gtest/pkixbuild_tests.cpp
@@ -0,0 +1,584 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2013 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if defined(_MSC_VER) && _MSC_VER < 1900
+// When building with -D_HAS_EXCEPTIONS=0, MSVC's <xtree> header triggers
+// warning C4702: unreachable code.
+// https://connect.microsoft.com/VisualStudio/feedback/details/809962
+#pragma warning(push)
+#pragma warning(disable: 4702)
+#endif
+
+#include <map>
+#include <vector>
+
+#if defined(_MSC_VER) && _MSC_VER < 1900
+#pragma warning(pop)
+#endif
+
+#include "pkixder.h"
+#include "pkixgtest.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+static ByteString
+CreateCert(const char* issuerCN, // null means "empty name"
+ const char* subjectCN, // null means "empty name"
+ EndEntityOrCA endEntityOrCA,
+ /*optional modified*/ std::map<ByteString, ByteString>*
+ subjectDERToCertDER = nullptr,
+ /*optional*/ const ByteString* extension = nullptr)
+{
+ static long serialNumberValue = 0;
+ ++serialNumberValue;
+ ByteString serialNumber(CreateEncodedSerialNumber(serialNumberValue));
+ EXPECT_FALSE(ENCODING_FAILED(serialNumber));
+
+ ByteString issuerDER(issuerCN ? CNToDERName(issuerCN) : Name(ByteString()));
+ ByteString subjectDER(subjectCN ? CNToDERName(subjectCN) : Name(ByteString()));
+
+ std::vector<ByteString> extensions;
+ if (endEntityOrCA == EndEntityOrCA::MustBeCA) {
+ ByteString basicConstraints =
+ CreateEncodedBasicConstraints(true, nullptr, Critical::Yes);
+ EXPECT_FALSE(ENCODING_FAILED(basicConstraints));
+ extensions.push_back(basicConstraints);
+ }
+ if (extension) {
+ extensions.push_back(*extension);
+ }
+ extensions.push_back(ByteString()); // marks the end of the list
+
+ ScopedTestKeyPair reusedKey(CloneReusedKeyPair());
+ ByteString certDER(CreateEncodedCertificate(
+ v3, sha256WithRSAEncryption(), serialNumber, issuerDER,
+ oneDayBeforeNow, oneDayAfterNow, subjectDER,
+ *reusedKey, extensions.data(), *reusedKey,
+ sha256WithRSAEncryption()));
+ EXPECT_FALSE(ENCODING_FAILED(certDER));
+
+ if (subjectDERToCertDER) {
+ (*subjectDERToCertDER)[subjectDER] = certDER;
+ }
+
+ return certDER;
+}
+
+class TestTrustDomain final : public DefaultCryptoTrustDomain
+{
+public:
+ // The "cert chain tail" is a longish chain of certificates that is used by
+ // all of the tests here. We share this chain across all the tests in order
+ // to speed up the tests (generating keypairs for the certs is very slow).
+ bool SetUpCertChainTail()
+ {
+ static char const* const names[] = {
+ "CA1 (Root)", "CA2", "CA3", "CA4", "CA5", "CA6", "CA7"
+ };
+
+ for (size_t i = 0; i < MOZILLA_PKIX_ARRAY_LENGTH(names); ++i) {
+ const char* issuerName = i == 0 ? names[0] : names[i-1];
+ CreateCACert(issuerName, names[i]);
+ if (i == 0) {
+ rootCACertDER = leafCACertDER;
+ }
+ }
+
+ return true;
+ }
+
+ void CreateCACert(const char* issuerName, const char* subjectName)
+ {
+ leafCACertDER = CreateCert(issuerName, subjectName,
+ EndEntityOrCA::MustBeCA, &subjectDERToCertDER);
+ assert(!ENCODING_FAILED(leafCACertDER));
+ }
+
+ ByteString GetLeafCACertDER() const { return leafCACertDER; }
+
+private:
+ Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert,
+ /*out*/ TrustLevel& trustLevel) override
+ {
+ trustLevel = InputEqualsByteString(candidateCert, rootCACertDER)
+ ? TrustLevel::TrustAnchor
+ : TrustLevel::InheritsTrust;
+ return Success;
+ }
+
+ Result FindIssuer(Input encodedIssuerName, IssuerChecker& checker, Time)
+ override
+ {
+ ByteString subjectDER(InputToByteString(encodedIssuerName));
+ ByteString certDER(subjectDERToCertDER[subjectDER]);
+ Input derCert;
+ Result rv = derCert.Init(certDER.data(), certDER.length());
+ if (rv != Success) {
+ return rv;
+ }
+ bool keepGoing;
+ rv = checker.Check(derCert, nullptr/*additionalNameConstraints*/,
+ keepGoing);
+ if (rv != Success) {
+ return rv;
+ }
+ return Success;
+ }
+
+ Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+ /*optional*/ const Input*, /*optional*/ const Input*)
+ override
+ {
+ return Success;
+ }
+
+ Result IsChainValid(const DERArray&, Time) override
+ {
+ return Success;
+ }
+
+ std::map<ByteString, ByteString> subjectDERToCertDER;
+ ByteString leafCACertDER;
+ ByteString rootCACertDER;
+};
+
+class pkixbuild : public ::testing::Test
+{
+public:
+ static void SetUpTestCase()
+ {
+ if (!trustDomain.SetUpCertChainTail()) {
+ abort();
+ }
+ }
+
+protected:
+
+ static TestTrustDomain trustDomain;
+};
+
+/*static*/ TestTrustDomain pkixbuild::trustDomain;
+
+TEST_F(pkixbuild, MaxAcceptableCertChainLength)
+{
+ {
+ ByteString leafCACert(trustDomain.GetLeafCACertDER());
+ Input certDER;
+ ASSERT_EQ(Success, certDER.Init(leafCACert.data(), leafCACert.length()));
+ ASSERT_EQ(Success,
+ BuildCertChain(trustDomain, certDER, Now(),
+ EndEntityOrCA::MustBeCA,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+ }
+
+ {
+ ByteString certDER(CreateCert("CA7", "Direct End-Entity",
+ EndEntityOrCA::MustBeEndEntity));
+ ASSERT_FALSE(ENCODING_FAILED(certDER));
+ Input certDERInput;
+ ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length()));
+ ASSERT_EQ(Success,
+ BuildCertChain(trustDomain, certDERInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+ }
+}
+
+TEST_F(pkixbuild, BeyondMaxAcceptableCertChainLength)
+{
+ static char const* const caCertName = "CA Too Far";
+
+ trustDomain.CreateCACert("CA7", caCertName);
+
+ {
+ ByteString certDER(trustDomain.GetLeafCACertDER());
+ Input certDERInput;
+ ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length()));
+ ASSERT_EQ(Result::ERROR_UNKNOWN_ISSUER,
+ BuildCertChain(trustDomain, certDERInput, Now(),
+ EndEntityOrCA::MustBeCA,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+ }
+
+ {
+ ByteString certDER(CreateCert(caCertName, "End-Entity Too Far",
+ EndEntityOrCA::MustBeEndEntity));
+ ASSERT_FALSE(ENCODING_FAILED(certDER));
+ Input certDERInput;
+ ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length()));
+ ASSERT_EQ(Result::ERROR_UNKNOWN_ISSUER,
+ BuildCertChain(trustDomain, certDERInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+ }
+}
+
+// A TrustDomain that checks certificates against a given root certificate.
+// It is initialized with the DER encoding of a root certificate that
+// is treated as a trust anchor and is assumed to have issued all certificates
+// (i.e. FindIssuer always attempts to build the next step in the chain with
+// it).
+class SingleRootTrustDomain : public DefaultCryptoTrustDomain
+{
+public:
+ explicit SingleRootTrustDomain(ByteString rootDER)
+ : rootDER(rootDER)
+ {
+ }
+
+ // The CertPolicyId argument is unused because we don't care about EV.
+ Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert,
+ /*out*/ TrustLevel& trustLevel) override
+ {
+ Input rootCert;
+ Result rv = rootCert.Init(rootDER.data(), rootDER.length());
+ if (rv != Success) {
+ return rv;
+ }
+ if (InputsAreEqual(candidateCert, rootCert)) {
+ trustLevel = TrustLevel::TrustAnchor;
+ } else {
+ trustLevel = TrustLevel::InheritsTrust;
+ }
+ return Success;
+ }
+
+ Result FindIssuer(Input, IssuerChecker& checker, Time) override
+ {
+ // keepGoing is an out parameter from IssuerChecker.Check. It would tell us
+ // whether or not to continue attempting other potential issuers. We only
+ // know of one potential issuer, however, so we ignore it.
+ bool keepGoing;
+ Input rootCert;
+ Result rv = rootCert.Init(rootDER.data(), rootDER.length());
+ if (rv != Success) {
+ return rv;
+ }
+ return checker.Check(rootCert, nullptr, keepGoing);
+ }
+
+ Result IsChainValid(const DERArray&, Time) override
+ {
+ return Success;
+ }
+
+ Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+ /*optional*/ const Input*, /*optional*/ const Input*)
+ override
+ {
+ return Success;
+ }
+
+private:
+ ByteString rootDER;
+};
+
+// A TrustDomain that explicitly fails if CheckRevocation is called.
+class ExpiredCertTrustDomain final : public SingleRootTrustDomain
+{
+public:
+ explicit ExpiredCertTrustDomain(ByteString rootDER)
+ : SingleRootTrustDomain(rootDER)
+ {
+ }
+
+ Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+ /*optional*/ const Input*, /*optional*/ const Input*)
+ override
+ {
+ ADD_FAILURE();
+ return NotReached("CheckRevocation should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+};
+
+TEST_F(pkixbuild, NoRevocationCheckingForExpiredCert)
+{
+ const char* rootCN = "Root CA";
+ ByteString rootDER(CreateCert(rootCN, rootCN, EndEntityOrCA::MustBeCA,
+ nullptr));
+ EXPECT_FALSE(ENCODING_FAILED(rootDER));
+ ExpiredCertTrustDomain expiredCertTrustDomain(rootDER);
+
+ ByteString serialNumber(CreateEncodedSerialNumber(100));
+ EXPECT_FALSE(ENCODING_FAILED(serialNumber));
+ ByteString issuerDER(CNToDERName(rootCN));
+ ByteString subjectDER(CNToDERName("Expired End-Entity Cert"));
+ ScopedTestKeyPair reusedKey(CloneReusedKeyPair());
+ ByteString certDER(CreateEncodedCertificate(
+ v3, sha256WithRSAEncryption(),
+ serialNumber, issuerDER,
+ twoDaysBeforeNow,
+ oneDayBeforeNow,
+ subjectDER, *reusedKey, nullptr, *reusedKey,
+ sha256WithRSAEncryption()));
+ EXPECT_FALSE(ENCODING_FAILED(certDER));
+
+ Input cert;
+ ASSERT_EQ(Success, cert.Init(certDER.data(), certDER.length()));
+ ASSERT_EQ(Result::ERROR_EXPIRED_CERTIFICATE,
+ BuildCertChain(expiredCertTrustDomain, cert, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
+ nullptr));
+}
+
+class DSSTrustDomain final : public EverythingFailsByDefaultTrustDomain
+{
+public:
+ Result GetCertTrust(EndEntityOrCA, const CertPolicyId&,
+ Input, /*out*/ TrustLevel& trustLevel) override
+ {
+ trustLevel = TrustLevel::TrustAnchor;
+ return Success;
+ }
+};
+
+class pkixbuild_DSS : public ::testing::Test { };
+
+TEST_F(pkixbuild_DSS, DSSEndEntityKeyNotAccepted)
+{
+ DSSTrustDomain trustDomain;
+
+ ByteString serialNumber(CreateEncodedSerialNumber(1));
+ ASSERT_FALSE(ENCODING_FAILED(serialNumber));
+
+ ByteString subjectDER(CNToDERName("DSS"));
+ ASSERT_FALSE(ENCODING_FAILED(subjectDER));
+ ScopedTestKeyPair subjectKey(GenerateDSSKeyPair());
+ ASSERT_TRUE(subjectKey.get());
+
+ ByteString issuerDER(CNToDERName("RSA"));
+ ASSERT_FALSE(ENCODING_FAILED(issuerDER));
+ ScopedTestKeyPair issuerKey(CloneReusedKeyPair());
+ ASSERT_TRUE(issuerKey.get());
+
+ ByteString cert(CreateEncodedCertificate(v3, sha256WithRSAEncryption(),
+ serialNumber, issuerDER,
+ oneDayBeforeNow, oneDayAfterNow,
+ subjectDER, *subjectKey, nullptr,
+ *issuerKey, sha256WithRSAEncryption()));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ Input certDER;
+ ASSERT_EQ(Success, certDER.Init(cert.data(), cert.length()));
+
+ ASSERT_EQ(Result::ERROR_UNSUPPORTED_KEYALG,
+ BuildCertChain(trustDomain, certDER, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+}
+
+class IssuerNameCheckTrustDomain final : public DefaultCryptoTrustDomain
+{
+public:
+ IssuerNameCheckTrustDomain(const ByteString& issuer, bool expectedKeepGoing)
+ : issuer(issuer)
+ , expectedKeepGoing(expectedKeepGoing)
+ {
+ }
+
+ Result GetCertTrust(EndEntityOrCA endEntityOrCA, const CertPolicyId&, Input,
+ /*out*/ TrustLevel& trustLevel) override
+ {
+ trustLevel = endEntityOrCA == EndEntityOrCA::MustBeCA
+ ? TrustLevel::TrustAnchor
+ : TrustLevel::InheritsTrust;
+ return Success;
+ }
+
+ Result FindIssuer(Input, IssuerChecker& checker, Time) override
+ {
+ Input issuerInput;
+ EXPECT_EQ(Success, issuerInput.Init(issuer.data(), issuer.length()));
+ bool keepGoing;
+ EXPECT_EQ(Success,
+ checker.Check(issuerInput, nullptr /*additionalNameConstraints*/,
+ keepGoing));
+ EXPECT_EQ(expectedKeepGoing, keepGoing);
+ return Success;
+ }
+
+ Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+ /*optional*/ const Input*, /*optional*/ const Input*)
+ override
+ {
+ return Success;
+ }
+
+ Result IsChainValid(const DERArray&, Time) override
+ {
+ return Success;
+ }
+
+private:
+ const ByteString issuer;
+ const bool expectedKeepGoing;
+};
+
+struct IssuerNameCheckParams
+{
+ const char* subjectIssuerCN; // null means "empty name"
+ const char* issuerSubjectCN; // null means "empty name"
+ bool matches;
+ Result expectedError;
+};
+
+static const IssuerNameCheckParams ISSUER_NAME_CHECK_PARAMS[] =
+{
+ { "foo", "foo", true, Success },
+ { "foo", "bar", false, Result::ERROR_UNKNOWN_ISSUER },
+ { "f", "foo", false, Result::ERROR_UNKNOWN_ISSUER }, // prefix
+ { "foo", "f", false, Result::ERROR_UNKNOWN_ISSUER }, // prefix
+ { "foo", "Foo", false, Result::ERROR_UNKNOWN_ISSUER }, // case sensitive
+ { "", "", true, Success },
+ { nullptr, nullptr, false, Result::ERROR_EMPTY_ISSUER_NAME }, // empty issuer
+
+ // check that certificate-related errors are deferred and superseded by
+ // ERROR_UNKNOWN_ISSUER when a chain can't be built due to name mismatches
+ { "foo", nullptr, false, Result::ERROR_UNKNOWN_ISSUER },
+ { nullptr, "foo", false, Result::ERROR_UNKNOWN_ISSUER }
+};
+
+class pkixbuild_IssuerNameCheck
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<IssuerNameCheckParams>
+{
+};
+
+TEST_P(pkixbuild_IssuerNameCheck, MatchingName)
+{
+ const IssuerNameCheckParams& params(GetParam());
+
+ ByteString issuerCertDER(CreateCert(params.issuerSubjectCN,
+ params.issuerSubjectCN,
+ EndEntityOrCA::MustBeCA, nullptr));
+ ASSERT_FALSE(ENCODING_FAILED(issuerCertDER));
+
+ ByteString subjectCertDER(CreateCert(params.subjectIssuerCN, "end-entity",
+ EndEntityOrCA::MustBeEndEntity,
+ nullptr));
+ ASSERT_FALSE(ENCODING_FAILED(subjectCertDER));
+
+ Input subjectCertDERInput;
+ ASSERT_EQ(Success, subjectCertDERInput.Init(subjectCertDER.data(),
+ subjectCertDER.length()));
+
+ IssuerNameCheckTrustDomain trustDomain(issuerCertDER, !params.matches);
+ ASSERT_EQ(params.expectedError,
+ BuildCertChain(trustDomain, subjectCertDERInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+}
+
+INSTANTIATE_TEST_CASE_P(pkixbuild_IssuerNameCheck, pkixbuild_IssuerNameCheck,
+ testing::ValuesIn(ISSUER_NAME_CHECK_PARAMS));
+
+
+// Records the embedded SCT list extension for later examination.
+class EmbeddedSCTListTestTrustDomain final : public SingleRootTrustDomain
+{
+public:
+ explicit EmbeddedSCTListTestTrustDomain(ByteString rootDER)
+ : SingleRootTrustDomain(rootDER)
+ {
+ }
+
+ virtual void NoteAuxiliaryExtension(AuxiliaryExtension extension,
+ Input extensionData) override
+ {
+ if (extension == AuxiliaryExtension::EmbeddedSCTList) {
+ signedCertificateTimestamps = InputToByteString(extensionData);
+ } else {
+ ADD_FAILURE();
+ }
+ }
+
+ ByteString signedCertificateTimestamps;
+};
+
+TEST_F(pkixbuild, CertificateTransparencyExtension)
+{
+ // python security/pkix/tools/DottedOIDToCode.py --tlv
+ // id-embeddedSctList 1.3.6.1.4.1.11129.2.4.2
+ static const uint8_t tlv_id_embeddedSctList[] = {
+ 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x02
+ };
+ static const uint8_t dummySctList[] = {
+ 0x01, 0x02, 0x03, 0x04, 0x05
+ };
+
+ ByteString ctExtension = TLV(der::SEQUENCE,
+ BytesToByteString(tlv_id_embeddedSctList) +
+ Boolean(false) +
+ TLV(der::OCTET_STRING,
+ // SignedCertificateTimestampList structure is encoded as an OCTET STRING
+ // within the X.509v3 extension (see RFC 6962 section 3.3).
+ // pkix decodes it internally and returns the actual structure.
+ TLV(der::OCTET_STRING, BytesToByteString(dummySctList))));
+
+ const char* rootCN = "Root CA";
+ ByteString rootDER(CreateCert(rootCN, rootCN, EndEntityOrCA::MustBeCA));
+ ASSERT_FALSE(ENCODING_FAILED(rootDER));
+
+ ByteString certDER(CreateCert(rootCN, "Cert with SCT list",
+ EndEntityOrCA::MustBeEndEntity,
+ nullptr, /*subjectDERToCertDER*/
+ &ctExtension));
+ ASSERT_FALSE(ENCODING_FAILED(certDER));
+
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length()));
+
+ EmbeddedSCTListTestTrustDomain extTrustDomain(rootDER);
+ ASSERT_EQ(Success,
+ BuildCertChain(extTrustDomain, certInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::anyExtendedKeyUsage,
+ CertPolicyId::anyPolicy,
+ nullptr /*stapledOCSPResponse*/));
+ ASSERT_EQ(BytesToByteString(dummySctList),
+ extTrustDomain.signedCertificateTimestamps);
+}
diff --git a/security/pkix/test/gtest/pkixcert_extension_tests.cpp b/security/pkix/test/gtest/pkixcert_extension_tests.cpp
new file mode 100644
index 000000000..464643134
--- /dev/null
+++ b/security/pkix/test/gtest/pkixcert_extension_tests.cpp
@@ -0,0 +1,270 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2013 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pkixder.h"
+#include "pkixgtest.h"
+#include "pkixtestutil.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+// Creates a self-signed certificate with the given extension.
+static ByteString
+CreateCertWithExtensions(const char* subjectCN,
+ const ByteString* extensions)
+{
+ static long serialNumberValue = 0;
+ ++serialNumberValue;
+ ByteString serialNumber(CreateEncodedSerialNumber(serialNumberValue));
+ EXPECT_FALSE(ENCODING_FAILED(serialNumber));
+ ByteString issuerDER(CNToDERName(subjectCN));
+ EXPECT_FALSE(ENCODING_FAILED(issuerDER));
+ ByteString subjectDER(CNToDERName(subjectCN));
+ EXPECT_FALSE(ENCODING_FAILED(subjectDER));
+ ScopedTestKeyPair subjectKey(CloneReusedKeyPair());
+ return CreateEncodedCertificate(v3, sha256WithRSAEncryption(),
+ serialNumber, issuerDER,
+ oneDayBeforeNow, oneDayAfterNow,
+ subjectDER, *subjectKey, extensions,
+ *subjectKey,
+ sha256WithRSAEncryption());
+}
+
+// Creates a self-signed certificate with the given extension.
+static ByteString
+CreateCertWithOneExtension(const char* subjectStr, const ByteString& extension)
+{
+ const ByteString extensions[] = { extension, ByteString() };
+ return CreateCertWithExtensions(subjectStr, extensions);
+}
+
+class TrustEverythingTrustDomain final : public DefaultCryptoTrustDomain
+{
+private:
+ Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input,
+ /*out*/ TrustLevel& trustLevel) override
+ {
+ trustLevel = TrustLevel::TrustAnchor;
+ return Success;
+ }
+
+ Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+ /*optional*/ const Input*, /*optional*/ const Input*)
+ override
+ {
+ return Success;
+ }
+
+ Result IsChainValid(const DERArray&, Time) override
+ {
+ return Success;
+ }
+};
+
+// python DottedOIDToCode.py --tlv unknownExtensionOID 1.3.6.1.4.1.13769.666.666.666.1.500.9.3
+static const uint8_t tlv_unknownExtensionOID[] = {
+ 0x06, 0x12, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xeb, 0x49, 0x85, 0x1a, 0x85, 0x1a,
+ 0x85, 0x1a, 0x01, 0x83, 0x74, 0x09, 0x03
+};
+
+// python DottedOIDToCode.py --tlv id-pe-authorityInformationAccess 1.3.6.1.5.5.7.1.1
+static const uint8_t tlv_id_pe_authorityInformationAccess[] = {
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01
+};
+
+// python DottedOIDToCode.py --tlv wrongExtensionOID 1.3.6.6.1.5.5.7.1.1
+// (there is an extra "6" that shouldn't be in this OID)
+static const uint8_t tlv_wrongExtensionOID[] = {
+ 0x06, 0x09, 0x2b, 0x06, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01
+};
+
+// python DottedOIDToCode.py --tlv id-ce-unknown 2.5.29.55
+// (this is a made-up OID for testing "id-ce"-prefixed OIDs that mozilla::pkix
+// doesn't handle)
+static const uint8_t tlv_id_ce_unknown[] = {
+ 0x06, 0x03, 0x55, 0x1d, 0x37
+};
+
+// python DottedOIDToCode.py --tlv id-ce-inhibitAnyPolicy 2.5.29.54
+static const uint8_t tlv_id_ce_inhibitAnyPolicy[] = {
+ 0x06, 0x03, 0x55, 0x1d, 0x36
+};
+
+// python DottedOIDToCode.py --tlv id-pkix-ocsp-nocheck 1.3.6.1.5.5.7.48.1.5
+static const uint8_t tlv_id_pkix_ocsp_nocheck[] = {
+ 0x06, 0x09, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x05
+};
+
+struct ExtensionTestcase
+{
+ ByteString extension;
+ Result expectedResult;
+};
+
+static const ExtensionTestcase EXTENSION_TESTCASES[] =
+{
+ // Tests that a non-critical extension not in the id-ce or id-pe arcs (which
+ // is thus unknown to us) verifies successfully even if empty (extensions we
+ // know about aren't normally allowed to be empty).
+ { TLV(der::SEQUENCE,
+ BytesToByteString(tlv_unknownExtensionOID) +
+ TLV(der::OCTET_STRING, ByteString())),
+ Success
+ },
+
+ // Tests that a critical extension not in the id-ce or id-pe arcs (which is
+ // thus unknown to us) is detected and that verification fails with the
+ // appropriate error.
+ { TLV(der::SEQUENCE,
+ BytesToByteString(tlv_unknownExtensionOID) +
+ Boolean(true) +
+ TLV(der::OCTET_STRING, ByteString())),
+ Result::ERROR_UNKNOWN_CRITICAL_EXTENSION
+ },
+
+ // Tests that a id-pe-authorityInformationAccess critical extension
+ // is detected and that verification succeeds.
+ // XXX: According to RFC 5280 an AIA that consists of an empty sequence is
+ // not legal, but we accept it and that is not what we're testing here.
+ { TLV(der::SEQUENCE,
+ BytesToByteString(tlv_id_pe_authorityInformationAccess) +
+ Boolean(true) +
+ TLV(der::OCTET_STRING, TLV(der::SEQUENCE, ByteString()))),
+ Success
+ },
+
+ // Tests that an incorrect OID for id-pe-authorityInformationAccess
+ // (when marked critical) is detected and that verification fails.
+ // (Until bug 1020993 was fixed, this wrong value was used for
+ // id-pe-authorityInformationAccess.)
+ { TLV(der::SEQUENCE,
+ BytesToByteString(tlv_wrongExtensionOID) +
+ Boolean(true) +
+ TLV(der::OCTET_STRING, ByteString())),
+ Result::ERROR_UNKNOWN_CRITICAL_EXTENSION
+ },
+
+ // We know about some id-ce extensions (OID arc 2.5.29), but not all of them.
+ // Tests that an unknown id-ce extension is detected and that verification
+ // fails.
+ { TLV(der::SEQUENCE,
+ BytesToByteString(tlv_id_ce_unknown) +
+ Boolean(true) +
+ TLV(der::OCTET_STRING, ByteString())),
+ Result::ERROR_UNKNOWN_CRITICAL_EXTENSION
+ },
+
+ // Tests that a certificate with a known critical id-ce extension (in this
+ // case, OID 2.5.29.54, which is id-ce-inhibitAnyPolicy), verifies
+ // successfully.
+ { TLV(der::SEQUENCE,
+ BytesToByteString(tlv_id_ce_inhibitAnyPolicy) +
+ Boolean(true) +
+ TLV(der::OCTET_STRING, Integer(0))),
+ Success
+ },
+
+ // Tests that a certificate with the id-pkix-ocsp-nocheck extension (marked
+ // critical) verifies successfully.
+ // RFC 6960:
+ // ext-ocsp-nocheck EXTENSION ::= { SYNTAX NULL IDENTIFIED
+ // BY id-pkix-ocsp-nocheck }
+ { TLV(der::SEQUENCE,
+ BytesToByteString(tlv_id_pkix_ocsp_nocheck) +
+ Boolean(true) +
+ TLV(der::OCTET_STRING, TLV(der::NULLTag, ByteString()))),
+ Success
+ },
+
+ // Tests that a certificate with another representation of the
+ // id-pkix-ocsp-nocheck extension (marked critical) verifies successfully.
+ // According to http://comments.gmane.org/gmane.ietf.x509/30947,
+ // some code creates certificates where value of the extension is
+ // an empty OCTET STRING.
+ { TLV(der::SEQUENCE,
+ BytesToByteString(tlv_id_pkix_ocsp_nocheck) +
+ Boolean(true) +
+ TLV(der::OCTET_STRING, ByteString())),
+ Success
+ },
+};
+
+class pkixcert_extension
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<ExtensionTestcase>
+{
+protected:
+ static TrustEverythingTrustDomain trustDomain;
+};
+
+/*static*/ TrustEverythingTrustDomain pkixcert_extension::trustDomain;
+
+TEST_P(pkixcert_extension, ExtensionHandledProperly)
+{
+ const ExtensionTestcase& testcase(GetParam());
+ const char* cn = "Cert Extension Test";
+ ByteString cert(CreateCertWithOneExtension(cn, testcase.extension));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
+ ASSERT_EQ(testcase.expectedResult,
+ BuildCertChain(trustDomain, certInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::anyExtendedKeyUsage,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+}
+
+INSTANTIATE_TEST_CASE_P(pkixcert_extension,
+ pkixcert_extension,
+ testing::ValuesIn(EXTENSION_TESTCASES));
+
+// Two subjectAltNames must result in an error.
+TEST_F(pkixcert_extension, DuplicateSubjectAltName)
+{
+ // python DottedOIDToCode.py --tlv id-ce-subjectAltName 2.5.29.17
+ static const uint8_t tlv_id_ce_subjectAltName[] = {
+ 0x06, 0x03, 0x55, 0x1d, 0x11
+ };
+
+ ByteString subjectAltName(
+ TLV(der::SEQUENCE,
+ BytesToByteString(tlv_id_ce_subjectAltName) +
+ TLV(der::OCTET_STRING, TLV(der::SEQUENCE, DNSName("example.com")))));
+ static const ByteString extensions[] = { subjectAltName, subjectAltName,
+ ByteString() };
+ static const char* certCN = "Cert With Duplicate subjectAltName";
+ ByteString cert(CreateCertWithExtensions(certCN, extensions));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
+ ASSERT_EQ(Result::ERROR_EXTENSION_VALUE_INVALID,
+ BuildCertChain(trustDomain, certInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::anyExtendedKeyUsage,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+}
diff --git a/security/pkix/test/gtest/pkixcert_signature_algorithm_tests.cpp b/security/pkix/test/gtest/pkixcert_signature_algorithm_tests.cpp
new file mode 100644
index 000000000..924c2b053
--- /dev/null
+++ b/security/pkix/test/gtest/pkixcert_signature_algorithm_tests.cpp
@@ -0,0 +1,247 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+#include "pkixder.h"
+#include "pkixgtest.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+static ByteString
+CreateCert(const char* issuerCN,
+ const char* subjectCN,
+ EndEntityOrCA endEntityOrCA,
+ const TestSignatureAlgorithm& signatureAlgorithm,
+ /*out*/ ByteString& subjectDER)
+{
+ static long serialNumberValue = 0;
+ ++serialNumberValue;
+ ByteString serialNumber(CreateEncodedSerialNumber(serialNumberValue));
+ EXPECT_FALSE(ENCODING_FAILED(serialNumber));
+
+ ByteString issuerDER(CNToDERName(issuerCN));
+ EXPECT_FALSE(ENCODING_FAILED(issuerDER));
+ subjectDER = CNToDERName(subjectCN);
+ EXPECT_FALSE(ENCODING_FAILED(subjectDER));
+
+ ByteString extensions[2];
+ if (endEntityOrCA == EndEntityOrCA::MustBeCA) {
+ extensions[0] =
+ CreateEncodedBasicConstraints(true, nullptr, Critical::Yes);
+ EXPECT_FALSE(ENCODING_FAILED(extensions[0]));
+ }
+
+ ScopedTestKeyPair reusedKey(CloneReusedKeyPair());
+ ByteString certDER(CreateEncodedCertificate(v3, signatureAlgorithm,
+ serialNumber, issuerDER,
+ oneDayBeforeNow, oneDayAfterNow,
+ subjectDER, *reusedKey,
+ extensions, *reusedKey,
+ signatureAlgorithm));
+ EXPECT_FALSE(ENCODING_FAILED(certDER));
+ return certDER;
+}
+
+class AlgorithmTestsTrustDomain final : public DefaultCryptoTrustDomain
+{
+public:
+ AlgorithmTestsTrustDomain(const ByteString& rootDER,
+ const ByteString& rootSubjectDER,
+ /*optional*/ const ByteString& intDER,
+ /*optional*/ const ByteString& intSubjectDER)
+ : rootDER(rootDER)
+ , rootSubjectDER(rootSubjectDER)
+ , intDER(intDER)
+ , intSubjectDER(intSubjectDER)
+ {
+ }
+
+private:
+ Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert,
+ /*out*/ TrustLevel& trustLevel) override
+ {
+ if (InputEqualsByteString(candidateCert, rootDER)) {
+ trustLevel = TrustLevel::TrustAnchor;
+ } else {
+ trustLevel = TrustLevel::InheritsTrust;
+ }
+ return Success;
+ }
+
+ Result FindIssuer(Input encodedIssuerName, IssuerChecker& checker, Time)
+ override
+ {
+ ByteString* issuerDER = nullptr;
+ if (InputEqualsByteString(encodedIssuerName, rootSubjectDER)) {
+ issuerDER = &rootDER;
+ } else if (InputEqualsByteString(encodedIssuerName, intSubjectDER)) {
+ issuerDER = &intDER;
+ } else {
+ // FindIssuer just returns success if it can't find a potential issuer.
+ return Success;
+ }
+ Input issuerCert;
+ Result rv = issuerCert.Init(issuerDER->data(), issuerDER->length());
+ if (rv != Success) {
+ return rv;
+ }
+ bool keepGoing;
+ return checker.Check(issuerCert, nullptr, keepGoing);
+ }
+
+ Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+ const Input*, const Input*) override
+ {
+ return Success;
+ }
+
+ Result IsChainValid(const DERArray&, Time) override
+ {
+ return Success;
+ }
+
+ ByteString rootDER;
+ ByteString rootSubjectDER;
+ ByteString intDER;
+ ByteString intSubjectDER;
+};
+
+static const TestSignatureAlgorithm NO_INTERMEDIATE
+{
+ TestPublicKeyAlgorithm(ByteString()),
+ TestDigestAlgorithmID::MD2,
+ ByteString(),
+ false
+};
+
+struct ChainValidity final
+{
+ ChainValidity(const TestSignatureAlgorithm& endEntitySignatureAlgorithm,
+ const TestSignatureAlgorithm& optionalIntSignatureAlgorithm,
+ const TestSignatureAlgorithm& rootSignatureAlgorithm,
+ bool isValid)
+ : endEntitySignatureAlgorithm(endEntitySignatureAlgorithm)
+ , optionalIntermediateSignatureAlgorithm(optionalIntSignatureAlgorithm)
+ , rootSignatureAlgorithm(rootSignatureAlgorithm)
+ , isValid(isValid)
+ { }
+
+ // In general, a certificate is generated for each of these. However, if
+ // optionalIntermediateSignatureAlgorithm is NO_INTERMEDIATE, then only 2
+ // certificates are generated.
+ // The certificate generated for the given rootSignatureAlgorithm is the
+ // trust anchor.
+ TestSignatureAlgorithm endEntitySignatureAlgorithm;
+ TestSignatureAlgorithm optionalIntermediateSignatureAlgorithm;
+ TestSignatureAlgorithm rootSignatureAlgorithm;
+ bool isValid;
+};
+
+static const ChainValidity CHAIN_VALIDITY[] =
+{
+ // The trust anchor may have a signature with an unsupported signature
+ // algorithm.
+ ChainValidity(sha256WithRSAEncryption(),
+ NO_INTERMEDIATE,
+ md5WithRSAEncryption(),
+ true),
+ ChainValidity(sha256WithRSAEncryption(),
+ NO_INTERMEDIATE,
+ md2WithRSAEncryption(),
+ true),
+
+ // Certificates that are not trust anchors must not have a signature with an
+ // unsupported signature algorithm.
+ ChainValidity(md5WithRSAEncryption(),
+ NO_INTERMEDIATE,
+ sha256WithRSAEncryption(),
+ false),
+ ChainValidity(md2WithRSAEncryption(),
+ NO_INTERMEDIATE,
+ sha256WithRSAEncryption(),
+ false),
+ ChainValidity(md2WithRSAEncryption(),
+ NO_INTERMEDIATE,
+ md5WithRSAEncryption(),
+ false),
+ ChainValidity(sha256WithRSAEncryption(),
+ md5WithRSAEncryption(),
+ sha256WithRSAEncryption(),
+ false),
+ ChainValidity(sha256WithRSAEncryption(),
+ md2WithRSAEncryption(),
+ sha256WithRSAEncryption(),
+ false),
+ ChainValidity(sha256WithRSAEncryption(),
+ md2WithRSAEncryption(),
+ md5WithRSAEncryption(),
+ false),
+};
+
+class pkixcert_IsValidChainForAlgorithm
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<ChainValidity>
+{
+};
+
+TEST_P(pkixcert_IsValidChainForAlgorithm, IsValidChainForAlgorithm)
+{
+ const ChainValidity& chainValidity(GetParam());
+ const char* rootCN = "CN=Root";
+ ByteString rootSubjectDER;
+ ByteString rootEncoded(
+ CreateCert(rootCN, rootCN, EndEntityOrCA::MustBeCA,
+ chainValidity.rootSignatureAlgorithm, rootSubjectDER));
+ EXPECT_FALSE(ENCODING_FAILED(rootEncoded));
+ EXPECT_FALSE(ENCODING_FAILED(rootSubjectDER));
+
+ const char* issuerCN = rootCN;
+
+ const char* intermediateCN = "CN=Intermediate";
+ ByteString intermediateSubjectDER;
+ ByteString intermediateEncoded;
+
+ // If the the algorithmIdentifier is empty, then it's NO_INTERMEDIATE.
+ if (!chainValidity.optionalIntermediateSignatureAlgorithm
+ .algorithmIdentifier.empty()) {
+ intermediateEncoded =
+ CreateCert(rootCN, intermediateCN, EndEntityOrCA::MustBeCA,
+ chainValidity.optionalIntermediateSignatureAlgorithm,
+ intermediateSubjectDER);
+ EXPECT_FALSE(ENCODING_FAILED(intermediateEncoded));
+ EXPECT_FALSE(ENCODING_FAILED(intermediateSubjectDER));
+ issuerCN = intermediateCN;
+ }
+
+ AlgorithmTestsTrustDomain trustDomain(rootEncoded, rootSubjectDER,
+ intermediateEncoded,
+ intermediateSubjectDER);
+
+ const char* endEntityCN = "CN=End Entity";
+ ByteString endEntitySubjectDER;
+ ByteString endEntityEncoded(
+ CreateCert(issuerCN, endEntityCN, EndEntityOrCA::MustBeEndEntity,
+ chainValidity.endEntitySignatureAlgorithm,
+ endEntitySubjectDER));
+ EXPECT_FALSE(ENCODING_FAILED(endEntityEncoded));
+ EXPECT_FALSE(ENCODING_FAILED(endEntitySubjectDER));
+
+ Input endEntity;
+ ASSERT_EQ(Success, endEntity.Init(endEntityEncoded.data(),
+ endEntityEncoded.length()));
+ Result expectedResult = chainValidity.isValid
+ ? Success
+ : Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED;
+ ASSERT_EQ(expectedResult,
+ BuildCertChain(trustDomain, endEntity, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy, nullptr));
+}
+
+INSTANTIATE_TEST_CASE_P(pkixcert_IsValidChainForAlgorithm,
+ pkixcert_IsValidChainForAlgorithm,
+ testing::ValuesIn(CHAIN_VALIDITY));
diff --git a/security/pkix/test/gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp b/security/pkix/test/gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp
new file mode 100644
index 000000000..4bfbd2265
--- /dev/null
+++ b/security/pkix/test/gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp
@@ -0,0 +1,711 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2016 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pkixder.h"
+#include "pkixgtest.h"
+#include "pkixutil.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+namespace mozilla { namespace pkix {
+
+extern Result CheckExtendedKeyUsage(EndEntityOrCA endEntityOrCA,
+ const Input* encodedExtendedKeyUsage,
+ KeyPurposeId requiredEKU,
+ TrustDomain& trustDomain, Time notBefore);
+
+} } // namespace mozilla::pkix
+
+class pkixcheck_CheckExtendedKeyUsage : public ::testing::Test
+{
+protected:
+ DefaultCryptoTrustDomain mTrustDomain;
+};
+
+#define ASSERT_BAD(x) ASSERT_EQ(Result::ERROR_INADEQUATE_CERT_TYPE, x)
+
+// tlv_id_kp_OCSPSigning and tlv_id_kp_serverAuth are defined in pkixtestutil.h
+
+// python DottedOIDToCode.py --tlv id-kp-clientAuth 1.3.6.1.5.5.7.3.2
+static const uint8_t tlv_id_kp_clientAuth[] = {
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02
+};
+
+// python DottedOIDToCode.py --tlv id-kp-codeSigning 1.3.6.1.5.5.7.3.3
+static const uint8_t tlv_id_kp_codeSigning[] = {
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x03
+};
+
+// python DottedOIDToCode.py --tlv id_kp_emailProtection 1.3.6.1.5.5.7.3.4
+static const uint8_t tlv_id_kp_emailProtection[] = {
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x04
+};
+
+// python DottedOIDToCode.py --tlv id-Netscape-stepUp 2.16.840.1.113730.4.1
+static const uint8_t tlv_id_Netscape_stepUp[] = {
+ 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01
+};
+
+// python DottedOIDToCode.py --tlv unknownOID 1.3.6.1.4.1.13769.666.666.666.1.500.9.3
+static const uint8_t tlv_unknownOID[] = {
+ 0x06, 0x12, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xeb, 0x49, 0x85, 0x1a, 0x85, 0x1a,
+ 0x85, 0x1a, 0x01, 0x83, 0x74, 0x09, 0x03
+};
+
+// python DottedOIDToCode.py --tlv anyExtendedKeyUsage 2.5.29.37.0
+static const uint8_t tlv_anyExtendedKeyUsage[] = {
+ 0x06, 0x04, 0x55, 0x1d, 0x25, 0x00
+};
+
+TEST_F(pkixcheck_CheckExtendedKeyUsage, none)
+{
+ // The input Input is nullptr. This means the cert had no extended key usage
+ // extension. This is always valid except for when the certificate is an
+ // end-entity and the required usage is id-kp-OCSPSigning.
+
+ ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity,
+ nullptr,
+ KeyPurposeId::anyExtendedKeyUsage,
+ mTrustDomain, Now()));
+ ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr,
+ KeyPurposeId::anyExtendedKeyUsage,
+ mTrustDomain, Now()));
+ ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity,
+ nullptr,
+ KeyPurposeId::id_kp_serverAuth,
+ mTrustDomain, Now()));
+ ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr,
+ KeyPurposeId::id_kp_serverAuth,
+ mTrustDomain, Now()));
+ ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity,
+ nullptr,
+ KeyPurposeId::id_kp_clientAuth,
+ mTrustDomain, Now()));
+ ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr,
+ KeyPurposeId::id_kp_clientAuth,
+ mTrustDomain, Now()));
+ ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity,
+ nullptr,
+ KeyPurposeId::id_kp_codeSigning,
+ mTrustDomain, Now()));
+ ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr,
+ KeyPurposeId::id_kp_codeSigning,
+ mTrustDomain, Now()));
+ ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity,
+ nullptr,
+ KeyPurposeId::id_kp_emailProtection,
+ mTrustDomain, Now()));
+ ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr,
+ KeyPurposeId::id_kp_emailProtection,
+ mTrustDomain, Now()));
+ ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
+ KeyPurposeId::id_kp_OCSPSigning,
+ mTrustDomain, Now()));
+ ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr,
+ KeyPurposeId::id_kp_OCSPSigning,
+ mTrustDomain, Now()));
+}
+
+static const Input empty_null;
+
+TEST_F(pkixcheck_CheckExtendedKeyUsage, empty)
+{
+ // The input Input is empty. The cert has an empty extended key usage
+ // extension, which is syntactically invalid.
+ ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, &empty_null,
+ KeyPurposeId::id_kp_serverAuth,
+ mTrustDomain, Now()));
+ ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, &empty_null,
+ KeyPurposeId::id_kp_serverAuth,
+ mTrustDomain, Now()));
+
+ static const uint8_t dummy = 0x00;
+ Input empty_nonnull;
+ ASSERT_EQ(Success, empty_nonnull.Init(&dummy, 0));
+ ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, &empty_nonnull,
+ KeyPurposeId::id_kp_serverAuth,
+ mTrustDomain, Now()));
+ ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, &empty_nonnull,
+ KeyPurposeId::id_kp_serverAuth,
+ mTrustDomain, Now()));
+}
+
+struct EKUTestcase
+{
+ ByteString ekuSEQUENCE;
+ KeyPurposeId keyPurposeId;
+ Result expectedResultEndEntity;
+ Result expectedResultCA;
+};
+
+class CheckExtendedKeyUsageTest
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<EKUTestcase>
+{
+protected:
+ DefaultCryptoTrustDomain mTrustDomain;
+};
+
+TEST_P(CheckExtendedKeyUsageTest, EKUTestcase)
+{
+ const EKUTestcase& param(GetParam());
+ Input encodedEKU;
+ ASSERT_EQ(Success, encodedEKU.Init(param.ekuSEQUENCE.data(),
+ param.ekuSEQUENCE.length()));
+ ASSERT_EQ(param.expectedResultEndEntity,
+ CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, &encodedEKU,
+ param.keyPurposeId,
+ mTrustDomain, Now()));
+ ASSERT_EQ(param.expectedResultCA,
+ CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, &encodedEKU,
+ param.keyPurposeId,
+ mTrustDomain, Now()));
+}
+
+#define SINGLE_EKU_SUCCESS(oidBytes, keyPurposeId) \
+ { TLV(der::SEQUENCE, BytesToByteString(oidBytes)), keyPurposeId, \
+ Success, Success }
+#define SINGLE_EKU_SUCCESS_CA(oidBytes, keyPurposeId) \
+ { TLV(der::SEQUENCE, BytesToByteString(oidBytes)), keyPurposeId, \
+ Result::ERROR_INADEQUATE_CERT_TYPE, Success }
+#define SINGLE_EKU_FAILURE(oidBytes, keyPurposeId) \
+ { TLV(der::SEQUENCE, BytesToByteString(oidBytes)), keyPurposeId, \
+ Result::ERROR_INADEQUATE_CERT_TYPE, Result::ERROR_INADEQUATE_CERT_TYPE }
+#define DOUBLE_EKU_SUCCESS(oidBytes1, oidBytes2, keyPurposeId) \
+ { TLV(der::SEQUENCE, \
+ BytesToByteString(oidBytes1) + BytesToByteString(oidBytes2)), \
+ keyPurposeId, \
+ Success, Success }
+#define DOUBLE_EKU_SUCCESS_CA(oidBytes1, oidBytes2, keyPurposeId) \
+ { TLV(der::SEQUENCE, \
+ BytesToByteString(oidBytes1) + BytesToByteString(oidBytes2)), \
+ keyPurposeId, \
+ Result::ERROR_INADEQUATE_CERT_TYPE, Success }
+#define DOUBLE_EKU_FAILURE(oidBytes1, oidBytes2, keyPurposeId) \
+ { TLV(der::SEQUENCE, \
+ BytesToByteString(oidBytes1) + BytesToByteString(oidBytes2)), \
+ keyPurposeId, \
+ Result::ERROR_INADEQUATE_CERT_TYPE, Result::ERROR_INADEQUATE_CERT_TYPE }
+
+static const EKUTestcase EKU_TESTCASES[] =
+{
+ SINGLE_EKU_SUCCESS(tlv_id_kp_serverAuth, KeyPurposeId::anyExtendedKeyUsage),
+ SINGLE_EKU_SUCCESS(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_serverAuth),
+ SINGLE_EKU_FAILURE(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_clientAuth),
+ SINGLE_EKU_FAILURE(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_codeSigning),
+ SINGLE_EKU_FAILURE(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_emailProtection),
+ SINGLE_EKU_FAILURE(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_OCSPSigning),
+
+ SINGLE_EKU_SUCCESS(tlv_id_kp_clientAuth, KeyPurposeId::anyExtendedKeyUsage),
+ SINGLE_EKU_FAILURE(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_serverAuth),
+ SINGLE_EKU_SUCCESS(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_clientAuth),
+ SINGLE_EKU_FAILURE(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_codeSigning),
+ SINGLE_EKU_FAILURE(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_emailProtection),
+ SINGLE_EKU_FAILURE(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_OCSPSigning),
+
+ SINGLE_EKU_SUCCESS(tlv_id_kp_codeSigning, KeyPurposeId::anyExtendedKeyUsage),
+ SINGLE_EKU_FAILURE(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_serverAuth),
+ SINGLE_EKU_FAILURE(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_clientAuth),
+ SINGLE_EKU_SUCCESS(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_codeSigning),
+ SINGLE_EKU_FAILURE(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_emailProtection),
+ SINGLE_EKU_FAILURE(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_OCSPSigning),
+
+ SINGLE_EKU_SUCCESS(tlv_id_kp_emailProtection, KeyPurposeId::anyExtendedKeyUsage),
+ SINGLE_EKU_FAILURE(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_serverAuth),
+ SINGLE_EKU_FAILURE(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_clientAuth),
+ SINGLE_EKU_FAILURE(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_codeSigning),
+ SINGLE_EKU_SUCCESS(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_emailProtection),
+ SINGLE_EKU_FAILURE(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_OCSPSigning),
+
+ // For end-entities, if id-kp-OCSPSigning is present, no usage is allowed
+ // except OCSPSigning.
+ SINGLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage),
+ SINGLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth),
+ SINGLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth),
+ SINGLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning),
+ SINGLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection),
+ SINGLE_EKU_SUCCESS(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning),
+
+ SINGLE_EKU_SUCCESS(tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage),
+ // For compatibility, id-Netscape-stepUp is treated as equivalent to
+ // id-kp-serverAuth for CAs.
+ SINGLE_EKU_SUCCESS_CA(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth),
+ SINGLE_EKU_FAILURE(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth),
+ SINGLE_EKU_FAILURE(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning),
+ SINGLE_EKU_FAILURE(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection),
+ SINGLE_EKU_FAILURE(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning),
+
+ SINGLE_EKU_SUCCESS(tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage),
+ SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_serverAuth),
+ SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_clientAuth),
+ SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_codeSigning),
+ SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_emailProtection),
+ SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning),
+
+ SINGLE_EKU_SUCCESS(tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
+ SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
+ SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
+ SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
+ SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
+ SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_SUCCESS(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_SUCCESS_CA(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
+
+ DOUBLE_EKU_SUCCESS(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage),
+ DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth),
+ DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth),
+ DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning),
+ DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection),
+ DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning),
+};
+
+INSTANTIATE_TEST_CASE_P(pkixcheck_CheckExtendedKeyUsage,
+ CheckExtendedKeyUsageTest,
+ ::testing::ValuesIn(EKU_TESTCASES));
+
+struct EKUChainTestcase
+{
+ ByteString ekuExtensionEE;
+ ByteString ekuExtensionCA;
+ KeyPurposeId keyPurposeId;
+ Result expectedResult;
+};
+
+class CheckExtendedKeyUsageChainTest
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<EKUChainTestcase>
+{
+};
+
+static ByteString
+CreateCert(const char* issuerCN, const char* subjectCN,
+ EndEntityOrCA endEntityOrCA, ByteString encodedEKU)
+{
+ static long serialNumberValue = 0;
+ ++serialNumberValue;
+ ByteString serialNumber(CreateEncodedSerialNumber(serialNumberValue));
+ EXPECT_FALSE(ENCODING_FAILED(serialNumber));
+
+ ByteString issuerDER(CNToDERName(issuerCN));
+ ByteString subjectDER(CNToDERName(subjectCN));
+
+ ByteString extensions[3];
+ extensions[0] =
+ CreateEncodedBasicConstraints(endEntityOrCA == EndEntityOrCA::MustBeCA,
+ nullptr, Critical::Yes);
+ EXPECT_FALSE(ENCODING_FAILED(extensions[0]));
+ if (encodedEKU.length() > 0) {
+ extensions[1] = encodedEKU;
+ }
+
+ ScopedTestKeyPair reusedKey(CloneReusedKeyPair());
+ ByteString certDER(CreateEncodedCertificate(
+ v3, sha256WithRSAEncryption(), serialNumber, issuerDER,
+ oneDayBeforeNow, oneDayAfterNow, subjectDER,
+ *reusedKey, extensions, *reusedKey,
+ sha256WithRSAEncryption()));
+ EXPECT_FALSE(ENCODING_FAILED(certDER));
+
+ return certDER;
+}
+
+class EKUTrustDomain final : public DefaultCryptoTrustDomain
+{
+public:
+ explicit EKUTrustDomain(ByteString issuerCertDER)
+ : mIssuerCertDER(issuerCertDER)
+ {
+ }
+
+private:
+ Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert,
+ TrustLevel& trustLevel) override
+ {
+ trustLevel = InputEqualsByteString(candidateCert, mIssuerCertDER)
+ ? TrustLevel::TrustAnchor
+ : TrustLevel::InheritsTrust;
+ return Success;
+ }
+
+ Result FindIssuer(Input, IssuerChecker& checker, Time) override
+ {
+ Input derCert;
+ Result rv = derCert.Init(mIssuerCertDER.data(), mIssuerCertDER.length());
+ if (rv != Success) {
+ return rv;
+ }
+ bool keepGoing;
+ return checker.Check(derCert, nullptr, keepGoing);
+ }
+
+ Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+ const Input*, const Input*) override
+ {
+ return Success;
+ }
+
+ Result IsChainValid(const DERArray&, Time) override
+ {
+ return Success;
+ }
+
+ ByteString mIssuerCertDER;
+};
+
+TEST_P(CheckExtendedKeyUsageChainTest, EKUChainTestcase)
+{
+ const EKUChainTestcase& param(GetParam());
+ ByteString issuerCertDER(CreateCert("CA", "CA", EndEntityOrCA::MustBeCA,
+ param.ekuExtensionCA));
+ ByteString subjectCertDER(CreateCert("CA", "EE",
+ EndEntityOrCA::MustBeEndEntity,
+ param.ekuExtensionEE));
+
+ EKUTrustDomain trustDomain(issuerCertDER);
+
+ Input subjectCertDERInput;
+ ASSERT_EQ(Success, subjectCertDERInput.Init(subjectCertDER.data(),
+ subjectCertDER.length()));
+ ASSERT_EQ(param.expectedResult,
+ BuildCertChain(trustDomain, subjectCertDERInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ param.keyPurposeId,
+ CertPolicyId::anyPolicy,
+ nullptr));
+}
+
+// python DottedOIDToCode.py --tlv id-ce-extKeyUsage 2.5.29.37
+static const uint8_t tlv_id_ce_extKeyUsage[] = {
+ 0x06, 0x03, 0x55, 0x1d, 0x25
+};
+
+static inline ByteString
+CreateEKUExtension(ByteString ekuOIDs)
+{
+ return TLV(der::SEQUENCE,
+ BytesToByteString(tlv_id_ce_extKeyUsage) +
+ TLV(der::OCTET_STRING, TLV(der::SEQUENCE, ekuOIDs)));
+}
+
+static const EKUChainTestcase EKU_CHAIN_TESTCASES[] =
+{
+ {
+ // Both end-entity and CA have id-kp-serverAuth => should succeed
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
+ KeyPurposeId::id_kp_serverAuth,
+ Success
+ },
+ {
+ // CA has no EKU extension => should succeed
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
+ ByteString(),
+ KeyPurposeId::id_kp_serverAuth,
+ Success
+ },
+ {
+ // End-entity has no EKU extension => should succeed
+ ByteString(),
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
+ KeyPurposeId::id_kp_serverAuth,
+ Success
+ },
+ {
+ // No EKU extensions at all => should succeed
+ ByteString(),
+ ByteString(),
+ KeyPurposeId::id_kp_serverAuth,
+ Success
+ },
+ {
+ // CA has EKU without id-kp-serverAuth => should fail
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)),
+ KeyPurposeId::id_kp_serverAuth,
+ Result::ERROR_INADEQUATE_CERT_TYPE
+ },
+ {
+ // End-entity has EKU without id-kp-serverAuth => should fail
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)),
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
+ KeyPurposeId::id_kp_serverAuth,
+ Result::ERROR_INADEQUATE_CERT_TYPE
+ },
+ {
+ // Both end-entity and CA have EKU without id-kp-serverAuth => should fail
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)),
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)),
+ KeyPurposeId::id_kp_serverAuth,
+ Result::ERROR_INADEQUATE_CERT_TYPE
+ },
+ {
+ // End-entity has no EKU, CA doesn't have id-kp-serverAuth => should fail
+ ByteString(),
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)),
+ KeyPurposeId::id_kp_serverAuth,
+ Result::ERROR_INADEQUATE_CERT_TYPE
+ },
+ {
+ // End-entity doesn't have id-kp-serverAuth, CA has no EKU => should fail
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)),
+ ByteString(),
+ KeyPurposeId::id_kp_serverAuth,
+ Result::ERROR_INADEQUATE_CERT_TYPE
+ },
+ {
+ // CA has id-Netscape-stepUp => should succeed
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
+ CreateEKUExtension(BytesToByteString(tlv_id_Netscape_stepUp)),
+ KeyPurposeId::id_kp_serverAuth,
+ Success
+ },
+ {
+ // End-entity has id-Netscape-stepUp => should fail
+ CreateEKUExtension(BytesToByteString(tlv_id_Netscape_stepUp)),
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)),
+ KeyPurposeId::id_kp_serverAuth,
+ Result::ERROR_INADEQUATE_CERT_TYPE
+ },
+ {
+ // End-entity and CA have id-kp-serverAuth and id-kp-clientAuth => should
+ // succeed
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) +
+ BytesToByteString(tlv_id_kp_clientAuth)),
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) +
+ BytesToByteString(tlv_id_kp_clientAuth)),
+ KeyPurposeId::id_kp_serverAuth,
+ Success
+ },
+ {
+ // End-entity has id-kp-serverAuth and id-kp-OCSPSigning => should fail
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) +
+ BytesToByteString(tlv_id_kp_OCSPSigning)),
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) +
+ BytesToByteString(tlv_id_kp_clientAuth)),
+ KeyPurposeId::id_kp_serverAuth,
+ Result::ERROR_INADEQUATE_CERT_TYPE
+ },
+ {
+ // CA has id-kp-serverAuth and id-kp-OCSPSigning => should succeed
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) +
+ BytesToByteString(tlv_id_kp_clientAuth)),
+ CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) +
+ BytesToByteString(tlv_id_kp_OCSPSigning)),
+ KeyPurposeId::id_kp_serverAuth,
+ Success
+ },
+};
+
+INSTANTIATE_TEST_CASE_P(pkixcheck_CheckExtendedKeyUsage,
+ CheckExtendedKeyUsageChainTest,
+ ::testing::ValuesIn(EKU_CHAIN_TESTCASES));
diff --git a/security/pkix/test/gtest/pkixcheck_CheckIssuer_tests.cpp b/security/pkix/test/gtest/pkixcheck_CheckIssuer_tests.cpp
new file mode 100644
index 000000000..d7fcfb210
--- /dev/null
+++ b/security/pkix/test/gtest/pkixcheck_CheckIssuer_tests.cpp
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2016 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pkixcheck.h"
+#include "pkixgtest.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+class pkixcheck_CheckIssuer : public ::testing::Test { };
+
+static const uint8_t EMPTY_NAME_DATA[] = {
+ 0x30, 0x00 /* tag, length */
+};
+static const Input EMPTY_NAME(EMPTY_NAME_DATA);
+
+static const uint8_t VALID_NAME_DATA[] = {
+ /* From https://www.example.com/: C=US, O=DigiCert Inc, OU=www.digicert.com,
+ * CN=DigiCert SHA2 High Assurance Server CA */
+ 0x30, 0x70, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
+ 0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0A,
+ 0x13, 0x0C, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49,
+ 0x6E, 0x63, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0B, 0x13,
+ 0x10, 0x77, 0x77, 0x77, 0x2E, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72,
+ 0x74, 0x2E, 0x63, 0x6F, 0x6D, 0x31, 0x2F, 0x30, 0x2D, 0x06, 0x03, 0x55,
+ 0x04, 0x03, 0x13, 0x26, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74,
+ 0x20, 0x53, 0x48, 0x41, 0x32, 0x20, 0x48, 0x69, 0x67, 0x68, 0x20, 0x41,
+ 0x73, 0x73, 0x75, 0x72, 0x61, 0x6E, 0x63, 0x65, 0x20, 0x53, 0x65, 0x72,
+ 0x76, 0x65, 0x72, 0x20, 0x43, 0x41
+};
+static const Input VALID_NAME(VALID_NAME_DATA);
+
+TEST_F(pkixcheck_CheckIssuer, ValidIssuer)
+{
+ ASSERT_EQ(Success, CheckIssuer(VALID_NAME));
+}
+
+TEST_F(pkixcheck_CheckIssuer, EmptyIssuer)
+{
+ ASSERT_EQ(Result::ERROR_EMPTY_ISSUER_NAME, CheckIssuer(EMPTY_NAME));
+}
diff --git a/security/pkix/test/gtest/pkixcheck_CheckKeyUsage_tests.cpp b/security/pkix/test/gtest/pkixcheck_CheckKeyUsage_tests.cpp
new file mode 100644
index 000000000..136f8719a
--- /dev/null
+++ b/security/pkix/test/gtest/pkixcheck_CheckKeyUsage_tests.cpp
@@ -0,0 +1,284 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2013 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pkixgtest.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+namespace mozilla { namespace pkix {
+
+extern Result CheckKeyUsage(EndEntityOrCA endEntityOrCA,
+ const Input* encodedKeyUsage,
+ KeyUsage requiredKeyUsageIfPresent);
+
+} } // namespace mozilla::pkix
+
+class pkixcheck_CheckKeyUsage : public ::testing::Test { };
+
+#define ASSERT_BAD(x) ASSERT_EQ(Result::ERROR_INADEQUATE_KEY_USAGE, x)
+
+// Make it easy to define test data for the common, simplest cases.
+#define NAMED_SIMPLE_KU(name, unusedBits, bits) \
+ const uint8_t name##_bytes[4] = { \
+ 0x03/*BIT STRING*/, 0x02/*LENGTH=2*/, unusedBits, bits \
+ }; \
+ const Input name(name##_bytes);
+
+static const Input empty_null;
+
+// Note that keyCertSign is really the only interesting case for CA
+// certificates since we don't support cRLSign.
+
+TEST_F(pkixcheck_CheckKeyUsage, EE_none)
+{
+ // The input Input is nullptr. This means the cert had no keyUsage
+ // extension. This is always valid because no key usage in an end-entity
+ // means that there are no key usage restrictions.
+
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
+ KeyUsage::noParticularKeyUsageRequired));
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
+ KeyUsage::digitalSignature));
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
+ KeyUsage::nonRepudiation));
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
+ KeyUsage::keyEncipherment));
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
+ KeyUsage::dataEncipherment));
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr,
+ KeyUsage::keyAgreement));
+}
+
+TEST_F(pkixcheck_CheckKeyUsage, EE_empty)
+{
+ // The input Input is empty. The cert had an empty keyUsage extension,
+ // which is syntactically invalid.
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &empty_null,
+ KeyUsage::digitalSignature));
+ static const uint8_t dummy = 0x00;
+ Input empty_nonnull;
+ ASSERT_EQ(Success, empty_nonnull.Init(&dummy, 0));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &empty_nonnull,
+ KeyUsage::digitalSignature));
+}
+
+TEST_F(pkixcheck_CheckKeyUsage, CA_none)
+{
+ // A CA certificate does not have a KU extension.
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeCA, nullptr,
+ KeyUsage::keyCertSign));
+}
+
+TEST_F(pkixcheck_CheckKeyUsage, CA_empty)
+{
+ // A CA certificate has an empty KU extension.
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &empty_null,
+ KeyUsage::keyCertSign));
+ static const uint8_t dummy = 0x00;
+ Input empty_nonnull;
+ ASSERT_EQ(Success, empty_nonnull.Init(&dummy, 0));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &empty_nonnull,
+ KeyUsage::keyCertSign));
+}
+
+TEST_F(pkixcheck_CheckKeyUsage, maxUnusedBits)
+{
+ NAMED_SIMPLE_KU(encoded, 7, 0x80);
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &encoded,
+ KeyUsage::digitalSignature));
+}
+
+TEST_F(pkixcheck_CheckKeyUsage, tooManyUnusedBits)
+{
+ static uint8_t oneValueByteData[] = {
+ 0x03/*BIT STRING*/, 0x02/*LENGTH=2*/, 8/*unused bits*/, 0x80
+ };
+ static const Input oneValueByte(oneValueByteData);
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &oneValueByte,
+ KeyUsage::digitalSignature));
+
+ static uint8_t twoValueBytesData[] = {
+ 0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, 8/*unused bits*/, 0x01, 0x00
+ };
+ static const Input twoValueBytes(twoValueBytesData);
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &twoValueBytes,
+ KeyUsage::digitalSignature));
+}
+
+TEST_F(pkixcheck_CheckKeyUsage, NoValueBytes_NoPaddingBits)
+{
+ static const uint8_t DER_BYTES[] = {
+ 0x03/*BIT STRING*/, 0x01/*LENGTH=1*/, 0/*unused bits*/
+ };
+ static const Input DER(DER_BYTES);
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &DER,
+ KeyUsage::digitalSignature));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &DER,
+ KeyUsage::keyCertSign));
+}
+
+TEST_F(pkixcheck_CheckKeyUsage, NoValueBytes_7PaddingBits)
+{
+ static const uint8_t DER_BYTES[] = {
+ 0x03/*BIT STRING*/, 0x01/*LENGTH=1*/, 7/*unused bits*/
+ };
+ static const Input DER(DER_BYTES);
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &DER,
+ KeyUsage::digitalSignature));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &DER,
+ KeyUsage::keyCertSign));
+}
+
+void ASSERT_SimpleCase(uint8_t unusedBits, uint8_t bits, KeyUsage usage)
+{
+ // Test that only the right bit is accepted for the usage for both EE and CA
+ // certs.
+ NAMED_SIMPLE_KU(good, unusedBits, bits);
+ ASSERT_EQ(Success,
+ CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &good, usage));
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeCA, &good, usage));
+
+ // We use (~bits >> unusedBits) << unusedBits) instead of using the same
+ // calculation that is in CheckKeyUsage to validate that the calculation in
+ // CheckKeyUsage is correct.
+
+ // Test that none of the other non-padding bits are mistaken for the given
+ // key usage in the single-byte value case.
+ NAMED_SIMPLE_KU(notGood, unusedBits,
+ static_cast<uint8_t>((~bits >> unusedBits) << unusedBits));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &notGood, usage));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &notGood, usage));
+
+ // Test that none of the other non-padding bits are mistaken for the given
+ // key usage in the two-byte value case.
+ const uint8_t twoByteNotGoodData[] = {
+ 0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, unusedBits,
+ static_cast<uint8_t>(~bits),
+ static_cast<uint8_t>((0xFFu >> unusedBits) << unusedBits)
+ };
+ Input twoByteNotGood(twoByteNotGoodData);
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &twoByteNotGood,
+ usage));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &twoByteNotGood, usage));
+}
+
+TEST_F(pkixcheck_CheckKeyUsage, simpleCases)
+{
+ ASSERT_SimpleCase(7, 0x80, KeyUsage::digitalSignature);
+ ASSERT_SimpleCase(6, 0x40, KeyUsage::nonRepudiation);
+ ASSERT_SimpleCase(5, 0x20, KeyUsage::keyEncipherment);
+ ASSERT_SimpleCase(4, 0x10, KeyUsage::dataEncipherment);
+ ASSERT_SimpleCase(3, 0x08, KeyUsage::keyAgreement);
+}
+
+// Only CAs are allowed to assert keyCertSign.
+// End-entity certs may assert it along with other key usages if keyCertSign
+// isn't the required key usage. This is for compatibility.
+TEST_F(pkixcheck_CheckKeyUsage, keyCertSign)
+{
+ NAMED_SIMPLE_KU(good, 2, 0x04);
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &good,
+ KeyUsage::keyCertSign));
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeCA, &good,
+ KeyUsage::keyCertSign));
+
+ // Test that none of the other non-padding bits are mistaken for the given
+ // key usage in the one-byte value case.
+ NAMED_SIMPLE_KU(notGood, 2, 0xFB);
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &notGood,
+ KeyUsage::keyCertSign));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &notGood,
+ KeyUsage::keyCertSign));
+
+ // Test that none of the other non-padding bits are mistaken for the given
+ // key usage in the two-byte value case.
+ static uint8_t twoByteNotGoodData[] = {
+ 0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, 2/*unused bits*/, 0xFBu, 0xFCu
+ };
+ static const Input twoByteNotGood(twoByteNotGoodData);
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &twoByteNotGood,
+ KeyUsage::keyCertSign));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &twoByteNotGood,
+ KeyUsage::keyCertSign));
+
+ // If an end-entity certificate does assert keyCertSign, this is allowed
+ // as long as that isn't the required key usage.
+ NAMED_SIMPLE_KU(digitalSignatureAndKeyCertSign, 2, 0x84);
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity,
+ &digitalSignatureAndKeyCertSign,
+ KeyUsage::digitalSignature));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity,
+ &digitalSignatureAndKeyCertSign,
+ KeyUsage::keyCertSign));
+}
+
+TEST_F(pkixcheck_CheckKeyUsage, unusedBitNotZero)
+{
+ // single byte control case
+ static uint8_t controlOneValueByteData[] = {
+ 0x03/*BIT STRING*/, 0x02/*LENGTH=2*/, 7/*unused bits*/, 0x80
+ };
+ static const Input controlOneValueByte(controlOneValueByteData);
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity,
+ &controlOneValueByte,
+ KeyUsage::digitalSignature));
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeCA,
+ &controlOneValueByte,
+ KeyUsage::digitalSignature));
+
+ // single-byte test case
+ static uint8_t oneValueByteData[] = {
+ 0x03/*BIT STRING*/, 0x02/*LENGTH=2*/, 7/*unused bits*/, 0x80 | 0x01
+ };
+ static const Input oneValueByte(oneValueByteData);
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &oneValueByte,
+ KeyUsage::digitalSignature));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &oneValueByte,
+ KeyUsage::digitalSignature));
+
+ // two-byte control case
+ static uint8_t controlTwoValueBytesData[] = {
+ 0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, 7/*unused bits*/,
+ 0x80 | 0x01, 0x80
+ };
+ static const Input controlTwoValueBytes(controlTwoValueBytesData);
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity,
+ &controlTwoValueBytes,
+ KeyUsage::digitalSignature));
+ ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeCA,
+ &controlTwoValueBytes,
+ KeyUsage::digitalSignature));
+
+ // two-byte test case
+ static uint8_t twoValueBytesData[] = {
+ 0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, 7/*unused bits*/,
+ 0x80 | 0x01, 0x80 | 0x01
+ };
+ static const Input twoValueBytes(twoValueBytesData);
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &twoValueBytes,
+ KeyUsage::digitalSignature));
+ ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &twoValueBytes,
+ KeyUsage::digitalSignature));
+}
diff --git a/security/pkix/test/gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp b/security/pkix/test/gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp
new file mode 100644
index 000000000..7ab9d095a
--- /dev/null
+++ b/security/pkix/test/gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp
@@ -0,0 +1,360 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2015 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pkixder.h"
+#include "pkixgtest.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+namespace mozilla { namespace pkix {
+
+extern Result CheckSignatureAlgorithm(
+ TrustDomain& trustDomain, EndEntityOrCA endEntityOrCA,
+ Time notBefore,
+ const der::SignedDataWithSignature& signedData,
+ Input signatureValue);
+
+} } // namespace mozilla::pkix
+
+struct CheckSignatureAlgorithmTestParams
+{
+ ByteString signatureAlgorithmValue;
+ ByteString signatureValue;
+ unsigned int signatureLengthInBytes;
+ Result expectedResult;
+};
+
+#define BS(s) ByteString(s, MOZILLA_PKIX_ARRAY_LENGTH(s))
+
+// python DottedOIDToCode.py --tlv sha256WithRSAEncryption 1.2.840.113549.1.1.11
+static const uint8_t tlv_sha256WithRSAEncryption[] = {
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b
+};
+
+// Same as tlv_sha256WithRSAEncryption, except one without the "0x0b" and with
+// the DER length decreased accordingly.
+static const uint8_t tlv_sha256WithRSAEncryption_truncated[] = {
+ 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01
+};
+
+// python DottedOIDToCode.py --tlv sha-1WithRSAEncryption 1.2.840.113549.1.1.5
+static const uint8_t tlv_sha_1WithRSAEncryption[] = {
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05
+};
+
+// python DottedOIDToCode.py --tlv sha1WithRSASignature 1.3.14.3.2.29
+static const uint8_t tlv_sha1WithRSASignature[] = {
+ 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1d
+};
+
+// python DottedOIDToCode.py --tlv md5WithRSAEncryption 1.2.840.113549.1.1.4
+static const uint8_t tlv_md5WithRSAEncryption[] = {
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x04
+};
+
+static const CheckSignatureAlgorithmTestParams
+ CHECKSIGNATUREALGORITHM_TEST_PARAMS[] =
+{
+ { // Both algorithm IDs are empty
+ ByteString(),
+ ByteString(),
+ 2048 / 8,
+ Result::ERROR_BAD_DER,
+ },
+ { // signatureAlgorithm is empty, signature is supported.
+ ByteString(),
+ BS(tlv_sha256WithRSAEncryption),
+ 2048 / 8,
+ Result::ERROR_BAD_DER,
+ },
+ { // signatureAlgorithm is supported, signature is empty.
+ BS(tlv_sha256WithRSAEncryption),
+ ByteString(),
+ 2048 / 8,
+ Result::ERROR_BAD_DER,
+ },
+ { // Algorithms match, both are supported.
+ BS(tlv_sha256WithRSAEncryption),
+ BS(tlv_sha256WithRSAEncryption),
+ 2048 / 8,
+ Success
+ },
+ { // Algorithms do not match because signatureAlgorithm is truncated.
+ BS(tlv_sha256WithRSAEncryption_truncated),
+ BS(tlv_sha256WithRSAEncryption),
+ 2048 / 8,
+ Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
+ },
+ { // Algorithms do not match because signature is truncated.
+ BS(tlv_sha256WithRSAEncryption),
+ BS(tlv_sha256WithRSAEncryption_truncated),
+ 2048 / 8,
+ Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
+ },
+ { // Algorithms do not match, both are supported.
+ BS(tlv_sha_1WithRSAEncryption),
+ BS(tlv_sha256WithRSAEncryption),
+ 2048 / 8,
+ Result::ERROR_SIGNATURE_ALGORITHM_MISMATCH,
+ },
+ { // Algorithms do not match, both are supported.
+ BS(tlv_sha256WithRSAEncryption),
+ BS(tlv_sha_1WithRSAEncryption),
+ 2048 / 8,
+ Result::ERROR_SIGNATURE_ALGORITHM_MISMATCH,
+ },
+ { // Algorithms match, both are unsupported.
+ BS(tlv_md5WithRSAEncryption),
+ BS(tlv_md5WithRSAEncryption),
+ 2048 / 8,
+ Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
+ },
+ { // signatureAlgorithm is unsupported, signature is supported.
+ BS(tlv_md5WithRSAEncryption),
+ BS(tlv_sha256WithRSAEncryption),
+ 2048 / 8,
+ Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
+ },
+ { // signatureAlgorithm is supported, signature is unsupported.
+ BS(tlv_sha256WithRSAEncryption),
+ BS(tlv_md5WithRSAEncryption),
+ 2048 / 8,
+ Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED
+ },
+ { // Both have the optional NULL parameter.
+ BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()),
+ BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()),
+ 2048 / 8,
+ Success
+ },
+ { // signatureAlgorithm has the optional NULL parameter, signature doesn't.
+ BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()),
+ BS(tlv_sha256WithRSAEncryption),
+ 2048 / 8,
+ Success
+ },
+ { // signatureAlgorithm does not have the optional NULL parameter, signature
+ // does.
+ BS(tlv_sha256WithRSAEncryption),
+ BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()),
+ 2048 / 8,
+ Success
+ },
+ { // The different OIDs for RSA-with-SHA1 we support are semantically
+ // equivalent.
+ BS(tlv_sha1WithRSASignature),
+ BS(tlv_sha_1WithRSAEncryption),
+ 2048 / 8,
+ Success,
+ },
+ { // The different OIDs for RSA-with-SHA1 we support are semantically
+ // equivalent (opposite order).
+ BS(tlv_sha_1WithRSAEncryption),
+ BS(tlv_sha1WithRSASignature),
+ 2048 / 8,
+ Success,
+ },
+ { // Algorithms match, both are supported, key size is not a multile of 128
+ // bits. This test verifies that we're not wrongly rounding up the
+ // signature size like we did in the original patch for bug 1131767.
+ BS(tlv_sha256WithRSAEncryption),
+ BS(tlv_sha256WithRSAEncryption),
+ (2048 / 8) - 1,
+ Success
+ },
+};
+
+class pkixcheck_CheckSignatureAlgorithm
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<CheckSignatureAlgorithmTestParams>
+{
+};
+
+class pkixcheck_CheckSignatureAlgorithm_TrustDomain final
+ : public EverythingFailsByDefaultTrustDomain
+{
+public:
+ explicit pkixcheck_CheckSignatureAlgorithm_TrustDomain(
+ unsigned int publicKeySizeInBits)
+ : publicKeySizeInBits(publicKeySizeInBits)
+ , checkedDigestAlgorithm(false)
+ , checkedModulusSizeInBits(false)
+ {
+ }
+
+ Result CheckSignatureDigestAlgorithm(DigestAlgorithm, EndEntityOrCA, Time)
+ override
+ {
+ checkedDigestAlgorithm = true;
+ return Success;
+ }
+
+ Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA endEntityOrCA,
+ unsigned int modulusSizeInBits)
+ override
+ {
+ EXPECT_EQ(EndEntityOrCA::MustBeEndEntity, endEntityOrCA);
+ EXPECT_EQ(publicKeySizeInBits, modulusSizeInBits);
+ checkedModulusSizeInBits = true;
+ return Success;
+ }
+
+ const unsigned int publicKeySizeInBits;
+ bool checkedDigestAlgorithm;
+ bool checkedModulusSizeInBits;
+};
+
+TEST_P(pkixcheck_CheckSignatureAlgorithm, CheckSignatureAlgorithm)
+{
+ const Time now(Now());
+ const CheckSignatureAlgorithmTestParams& params(GetParam());
+
+ Input signatureValueInput;
+ ASSERT_EQ(Success,
+ signatureValueInput.Init(params.signatureValue.data(),
+ params.signatureValue.length()));
+
+ pkixcheck_CheckSignatureAlgorithm_TrustDomain
+ trustDomain(params.signatureLengthInBytes * 8);
+
+ der::SignedDataWithSignature signedData;
+ ASSERT_EQ(Success,
+ signedData.algorithm.Init(params.signatureAlgorithmValue.data(),
+ params.signatureAlgorithmValue.length()));
+
+ ByteString dummySignature(params.signatureLengthInBytes, 0xDE);
+ ASSERT_EQ(Success,
+ signedData.signature.Init(dummySignature.data(),
+ dummySignature.length()));
+
+ ASSERT_EQ(params.expectedResult,
+ CheckSignatureAlgorithm(trustDomain, EndEntityOrCA::MustBeEndEntity,
+ now, signedData, signatureValueInput));
+ ASSERT_EQ(params.expectedResult == Success,
+ trustDomain.checkedDigestAlgorithm);
+ ASSERT_EQ(params.expectedResult == Success,
+ trustDomain.checkedModulusSizeInBits);
+}
+
+INSTANTIATE_TEST_CASE_P(
+ pkixcheck_CheckSignatureAlgorithm, pkixcheck_CheckSignatureAlgorithm,
+ testing::ValuesIn(CHECKSIGNATUREALGORITHM_TEST_PARAMS));
+
+class pkixcheck_CheckSignatureAlgorithm_BuildCertChain_TrustDomain
+ : public DefaultCryptoTrustDomain
+{
+public:
+ explicit pkixcheck_CheckSignatureAlgorithm_BuildCertChain_TrustDomain(
+ const ByteString& issuer)
+ : issuer(issuer)
+ {
+ }
+
+ Result GetCertTrust(EndEntityOrCA, const CertPolicyId&,
+ Input cert, /*out*/ TrustLevel& trustLevel) override
+ {
+ trustLevel = InputEqualsByteString(cert, issuer)
+ ? TrustLevel::TrustAnchor
+ : TrustLevel::InheritsTrust;
+ return Success;
+ }
+
+ Result FindIssuer(Input, IssuerChecker& checker, Time) override
+ {
+ EXPECT_FALSE(ENCODING_FAILED(issuer));
+
+ Input issuerInput;
+ EXPECT_EQ(Success, issuerInput.Init(issuer.data(), issuer.length()));
+
+ bool keepGoing;
+ EXPECT_EQ(Success, checker.Check(issuerInput, nullptr, keepGoing));
+ EXPECT_FALSE(keepGoing);
+
+ return Success;
+ }
+
+ Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+ /*optional*/ const Input*,
+ /*optional*/ const Input*) override
+ {
+ return Success;
+ }
+
+ Result IsChainValid(const DERArray&, Time) override
+ {
+ return Success;
+ }
+
+ ByteString issuer;
+};
+
+// Test that CheckSignatureAlgorithm actually gets called at some point when
+// BuildCertChain is called.
+TEST_F(pkixcheck_CheckSignatureAlgorithm, BuildCertChain)
+{
+ ScopedTestKeyPair keyPair(CloneReusedKeyPair());
+ ASSERT_TRUE(keyPair.get());
+
+ ByteString issuerExtensions[2];
+ issuerExtensions[0] = CreateEncodedBasicConstraints(true, nullptr,
+ Critical::No);
+ ASSERT_FALSE(ENCODING_FAILED(issuerExtensions[0]));
+
+ ByteString issuer(CreateEncodedCertificate(3,
+ sha256WithRSAEncryption(),
+ CreateEncodedSerialNumber(1),
+ CNToDERName("issuer"),
+ oneDayBeforeNow, oneDayAfterNow,
+ CNToDERName("issuer"),
+ *keyPair,
+ issuerExtensions,
+ *keyPair,
+ sha256WithRSAEncryption()));
+ ASSERT_FALSE(ENCODING_FAILED(issuer));
+
+ ByteString subject(CreateEncodedCertificate(3,
+ sha1WithRSAEncryption(),
+ CreateEncodedSerialNumber(2),
+ CNToDERName("issuer"),
+ oneDayBeforeNow, oneDayAfterNow,
+ CNToDERName("subject"),
+ *keyPair,
+ nullptr,
+ *keyPair,
+ sha256WithRSAEncryption()));
+ ASSERT_FALSE(ENCODING_FAILED(subject));
+
+ Input subjectInput;
+ ASSERT_EQ(Success, subjectInput.Init(subject.data(), subject.length()));
+ pkixcheck_CheckSignatureAlgorithm_BuildCertChain_TrustDomain
+ trustDomain(issuer);
+ Result rv = BuildCertChain(trustDomain, subjectInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::anyExtendedKeyUsage,
+ CertPolicyId::anyPolicy,
+ nullptr);
+ ASSERT_EQ(Result::ERROR_SIGNATURE_ALGORITHM_MISMATCH, rv);
+}
diff --git a/security/pkix/test/gtest/pkixcheck_CheckValidity_tests.cpp b/security/pkix/test/gtest/pkixcheck_CheckValidity_tests.cpp
new file mode 100644
index 000000000..a77a2f47c
--- /dev/null
+++ b/security/pkix/test/gtest/pkixcheck_CheckValidity_tests.cpp
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2014 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pkixcheck.h"
+#include "pkixgtest.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+static const Time PAST_TIME(YMDHMS(1998, 12, 31, 12, 23, 56));
+
+#define OLDER_GENERALIZEDTIME \
+ 0x18, 15, /* tag, length */ \
+ '1', '9', '9', '9', '0', '1', '0', '1', /* 1999-01-01 */ \
+ '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */
+
+#define OLDER_UTCTIME \
+ 0x17, 13, /* tag, length */ \
+ '9', '9', '0', '1', '0', '1', /* (19)99-01-01 */ \
+ '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */
+
+static const Time NOW(YMDHMS(2016, 12, 31, 12, 23, 56));
+
+#define NEWER_GENERALIZEDTIME \
+ 0x18, 15, /* tag, length */ \
+ '2', '0', '2', '1', '0', '1', '0', '1', /* 2021-01-01 */ \
+ '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */
+
+#define NEWER_UTCTIME \
+ 0x17, 13, /* tag, length */ \
+ '2', '1', '0', '1', '0', '1', /* 2021-01-01 */ \
+ '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */
+
+static const Time FUTURE_TIME(YMDHMS(2025, 12, 31, 12, 23, 56));
+
+class pkixcheck_CheckValidity : public ::testing::Test { };
+
+static const uint8_t OLDER_UTCTIME_NEWER_UTCTIME_DATA[] = {
+ OLDER_UTCTIME,
+ NEWER_UTCTIME,
+};
+static const Input
+OLDER_UTCTIME_NEWER_UTCTIME(OLDER_UTCTIME_NEWER_UTCTIME_DATA);
+
+TEST_F(pkixcheck_CheckValidity, Valid_UTCTIME_UTCTIME)
+{
+ static Time notBefore(Time::uninitialized);
+ static Time notAfter(Time::uninitialized);
+ ASSERT_EQ(Success, ParseValidity(OLDER_UTCTIME_NEWER_UTCTIME, &notBefore, &notAfter));
+ ASSERT_EQ(Success, CheckValidity(NOW, notBefore, notAfter));
+}
+
+TEST_F(pkixcheck_CheckValidity, Valid_GENERALIZEDTIME_GENERALIZEDTIME)
+{
+ static const uint8_t DER[] = {
+ OLDER_GENERALIZEDTIME,
+ NEWER_GENERALIZEDTIME,
+ };
+ static const Input validity(DER);
+ static Time notBefore(Time::uninitialized);
+ static Time notAfter(Time::uninitialized);
+ ASSERT_EQ(Success, ParseValidity(validity, &notBefore, &notAfter));
+ ASSERT_EQ(Success, CheckValidity(NOW, notBefore, notAfter));
+}
+
+TEST_F(pkixcheck_CheckValidity, Valid_GENERALIZEDTIME_UTCTIME)
+{
+ static const uint8_t DER[] = {
+ OLDER_GENERALIZEDTIME,
+ NEWER_UTCTIME,
+ };
+ static const Input validity(DER);
+ static Time notBefore(Time::uninitialized);
+ static Time notAfter(Time::uninitialized);
+ ASSERT_EQ(Success, ParseValidity(validity, &notBefore, &notAfter));
+ ASSERT_EQ(Success, CheckValidity(NOW, notBefore, notAfter));
+}
+
+TEST_F(pkixcheck_CheckValidity, Valid_UTCTIME_GENERALIZEDTIME)
+{
+ static const uint8_t DER[] = {
+ OLDER_UTCTIME,
+ NEWER_GENERALIZEDTIME,
+ };
+ static const Input validity(DER);
+ static Time notBefore(Time::uninitialized);
+ static Time notAfter(Time::uninitialized);
+ ASSERT_EQ(Success, ParseValidity(validity, &notBefore, &notAfter));
+ ASSERT_EQ(Success, CheckValidity(NOW, notBefore, notAfter));
+}
+
+TEST_F(pkixcheck_CheckValidity, InvalidBeforeNotBefore)
+{
+ static Time notBefore(Time::uninitialized);
+ static Time notAfter(Time::uninitialized);
+ ASSERT_EQ(Success, ParseValidity(OLDER_UTCTIME_NEWER_UTCTIME, &notBefore, &notAfter));
+ ASSERT_EQ(Result::ERROR_NOT_YET_VALID_CERTIFICATE, CheckValidity(PAST_TIME, notBefore, notAfter));
+}
+
+TEST_F(pkixcheck_CheckValidity, InvalidAfterNotAfter)
+{
+ static Time notBefore(Time::uninitialized);
+ static Time notAfter(Time::uninitialized);
+ ASSERT_EQ(Success, ParseValidity(OLDER_UTCTIME_NEWER_UTCTIME, &notBefore, &notAfter));
+ ASSERT_EQ(Result::ERROR_EXPIRED_CERTIFICATE, CheckValidity(FUTURE_TIME, notBefore, notAfter));
+}
diff --git a/security/pkix/test/gtest/pkixcheck_ParseValidity_tests.cpp b/security/pkix/test/gtest/pkixcheck_ParseValidity_tests.cpp
new file mode 100644
index 000000000..5206ce14f
--- /dev/null
+++ b/security/pkix/test/gtest/pkixcheck_ParseValidity_tests.cpp
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2014 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pkixcheck.h"
+#include "pkixgtest.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+#define OLDER_UTCTIME \
+ 0x17, 13, /* tag, length */ \
+ '9', '9', '0', '1', '0', '1', /* (19)99-01-01 */ \
+ '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */
+
+#define NEWER_UTCTIME \
+ 0x17, 13, /* tag, length */ \
+ '2', '1', '0', '1', '0', '1', /* 2021-01-01 */ \
+ '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */
+
+static const Time FUTURE_TIME(YMDHMS(2025, 12, 31, 12, 23, 56));
+
+class pkixcheck_ParseValidity : public ::testing::Test { };
+
+TEST_F(pkixcheck_ParseValidity, BothEmptyNull)
+{
+ static const uint8_t DER[] = {
+ 0x17/*UTCTime*/, 0/*length*/,
+ 0x17/*UTCTime*/, 0/*length*/,
+ };
+ static const Input validity(DER);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity));
+}
+
+TEST_F(pkixcheck_ParseValidity, NotBeforeEmptyNull)
+{
+ static const uint8_t DER[] = {
+ 0x17/*UTCTime*/, 0x00/*length*/,
+ NEWER_UTCTIME
+ };
+ static const Input validity(DER);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity));
+}
+
+TEST_F(pkixcheck_ParseValidity, NotAfterEmptyNull)
+{
+ static const uint8_t DER[] = {
+ NEWER_UTCTIME,
+ 0x17/*UTCTime*/, 0x00/*length*/,
+ };
+ static const Input validity(DER);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity));
+}
+
+TEST_F(pkixcheck_ParseValidity, InvalidNotAfterBeforeNotBefore)
+{
+ static const uint8_t DER[] = {
+ NEWER_UTCTIME,
+ OLDER_UTCTIME,
+ };
+ static const Input validity(DER);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity));
+}
diff --git a/security/pkix/test/gtest/pkixcheck_TLSFeaturesSatisfiedInternal_tests.cpp b/security/pkix/test/gtest/pkixcheck_TLSFeaturesSatisfiedInternal_tests.cpp
new file mode 100644
index 000000000..28db4d150
--- /dev/null
+++ b/security/pkix/test/gtest/pkixcheck_TLSFeaturesSatisfiedInternal_tests.cpp
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2015 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pkixder.h"
+#include "pkixgtest.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+namespace mozilla { namespace pkix {
+ extern Result TLSFeaturesSatisfiedInternal(const Input* requiredTLSFeatures,
+ const Input* stapledOCSPResponse);
+} } // namespace mozilla::pkix
+
+struct TLSFeaturesTestParams
+{
+ ByteString requiredTLSFeatures;
+ Result expectedResultWithResponse;
+ Result expectedResultWithoutResponse;
+};
+
+#define BS(s) ByteString(s, MOZILLA_PKIX_ARRAY_LENGTH(s))
+static const uint8_t statusRequest[] = {
+ 0x30, 0x03, 0x02, 0x01, 0x05
+};
+
+static const uint8_t unknown[] = {
+ 0x30, 0x03, 0x02, 0x01, 0x06
+};
+
+static const uint8_t statusRequestAndUnknown[] = {
+ 0x30, 0x06, 0x02, 0x01, 0x05, 0x02, 0x01, 0x06
+};
+
+static const uint8_t duplicateStatusRequest[] = {
+ 0x30, 0x06, 0x02, 0x01, 0x05, 0x02, 0x01, 0x05
+};
+
+static const uint8_t twoByteUnknown[] = {
+ 0x30, 0x04, 0x02, 0x02, 0x05, 0x05
+};
+
+static const uint8_t zeroByteInteger[] = {
+ 0x30, 0x02, 0x02, 0x00
+};
+
+static const TLSFeaturesTestParams
+ TLSFEATURESSATISFIED_TEST_PARAMS[] =
+{
+ // some tests with checks enforced
+ { ByteString(), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER },
+ { BS(statusRequest), Success, Result::ERROR_REQUIRED_TLS_FEATURE_MISSING },
+ { BS(unknown), Result::ERROR_REQUIRED_TLS_FEATURE_MISSING,
+ Result::ERROR_REQUIRED_TLS_FEATURE_MISSING },
+ { BS(statusRequestAndUnknown), Result::ERROR_REQUIRED_TLS_FEATURE_MISSING,
+ Result::ERROR_REQUIRED_TLS_FEATURE_MISSING },
+ { BS(duplicateStatusRequest), Success,
+ Result::ERROR_REQUIRED_TLS_FEATURE_MISSING },
+ { BS(twoByteUnknown), Result::ERROR_REQUIRED_TLS_FEATURE_MISSING,
+ Result::ERROR_REQUIRED_TLS_FEATURE_MISSING },
+ { BS(zeroByteInteger), Result::ERROR_REQUIRED_TLS_FEATURE_MISSING,
+ Result::ERROR_REQUIRED_TLS_FEATURE_MISSING },
+};
+
+class pkixcheck_TLSFeaturesSatisfiedInternal
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<TLSFeaturesTestParams>
+{
+};
+
+TEST_P(pkixcheck_TLSFeaturesSatisfiedInternal, TLSFeaturesSatisfiedInternal) {
+ const TLSFeaturesTestParams& params(GetParam());
+
+ Input featuresInput;
+ ASSERT_EQ(Success, featuresInput.Init(params.requiredTLSFeatures.data(),
+ params.requiredTLSFeatures.length()));
+ Input responseInput;
+ // just create an input with any data in it
+ ByteString stapledOCSPResponse = BS(statusRequest);
+ ASSERT_EQ(Success, responseInput.Init(stapledOCSPResponse.data(),
+ stapledOCSPResponse.length()));
+ // first we omit the response
+ ASSERT_EQ(params.expectedResultWithoutResponse,
+ TLSFeaturesSatisfiedInternal(&featuresInput, nullptr));
+ // then we try again with the response
+ ASSERT_EQ(params.expectedResultWithResponse,
+ TLSFeaturesSatisfiedInternal(&featuresInput, &responseInput));
+}
+
+INSTANTIATE_TEST_CASE_P(
+ pkixcheck_TLSFeaturesSatisfiedInternal,
+ pkixcheck_TLSFeaturesSatisfiedInternal,
+ testing::ValuesIn(TLSFEATURESSATISFIED_TEST_PARAMS));
diff --git a/security/pkix/test/gtest/pkixder_input_tests.cpp b/security/pkix/test/gtest/pkixder_input_tests.cpp
new file mode 100644
index 000000000..b0a6c8bf0
--- /dev/null
+++ b/security/pkix/test/gtest/pkixder_input_tests.cpp
@@ -0,0 +1,920 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2013 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <functional>
+#include <vector>
+#include "pkixgtest.h"
+
+#include "pkixder.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::der;
+
+namespace {
+
+class pkixder_input_tests : public ::testing::Test { };
+
+static const uint8_t DER_SEQUENCE_EMPTY[] = {
+ 0x30, // SEQUENCE
+ 0x00, // length
+};
+
+static const uint8_t DER_SEQUENCE_NOT_EMPTY[] = {
+ 0x30, // SEQUENCE
+ 0x01, // length
+ 'X', // value
+};
+
+static const uint8_t DER_SEQUENCE_NOT_EMPTY_VALUE[] = {
+ 'X', // value
+};
+
+static const uint8_t DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED[] = {
+ 0x30, // SEQUENCE
+ 0x01, // length
+};
+
+const uint8_t DER_SEQUENCE_OF_INT8[] = {
+ 0x30, // SEQUENCE
+ 0x09, // length
+ 0x02, 0x01, 0x01, // INTEGER length 1 value 0x01
+ 0x02, 0x01, 0x02, // INTEGER length 1 value 0x02
+ 0x02, 0x01, 0x03 // INTEGER length 1 value 0x03
+};
+
+const uint8_t DER_TRUNCATED_SEQUENCE_OF_INT8[] = {
+ 0x30, // SEQUENCE
+ 0x09, // length
+ 0x02, 0x01, 0x01, // INTEGER length 1 value 0x01
+ 0x02, 0x01, 0x02 // INTEGER length 1 value 0x02
+ // MISSING DATA HERE ON PURPOSE
+};
+
+const uint8_t DER_OVERRUN_SEQUENCE_OF_INT8[] = {
+ 0x30, // SEQUENCE
+ 0x09, // length
+ 0x02, 0x01, 0x01, // INTEGER length 1 value 0x01
+ 0x02, 0x01, 0x02, // INTEGER length 1 value 0x02
+ 0x02, 0x02, 0xFF, 0x03 // INTEGER length 2 value 0xFF03
+};
+
+const uint8_t DER_INT16[] = {
+ 0x02, // INTEGER
+ 0x02, // length
+ 0x12, 0x34 // 0x1234
+};
+
+static const Input EMPTY_INPUT;
+
+TEST_F(pkixder_input_tests, InputInit)
+{
+ Input buf;
+ ASSERT_EQ(Success,
+ buf.Init(DER_SEQUENCE_OF_INT8, sizeof DER_SEQUENCE_OF_INT8));
+}
+
+TEST_F(pkixder_input_tests, InputInitWithNullPointerOrZeroLength)
+{
+ Input buf;
+ ASSERT_EQ(Result::ERROR_BAD_DER, buf.Init(nullptr, 0));
+
+ ASSERT_EQ(Result::ERROR_BAD_DER, buf.Init(nullptr, 100));
+
+ // Though it seems odd to initialize with zero-length and non-null ptr, this
+ // is working as intended. The Reader class was intended to protect against
+ // buffer overflows, and there's no risk with the current behavior. See bug
+ // 1000354.
+ ASSERT_EQ(Success, buf.Init((const uint8_t*) "hello", 0));
+ ASSERT_TRUE(buf.GetLength() == 0);
+}
+
+TEST_F(pkixder_input_tests, InputInitWithLargeData)
+{
+ Input buf;
+ // Data argument length does not matter, it is not touched, just
+ // needs to be non-null
+ ASSERT_EQ(Result::ERROR_BAD_DER, buf.Init((const uint8_t*) "", 0xffff+1));
+
+ ASSERT_EQ(Success, buf.Init((const uint8_t*) "", 0xffff));
+}
+
+TEST_F(pkixder_input_tests, InputInitMultipleTimes)
+{
+ Input buf;
+
+ ASSERT_EQ(Success,
+ buf.Init(DER_SEQUENCE_OF_INT8, sizeof DER_SEQUENCE_OF_INT8));
+
+ ASSERT_EQ(Result::FATAL_ERROR_INVALID_ARGS,
+ buf.Init(DER_SEQUENCE_OF_INT8, sizeof DER_SEQUENCE_OF_INT8));
+}
+
+TEST_F(pkixder_input_tests, PeekWithinBounds)
+{
+ const uint8_t der[] = { 0x11, 0x11 };
+ Input buf(der);
+ Reader input(buf);
+ ASSERT_TRUE(input.Peek(0x11));
+ ASSERT_FALSE(input.Peek(0x22));
+}
+
+TEST_F(pkixder_input_tests, PeekPastBounds)
+{
+ const uint8_t der[] = { 0x11, 0x22 };
+ Input buf;
+ ASSERT_EQ(Success, buf.Init(der, 1));
+ Reader input(buf);
+
+ uint8_t readByte;
+ ASSERT_EQ(Success, input.Read(readByte));
+ ASSERT_EQ(0x11, readByte);
+ ASSERT_FALSE(input.Peek(0x22));
+}
+
+TEST_F(pkixder_input_tests, ReadByte)
+{
+ const uint8_t der[] = { 0x11, 0x22 };
+ Input buf(der);
+ Reader input(buf);
+
+ uint8_t readByte1;
+ ASSERT_EQ(Success, input.Read(readByte1));
+ ASSERT_EQ(0x11, readByte1);
+
+ uint8_t readByte2;
+ ASSERT_EQ(Success, input.Read(readByte2));
+ ASSERT_EQ(0x22, readByte2);
+}
+
+TEST_F(pkixder_input_tests, ReadBytePastEnd)
+{
+ const uint8_t der[] = { 0x11, 0x22 };
+ Input buf;
+ ASSERT_EQ(Success, buf.Init(der, 1));
+ Reader input(buf);
+
+ uint8_t readByte1 = 0;
+ ASSERT_EQ(Success, input.Read(readByte1));
+ ASSERT_EQ(0x11, readByte1);
+
+ uint8_t readByte2 = 0;
+ ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(readByte2));
+ ASSERT_NE(0x22, readByte2);
+}
+
+TEST_F(pkixder_input_tests, ReadByteWrapAroundPointer)
+{
+ // The original implementation of our buffer read overflow checks was
+ // susceptible to integer overflows which could make the checks ineffective.
+ // This attempts to verify that we've fixed that. Unfortunately, decrementing
+ // a null pointer is undefined behavior according to the C++ language spec.,
+ // but this should catch the problem on at least some compilers, if not all of
+ // them.
+ const uint8_t* der = nullptr;
+ --der;
+ Input buf;
+ ASSERT_EQ(Success, buf.Init(der, 0));
+ Reader input(buf);
+
+ uint8_t b;
+ ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(b));
+}
+
+TEST_F(pkixder_input_tests, ReadWord)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+
+ uint16_t readWord1 = 0;
+ ASSERT_EQ(Success, input.Read(readWord1));
+ ASSERT_EQ(0x1122, readWord1);
+
+ uint16_t readWord2 = 0;
+ ASSERT_EQ(Success, input.Read(readWord2));
+ ASSERT_EQ(0x3344, readWord2);
+}
+
+TEST_F(pkixder_input_tests, ReadWordPastEnd)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf;
+ ASSERT_EQ(Success, buf.Init(der, 2)); // Initialize with too-short length
+ Reader input(buf);
+
+ uint16_t readWord1 = 0;
+ ASSERT_EQ(Success, input.Read(readWord1));
+ ASSERT_EQ(0x1122, readWord1);
+
+ uint16_t readWord2 = 0;
+ ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(readWord2));
+ ASSERT_NE(0x3344, readWord2);
+}
+
+TEST_F(pkixder_input_tests, ReadWordWithInsufficentData)
+{
+ const uint8_t der[] = { 0x11, 0x22 };
+ Input buf;
+ ASSERT_EQ(Success, buf.Init(der, 1));
+ Reader input(buf);
+
+ uint16_t readWord1 = 0;
+ ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(readWord1));
+ ASSERT_NE(0x1122, readWord1);
+}
+
+TEST_F(pkixder_input_tests, ReadWordWrapAroundPointer)
+{
+ // The original implementation of our buffer read overflow checks was
+ // susceptible to integer overflows which could make the checks ineffective.
+ // This attempts to verify that we've fixed that. Unfortunately, decrementing
+ // a null pointer is undefined behavior according to the C++ language spec.,
+ // but this should catch the problem on at least some compilers, if not all of
+ // them.
+ const uint8_t* der = nullptr;
+ --der;
+ Input buf;
+ ASSERT_EQ(Success, buf.Init(der, 0));
+ Reader input(buf);
+ uint16_t b;
+ ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(b));
+}
+
+TEST_F(pkixder_input_tests, Skip)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+
+ ASSERT_EQ(Success, input.Skip(1));
+
+ uint8_t readByte1 = 0;
+ ASSERT_EQ(Success, input.Read(readByte1));
+ ASSERT_EQ(0x22, readByte1);
+
+ ASSERT_EQ(Success, input.Skip(1));
+
+ uint8_t readByte2 = 0;
+ ASSERT_EQ(Success, input.Read(readByte2));
+ ASSERT_EQ(0x44, readByte2);
+}
+
+TEST_F(pkixder_input_tests, Skip_ToEnd)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+ ASSERT_EQ(Success, input.Skip(sizeof der));
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, Skip_PastEnd)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+
+ ASSERT_EQ(Result::ERROR_BAD_DER, input.Skip(sizeof der + 1));
+}
+
+TEST_F(pkixder_input_tests, Skip_ToNewInput)
+{
+ const uint8_t der[] = { 0x01, 0x02, 0x03, 0x04 };
+ Input buf(der);
+ Reader input(buf);
+
+ Reader skippedInput;
+ ASSERT_EQ(Success, input.Skip(3, skippedInput));
+
+ uint8_t readByte1 = 0;
+ ASSERT_EQ(Success, input.Read(readByte1));
+ ASSERT_EQ(0x04, readByte1);
+
+ ASSERT_TRUE(input.AtEnd());
+
+ // Reader has no Remaining() or Length() so we simply read the bytes
+ // and then expect to be at the end.
+
+ for (uint8_t i = 1; i <= 3; ++i) {
+ uint8_t readByte = 0;
+ ASSERT_EQ(Success, skippedInput.Read(readByte));
+ ASSERT_EQ(i, readByte);
+ }
+
+ ASSERT_TRUE(skippedInput.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, Skip_ToNewInputPastEnd)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+
+ Reader skippedInput;
+ ASSERT_EQ(Result::ERROR_BAD_DER, input.Skip(sizeof der * 2, skippedInput));
+}
+
+TEST_F(pkixder_input_tests, Skip_ToInput)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+
+ const uint8_t expectedItemData[] = { 0x11, 0x22, 0x33 };
+
+ Input item;
+ ASSERT_EQ(Success, input.Skip(sizeof expectedItemData, item));
+
+ Input expected(expectedItemData);
+ ASSERT_TRUE(InputsAreEqual(expected, item));
+}
+
+TEST_F(pkixder_input_tests, Skip_WrapAroundPointer)
+{
+ // The original implementation of our buffer read overflow checks was
+ // susceptible to integer overflows which could make the checks ineffective.
+ // This attempts to verify that we've fixed that. Unfortunately, decrementing
+ // a null pointer is undefined behavior according to the C++ language spec.,
+ // but this should catch the problem on at least some compilers, if not all of
+ // them.
+ const uint8_t* der = nullptr;
+ --der;
+ Input buf;
+ ASSERT_EQ(Success, buf.Init(der, 0));
+ Reader input(buf);
+ ASSERT_EQ(Result::ERROR_BAD_DER, input.Skip(1));
+}
+
+TEST_F(pkixder_input_tests, Skip_ToInputPastEnd)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+
+ Input skipped;
+ ASSERT_EQ(Result::ERROR_BAD_DER, input.Skip(sizeof der + 1, skipped));
+}
+
+TEST_F(pkixder_input_tests, SkipToEnd_ToInput)
+{
+ static const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+
+ Input skipped;
+ ASSERT_EQ(Success, input.SkipToEnd(skipped));
+}
+
+TEST_F(pkixder_input_tests, SkipToEnd_ToInput_InputAlreadyInited)
+{
+ static const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+
+ static const uint8_t initialValue[] = { 0x01, 0x02, 0x03 };
+ Input x(initialValue);
+ // Fails because skipped was already initialized once, and Inputs are not
+ // allowed to be Init()d multiple times.
+ ASSERT_EQ(Result::FATAL_ERROR_INVALID_ARGS, input.SkipToEnd(x));
+ ASSERT_TRUE(InputsAreEqual(x, Input(initialValue)));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndSkipValue)
+{
+ Input buf(DER_SEQUENCE_OF_INT8);
+ Reader input(buf);
+
+ ASSERT_EQ(Success, ExpectTagAndSkipValue(input, SEQUENCE));
+ ASSERT_EQ(Success, End(input));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndSkipValueWithTruncatedData)
+{
+ Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8);
+ Reader input(buf);
+
+ ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndSkipValue(input, SEQUENCE));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndSkipValueWithOverrunData)
+{
+ Input buf(DER_OVERRUN_SEQUENCE_OF_INT8);
+ Reader input(buf);
+ ASSERT_EQ(Success, ExpectTagAndSkipValue(input, SEQUENCE));
+ ASSERT_EQ(Result::ERROR_BAD_DER, End(input));
+}
+
+TEST_F(pkixder_input_tests, AtEndOnUnInitializedInput)
+{
+ Reader input;
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, AtEndAtBeginning)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+ ASSERT_FALSE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, AtEndAtEnd)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+ ASSERT_EQ(Success, input.Skip(sizeof der));
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, MarkAndGetInput)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+
+ Reader::Mark mark = input.GetMark();
+
+ const uint8_t expectedItemData[] = { 0x11, 0x22, 0x33 };
+
+ ASSERT_EQ(Success, input.Skip(sizeof expectedItemData));
+
+ Input item;
+ ASSERT_EQ(Success, input.GetInput(mark, item));
+ Input expected(expectedItemData);
+ ASSERT_TRUE(InputsAreEqual(expected, item));
+}
+
+// Cannot run this test on debug builds because of the NotReached
+#ifdef NDEBUG
+TEST_F(pkixder_input_tests, MarkAndGetInputDifferentInput)
+{
+ const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 };
+ Input buf(der);
+ Reader input(buf);
+
+ Reader another;
+ Reader::Mark mark = another.GetMark();
+
+ ASSERT_EQ(Success, input.Skip(3));
+
+ Input item;
+ ASSERT_EQ(Result::FATAL_ERROR_INVALID_ARGS, input.GetInput(mark, item));
+}
+#endif
+
+TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_AtEnd)
+{
+ Reader input(EMPTY_INPUT);
+ uint8_t tag;
+ Input value;
+ ASSERT_EQ(Result::ERROR_BAD_DER, ReadTagAndGetValue(input, tag, value));
+}
+
+TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_TruncatedAfterTag)
+{
+ static const uint8_t DER[] = { SEQUENCE };
+ Input buf(DER);
+ Reader input(buf);
+ uint8_t tag;
+ Input value;
+ ASSERT_EQ(Result::ERROR_BAD_DER, ReadTagAndGetValue(input, tag, value));
+}
+
+TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_ValidEmpty)
+{
+ Input buf(DER_SEQUENCE_EMPTY);
+ Reader input(buf);
+ uint8_t tag = 0;
+ Input value;
+ ASSERT_EQ(Success, ReadTagAndGetValue(input, tag, value));
+ ASSERT_EQ(SEQUENCE, tag);
+ ASSERT_EQ(0u, value.GetLength());
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_ValidNotEmpty)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY);
+ Reader input(buf);
+ uint8_t tag = 0;
+ Input value;
+ ASSERT_EQ(Success, ReadTagAndGetValue(input, tag, value));
+ ASSERT_EQ(SEQUENCE, tag);
+ Input expected(DER_SEQUENCE_NOT_EMPTY_VALUE);
+ ASSERT_TRUE(InputsAreEqual(expected, value));
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests,
+ ReadTagAndGetValue_Input_InvalidNotEmptyValueTruncated)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED);
+ Reader input(buf);
+ uint8_t tag;
+ Input value;
+ ASSERT_EQ(Result::ERROR_BAD_DER, ReadTagAndGetValue(input, tag, value));
+}
+
+TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_InvalidWrongLength)
+{
+ Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8);
+ Reader input(buf);
+ uint8_t tag;
+ Input value;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ ReadTagAndGetValue(input, tag, value));
+}
+
+TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_InvalidHighTagNumberForm1)
+{
+ // High tag number form is not allowed (illegal 1 byte tag)
+ //
+ // If the decoder treats 0x1F as a valid low tag number tag, then it will
+ // treat the actual tag (1) as a length, and then it will return Success
+ // with value == { 0x00 } and tag == 0x1f.
+ //
+ // It is illegal to encode tag 1 in the high tag number form because it isn't
+ // the shortest encoding (the low tag number form is).
+ static const uint8_t DER[] = {
+ 0x1F, // high tag number form indicator
+ 1, // tag 1 (not legal!)
+ 0 // length zero
+ };
+ Input buf(DER);
+ Reader input(buf);
+ uint8_t tag;
+ Input value;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ ReadTagAndGetValue(input, tag, value));
+}
+
+TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_InvalidHighTagNumberForm2)
+{
+ // High tag number form is not allowed (legal 1 byte tag).
+ //
+ // ReadTagAndGetValue's check to prohibit the high tag number form has no
+ // effect on whether this test passes or fails, because ReadTagAndGetValue
+ // will interpret the second byte (31) as a length, and the input doesn't
+ // have 31 bytes following it. This test is here to guard against the case
+ // where somebody actually implements high tag number form parsing, to remind
+ // that person that they need to add tests here, including in particular
+ // tests for overly-long encodings.
+ static const uint8_t DER[] = {
+ 0x1F, // high tag number form indicator
+ 31, // tag 31
+ 0 // length zero
+ };
+ Input buf(DER);
+ Reader input(buf);
+ uint8_t tag;
+ Input value;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ ReadTagAndGetValue(input, tag, value));
+}
+
+TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_InvalidHighTagNumberForm3)
+{
+ // High tag number form is not allowed (2 byte legal tag)
+ //
+ // ReadTagAndGetValue's check to prohibit the high tag number form has no
+ // effect on whether this test passes or fails, because ReadTagAndGetValue
+ // will interpret the second byte as a length, and the input doesn't have
+ // that many bytes following it. This test is here to guard against the case
+ // where somebody actually implements high tag number form parsing, to remind
+ // that person that they need to add tests here, including in particular
+ // tests for overly-long encodings.
+ static const uint8_t DER[] = {
+ 0x1F, // high tag number form indicator
+ 0x80 | 0x01, 0x00, // tag 0x100 (256)
+ 0 // length zero
+ };
+ Input buf(DER);
+ Reader input(buf);
+ uint8_t tag;
+ Input value;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ ReadTagAndGetValue(input, tag, value));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Reader_ValidEmpty)
+{
+ Input buf(DER_SEQUENCE_EMPTY);
+ Reader input(buf);
+ Reader value;
+ ASSERT_EQ(Success, ExpectTagAndGetValue(input, SEQUENCE, value));
+ ASSERT_TRUE(value.AtEnd());
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Reader_ValidNotEmpty)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY);
+ Reader input(buf);
+ Reader value;
+ ASSERT_EQ(Success, ExpectTagAndGetValue(input, SEQUENCE, value));
+ ASSERT_TRUE(value.MatchRest(DER_SEQUENCE_NOT_EMPTY_VALUE));
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests,
+ ExpectTagAndGetValue_Reader_InvalidNotEmptyValueTruncated)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED);
+ Reader input(buf);
+ Reader value;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ ExpectTagAndGetValue(input, SEQUENCE, value));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Reader_InvalidWrongLength)
+{
+ Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8);
+ Reader input(buf);
+ Reader value;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ ExpectTagAndGetValue(input, SEQUENCE, value));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Reader_InvalidWrongTag)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY);
+ Reader input(buf);
+ Reader value;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ ExpectTagAndGetValue(input, INTEGER, value));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Input_ValidEmpty)
+{
+ Input buf(DER_SEQUENCE_EMPTY);
+ Reader input(buf);
+ Input value;
+ ASSERT_EQ(Success, ExpectTagAndGetValue(input, SEQUENCE, value));
+ ASSERT_EQ(0u, value.GetLength());
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Input_ValidNotEmpty)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY);
+ Reader input(buf);
+ Input value;
+ ASSERT_EQ(Success, ExpectTagAndGetValue(input, SEQUENCE, value));
+ Input expected(DER_SEQUENCE_NOT_EMPTY_VALUE);
+ ASSERT_TRUE(InputsAreEqual(expected, value));
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests,
+ ExpectTagAndGetValue_Input_InvalidNotEmptyValueTruncated)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED);
+ Reader input(buf);
+ Input value;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ ExpectTagAndGetValue(input, SEQUENCE, value));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Input_InvalidWrongLength)
+{
+ Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8);
+ Reader input(buf);
+ Input value;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ ExpectTagAndGetValue(input, SEQUENCE, value));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Input_InvalidWrongTag)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY);
+ Reader input(buf);
+ Input value;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ ExpectTagAndGetValue(input, INTEGER, value));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndEmptyValue_ValidEmpty)
+{
+ Input buf(DER_SEQUENCE_EMPTY);
+ Reader input(buf);
+ ASSERT_EQ(Success, ExpectTagAndEmptyValue(input, SEQUENCE));
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndEmptyValue_InValidNotEmpty)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY);
+ Reader input(buf);
+ ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndEmptyValue(input, SEQUENCE));
+}
+
+TEST_F(pkixder_input_tests,
+ ExpectTagAndEmptyValue_Input_InvalidNotEmptyValueTruncated)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED);
+ Reader input(buf);
+ ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndEmptyValue(input, SEQUENCE));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndEmptyValue_InvalidWrongLength)
+{
+ Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8);
+ Reader input(buf);
+ ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndEmptyValue(input, SEQUENCE));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndEmptyValue_InvalidWrongTag)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY);
+ Reader input(buf);
+ ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndEmptyValue(input, INTEGER));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetTLV_Input_ValidEmpty)
+{
+ Input buf(DER_SEQUENCE_EMPTY);
+ Reader input(buf);
+ Input tlv;
+ ASSERT_EQ(Success, ExpectTagAndGetTLV(input, SEQUENCE, tlv));
+ Input expected(DER_SEQUENCE_EMPTY);
+ ASSERT_TRUE(InputsAreEqual(expected, tlv));
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetTLV_Input_ValidNotEmpty)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY);
+ Reader input(buf);
+ Input tlv;
+ ASSERT_EQ(Success, ExpectTagAndGetTLV(input, SEQUENCE, tlv));
+ Input expected(DER_SEQUENCE_NOT_EMPTY);
+ ASSERT_TRUE(InputsAreEqual(expected, tlv));
+ ASSERT_TRUE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests,
+ ExpectTagAndGetTLV_Input_InvalidNotEmptyValueTruncated)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED);
+ Reader input(buf);
+ Input tlv;
+ ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndGetTLV(input, SEQUENCE, tlv));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetTLV_Input_InvalidWrongLength)
+{
+ Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8);
+ Reader input(buf);
+ Input tlv;
+ ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndGetTLV(input, SEQUENCE, tlv));
+}
+
+TEST_F(pkixder_input_tests, ExpectTagAndGetTLV_Input_InvalidWrongTag)
+{
+ Input buf(DER_SEQUENCE_NOT_EMPTY);
+ Reader input(buf);
+ Input tlv;
+ ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndGetTLV(input, INTEGER, tlv));
+}
+
+TEST_F(pkixder_input_tests, EndAtEnd)
+{
+ Input buf(DER_INT16);
+ Reader input(buf);
+ ASSERT_EQ(Success, input.Skip(4));
+ ASSERT_EQ(Success, End(input));
+}
+
+TEST_F(pkixder_input_tests, EndBeforeEnd)
+{
+ Input buf(DER_INT16);
+ Reader input(buf);
+ ASSERT_EQ(Success, input.Skip(2));
+ ASSERT_EQ(Result::ERROR_BAD_DER, End(input));
+}
+
+TEST_F(pkixder_input_tests, EndAtBeginning)
+{
+ Input buf(DER_INT16);
+ Reader input(buf);
+ ASSERT_EQ(Result::ERROR_BAD_DER, End(input));
+}
+
+// TODO: Need tests for Nested too?
+
+Result NestedOfHelper(Reader& input, std::vector<uint8_t>& readValues)
+{
+ uint8_t value = 0;
+ Result rv = input.Read(value);
+ EXPECT_EQ(Success, rv);
+ if (rv != Success) {
+ return rv;
+ }
+ readValues.push_back(value);
+ return Success;
+}
+
+TEST_F(pkixder_input_tests, NestedOf)
+{
+ Input buf(DER_SEQUENCE_OF_INT8);
+ Reader input(buf);
+
+ std::vector<uint8_t> readValues;
+ ASSERT_EQ(Success,
+ NestedOf(input, SEQUENCE, INTEGER, EmptyAllowed::No,
+ [&readValues](Reader& r) {
+ return NestedOfHelper(r, readValues);
+ }));
+ ASSERT_EQ(3u, readValues.size());
+ ASSERT_EQ(0x01, readValues[0]);
+ ASSERT_EQ(0x02, readValues[1]);
+ ASSERT_EQ(0x03, readValues[2]);
+ ASSERT_EQ(Success, End(input));
+}
+
+TEST_F(pkixder_input_tests, NestedOfWithTruncatedData)
+{
+ Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8);
+ Reader input(buf);
+
+ std::vector<uint8_t> readValues;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ NestedOf(input, SEQUENCE, INTEGER, EmptyAllowed::No,
+ [&readValues](Reader& r) {
+ return NestedOfHelper(r, readValues);
+ }));
+ ASSERT_EQ(0u, readValues.size());
+}
+
+TEST_F(pkixder_input_tests, MatchRestAtEnd)
+{
+ static const uint8_t der[1] = { };
+ Input buf;
+ ASSERT_EQ(Success, buf.Init(der, 0));
+ Reader input(buf);
+ ASSERT_TRUE(input.AtEnd());
+ static const uint8_t toMatch[] = { 1 };
+ ASSERT_FALSE(input.MatchRest(toMatch));
+}
+
+TEST_F(pkixder_input_tests, MatchRest1Match)
+{
+ static const uint8_t der[] = { 1 };
+ Input buf(der);
+ Reader input(buf);
+ ASSERT_FALSE(input.AtEnd());
+ ASSERT_TRUE(input.MatchRest(der));
+}
+
+TEST_F(pkixder_input_tests, MatchRest1Mismatch)
+{
+ static const uint8_t der[] = { 1 };
+ Input buf(der);
+ Reader input(buf);
+ static const uint8_t toMatch[] = { 2 };
+ ASSERT_FALSE(input.MatchRest(toMatch));
+ ASSERT_FALSE(input.AtEnd());
+}
+
+TEST_F(pkixder_input_tests, MatchRest2WithTrailingByte)
+{
+ static const uint8_t der[] = { 1, 2, 3 };
+ Input buf(der);
+ Reader input(buf);
+ static const uint8_t toMatch[] = { 1, 2 };
+ ASSERT_FALSE(input.MatchRest(toMatch));
+}
+
+TEST_F(pkixder_input_tests, MatchRest2Mismatch)
+{
+ static const uint8_t der[] = { 1, 2, 3 };
+ Input buf(der);
+ Reader input(buf);
+ static const uint8_t toMatchMismatch[] = { 1, 3 };
+ ASSERT_FALSE(input.MatchRest(toMatchMismatch));
+ ASSERT_TRUE(input.MatchRest(der));
+}
+
+} // namespace
diff --git a/security/pkix/test/gtest/pkixder_pki_types_tests.cpp b/security/pkix/test/gtest/pkixder_pki_types_tests.cpp
new file mode 100644
index 000000000..e40c2a4c3
--- /dev/null
+++ b/security/pkix/test/gtest/pkixder_pki_types_tests.cpp
@@ -0,0 +1,479 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2013 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <functional>
+#include <vector>
+
+#include "pkixgtest.h"
+#include "pkix/pkixtypes.h"
+#include "pkixder.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::der;
+
+class pkixder_pki_types_tests : public ::testing::Test { };
+
+TEST_F(pkixder_pki_types_tests, CertificateSerialNumber)
+{
+ const uint8_t DER_CERT_SERIAL[] = {
+ 0x02, // INTEGER
+ 8, // length
+ 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef
+ };
+ Input input(DER_CERT_SERIAL);
+ Reader reader(input);
+
+ Input item;
+ ASSERT_EQ(Success, CertificateSerialNumber(reader, item));
+
+ Input expected;
+ ASSERT_EQ(Success,
+ expected.Init(DER_CERT_SERIAL + 2, sizeof DER_CERT_SERIAL - 2));
+ ASSERT_TRUE(InputsAreEqual(expected, item));
+}
+
+TEST_F(pkixder_pki_types_tests, CertificateSerialNumberLongest)
+{
+ const uint8_t DER_CERT_SERIAL_LONGEST[] = {
+ 0x02, // INTEGER
+ 20, // length
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
+ };
+ Input input(DER_CERT_SERIAL_LONGEST);
+ Reader reader(input);
+
+ Input item;
+ ASSERT_EQ(Success, CertificateSerialNumber(reader, item));
+
+ Input expected;
+ ASSERT_EQ(Success,
+ expected.Init(DER_CERT_SERIAL_LONGEST + 2,
+ sizeof DER_CERT_SERIAL_LONGEST - 2));
+ ASSERT_TRUE(InputsAreEqual(expected, item));
+}
+
+TEST_F(pkixder_pki_types_tests, CertificateSerialNumberCrazyLong)
+{
+ const uint8_t DER_CERT_SERIAL_CRAZY_LONG[] = {
+ 0x02, // INTEGER
+ 32, // length
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32
+ };
+ Input input(DER_CERT_SERIAL_CRAZY_LONG);
+ Reader reader(input);
+
+ Input item;
+ ASSERT_EQ(Success, CertificateSerialNumber(reader, item));
+}
+
+TEST_F(pkixder_pki_types_tests, CertificateSerialNumberZeroLength)
+{
+ const uint8_t DER_CERT_SERIAL_ZERO_LENGTH[] = {
+ 0x02, // INTEGER
+ 0x00 // length
+ };
+ Input input(DER_CERT_SERIAL_ZERO_LENGTH);
+ Reader reader(input);
+
+ Input item;
+ ASSERT_EQ(Result::ERROR_INVALID_INTEGER_ENCODING,
+ CertificateSerialNumber(reader, item));
+}
+
+TEST_F(pkixder_pki_types_tests, OptionalVersionV1ExplicitEncodingAllowed)
+{
+ const uint8_t DER_OPTIONAL_VERSION_V1[] = {
+ 0xa0, 0x03, // context specific 0
+ 0x02, 0x01, 0x00 // INTEGER(0)
+ };
+ Input input(DER_OPTIONAL_VERSION_V1);
+ Reader reader(input);
+
+ // XXX(bug 1031093): We shouldn't accept an explicit encoding of v1, but we
+ // do here for compatibility reasons.
+ // Version version;
+ // ASSERT_EQ(Result::ERROR_BAD_DER, OptionalVersion(reader, version));
+ der::Version version = der::Version::v3;
+ ASSERT_EQ(Success, OptionalVersion(reader, version));
+ ASSERT_EQ(der::Version::v1, version);
+}
+
+TEST_F(pkixder_pki_types_tests, OptionalVersionV2)
+{
+ const uint8_t DER_OPTIONAL_VERSION_V2[] = {
+ 0xa0, 0x03, // context specific 0
+ 0x02, 0x01, 0x01 // INTEGER(1)
+ };
+ Input input(DER_OPTIONAL_VERSION_V2);
+ Reader reader(input);
+
+ der::Version version = der::Version::v1;
+ ASSERT_EQ(Success, OptionalVersion(reader, version));
+ ASSERT_EQ(der::Version::v2, version);
+}
+
+TEST_F(pkixder_pki_types_tests, OptionalVersionV3)
+{
+ const uint8_t DER_OPTIONAL_VERSION_V3[] = {
+ 0xa0, 0x03, // context specific 0
+ 0x02, 0x01, 0x02 // INTEGER(2)
+ };
+ Input input(DER_OPTIONAL_VERSION_V3);
+ Reader reader(input);
+
+ der::Version version = der::Version::v1;
+ ASSERT_EQ(Success, OptionalVersion(reader, version));
+ ASSERT_EQ(der::Version::v3, version);
+}
+
+TEST_F(pkixder_pki_types_tests, OptionalVersionUnknown)
+{
+ const uint8_t DER_OPTIONAL_VERSION_INVALID[] = {
+ 0xa0, 0x03, // context specific 0
+ 0x02, 0x01, 0x42 // INTEGER(0x42)
+ };
+ Input input(DER_OPTIONAL_VERSION_INVALID);
+ Reader reader(input);
+
+ der::Version version = der::Version::v1;
+ ASSERT_EQ(Result::ERROR_BAD_DER, OptionalVersion(reader, version));
+}
+
+TEST_F(pkixder_pki_types_tests, OptionalVersionInvalidTooLong)
+{
+ const uint8_t DER_OPTIONAL_VERSION_INVALID_TOO_LONG[] = {
+ 0xa0, 0x03, // context specific 0
+ 0x02, 0x02, 0x12, 0x34 // INTEGER(0x1234)
+ };
+ Input input(DER_OPTIONAL_VERSION_INVALID_TOO_LONG);
+ Reader reader(input);
+
+ der::Version version;
+ ASSERT_EQ(Result::ERROR_BAD_DER, OptionalVersion(reader, version));
+}
+
+TEST_F(pkixder_pki_types_tests, OptionalVersionMissing)
+{
+ const uint8_t DER_OPTIONAL_VERSION_MISSING[] = {
+ 0x02, 0x11, 0x22 // INTEGER
+ };
+ Input input(DER_OPTIONAL_VERSION_MISSING);
+ Reader reader(input);
+
+ der::Version version = der::Version::v3;
+ ASSERT_EQ(Success, OptionalVersion(reader, version));
+ ASSERT_EQ(der::Version::v1, version);
+}
+
+static const size_t MAX_ALGORITHM_OID_DER_LENGTH = 13;
+
+struct InvalidAlgorithmIdentifierTestInfo
+{
+ uint8_t der[MAX_ALGORITHM_OID_DER_LENGTH];
+ size_t derLength;
+};
+
+struct ValidDigestAlgorithmIdentifierTestInfo
+{
+ DigestAlgorithm algorithm;
+ uint8_t der[MAX_ALGORITHM_OID_DER_LENGTH];
+ size_t derLength;
+};
+
+class pkixder_DigestAlgorithmIdentifier_Valid
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<ValidDigestAlgorithmIdentifierTestInfo>
+{
+};
+
+static const ValidDigestAlgorithmIdentifierTestInfo
+ VALID_DIGEST_ALGORITHM_TEST_INFO[] =
+{
+ { DigestAlgorithm::sha512,
+ { 0x30, 0x0b, 0x06, 0x09,
+ 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03 },
+ 13
+ },
+ { DigestAlgorithm::sha384,
+ { 0x30, 0x0b, 0x06, 0x09,
+ 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02 },
+ 13
+ },
+ { DigestAlgorithm::sha256,
+ { 0x30, 0x0b, 0x06, 0x09,
+ 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 },
+ 13
+ },
+ { DigestAlgorithm::sha1,
+ { 0x30, 0x07, 0x06, 0x05,
+ 0x2b, 0x0e, 0x03, 0x02, 0x1a },
+ 9
+ },
+};
+
+TEST_P(pkixder_DigestAlgorithmIdentifier_Valid, Valid)
+{
+ const ValidDigestAlgorithmIdentifierTestInfo& param(GetParam());
+
+ {
+ Input input;
+ ASSERT_EQ(Success, input.Init(param.der, param.derLength));
+ Reader reader(input);
+ DigestAlgorithm alg;
+ ASSERT_EQ(Success, DigestAlgorithmIdentifier(reader, alg));
+ ASSERT_EQ(param.algorithm, alg);
+ ASSERT_EQ(Success, End(reader));
+ }
+
+ {
+ uint8_t derWithNullParam[MAX_ALGORITHM_OID_DER_LENGTH + 2];
+ memcpy(derWithNullParam, param.der, param.derLength);
+ derWithNullParam[1] += 2; // we're going to expand the value by 2 bytes
+ derWithNullParam[param.derLength] = 0x05; // NULL tag
+ derWithNullParam[param.derLength + 1] = 0x00; // length zero
+
+ Input input;
+ ASSERT_EQ(Success, input.Init(derWithNullParam, param.derLength + 2));
+ Reader reader(input);
+ DigestAlgorithm alg;
+ ASSERT_EQ(Success, DigestAlgorithmIdentifier(reader, alg));
+ ASSERT_EQ(param.algorithm, alg);
+ ASSERT_EQ(Success, End(reader));
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(pkixder_DigestAlgorithmIdentifier_Valid,
+ pkixder_DigestAlgorithmIdentifier_Valid,
+ testing::ValuesIn(VALID_DIGEST_ALGORITHM_TEST_INFO));
+
+class pkixder_DigestAlgorithmIdentifier_Invalid
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<InvalidAlgorithmIdentifierTestInfo>
+{
+};
+
+static const InvalidAlgorithmIdentifierTestInfo
+ INVALID_DIGEST_ALGORITHM_TEST_INFO[] =
+{
+ { // MD5
+ { 0x30, 0x0a, 0x06, 0x08,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05 },
+ 12,
+ },
+ { // ecdsa-with-SHA256 (1.2.840.10045.4.3.2) (not a hash algorithm)
+ { 0x30, 0x0a, 0x06, 0x08,
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02 },
+ 12,
+ },
+};
+
+TEST_P(pkixder_DigestAlgorithmIdentifier_Invalid, Invalid)
+{
+ const InvalidAlgorithmIdentifierTestInfo& param(GetParam());
+ Input input;
+ ASSERT_EQ(Success, input.Init(param.der, param.derLength));
+ Reader reader(input);
+ DigestAlgorithm alg;
+ ASSERT_EQ(Result::ERROR_INVALID_ALGORITHM,
+ DigestAlgorithmIdentifier(reader, alg));
+}
+
+INSTANTIATE_TEST_CASE_P(pkixder_DigestAlgorithmIdentifier_Invalid,
+ pkixder_DigestAlgorithmIdentifier_Invalid,
+ testing::ValuesIn(INVALID_DIGEST_ALGORITHM_TEST_INFO));
+
+struct ValidSignatureAlgorithmIdentifierValueTestInfo
+{
+ PublicKeyAlgorithm publicKeyAlg;
+ DigestAlgorithm digestAlg;
+ uint8_t der[MAX_ALGORITHM_OID_DER_LENGTH];
+ size_t derLength;
+};
+
+static const ValidSignatureAlgorithmIdentifierValueTestInfo
+ VALID_SIGNATURE_ALGORITHM_VALUE_TEST_INFO[] =
+{
+ // ECDSA
+ { PublicKeyAlgorithm::ECDSA,
+ DigestAlgorithm::sha512,
+ { 0x06, 0x08,
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04 },
+ 10,
+ },
+ { PublicKeyAlgorithm::ECDSA,
+ DigestAlgorithm::sha384,
+ { 0x06, 0x08,
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03 },
+ 10,
+ },
+ { PublicKeyAlgorithm::ECDSA,
+ DigestAlgorithm::sha256,
+ { 0x06, 0x08,
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02 },
+ 10,
+ },
+ { PublicKeyAlgorithm::ECDSA,
+ DigestAlgorithm::sha1,
+ { 0x06, 0x07,
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x01 },
+ 9,
+ },
+
+ // RSA
+ { PublicKeyAlgorithm::RSA_PKCS1,
+ DigestAlgorithm::sha512,
+ { 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0d },
+ 11,
+ },
+ { PublicKeyAlgorithm::RSA_PKCS1,
+ DigestAlgorithm::sha384,
+ { 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c },
+ 11,
+ },
+ { PublicKeyAlgorithm::RSA_PKCS1,
+ DigestAlgorithm::sha256,
+ { 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b },
+ 11,
+ },
+ { PublicKeyAlgorithm::RSA_PKCS1,
+ DigestAlgorithm::sha1,
+ // IETF Standard OID
+ { 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05 },
+ 11,
+ },
+ { PublicKeyAlgorithm::RSA_PKCS1,
+ DigestAlgorithm::sha1,
+ // Legacy OIW OID (bug 1042479)
+ { 0x06, 0x05,
+ 0x2b, 0x0e, 0x03, 0x02, 0x1d },
+ 7,
+ },
+};
+
+class pkixder_SignatureAlgorithmIdentifierValue_Valid
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<
+ ValidSignatureAlgorithmIdentifierValueTestInfo>
+{
+};
+
+TEST_P(pkixder_SignatureAlgorithmIdentifierValue_Valid, Valid)
+{
+ const ValidSignatureAlgorithmIdentifierValueTestInfo& param(GetParam());
+
+ {
+ Input input;
+ ASSERT_EQ(Success, input.Init(param.der, param.derLength));
+ Reader reader(input);
+ PublicKeyAlgorithm publicKeyAlg;
+ DigestAlgorithm digestAlg;
+ ASSERT_EQ(Success,
+ SignatureAlgorithmIdentifierValue(reader, publicKeyAlg,
+ digestAlg));
+ ASSERT_EQ(param.publicKeyAlg, publicKeyAlg);
+ ASSERT_EQ(param.digestAlg, digestAlg);
+ ASSERT_EQ(Success, End(reader));
+ }
+
+ {
+ uint8_t derWithNullParam[MAX_ALGORITHM_OID_DER_LENGTH + 2];
+ memcpy(derWithNullParam, param.der, param.derLength);
+ derWithNullParam[param.derLength] = 0x05; // NULL tag
+ derWithNullParam[param.derLength + 1] = 0x00; // length zero
+
+ Input input;
+ ASSERT_EQ(Success, input.Init(derWithNullParam, param.derLength + 2));
+ Reader reader(input);
+ PublicKeyAlgorithm publicKeyAlg;
+ DigestAlgorithm digestAlg;
+ ASSERT_EQ(Success,
+ SignatureAlgorithmIdentifierValue(reader, publicKeyAlg,
+ digestAlg));
+ ASSERT_EQ(param.publicKeyAlg, publicKeyAlg);
+ ASSERT_EQ(param.digestAlg, digestAlg);
+ ASSERT_EQ(Success, End(reader));
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(
+ pkixder_SignatureAlgorithmIdentifierValue_Valid,
+ pkixder_SignatureAlgorithmIdentifierValue_Valid,
+ testing::ValuesIn(VALID_SIGNATURE_ALGORITHM_VALUE_TEST_INFO));
+
+static const InvalidAlgorithmIdentifierTestInfo
+ INVALID_SIGNATURE_ALGORITHM_VALUE_TEST_INFO[] =
+{
+ // id-dsa-with-sha256 (2.16.840.1.101.3.4.3.2)
+ { { 0x06, 0x09,
+ 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x02 },
+ 11,
+ },
+
+ // id-dsa-with-sha1 (1.2.840.10040.4.3)
+ { { 0x06, 0x07,
+ 0x2a, 0x86, 0x48, 0xce, 0x38, 0x04, 0x03 },
+ 9,
+ },
+
+ // RSA-with-MD5 (1.2.840.113549.1.1.4)
+ { { 0x06, 0x09,
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x04 },
+ 11,
+ },
+
+ // id-sha256 (2.16.840.1.101.3.4.2.1). It is invalid because SHA-256 is not
+ // a signature algorithm.
+ { { 0x06, 0x09,
+ 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 },
+ 11,
+ },
+};
+
+class pkixder_SignatureAlgorithmIdentifier_Invalid
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<InvalidAlgorithmIdentifierTestInfo>
+{
+};
+
+TEST_P(pkixder_SignatureAlgorithmIdentifier_Invalid, Invalid)
+{
+ const InvalidAlgorithmIdentifierTestInfo& param(GetParam());
+ Input input;
+ ASSERT_EQ(Success, input.Init(param.der, param.derLength));
+ Reader reader(input);
+ der::PublicKeyAlgorithm publicKeyAlg;
+ DigestAlgorithm digestAlg;
+ ASSERT_EQ(Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED,
+ SignatureAlgorithmIdentifierValue(reader, publicKeyAlg, digestAlg));
+}
+
+INSTANTIATE_TEST_CASE_P(
+ pkixder_SignatureAlgorithmIdentifier_Invalid,
+ pkixder_SignatureAlgorithmIdentifier_Invalid,
+ testing::ValuesIn(INVALID_SIGNATURE_ALGORITHM_VALUE_TEST_INFO));
diff --git a/security/pkix/test/gtest/pkixder_universal_types_tests.cpp b/security/pkix/test/gtest/pkixder_universal_types_tests.cpp
new file mode 100644
index 000000000..bf4175bac
--- /dev/null
+++ b/security/pkix/test/gtest/pkixder_universal_types_tests.cpp
@@ -0,0 +1,1220 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2013 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <limits>
+#include <stdint.h>
+#include <vector>
+
+#include "pkixder.h"
+#include "pkixgtest.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::der;
+using namespace mozilla::pkix::test;
+using namespace std;
+
+class pkixder_universal_types_tests : public ::testing::Test { };
+
+TEST_F(pkixder_universal_types_tests, BooleanTrue01)
+{
+ const uint8_t DER_BOOLEAN_TRUE_01[] = {
+ 0x01, // BOOLEAN
+ 0x01, // length
+ 0x01 // invalid
+ };
+ Input input(DER_BOOLEAN_TRUE_01);
+ Reader reader(input);
+ bool value = false;
+ ASSERT_EQ(Result::ERROR_BAD_DER, Boolean(reader, value));
+}
+
+TEST_F(pkixder_universal_types_tests, BooleanTrue42)
+{
+ const uint8_t DER_BOOLEAN_TRUE_42[] = {
+ 0x01, // BOOLEAN
+ 0x01, // length
+ 0x42 // invalid
+ };
+ Input input(DER_BOOLEAN_TRUE_42);
+ Reader reader(input);
+ bool value = false;
+ ASSERT_EQ(Result::ERROR_BAD_DER, Boolean(reader, value));
+}
+
+static const uint8_t DER_BOOLEAN_TRUE[] = {
+ 0x01, // BOOLEAN
+ 0x01, // length
+ 0xff // true
+};
+
+TEST_F(pkixder_universal_types_tests, BooleanTrueFF)
+{
+ Input input(DER_BOOLEAN_TRUE);
+ Reader reader(input);
+ bool value = false;
+ ASSERT_EQ(Success, Boolean(reader, value));
+ ASSERT_TRUE(value);
+}
+
+TEST_F(pkixder_universal_types_tests, BooleanFalse)
+{
+ const uint8_t DER_BOOLEAN_FALSE[] = {
+ 0x01, // BOOLEAN
+ 0x01, // length
+ 0x00 // false
+ };
+ Input input(DER_BOOLEAN_FALSE);
+ Reader reader(input);
+
+ bool value = true;
+ ASSERT_EQ(Success, Boolean(reader, value));
+ ASSERT_FALSE(value);
+}
+
+TEST_F(pkixder_universal_types_tests, BooleanInvalidLength)
+{
+ const uint8_t DER_BOOLEAN_INVALID_LENGTH[] = {
+ 0x01, // BOOLEAN
+ 0x02, // length
+ 0x42, 0x42 // invalid
+ };
+ Input input(DER_BOOLEAN_INVALID_LENGTH);
+ Reader reader(input);
+
+ bool value = true;
+ ASSERT_EQ(Result::ERROR_BAD_DER, Boolean(reader, value));
+}
+
+TEST_F(pkixder_universal_types_tests, BooleanInvalidZeroLength)
+{
+ const uint8_t DER_BOOLEAN_INVALID_ZERO_LENGTH[] = {
+ 0x01, // BOOLEAN
+ 0x00 // length
+ };
+ Input input(DER_BOOLEAN_INVALID_ZERO_LENGTH);
+ Reader reader(input);
+
+ bool value = true;
+ ASSERT_EQ(Result::ERROR_BAD_DER, Boolean(reader, value));
+}
+
+// OptionalBoolean implements decoding of OPTIONAL BOOLEAN DEFAULT FALSE.
+// If the field is present, it must be a valid encoding of a BOOLEAN with
+// value TRUE. If the field is not present, it defaults to FALSE. For
+// compatibility reasons, OptionalBoolean also accepts encodings where the field
+// is present with value FALSE (this is technically not a valid DER encoding).
+TEST_F(pkixder_universal_types_tests, OptionalBooleanValidEncodings)
+{
+ {
+ const uint8_t DER_OPTIONAL_BOOLEAN_PRESENT_TRUE[] = {
+ 0x01, // BOOLEAN
+ 0x01, // length
+ 0xff // true
+ };
+ Input input(DER_OPTIONAL_BOOLEAN_PRESENT_TRUE);
+ Reader reader(input);
+ bool value = false;
+ ASSERT_EQ(Success, OptionalBoolean(reader, value)) <<
+ "Should accept the only valid encoding of a present OPTIONAL BOOLEAN";
+ ASSERT_TRUE(value);
+ ASSERT_TRUE(reader.AtEnd());
+ }
+
+ {
+ // The OPTIONAL BOOLEAN is omitted in this data.
+ const uint8_t DER_INTEGER_05[] = {
+ 0x02, // INTEGER
+ 0x01, // length
+ 0x05
+ };
+ Input input(DER_INTEGER_05);
+ Reader reader(input);
+ bool value = true;
+ ASSERT_EQ(Success, OptionalBoolean(reader, value)) <<
+ "Should accept a valid encoding of an omitted OPTIONAL BOOLEAN";
+ ASSERT_FALSE(value);
+ ASSERT_FALSE(reader.AtEnd());
+ }
+
+ {
+ Input input;
+ ASSERT_EQ(Success, input.Init(reinterpret_cast<const uint8_t*>(""), 0));
+ Reader reader(input);
+ bool value = true;
+ ASSERT_EQ(Success, OptionalBoolean(reader, value)) <<
+ "Should accept another valid encoding of an omitted OPTIONAL BOOLEAN";
+ ASSERT_FALSE(value);
+ ASSERT_TRUE(reader.AtEnd());
+ }
+}
+
+TEST_F(pkixder_universal_types_tests, OptionalBooleanInvalidEncodings)
+{
+ const uint8_t DER_OPTIONAL_BOOLEAN_PRESENT_FALSE[] = {
+ 0x01, // BOOLEAN
+ 0x01, // length
+ 0x00 // false
+ };
+
+ {
+ Input input(DER_OPTIONAL_BOOLEAN_PRESENT_FALSE);
+ Reader reader(input);
+ bool value = true;
+ ASSERT_EQ(Success, OptionalBoolean(reader, value)) <<
+ "Should accept an invalid, default-value encoding of OPTIONAL BOOLEAN";
+ ASSERT_FALSE(value);
+ ASSERT_TRUE(reader.AtEnd());
+ }
+
+ const uint8_t DER_OPTIONAL_BOOLEAN_PRESENT_42[] = {
+ 0x01, // BOOLEAN
+ 0x01, // length
+ 0x42 // (invalid value for a BOOLEAN)
+ };
+
+ {
+ Input input(DER_OPTIONAL_BOOLEAN_PRESENT_42);
+ Reader reader(input);
+ bool value;
+ ASSERT_EQ(Result::ERROR_BAD_DER, OptionalBoolean(reader, value)) <<
+ "Should reject an invalid-valued encoding of OPTIONAL BOOLEAN";
+ }
+}
+
+TEST_F(pkixder_universal_types_tests, Enumerated)
+{
+ const uint8_t DER_ENUMERATED[] = {
+ 0x0a, // ENUMERATED
+ 0x01, // length
+ 0x42 // value
+ };
+ Input input(DER_ENUMERATED);
+ Reader reader(input);
+
+ uint8_t value = 0;
+ ASSERT_EQ(Success, Enumerated(reader, value));
+ ASSERT_EQ(0x42, value);
+}
+
+TEST_F(pkixder_universal_types_tests, EnumeratedNotShortestPossibleDER)
+{
+ const uint8_t DER_ENUMERATED[] = {
+ 0x0a, // ENUMERATED
+ 0x02, // length
+ 0x00, 0x01 // value
+ };
+ Input input(DER_ENUMERATED);
+ Reader reader(input);
+
+ uint8_t value = 0;
+ ASSERT_EQ(Result::ERROR_INVALID_INTEGER_ENCODING, Enumerated(reader, value));
+}
+
+TEST_F(pkixder_universal_types_tests, EnumeratedOutOfAcceptedRange)
+{
+ // Although this is a valid ENUMERATED value according to ASN.1, we
+ // intentionally don't support these large values because there are no
+ // ENUMERATED values in X.509 certs or OCSP this large, and we're trying to
+ // keep the parser simple and fast.
+ const uint8_t DER_ENUMERATED_INVALID_LENGTH[] = {
+ 0x0a, // ENUMERATED
+ 0x02, // length
+ 0x12, 0x34 // value
+ };
+ Input input(DER_ENUMERATED_INVALID_LENGTH);
+ Reader reader(input);
+
+ uint8_t value = 0;
+ ASSERT_EQ(Result::ERROR_INVALID_INTEGER_ENCODING, Enumerated(reader, value));
+}
+
+TEST_F(pkixder_universal_types_tests, EnumeratedInvalidZeroLength)
+{
+ const uint8_t DER_ENUMERATED_INVALID_ZERO_LENGTH[] = {
+ 0x0a, // ENUMERATED
+ 0x00 // length
+ };
+ Input input(DER_ENUMERATED_INVALID_ZERO_LENGTH);
+ Reader reader(input);
+
+ uint8_t value = 0;
+ ASSERT_EQ(Result::ERROR_INVALID_INTEGER_ENCODING, Enumerated(reader, value));
+}
+
+////////////////////////////////////////
+// GeneralizedTime and TimeChoice
+//
+// From RFC 5280 section 4.1.2.5.2
+//
+// For the purposes of this profile, GeneralizedTime values MUST be
+// expressed in Greenwich Mean Time (Zulu) and MUST include seconds
+// (i.e., times are YYYYMMDDHHMMSSZ), even where the number of seconds
+// is zero. GeneralizedTime values MUST NOT include fractional seconds.
+//
+// And from from RFC 6960 (OCSP) section 4.2.2.1:
+//
+// Responses can contain four times -- thisUpdate, nextUpdate,
+// producedAt, and revocationTime. The semantics of these fields are
+// defined in Section 2.4. The format for GeneralizedTime is as
+// specified in Section 4.1.2.5.2 of [RFC5280].
+//
+// So while we can could accept other ASN1 (ITU-T X.680) encodings for
+// GeneralizedTime we should not accept them, and breaking reading of these
+// other encodings is actually encouraged.
+
+// e.g. TWO_CHARS(53) => '5', '3'
+#define TWO_CHARS(t) \
+ static_cast<uint8_t>('0' + (static_cast<uint8_t>(t) / 10u)), \
+ static_cast<uint8_t>('0' + (static_cast<uint8_t>(t) % 10u))
+
+// Calls TimeChoice on the UTCTime variant of the given generalized time.
+template <uint16_t LENGTH>
+Result
+TimeChoiceForEquivalentUTCTime(const uint8_t (&generalizedTimeDER)[LENGTH],
+ /*out*/ Time& value)
+{
+ static_assert(LENGTH >= 4,
+ "TimeChoiceForEquivalentUTCTime input too small");
+ uint8_t utcTimeDER[LENGTH - 2];
+ utcTimeDER[0] = 0x17; // tag UTCTime
+ utcTimeDER[1] = LENGTH - 1/*tag*/ - 1/*value*/ - 2/*century*/;
+ // Copy the value except for the first two digits of the year
+ for (size_t i = 2; i < LENGTH - 2; ++i) {
+ utcTimeDER[i] = generalizedTimeDER[i + 2];
+ }
+
+ Input input(utcTimeDER);
+ Reader reader(input);
+ return TimeChoice(reader, value);
+}
+
+template <uint16_t LENGTH>
+void
+ExpectGoodTime(Time expectedValue,
+ const uint8_t (&generalizedTimeDER)[LENGTH])
+{
+ // GeneralizedTime
+ {
+ Input input(generalizedTimeDER);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Success, GeneralizedTime(reader, value));
+ EXPECT_EQ(expectedValue, value);
+ }
+
+ // TimeChoice: GeneralizedTime
+ {
+ Input input(generalizedTimeDER);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Success, TimeChoice(reader, value));
+ EXPECT_EQ(expectedValue, value);
+ }
+
+ // TimeChoice: UTCTime
+ {
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Success,
+ TimeChoiceForEquivalentUTCTime(generalizedTimeDER, value));
+ EXPECT_EQ(expectedValue, value);
+ }
+}
+
+template <uint16_t LENGTH>
+void
+ExpectBadTime(const uint8_t (&generalizedTimeDER)[LENGTH])
+{
+ // GeneralizedTime
+ {
+ Input input(generalizedTimeDER);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, GeneralizedTime(reader, value));
+ }
+
+ // TimeChoice: GeneralizedTime
+ {
+ Input input(generalizedTimeDER);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, TimeChoice(reader, value));
+ }
+
+ // TimeChoice: UTCTime
+ {
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME,
+ TimeChoiceForEquivalentUTCTime(generalizedTimeDER, value));
+ }
+}
+
+// Control value: a valid time
+TEST_F(pkixder_universal_types_tests, ValidControl)
+{
+ const uint8_t GT_DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '1', '9', '9', '1', '0', '5', '0', '6', '1', '6', '4', '5', '4', '0', 'Z'
+ };
+ ExpectGoodTime(YMDHMS(1991, 5, 6, 16, 45, 40), GT_DER);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeTimeZoneOffset)
+{
+ const uint8_t DER_GENERALIZED_TIME_OFFSET[] = {
+ 0x18, // Generalized Time
+ 19, // Length = 19
+ '1', '9', '9', '1', '0', '5', '0', '6', '1', '6', '4', '5', '4', '0', '-',
+ '0', '7', '0', '0'
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_OFFSET);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeInvalidZeroLength)
+{
+ const uint8_t DER_GENERALIZED_TIME_INVALID_ZERO_LENGTH[] = {
+ 0x18, // GeneralizedTime
+ 0x00 // Length = 0
+ };
+
+ Time value(Time::uninitialized);
+
+ // GeneralizedTime
+ Input gtBuf(DER_GENERALIZED_TIME_INVALID_ZERO_LENGTH);
+ Reader gt(gtBuf);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, GeneralizedTime(gt, value));
+
+ // TimeChoice: GeneralizedTime
+ Input tc_gt_buf(DER_GENERALIZED_TIME_INVALID_ZERO_LENGTH);
+ Reader tc_gt(tc_gt_buf);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, TimeChoice(tc_gt, value));
+
+ // TimeChoice: UTCTime
+ const uint8_t DER_UTCTIME_INVALID_ZERO_LENGTH[] = {
+ 0x17, // UTCTime
+ 0x00 // Length = 0
+ };
+ Input tc_utc_buf(DER_UTCTIME_INVALID_ZERO_LENGTH);
+ Reader tc_utc(tc_utc_buf);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, TimeChoice(tc_utc, value));
+}
+
+// A non zulu time should fail
+TEST_F(pkixder_universal_types_tests, TimeInvalidLocal)
+{
+ const uint8_t DER_GENERALIZED_TIME_INVALID_LOCAL[] = {
+ 0x18, // Generalized Time
+ 14, // Length = 14
+ '1', '9', '9', '1', '0', '5', '0', '6', '1', '6', '4', '5', '4', '0'
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_INVALID_LOCAL);
+}
+
+// A time missing seconds and zulu should fail
+TEST_F(pkixder_universal_types_tests, TimeInvalidTruncated)
+{
+ const uint8_t DER_GENERALIZED_TIME_INVALID_TRUNCATED[] = {
+ 0x18, // Generalized Time
+ 12, // Length = 12
+ '1', '9', '9', '1', '0', '5', '0', '6', '1', '6', '4', '5'
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_INVALID_TRUNCATED);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeNoSeconds)
+{
+ const uint8_t DER_GENERALIZED_TIME_NO_SECONDS[] = {
+ 0x18, // Generalized Time
+ 13, // Length = 13
+ '1', '9', '9', '1', '0', '5', '0', '6', '1', '6', '4', '5', 'Z'
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_NO_SECONDS);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeInvalidPrefixedYear)
+{
+ const uint8_t DER_GENERALIZED_TIME_INVALID_PREFIXED_YEAR[] = {
+ 0x18, // Generalized Time
+ 16, // Length = 16
+ ' ', '1', '9', '9', '1', '0', '1', '0', '1', '0', '1', '0', '1', '0', '1', 'Z'
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_INVALID_PREFIXED_YEAR);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeTooManyDigits)
+{
+ const uint8_t DER_GENERALIZED_TIME_TOO_MANY_DIGITS[] = {
+ 0x18, // Generalized Time
+ 16, // Length = 16
+ '1', '1', '1', '1', '1', '0', '1', '0', '1', '0', '1', '0', '1', '0', '1', 'Z'
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_TOO_MANY_DIGITS);
+}
+
+// In order to ensure we we don't run into any trouble with conversions to and
+// from time_t we only accept times from 1970 onwards.
+TEST_F(pkixder_universal_types_tests, GeneralizedTimeYearValidRange)
+{
+ // Note that by using the last second of the last day of the year, we're also
+ // effectively testing all the accumulated conversions from Gregorian to to
+ // Julian time, including in particular the effects of leap years.
+
+ for (uint16_t i = 1970; i <= 9999; ++i) {
+ const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ TWO_CHARS(i / 100), TWO_CHARS(i % 100), // YYYY
+ '1', '2', '3', '1', // 12-31
+ '2', '3', '5', '9', '5', '9', 'Z' // 23:59:59Z
+ };
+
+ Time expectedValue = YMDHMS(i, 12, 31, 23, 59, 59);
+
+ // We have to test GeneralizedTime separately from UTCTime instead of using
+ // ExpectGooDtime because the range of UTCTime is less than the range of
+ // GeneralizedTime.
+
+ // GeneralizedTime
+ {
+ Input input(DER);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Success, GeneralizedTime(reader, value));
+ EXPECT_EQ(expectedValue, value);
+ }
+
+ // TimeChoice: GeneralizedTime
+ {
+ Input input(DER);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Success, TimeChoice(reader, value));
+ EXPECT_EQ(expectedValue, value);
+ }
+
+ // TimeChoice: UTCTime, which is limited to years less than 2049.
+ if (i <= 2049) {
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Success, TimeChoiceForEquivalentUTCTime(DER, value));
+ EXPECT_EQ(expectedValue, value);
+ }
+ }
+}
+
+// In order to ensure we we don't run into any trouble with conversions to and
+// from time_t we only accept times from 1970 onwards.
+TEST_F(pkixder_universal_types_tests, TimeYearInvalid1969)
+{
+ static const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '1', '9', '6', '9', '1', '2', '3', '1', // !!!1969!!!-12-31
+ '2', '3', '5', '9', '5', '9', 'Z' // 23:59:59Z
+ };
+ ExpectBadTime(DER);
+}
+
+static const uint8_t DAYS_IN_MONTH[] = {
+ 0, // unused
+ 31, // January
+ 28, // February (leap years tested separately)
+ 31, // March
+ 30, // April
+ 31, // May
+ 30, // Jun
+ 31, // July
+ 31, // August
+ 30, // September
+ 31, // October
+ 30, // November
+ 31, // December
+};
+
+TEST_F(pkixder_universal_types_tests, TimeMonthDaysValidRange)
+{
+ for (uint16_t month = 1; month <= 12; ++month) {
+ for (uint8_t day = 1; day <= DAYS_IN_MONTH[month]; ++day) {
+ const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '5', TWO_CHARS(month), TWO_CHARS(day), // (2015-mm-dd)
+ '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40
+ };
+ ExpectGoodTime(YMDHMS(2015, month, day, 16, 45, 40), DER);
+ }
+ }
+}
+
+TEST_F(pkixder_universal_types_tests, TimeMonthInvalid0)
+{
+ static const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '5', '0', '0', '1', '5', // 2015-!!!00!!!-15
+ '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40
+ };
+ ExpectBadTime(DER);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeMonthInvalid13)
+{
+ const uint8_t DER_GENERALIZED_TIME_13TH_MONTH[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '1', '9', '9', '1', //YYYY (1991)
+ '1', '3', //MM 13th month of the year
+ '0', '6', '1', '6', '4', '5', '4', '0', 'Z'
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_13TH_MONTH);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeDayInvalid0)
+{
+ static const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '5', '0', '1', '0', '0', // 2015-01-!!!00!!!
+ '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40
+ };
+ ExpectBadTime(DER);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeMonthDayInvalidPastEndOfMonth)
+{
+ for (int16_t month = 1; month <= 12; ++month) {
+ const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '1', '9', '9', '1', // YYYY 1991
+ TWO_CHARS(month), // MM
+ TWO_CHARS(1 + (month == 2 ? 29 : DAYS_IN_MONTH[month])), // !!!DD!!!
+ '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40
+ };
+ ExpectBadTime(DER);
+ }
+}
+
+TEST_F(pkixder_universal_types_tests, TimeMonthFebLeapYear2016)
+{
+ static const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '6', '0', '2', '2', '9', // 2016-02-29
+ '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40
+ };
+ ExpectGoodTime(YMDHMS(2016, 2, 29, 16, 45, 40), DER);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeMonthFebLeapYear2000)
+{
+ static const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '0', '0', '0', '2', '2', '9', // 2000-02-29
+ '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40
+ };
+ ExpectGoodTime(YMDHMS(2000, 2, 29, 16, 45, 40), DER);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeMonthFebLeapYear2400)
+{
+ static const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '4', '0', '0', '0', '2', '2', '9', // 2400-02-29
+ '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40
+ };
+
+ // We don't use ExpectGoodTime here because UTCTime can't represent 2400.
+
+ Time expectedValue = YMDHMS(2400, 2, 29, 16, 45, 40);
+
+ // GeneralizedTime
+ {
+ Input input(DER);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Success, GeneralizedTime(reader, value));
+ EXPECT_EQ(expectedValue, value);
+ }
+
+ // TimeChoice: GeneralizedTime
+ {
+ Input input(DER);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Success, TimeChoice(reader, value));
+ EXPECT_EQ(expectedValue, value);
+ }
+}
+
+TEST_F(pkixder_universal_types_tests, TimeMonthFebNotLeapYear2014)
+{
+ static const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '4', '0', '2', '2', '9', // 2014-02-29
+ '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40
+ };
+ ExpectBadTime(DER);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeMonthFebNotLeapYear2100)
+{
+ static const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '1', '0', '0', '0', '2', '2', '9', // 2100-02-29
+ '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40
+ };
+
+ // We don't use ExpectBadTime here because UTCTime can't represent 2100.
+
+ // GeneralizedTime
+ {
+ Input input(DER);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, GeneralizedTime(reader, value));
+ }
+
+ // TimeChoice: GeneralizedTime
+ {
+ Input input(DER);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, TimeChoice(reader, value));
+ }
+}
+
+TEST_F(pkixder_universal_types_tests, TimeHoursValidRange)
+{
+ for (uint8_t i = 0; i <= 23; ++i) {
+ const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30)
+ TWO_CHARS(i), '5', '9', '0', '1', 'Z' // HHMMSSZ (!!!!ii!!!!:59:01 Zulu)
+ };
+ ExpectGoodTime(YMDHMS(2012, 6, 30, i, 59, 1), DER);
+ }
+}
+
+TEST_F(pkixder_universal_types_tests, TimeHoursInvalid_24_00_00)
+{
+ static const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30)
+ '2', '4', '0', '0', '0', '0', 'Z' // HHMMSSZ (!!24!!:00:00 Zulu)
+ };
+ ExpectBadTime(DER);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeMinutesValidRange)
+{
+ for (uint8_t i = 0; i <= 59; ++i) {
+ const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30)
+ '2', '3', TWO_CHARS(i), '0', '1', 'Z' // HHMMSSZ (23:!!!!ii!!!!:01 Zulu)
+ };
+ ExpectGoodTime(YMDHMS(2012, 6, 30, 23, i, 1), DER);
+ }
+}
+
+TEST_F(pkixder_universal_types_tests, TimeMinutesInvalid60)
+{
+ const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30)
+ '2', '3', '6', '0', '5', '9', 'Z' // HHMMSSZ (23:!!!60!!!:01 Zulu)
+ };
+ ExpectBadTime(DER);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeSecondsValidRange)
+{
+ for (uint8_t i = 0; i <= 59; ++i) {
+ const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30)
+ '2', '3', '5', '9', TWO_CHARS(i), 'Z' // HHMMSSZ (23:59:!!!!ii!!!! Zulu)
+ };
+ ExpectGoodTime(YMDHMS(2012, 6, 30, 23, 59, i), DER);
+ }
+}
+
+// No Leap Seconds (60)
+TEST_F(pkixder_universal_types_tests, TimeSecondsInvalid60)
+{
+ static const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30)
+ '2', '3', '5', '9', '6', '0', 'Z' // HHMMSSZ (23:59:!!!!60!!!! Zulu)
+ };
+ ExpectBadTime(DER);
+}
+
+// No Leap Seconds (61)
+TEST_F(pkixder_universal_types_tests, TimeSecondsInvalid61)
+{
+ static const uint8_t DER[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30)
+ '2', '3', '5', '9', '6', '1', 'Z' // HHMMSSZ (23:59:!!!!61!!!! Zulu)
+ };
+ ExpectBadTime(DER);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeInvalidZulu)
+{
+ const uint8_t DER_GENERALIZED_TIME_INVALID_ZULU[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30)
+ '2', '3', '5', '9', '5', '9', 'z' // HHMMSSZ (23:59:59 !!!z!!!) should be Z
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_INVALID_ZULU);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeInvalidExtraData)
+{
+ const uint8_t DER_GENERALIZED_TIME_INVALID_EXTRA_DATA[] = {
+ 0x18, // Generalized Time
+ 16, // Length = 16
+ '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30)
+ '2', '3', '5', '9', '5', '9', 'Z', // HHMMSSZ (23:59:59Z)
+ 0 // Extra null character
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_INVALID_EXTRA_DATA);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeInvalidCenturyChar)
+{
+ const uint8_t DER_GENERALIZED_TIME_INVALID_CENTURY_CHAR[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ 'X', '9', '9', '1', '1', '2', '0', '6', // YYYYMMDD (X991-12-06)
+ '1', '6', '4', '5', '4', '0', 'Z' // HHMMSSZ (16:45:40Z)
+ };
+
+ // We can't use ExpectBadTime here, because ExpectBadTime requires
+ // consistent results for GeneralizedTime and UTCTime, but the results
+ // for this input are different.
+
+ // GeneralizedTime
+ {
+ Input input(DER_GENERALIZED_TIME_INVALID_CENTURY_CHAR);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, GeneralizedTime(reader, value));
+ }
+
+ // TimeChoice: GeneralizedTime
+ {
+ Input input(DER_GENERALIZED_TIME_INVALID_CENTURY_CHAR);
+ Reader reader(input);
+ Time value(Time::uninitialized);
+ ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, TimeChoice(reader, value));
+ }
+
+ // This test is not applicable to TimeChoice: UTCTime
+}
+
+TEST_F(pkixder_universal_types_tests, TimeInvalidYearChar)
+{
+ const uint8_t DER_GENERALIZED_TIME_INVALID_YEAR_CHAR[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '1', '9', '9', 'I', '0', '1', '0', '6', // YYYYMMDD (199I-12-06)
+ '1', '6', '4', '5', '4', '0', 'Z' // HHMMSSZ (16:45:40Z)
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_INVALID_YEAR_CHAR);
+}
+
+TEST_F(pkixder_universal_types_tests, GeneralizedTimeInvalidMonthChar)
+{
+ const uint8_t DER_GENERALIZED_TIME_INVALID_MONTH_CHAR[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '1', '9', '9', '1', '0', 'I', '0', '6', // YYYYMMDD (1991-0I-06)
+ '1', '6', '4', '5', '4', '0', 'Z' // HHMMSSZ (16:45:40Z)
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_INVALID_MONTH_CHAR);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeInvalidDayChar)
+{
+ const uint8_t DER_GENERALIZED_TIME_INVALID_DAY_CHAR[] = {
+ 0x18, // Generalized Time
+ 15, // Length = 15
+ '1', '9', '9', '1', '0', '1', '0', 'S', // YYYYMMDD (1991-01-0S)
+ '1', '6', '4', '5', '4', '0', 'Z' // HHMMSSZ (16:45:40Z)
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_INVALID_DAY_CHAR);
+}
+
+TEST_F(pkixder_universal_types_tests, TimeInvalidFractionalSeconds)
+{
+ const uint8_t DER_GENERALIZED_TIME_INVALID_FRACTIONAL_SECONDS[] = {
+ 0x18, // Generalized Time
+ 17, // Length = 17
+ '1', '9', '9', '1', '0', '1', '0', '1', // YYYYMMDD (1991-01-01)
+ '1', '6', '4', '5', '4', '0', '.', '3', 'Z' // HHMMSS.FFF (16:45:40.3Z)
+ };
+ ExpectBadTime(DER_GENERALIZED_TIME_INVALID_FRACTIONAL_SECONDS);
+}
+
+struct IntegerTestParams
+{
+ ByteString encoded;
+ struct PositiveIntegerParams
+ {
+ Result expectedResult;
+ Input::size_type significantBytesIfValid;
+ } positiveInteger;
+ struct SmallNonnegativeIntegerParams
+ {
+ Result expectedResult;
+ uint8_t valueIfValid;
+ } smallNonnegativeInteger;
+};
+
+class pkixder_universal_types_tests_Integer
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<IntegerTestParams>
+{
+};
+
+#define INVALID 0xFF
+
+static const IntegerTestParams INTEGER_TEST_PARAMS[] =
+{
+ // Zero is encoded with one value byte of 0x00.
+ { TLV(2, ByteString()),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\x00"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Success, 0 } },
+
+ // Positive single-byte values
+ { TLV(2, "\x01"), { Success, 1 }, { Success, 1} },
+ { TLV(2, "\x02"), { Success, 1 }, { Success, 2} },
+ { TLV(2, "\x7e"), { Success, 1 }, { Success, 0x7e} },
+ { TLV(2, "\x7f"), { Success, 1 }, { Success, 0x7f} },
+
+ // Negative single-byte values
+ { TLV(2, "\x80"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\x81"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\xFE"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\xFF"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+
+ // Positive two-byte values not starting with 0x00
+ { TLV(2, "\x7F\x00"),
+ { Success, 2 },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\x01\x00"),
+ { Success, 2 },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\x01\x02"),
+ { Success, 2 },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+
+ // Negative two-byte values not starting with 0xFF
+ { TLV(2, "\x80\x00"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\x80\x7F"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\x80\x80"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\x80\xFF"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+
+ // The leading zero is necessary.
+ { TLV(2, "\x00\x80"),
+ { Success, 1},
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\x00\x81"),
+ { Success, 1},
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\x00\xFF"),
+ { Success, 1},
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+
+ // The leading zero is unnecessary.
+ { TLV(2, "\x00\x01"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\x00\x7F"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+
+ // The leading 0xFF is necessary.
+ { TLV(2, "\xFF\x00"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\xFF\x7F"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+
+ // The leading 0xFF is unnecessary.
+ { TLV(2, "\xFF\x80"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2, "\xFF\xFF"),
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+
+ // Truncated values
+ { TLV(2, 1, ByteString(/*missing value*/)),
+ { Result::ERROR_BAD_DER, INVALID },
+ { Result::ERROR_BAD_DER, INVALID } },
+ { TLV(2, 3, "\x11\x22" /*truncated*/),
+ { Result::ERROR_BAD_DER, INVALID },
+ { Result::ERROR_BAD_DER, INVALID } },
+ { TLV(2, 4, "\x11\x22" /*truncated*/),
+ { Result::ERROR_BAD_DER, INVALID },
+ { Result::ERROR_BAD_DER, INVALID } },
+ { TLV(2, 2, "\x00" /*truncated*/),
+ { Result::ERROR_BAD_DER, INVALID },
+ { Result::ERROR_BAD_DER, INVALID } },
+ { TLV(2, 2, "\xFF" /*truncated*/),
+ { Result::ERROR_BAD_DER, INVALID },
+ { Result::ERROR_BAD_DER, INVALID } },
+ { TLV(2, 3, "\x00\x80" /*truncated*/),
+ { Result::ERROR_BAD_DER, INVALID },
+ { Result::ERROR_BAD_DER, INVALID } },
+ { TLV(2, 3, "\xFF\x00" /*truncated*/),
+ { Result::ERROR_BAD_DER, INVALID },
+ { Result::ERROR_BAD_DER, INVALID } },
+
+ // Misc. larger values
+ { TLV(2, 4, "\x11\x22\x33\x44"),
+ { Success, 4 },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+ { TLV(2,
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"),
+ { Success, 256 },
+ { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } },
+};
+
+TEST_P(pkixder_universal_types_tests_Integer, Integer)
+{
+ const IntegerTestParams& params(GetParam());
+ Input input;
+ ASSERT_EQ(Success, input.Init(params.encoded.data(),
+ params.encoded.length()));
+ Reader reader(input);
+ Result expectedResult = params.smallNonnegativeInteger.expectedResult;
+ uint8_t value;
+ ASSERT_EQ(expectedResult, der::Integer(reader, value));
+ if (expectedResult == Success) {
+ ASSERT_EQ(params.smallNonnegativeInteger.valueIfValid, value);
+ ASSERT_TRUE(reader.AtEnd());
+ }
+}
+
+TEST_P(pkixder_universal_types_tests_Integer,
+ PositiveInteger_without_significantBytes)
+{
+ const IntegerTestParams& params(GetParam());
+ Input input;
+ ASSERT_EQ(Success, input.Init(params.encoded.data(),
+ params.encoded.length()));
+ Reader reader(input);
+ Result expectedResult = params.positiveInteger.expectedResult;
+ Input value;
+ ASSERT_EQ(expectedResult, der::PositiveInteger(reader, value));
+ if (expectedResult == Success) {
+ Reader anotherReader(input);
+ Input expectedValue;
+ ASSERT_EQ(Success, ExpectTagAndGetValue(anotherReader,
+ der::INTEGER, expectedValue));
+ ASSERT_TRUE(InputsAreEqual(expectedValue, value));
+ ASSERT_TRUE(reader.AtEnd());
+ }
+}
+
+TEST_P(pkixder_universal_types_tests_Integer,
+ PositiveInteger_with_significantBytes)
+{
+ const IntegerTestParams& params(GetParam());
+ Input input;
+ ASSERT_EQ(Success, input.Init(params.encoded.data(),
+ params.encoded.length()));
+ Reader reader(input);
+ Result expectedResult = params.positiveInteger.expectedResult;
+ Input value;
+ Input::size_type significantBytes = INVALID;
+ ASSERT_EQ(expectedResult, der::PositiveInteger(reader, value,
+ &significantBytes));
+ if (expectedResult == Success) {
+ ASSERT_NE(INVALID, params.positiveInteger.significantBytesIfValid);
+ ASSERT_EQ(params.positiveInteger.significantBytesIfValid,
+ significantBytes);
+
+ Reader anotherReader(input);
+ Input expectedValue;
+ ASSERT_EQ(Success, ExpectTagAndGetValue(anotherReader,
+ der::INTEGER, expectedValue));
+ ASSERT_TRUE(InputsAreEqual(expectedValue, value));
+ ASSERT_TRUE(reader.AtEnd());
+ }
+}
+
+#undef INVALID
+
+INSTANTIATE_TEST_CASE_P(pkixder_universal_types_tests_Integer,
+ pkixder_universal_types_tests_Integer,
+ testing::ValuesIn(INTEGER_TEST_PARAMS));
+
+TEST_F(pkixder_universal_types_tests, OptionalIntegerSupportedDefault)
+{
+ // The input is a BOOLEAN and not INTEGER for the input so we'll not parse
+ // anything and instead use the default value.
+ Input input(DER_BOOLEAN_TRUE);
+ Reader reader(input);
+
+ long value = 1;
+ ASSERT_EQ(Success, OptionalInteger(reader, -1, value));
+ ASSERT_EQ(-1, value);
+ bool boolValue;
+ ASSERT_EQ(Success, Boolean(reader, boolValue));
+}
+
+TEST_F(pkixder_universal_types_tests, OptionalIntegerUnsupportedDefault)
+{
+ // The same as the previous test, except with an unsupported default value
+ // passed in.
+ Input input(DER_BOOLEAN_TRUE);
+ Reader reader(input);
+
+ long value;
+ ASSERT_EQ(Result::FATAL_ERROR_INVALID_ARGS, OptionalInteger(reader, 0, value));
+}
+
+TEST_F(pkixder_universal_types_tests, OptionalIntegerSupportedDefaultAtEnd)
+{
+ static const uint8_t dummy = 1;
+ Input input;
+ ASSERT_EQ(Success, input.Init(&dummy, 0));
+ Reader reader(input);
+
+ long value = 1;
+ ASSERT_EQ(Success, OptionalInteger(reader, -1, value));
+ ASSERT_EQ(-1, value);
+}
+
+TEST_F(pkixder_universal_types_tests, OptionalIntegerNonDefaultValue)
+{
+ static const uint8_t DER[] = {
+ 0x02, // INTEGER
+ 0x01, // length
+ 0x00
+ };
+ Input input(DER);
+ Reader reader(input);
+
+ long value = 2;
+ ASSERT_EQ(Success, OptionalInteger(reader, -1, value));
+ ASSERT_EQ(0, value);
+ ASSERT_TRUE(reader.AtEnd());
+}
+
+TEST_F(pkixder_universal_types_tests, Null)
+{
+ const uint8_t DER_NUL[] = {
+ 0x05,
+ 0x00
+ };
+ Input input(DER_NUL);
+ Reader reader(input);
+
+ ASSERT_EQ(Success, Null(reader));
+}
+
+TEST_F(pkixder_universal_types_tests, NullWithBadLength)
+{
+ const uint8_t DER_NULL_BAD_LENGTH[] = {
+ 0x05,
+ 0x01,
+ 0x00
+ };
+ Input input(DER_NULL_BAD_LENGTH);
+ Reader reader(input);
+
+ ASSERT_EQ(Result::ERROR_BAD_DER, Null(reader));
+}
+
+TEST_F(pkixder_universal_types_tests, OID)
+{
+ const uint8_t DER_VALID_OID[] = {
+ 0x06,
+ 0x09,
+ 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01
+ };
+ Input input(DER_VALID_OID);
+ Reader reader(input);
+
+ const uint8_t expectedOID[] = {
+ 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01
+ };
+
+ ASSERT_EQ(Success, OID(reader, expectedOID));
+}
diff --git a/security/pkix/test/gtest/pkixgtest.cpp b/security/pkix/test/gtest/pkixgtest.cpp
new file mode 100644
index 000000000..77baef857
--- /dev/null
+++ b/security/pkix/test/gtest/pkixgtest.cpp
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2013 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pkixgtest.h"
+
+#include <ctime>
+
+#include "pkix/Time.h"
+
+namespace mozilla { namespace pkix { namespace test {
+
+static const std::time_t ONE_DAY_IN_SECONDS_AS_TIME_T =
+ static_cast<std::time_t>(Time::ONE_DAY_IN_SECONDS);
+
+// This assumes that time/time_t are POSIX-compliant in that time() returns
+// the number of seconds since the Unix epoch.
+static const std::time_t now(time(nullptr));
+const std::time_t oneDayBeforeNow(now - ONE_DAY_IN_SECONDS_AS_TIME_T);
+const std::time_t oneDayAfterNow(now + ONE_DAY_IN_SECONDS_AS_TIME_T);
+const std::time_t twoDaysBeforeNow(now - (2 * ONE_DAY_IN_SECONDS_AS_TIME_T));
+const std::time_t twoDaysAfterNow(now + (2 * ONE_DAY_IN_SECONDS_AS_TIME_T));
+const std::time_t tenDaysBeforeNow(now - (10 * ONE_DAY_IN_SECONDS_AS_TIME_T));
+const std::time_t tenDaysAfterNow(now + (10 * ONE_DAY_IN_SECONDS_AS_TIME_T));
+
+} } } // namespace mozilla::pkix::test
diff --git a/security/pkix/test/gtest/pkixgtest.h b/security/pkix/test/gtest/pkixgtest.h
new file mode 100644
index 000000000..1ec8727e2
--- /dev/null
+++ b/security/pkix/test/gtest/pkixgtest.h
@@ -0,0 +1,255 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2014 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef mozilla_pkix_pkixgtest_h
+#define mozilla_pkix_pkixgtest_h
+
+#include <ostream>
+
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated"
+#pragma clang diagnostic ignored "-Wmissing-noreturn"
+#pragma clang diagnostic ignored "-Wshift-sign-overflow"
+#pragma clang diagnostic ignored "-Wsign-conversion"
+#pragma clang diagnostic ignored "-Wundef"
+#elif defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wextra"
+#elif defined(_MSC_VER)
+#pragma warning(push, 3)
+// C4224: Nonstandard extension used: formal parameter 'X' was previously
+// defined as a type.
+#pragma warning(disable: 4224)
+// C4826: Conversion from 'type1 ' to 'type_2' is sign - extended. This may
+// cause unexpected runtime behavior.
+#pragma warning(disable: 4826)
+#endif
+
+#include "gtest/gtest.h"
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#elif defined(__GNUC__)
+#pragma GCC diagnostic pop
+#elif defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+#include "pkix/pkix.h"
+#include "pkixtestutil.h"
+
+// PrintTo must be in the same namespace as the type we're overloading it for.
+namespace mozilla { namespace pkix {
+
+inline void
+PrintTo(const Result& result, ::std::ostream* os)
+{
+ const char* stringified = MapResultToName(result);
+ if (stringified) {
+ *os << stringified;
+ } else {
+ *os << "mozilla::pkix::Result(" << static_cast<unsigned int>(result) << ")";
+ }
+}
+
+} } // namespace mozilla::pkix
+
+namespace mozilla { namespace pkix { namespace test {
+
+extern const std::time_t oneDayBeforeNow;
+extern const std::time_t oneDayAfterNow;
+extern const std::time_t twoDaysBeforeNow;
+extern const std::time_t twoDaysAfterNow;
+extern const std::time_t tenDaysBeforeNow;
+extern const std::time_t tenDaysAfterNow;
+
+
+class EverythingFailsByDefaultTrustDomain : public TrustDomain
+{
+public:
+ Result GetCertTrust(EndEntityOrCA, const CertPolicyId&,
+ Input, /*out*/ TrustLevel&) override
+ {
+ ADD_FAILURE();
+ return NotReached("GetCertTrust should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ Result FindIssuer(Input, IssuerChecker&, Time) override
+ {
+ ADD_FAILURE();
+ return NotReached("FindIssuer should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+ /*optional*/ const Input*,
+ /*optional*/ const Input*) override
+ {
+ ADD_FAILURE();
+ return NotReached("CheckRevocation should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ Result IsChainValid(const DERArray&, Time) override
+ {
+ ADD_FAILURE();
+ return NotReached("IsChainValid should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ Result DigestBuf(Input, DigestAlgorithm, /*out*/ uint8_t*, size_t) override
+ {
+ ADD_FAILURE();
+ return NotReached("DigestBuf should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ Result CheckSignatureDigestAlgorithm(DigestAlgorithm,
+ EndEntityOrCA,
+ Time) override
+ {
+ ADD_FAILURE();
+ return NotReached("CheckSignatureDigestAlgorithm should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override
+ {
+ ADD_FAILURE();
+ return NotReached("CheckECDSACurveIsAcceptable should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ Result VerifyECDSASignedDigest(const SignedDigest&, Input) override
+ {
+ ADD_FAILURE();
+ return NotReached("VerifyECDSASignedDigest should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, unsigned int)
+ override
+ {
+ ADD_FAILURE();
+ return NotReached("CheckRSAPublicKeyModulusSizeInBits should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ Result VerifyRSAPKCS1SignedDigest(const SignedDigest&, Input) override
+ {
+ ADD_FAILURE();
+ return NotReached("VerifyRSAPKCS1SignedDigest should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ Result CheckValidityIsAcceptable(Time, Time, EndEntityOrCA, KeyPurposeId)
+ override
+ {
+ ADD_FAILURE();
+ return NotReached("CheckValidityIsAcceptable should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ Result NetscapeStepUpMatchesServerAuth(Time, bool&) override
+ {
+ ADD_FAILURE();
+ return NotReached("NetscapeStepUpMatchesServerAuth should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+
+ virtual void NoteAuxiliaryExtension(AuxiliaryExtension, Input) override
+ {
+ ADD_FAILURE();
+ }
+};
+
+class DefaultCryptoTrustDomain : public EverythingFailsByDefaultTrustDomain
+{
+ Result DigestBuf(Input item, DigestAlgorithm digestAlg,
+ /*out*/ uint8_t* digestBuf, size_t digestBufLen) override
+ {
+ return TestDigestBuf(item, digestAlg, digestBuf, digestBufLen);
+ }
+
+ Result CheckSignatureDigestAlgorithm(DigestAlgorithm, EndEntityOrCA, Time)
+ override
+ {
+ return Success;
+ }
+
+ Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override
+ {
+ return Success;
+ }
+
+ Result VerifyECDSASignedDigest(const SignedDigest& signedDigest,
+ Input subjectPublicKeyInfo) override
+ {
+ return TestVerifyECDSASignedDigest(signedDigest, subjectPublicKeyInfo);
+ }
+
+ Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, unsigned int)
+ override
+ {
+ return Success;
+ }
+
+ Result VerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest,
+ Input subjectPublicKeyInfo) override
+ {
+ return TestVerifyRSAPKCS1SignedDigest(signedDigest, subjectPublicKeyInfo);
+ }
+
+ Result CheckValidityIsAcceptable(Time, Time, EndEntityOrCA, KeyPurposeId)
+ override
+ {
+ return Success;
+ }
+
+ Result NetscapeStepUpMatchesServerAuth(Time, /*out*/ bool& matches) override
+ {
+ matches = true;
+ return Success;
+ }
+
+ void NoteAuxiliaryExtension(AuxiliaryExtension, Input) override
+ {
+ }
+};
+
+class DefaultNameMatchingPolicy : public NameMatchingPolicy
+{
+public:
+ virtual Result FallBackToCommonName(
+ Time, /*out*/ FallBackToSearchWithinSubject& fallBackToCommonName) override
+ {
+ fallBackToCommonName = FallBackToSearchWithinSubject::Yes;
+ return Success;
+ }
+};
+
+} } } // namespace mozilla::pkix::test
+
+#endif // mozilla_pkix_pkixgtest_h
diff --git a/security/pkix/test/gtest/pkixnames_tests.cpp b/security/pkix/test/gtest/pkixnames_tests.cpp
new file mode 100644
index 000000000..9e2303ad0
--- /dev/null
+++ b/security/pkix/test/gtest/pkixnames_tests.cpp
@@ -0,0 +1,2811 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2014 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "pkixcheck.h"
+#include "pkixder.h"
+#include "pkixgtest.h"
+#include "pkixutil.h"
+
+namespace mozilla { namespace pkix {
+
+Result MatchPresentedDNSIDWithReferenceDNSID(Input presentedDNSID,
+ Input referenceDNSID,
+ /*out*/ bool& matches);
+
+bool IsValidReferenceDNSID(Input hostname);
+bool IsValidPresentedDNSID(Input hostname);
+bool ParseIPv4Address(Input hostname, /*out*/ uint8_t (&out)[4]);
+bool ParseIPv6Address(Input hostname, /*out*/ uint8_t (&out)[16]);
+
+} } // namespace mozilla::pkix
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+struct PresentedMatchesReference
+{
+ ByteString presentedDNSID;
+ ByteString referenceDNSID;
+ Result expectedResult;
+ bool expectedMatches; // only valid when expectedResult == Success
+};
+
+#define DNS_ID_MATCH(a, b) \
+ { \
+ ByteString(reinterpret_cast<const uint8_t*>(a), sizeof(a) - 1), \
+ ByteString(reinterpret_cast<const uint8_t*>(b), sizeof(b) - 1), \
+ Success, \
+ true \
+ }
+
+#define DNS_ID_MISMATCH(a, b) \
+ { \
+ ByteString(reinterpret_cast<const uint8_t*>(a), sizeof(a) - 1), \
+ ByteString(reinterpret_cast<const uint8_t*>(b), sizeof(b) - 1), \
+ Success, \
+ false \
+ }
+
+#define DNS_ID_BAD_DER(a, b) \
+ { \
+ ByteString(reinterpret_cast<const uint8_t*>(a), sizeof(a) - 1), \
+ ByteString(reinterpret_cast<const uint8_t*>(b), sizeof(b) - 1), \
+ Result::ERROR_BAD_DER, \
+ false \
+ }
+
+static const PresentedMatchesReference DNSID_MATCH_PARAMS[] =
+{
+ DNS_ID_BAD_DER("", "a"),
+
+ DNS_ID_MATCH("a", "a"),
+ DNS_ID_MISMATCH("b", "a"),
+
+ DNS_ID_MATCH("*.b.a", "c.b.a"),
+ DNS_ID_MISMATCH("*.b.a", "b.a"),
+ DNS_ID_MISMATCH("*.b.a", "b.a."),
+
+ // We allow underscores for compatibility with existing practices.
+ DNS_ID_MATCH("a_b", "a_b"),
+ DNS_ID_MATCH("*.example.com", "uses_underscore.example.com"),
+ DNS_ID_MATCH("*.uses_underscore.example.com", "a.uses_underscore.example.com"),
+
+ // See bug 1139039
+ DNS_ID_MATCH("_.example.com", "_.example.com"),
+ DNS_ID_MATCH("*.example.com", "_.example.com"),
+ DNS_ID_MATCH("_", "_"),
+ DNS_ID_MATCH("___", "___"),
+ DNS_ID_MATCH("example_", "example_"),
+ DNS_ID_MATCH("_example", "_example"),
+ DNS_ID_MATCH("*._._", "x._._"),
+
+ // See bug 1139039
+ // A DNS-ID must not end in an all-numeric label. We don't consider
+ // underscores to be numeric.
+ DNS_ID_MATCH("_1", "_1"),
+ DNS_ID_MATCH("example._1", "example._1"),
+ DNS_ID_MATCH("example.1_", "example.1_"),
+
+ // Wildcard not in leftmost label
+ DNS_ID_MATCH("d.c.b.a", "d.c.b.a"),
+ DNS_ID_BAD_DER("d.*.b.a", "d.c.b.a"),
+ DNS_ID_BAD_DER("d.c*.b.a", "d.c.b.a"),
+ DNS_ID_BAD_DER("d.c*.b.a", "d.cc.b.a"),
+
+ // case sensitivity
+ DNS_ID_MATCH("abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
+ DNS_ID_MATCH("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"),
+ DNS_ID_MATCH("aBc", "Abc"),
+
+ // digits
+ DNS_ID_MATCH("a1", "a1"),
+
+ // A trailing dot indicates an absolute name. Absolute presented names are
+ // not allowed, but absolute reference names are allowed.
+ DNS_ID_MATCH("example", "example"),
+ DNS_ID_BAD_DER("example.", "example."),
+ DNS_ID_MATCH("example", "example."),
+ DNS_ID_BAD_DER("example.", "example"),
+ DNS_ID_MATCH("example.com", "example.com"),
+ DNS_ID_BAD_DER("example.com.", "example.com."),
+ DNS_ID_MATCH("example.com", "example.com."),
+ DNS_ID_BAD_DER("example.com.", "example.com"),
+ DNS_ID_BAD_DER("example.com..", "example.com."),
+ DNS_ID_BAD_DER("example.com..", "example.com"),
+ DNS_ID_BAD_DER("example.com...", "example.com."),
+
+ // xn-- IDN prefix
+ DNS_ID_BAD_DER("x*.b.a", "xa.b.a"),
+ DNS_ID_BAD_DER("x*.b.a", "xna.b.a"),
+ DNS_ID_BAD_DER("x*.b.a", "xn-a.b.a"),
+ DNS_ID_BAD_DER("x*.b.a", "xn--a.b.a"),
+ DNS_ID_BAD_DER("xn*.b.a", "xn--a.b.a"),
+ DNS_ID_BAD_DER("xn-*.b.a", "xn--a.b.a"),
+ DNS_ID_BAD_DER("xn--*.b.a", "xn--a.b.a"),
+ DNS_ID_BAD_DER("xn*.b.a", "xn--a.b.a"),
+ DNS_ID_BAD_DER("xn-*.b.a", "xn--a.b.a"),
+ DNS_ID_BAD_DER("xn--*.b.a", "xn--a.b.a"),
+ DNS_ID_BAD_DER("xn---*.b.a", "xn--a.b.a"),
+
+ // "*" cannot expand to nothing.
+ DNS_ID_BAD_DER("c*.b.a", "c.b.a"),
+
+ /////////////////////////////////////////////////////////////////////////////
+ // These are test cases adapted from Chromium's x509_certificate_unittest.cc.
+ // The parameter order is the opposite in Chromium's tests. Also, some tests
+ // were modified to fit into this framework or due to intentional differences
+ // between mozilla::pkix and Chromium.
+
+ DNS_ID_MATCH("foo.com", "foo.com"),
+ DNS_ID_MATCH("f", "f"),
+ DNS_ID_MISMATCH("i", "h"),
+ DNS_ID_MATCH("*.foo.com", "bar.foo.com"),
+ DNS_ID_MATCH("*.test.fr", "www.test.fr"),
+ DNS_ID_MATCH("*.test.FR", "wwW.tESt.fr"),
+ DNS_ID_BAD_DER(".uk", "f.uk"),
+ DNS_ID_BAD_DER("?.bar.foo.com", "w.bar.foo.com"),
+ DNS_ID_BAD_DER("(www|ftp).foo.com", "www.foo.com"), // regex!
+ DNS_ID_BAD_DER("www.foo.com\0", "www.foo.com"),
+ DNS_ID_BAD_DER("www.foo.com\0*.foo.com", "www.foo.com"),
+ DNS_ID_MISMATCH("ww.house.example", "www.house.example"),
+ DNS_ID_MISMATCH("www.test.org", "test.org"),
+ DNS_ID_MISMATCH("*.test.org", "test.org"),
+ DNS_ID_BAD_DER("*.org", "test.org"),
+ DNS_ID_BAD_DER("w*.bar.foo.com", "w.bar.foo.com"),
+ DNS_ID_BAD_DER("ww*ww.bar.foo.com", "www.bar.foo.com"),
+ DNS_ID_BAD_DER("ww*ww.bar.foo.com", "wwww.bar.foo.com"),
+
+ // Different than Chromium, matches NSS.
+ DNS_ID_BAD_DER("w*w.bar.foo.com", "wwww.bar.foo.com"),
+
+ DNS_ID_BAD_DER("w*w.bar.foo.c0m", "wwww.bar.foo.com"),
+
+ // '*' must be the only character in the wildcard label
+ DNS_ID_BAD_DER("wa*.bar.foo.com", "WALLY.bar.foo.com"),
+
+ // We require "*" to be the last character in a wildcard label, but
+ // Chromium does not.
+ DNS_ID_BAD_DER("*Ly.bar.foo.com", "wally.bar.foo.com"),
+
+ // Chromium does URL decoding of the reference ID, but we don't, and we also
+ // require that the reference ID is valid, so we can't test these two.
+ // DNS_ID_MATCH("www.foo.com", "ww%57.foo.com"),
+ // DNS_ID_MATCH("www&.foo.com", "www%26.foo.com"),
+
+ DNS_ID_MISMATCH("*.test.de", "www.test.co.jp"),
+ DNS_ID_BAD_DER("*.jp", "www.test.co.jp"),
+ DNS_ID_MISMATCH("www.test.co.uk", "www.test.co.jp"),
+ DNS_ID_BAD_DER("www.*.co.jp", "www.test.co.jp"),
+ DNS_ID_MATCH("www.bar.foo.com", "www.bar.foo.com"),
+ DNS_ID_MISMATCH("*.foo.com", "www.bar.foo.com"),
+ DNS_ID_BAD_DER("*.*.foo.com", "www.bar.foo.com"),
+ DNS_ID_BAD_DER("*.*.foo.com", "www.bar.foo.com"),
+
+ // Our matcher requires the reference ID to be a valid DNS name, so we cannot
+ // test this case.
+ //DNS_ID_BAD_DER("*.*.bar.foo.com", "*..bar.foo.com"),
+
+ DNS_ID_MATCH("www.bath.org", "www.bath.org"),
+
+ // Our matcher requires the reference ID to be a valid DNS name, so we cannot
+ // test these cases.
+ // DNS_ID_BAD_DER("www.bath.org", ""),
+ // DNS_ID_BAD_DER("www.bath.org", "20.30.40.50"),
+ // DNS_ID_BAD_DER("www.bath.org", "66.77.88.99"),
+
+ // IDN tests
+ DNS_ID_MATCH("xn--poema-9qae5a.com.br", "xn--poema-9qae5a.com.br"),
+ DNS_ID_MATCH("*.xn--poema-9qae5a.com.br", "www.xn--poema-9qae5a.com.br"),
+ DNS_ID_MISMATCH("*.xn--poema-9qae5a.com.br", "xn--poema-9qae5a.com.br"),
+ DNS_ID_BAD_DER("xn--poema-*.com.br", "xn--poema-9qae5a.com.br"),
+ DNS_ID_BAD_DER("xn--*-9qae5a.com.br", "xn--poema-9qae5a.com.br"),
+ DNS_ID_BAD_DER("*--poema-9qae5a.com.br", "xn--poema-9qae5a.com.br"),
+
+ // The following are adapted from the examples quoted from
+ // http://tools.ietf.org/html/rfc6125#section-6.4.3
+ // (e.g., *.example.com would match foo.example.com but
+ // not bar.foo.example.com or example.com).
+ DNS_ID_MATCH("*.example.com", "foo.example.com"),
+ DNS_ID_MISMATCH("*.example.com", "bar.foo.example.com"),
+ DNS_ID_MISMATCH("*.example.com", "example.com"),
+ // (e.g., baz*.example.net and *baz.example.net and b*z.example.net would
+ // be taken to match baz1.example.net and foobaz.example.net and
+ // buzz.example.net, respectively. However, we don't allow any characters
+ // other than '*' in the wildcard label.
+ DNS_ID_BAD_DER("baz*.example.net", "baz1.example.net"),
+
+ // Both of these are different from Chromium, but match NSS, becaues the
+ // wildcard character "*" is not the last character of the label.
+ DNS_ID_BAD_DER("*baz.example.net", "foobaz.example.net"),
+ DNS_ID_BAD_DER("b*z.example.net", "buzz.example.net"),
+
+ // Wildcards should not be valid for public registry controlled domains,
+ // and unknown/unrecognized domains, at least three domain components must
+ // be present. For mozilla::pkix and NSS, there must always be at least two
+ // labels after the wildcard label.
+ DNS_ID_MATCH("*.test.example", "www.test.example"),
+ DNS_ID_MATCH("*.example.co.uk", "test.example.co.uk"),
+ DNS_ID_BAD_DER("*.exmaple", "test.example"),
+
+ // The result is different than Chromium, because Chromium takes into account
+ // the additional knowledge it has that "co.uk" is a TLD. mozilla::pkix does
+ // not know that.
+ DNS_ID_MATCH("*.co.uk", "example.co.uk"),
+
+ DNS_ID_BAD_DER("*.com", "foo.com"),
+ DNS_ID_BAD_DER("*.us", "foo.us"),
+ DNS_ID_BAD_DER("*", "foo"),
+
+ // IDN variants of wildcards and registry controlled domains.
+ DNS_ID_MATCH("*.xn--poema-9qae5a.com.br", "www.xn--poema-9qae5a.com.br"),
+ DNS_ID_MATCH("*.example.xn--mgbaam7a8h", "test.example.xn--mgbaam7a8h"),
+
+ // RFC6126 allows this, and NSS accepts it, but Chromium disallows it.
+ // TODO: File bug against Chromium.
+ DNS_ID_MATCH("*.com.br", "xn--poema-9qae5a.com.br"),
+
+ DNS_ID_BAD_DER("*.xn--mgbaam7a8h", "example.xn--mgbaam7a8h"),
+ // Wildcards should be permissible for 'private' registry-controlled
+ // domains. (In mozilla::pkix, we do not know if it is a private registry-
+ // controlled domain or not.)
+ DNS_ID_MATCH("*.appspot.com", "www.appspot.com"),
+ DNS_ID_MATCH("*.s3.amazonaws.com", "foo.s3.amazonaws.com"),
+
+ // Multiple wildcards are not valid.
+ DNS_ID_BAD_DER("*.*.com", "foo.example.com"),
+ DNS_ID_BAD_DER("*.bar.*.com", "foo.bar.example.com"),
+
+ // Absolute vs relative DNS name tests. Although not explicitly specified
+ // in RFC 6125, absolute reference names (those ending in a .) should
+ // match either absolute or relative presented names. We don't allow
+ // absolute presented names.
+ // TODO: File errata against RFC 6125 about this.
+ DNS_ID_BAD_DER("foo.com.", "foo.com"),
+ DNS_ID_MATCH("foo.com", "foo.com."),
+ DNS_ID_BAD_DER("foo.com.", "foo.com."),
+ DNS_ID_BAD_DER("f.", "f"),
+ DNS_ID_MATCH("f", "f."),
+ DNS_ID_BAD_DER("f.", "f."),
+ DNS_ID_BAD_DER("*.bar.foo.com.", "www-3.bar.foo.com"),
+ DNS_ID_MATCH("*.bar.foo.com", "www-3.bar.foo.com."),
+ DNS_ID_BAD_DER("*.bar.foo.com.", "www-3.bar.foo.com."),
+
+ // We require the reference ID to be a valid DNS name, so we cannot test this
+ // case.
+ // DNS_ID_MISMATCH(".", "."),
+
+ DNS_ID_BAD_DER("*.com.", "example.com"),
+ DNS_ID_BAD_DER("*.com", "example.com."),
+ DNS_ID_BAD_DER("*.com.", "example.com."),
+ DNS_ID_BAD_DER("*.", "foo."),
+ DNS_ID_BAD_DER("*.", "foo"),
+
+ // The result is different than Chromium because we don't know that co.uk is
+ // a TLD.
+ DNS_ID_MATCH("*.co.uk", "foo.co.uk"),
+ DNS_ID_MATCH("*.co.uk", "foo.co.uk."),
+ DNS_ID_BAD_DER("*.co.uk.", "foo.co.uk"),
+ DNS_ID_BAD_DER("*.co.uk.", "foo.co.uk."),
+
+ DNS_ID_MISMATCH("*.example.com", "localhost"),
+ DNS_ID_MISMATCH("*.example.com", "localhost."),
+ // Note that we already have the testcase DNS_ID_BAD_DER("*", "foo") above
+};
+
+struct InputValidity
+{
+ ByteString input;
+ bool isValidReferenceID;
+ bool isValidPresentedID;
+};
+
+// str is null-terminated, which is why we subtract 1. str may contain embedded
+// nulls (including at the end) preceding the null terminator though.
+#define I(str, validReferenceID, validPresentedID) \
+ { \
+ ByteString(reinterpret_cast<const uint8_t*>(str), sizeof(str) - 1), \
+ validReferenceID, \
+ validPresentedID, \
+ }
+
+static const InputValidity DNSNAMES_VALIDITY[] =
+{
+ I("a", true, true),
+ I("a.b", true, true),
+ I("a.b.c", true, true),
+ I("a.b.c.d", true, true),
+
+ // empty labels
+ I("", false, false),
+ I(".", false, false),
+ I("a", true, true),
+ I(".a", false, false),
+ I(".a.b", false, false),
+ I("..a", false, false),
+ I("a..b", false, false),
+ I("a...b", false, false),
+ I("a..b.c", false, false),
+ I("a.b..c", false, false),
+ I(".a.b.c.", false, false),
+
+ // absolute names (only allowed for reference names)
+ I("a.", true, false),
+ I("a.b.", true, false),
+ I("a.b.c.", true, false),
+
+ // absolute names with empty label at end
+ I("a..", false, false),
+ I("a.b..", false, false),
+ I("a.b.c..", false, false),
+ I("a...", false, false),
+
+ // Punycode
+ I("xn--", false, false),
+ I("xn--.", false, false),
+ I("xn--.a", false, false),
+ I("a.xn--", false, false),
+ I("a.xn--.", false, false),
+ I("a.xn--.b", false, false),
+ I("a.xn--.b", false, false),
+ I("a.xn--\0.b", false, false),
+ I("a.xn--a.b", true, true),
+ I("xn--a", true, true),
+ I("a.xn--a", true, true),
+ I("a.xn--a.a", true, true),
+ I("\xc4\x95.com", false, false), // UTF-8 ĕ
+ I("xn--jea.com", true, true), // punycode ĕ
+ I("xn--\xc4\x95.com", false, false), // UTF-8 ĕ, malformed punycode + UTF-8 mashup
+
+ // Surprising punycode
+ I("xn--google.com", true, true), // 䕮䕵䕶䕱.com
+ I("xn--citibank.com", true, true), // 岍岊岊岅岉岎.com
+ I("xn--cnn.com", true, true), // 䁾.com
+ I("a.xn--cnn", true, true), // a.䁾
+ I("a.xn--cnn.com", true, true), // a.䁾.com
+
+ I("1.2.3.4", false, false), // IPv4 address
+ I("1::2", false, false), // IPV6 address
+
+ // whitespace not allowed anywhere.
+ I(" ", false, false),
+ I(" a", false, false),
+ I("a ", false, false),
+ I("a b", false, false),
+ I("a.b 1", false, false),
+ I("a\t", false, false),
+
+ // Nulls not allowed
+ I("\0", false, false),
+ I("a\0", false, false),
+ I("example.org\0.example.com", false, false), // Hi Moxie!
+ I("\0a", false, false),
+ I("xn--\0", false, false),
+
+ // Allowed character set
+ I("a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z", true, true),
+ I("A.B.C.D.E.F.G.H.I.J.K.L.M.N.O.P.Q.R.S.T.U.V.W.X.Y.Z", true, true),
+ I("0.1.2.3.4.5.6.7.8.9.a", true, true), // "a" needed to avoid numeric last label
+ I("a-b", true, true), // hyphen (a label cannot start or end with a hyphen)
+
+ // Underscores
+ I("a_b", true, true),
+ // See bug 1139039
+ I("_", true, true),
+ I("a_", true, true),
+ I("_a", true, true),
+ I("_1", true, true),
+ I("1_", true, true),
+ I("___", true, true),
+
+ // An invalid character in various positions
+ I("!", false, false),
+ I("!a", false, false),
+ I("a!", false, false),
+ I("a!b", false, false),
+ I("a.!", false, false),
+ I("a.a!", false, false),
+ I("a.!a", false, false),
+ I("a.a!a", false, false),
+ I("a.!a.a", false, false),
+ I("a.a!.a", false, false),
+ I("a.a!a.a", false, false),
+
+ // Various other invalid characters
+ I("a!", false, false),
+ I("a@", false, false),
+ I("a#", false, false),
+ I("a$", false, false),
+ I("a%", false, false),
+ I("a^", false, false),
+ I("a&", false, false),
+ I("a*", false, false),
+ I("a(", false, false),
+ I("a)", false, false),
+
+ // last label can't be fully numeric
+ I("1", false, false),
+ I("a.1", false, false),
+
+ // other labels can be fully numeric
+ I("1.a", true, true),
+ I("1.2.a", true, true),
+ I("1.2.3.a", true, true),
+
+ // last label can be *partly* numeric
+ I("1a", true, true),
+ I("1.1a", true, true),
+ I("1-1", true, true),
+ I("a.1-1", true, true),
+ I("a.1-a", true, true),
+
+ // labels cannot start with a hyphen
+ I("-", false, false),
+ I("-1", false, false),
+
+ // labels cannot end with a hyphen
+ I("1-", false, false),
+ I("1-.a", false, false),
+ I("a-", false, false),
+ I("a-.a", false, false),
+ I("a.1-.a", false, false),
+ I("a.a-.a", false, false),
+
+ // labels can contain a hyphen in the middle
+ I("a-b", true, true),
+ I("1-2", true, true),
+ I("a.a-1", true, true),
+
+ // multiple consecutive hyphens allowed
+ I("a--1", true, true),
+ I("1---a", true, true),
+ I("a-----------------b", true, true),
+
+ // Wildcard specifications are not valid reference names, but are valid
+ // presented names if there are enough labels and if '*' is the only
+ // character in the wildcard label.
+ I("*.a", false, false),
+ I("a*", false, false),
+ I("a*.", false, false),
+ I("a*.a", false, false),
+ I("a*.a.", false, false),
+ I("*.a.b", false, true),
+ I("*.a.b.", false, false),
+ I("a*.b.c", false, false),
+ I("*.a.b.c", false, true),
+ I("a*.b.c.d", false, false),
+
+ // Multiple wildcards are not allowed.
+ I("a**.b.c", false, false),
+ I("a*b*.c.d", false, false),
+ I("a*.b*.c", false, false),
+
+ // Wildcards are only allowed in the first label.
+ I("a.*", false, false),
+ I("a.*.b", false, false),
+ I("a.b.*", false, false),
+ I("a.b*.c", false, false),
+ I("*.b*.c", false, false),
+ I(".*.a.b", false, false),
+ I(".a*.b.c", false, false),
+
+ // Wildcards must be at the *end* of the first label.
+ I("*a.b.c", false, false),
+ I("a*b.c.d", false, false),
+
+ // Wildcards not allowed with IDNA prefix
+ I("x*.a.b", false, false),
+ I("xn*.a.b", false, false),
+ I("xn-*.a.b", false, false),
+ I("xn--*.a.b", false, false),
+ I("xn--w*.a.b", false, false),
+
+ // Redacted labels from RFC6962bis draft 4
+ // https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-04#section-3.2.2
+ I("(PRIVATE).foo", false, false),
+
+ // maximum label length is 63 characters
+ I("1234567890" "1234567890" "1234567890"
+ "1234567890" "1234567890" "1234567890" "abc", true, true),
+ I("1234567890" "1234567890" "1234567890"
+ "1234567890" "1234567890" "1234567890" "abcd", false, false),
+
+ // maximum total length is 253 characters
+ I("1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "."
+ "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "."
+ "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "."
+ "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "."
+ "1234567890" "1234567890" "1234567890" "1234567890" "12345678" "a",
+ true, true),
+ I("1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "."
+ "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "."
+ "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "."
+ "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "."
+ "1234567890" "1234567890" "1234567890" "1234567890" "123456789" "a",
+ false, false),
+};
+
+static const InputValidity DNSNAMES_VALIDITY_TURKISH_I[] =
+{
+ // http://en.wikipedia.org/wiki/Dotted_and_dotless_I#In_computing
+ // IDN registration rules disallow "latin capital letter i with dot above,"
+ // but our checks aren't intended to enforce those rules.
+ I("I", true, true), // ASCII capital I
+ I("i", true, true), // ASCII lowercase i
+ I("\xC4\xB0", false, false), // latin capital letter i with dot above
+ I("\xC4\xB1", false, false), // latin small letter dotless i
+ I("xn--i-9bb", true, true), // latin capital letter i with dot above, in punycode
+ I("xn--cfa", true, true), // latin small letter dotless i, in punycode
+ I("xn--\xC4\xB0", false, false), // latin capital letter i with dot above, mashup
+ I("xn--\xC4\xB1", false, false), // latin small letter dotless i, mashup
+};
+
+static const uint8_t LOWERCASE_I_VALUE[1] = { 'i' };
+static const uint8_t UPPERCASE_I_VALUE[1] = { 'I' };
+static const Input LOWERCASE_I(LOWERCASE_I_VALUE);
+static const Input UPPERCASE_I(UPPERCASE_I_VALUE);
+
+template <unsigned int L>
+struct IPAddressParams
+{
+ ByteString input;
+ bool isValid;
+ uint8_t expectedValueIfValid[L];
+};
+
+#define IPV4_VALID(str, a, b, c, d) \
+ { \
+ ByteString(reinterpret_cast<const uint8_t*>(str), sizeof(str) - 1), \
+ true, \
+ { a, b, c, d } \
+ }
+
+// The value of expectedValueIfValid must be ignored for invalid IP addresses.
+// The value { 73, 73, 73, 73 } is used because it is unlikely to result in an
+// accidental match, unlike { 0, 0, 0, 0 }, which is a value we actually test.
+#define IPV4_INVALID(str) \
+ { \
+ ByteString(reinterpret_cast<const uint8_t*>(str), sizeof(str) - 1), \
+ false, \
+ { 73, 73, 73, 73 } \
+ }
+
+static const IPAddressParams<4> IPV4_ADDRESSES[] =
+{
+ IPV4_INVALID(""),
+ IPV4_INVALID("1"),
+ IPV4_INVALID("1.2"),
+ IPV4_INVALID("1.2.3"),
+ IPV4_VALID("1.2.3.4", 1, 2, 3, 4),
+ IPV4_INVALID("1.2.3.4.5"),
+
+ IPV4_INVALID("1.2.3.4a"), // a DNSName!
+ IPV4_INVALID("a.2.3.4"), // not even a DNSName!
+ IPV4_INVALID("1::2"), // IPv6 address
+
+ // Whitespace not allowed
+ IPV4_INVALID(" 1.2.3.4"),
+ IPV4_INVALID("1.2.3.4 "),
+ IPV4_INVALID("1 .2.3.4"),
+ IPV4_INVALID("\n1.2.3.4"),
+ IPV4_INVALID("1.2.3.4\n"),
+
+ // Nulls not allowed
+ IPV4_INVALID("\0"),
+ IPV4_INVALID("\0" "1.2.3.4"),
+ IPV4_INVALID("1.2.3.4\0"),
+ IPV4_INVALID("1.2.3.4\0.5"),
+
+ // Range
+ IPV4_VALID("0.0.0.0", 0, 0, 0, 0),
+ IPV4_VALID("255.255.255.255", 255, 255, 255, 255),
+ IPV4_INVALID("256.0.0.0"),
+ IPV4_INVALID("0.256.0.0"),
+ IPV4_INVALID("0.0.256.0"),
+ IPV4_INVALID("0.0.0.256"),
+ IPV4_INVALID("999.0.0.0"),
+ IPV4_INVALID("9999999999999999999.0.0.0"),
+
+ // All digits allowed
+ IPV4_VALID("0.1.2.3", 0, 1, 2, 3),
+ IPV4_VALID("4.5.6.7", 4, 5, 6, 7),
+ IPV4_VALID("8.9.0.1", 8, 9, 0, 1),
+
+ // Leading zeros not allowed
+ IPV4_INVALID("01.2.3.4"),
+ IPV4_INVALID("001.2.3.4"),
+ IPV4_INVALID("00000000001.2.3.4"),
+ IPV4_INVALID("010.2.3.4"),
+ IPV4_INVALID("1.02.3.4"),
+ IPV4_INVALID("1.2.03.4"),
+ IPV4_INVALID("1.2.3.04"),
+
+ // Empty components
+ IPV4_INVALID(".2.3.4"),
+ IPV4_INVALID("1..3.4"),
+ IPV4_INVALID("1.2..4"),
+ IPV4_INVALID("1.2.3."),
+
+ // Too many components
+ IPV4_INVALID("1.2.3.4.5"),
+ IPV4_INVALID("1.2.3.4.5.6"),
+ IPV4_INVALID("0.1.2.3.4"),
+ IPV4_INVALID("1.2.3.4.0"),
+
+ // Leading/trailing dot
+ IPV4_INVALID(".1.2.3.4"),
+ IPV4_INVALID("1.2.3.4."),
+
+ // Other common forms of IPv4 address
+ // http://en.wikipedia.org/wiki/IPv4#Address_representations
+ IPV4_VALID("192.0.2.235", 192, 0, 2, 235), // dotted decimal (control value)
+ IPV4_INVALID("0xC0.0x00.0x02.0xEB"), // dotted hex
+ IPV4_INVALID("0301.0000.0002.0353"), // dotted octal
+ IPV4_INVALID("0xC00002EB"), // non-dotted hex
+ IPV4_INVALID("3221226219"), // non-dotted decimal
+ IPV4_INVALID("030000001353"), // non-dotted octal
+ IPV4_INVALID("192.0.0002.0xEB"), // mixed
+};
+
+#define IPV6_VALID(str, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) \
+ { \
+ ByteString(reinterpret_cast<const uint8_t*>(str), sizeof(str) - 1), \
+ true, \
+ { a, b, c, d, \
+ e, f, g, h, \
+ i, j, k, l, \
+ m, n, o, p } \
+ }
+
+#define IPV6_INVALID(str) \
+ { \
+ ByteString(reinterpret_cast<const uint8_t*>(str), sizeof(str) - 1), \
+ false, \
+ { 73, 73, 73, 73, \
+ 73, 73, 73, 73, \
+ 73, 73, 73, 73, \
+ 73, 73, 73, 73 } \
+ }
+
+static const IPAddressParams<16> IPV6_ADDRESSES[] =
+{
+ IPV6_INVALID(""),
+ IPV6_INVALID("1234"),
+ IPV6_INVALID("1234:5678"),
+ IPV6_INVALID("1234:5678:9abc"),
+ IPV6_INVALID("1234:5678:9abc:def0"),
+ IPV6_INVALID("1234:5678:9abc:def0:1234:"),
+ IPV6_INVALID("1234:5678:9abc:def0:1234:5678:"),
+ IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:"),
+ IPV6_VALID("1234:5678:9abc:def0:1234:5678:9abc:def0",
+ 0x12, 0x34, 0x56, 0x78,
+ 0x9a, 0xbc, 0xde, 0xf0,
+ 0x12, 0x34, 0x56, 0x78,
+ 0x9a, 0xbc, 0xde, 0xf0),
+ IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:def0:"),
+ IPV6_INVALID(":1234:5678:9abc:def0:1234:5678:9abc:def0"),
+ IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:def0:0000"),
+
+ // Valid contractions
+ IPV6_VALID("::1",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01),
+ IPV6_VALID("::1234",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x12, 0x34),
+ IPV6_VALID("1234::",
+ 0x12, 0x34, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00),
+ IPV6_VALID("1234::5678",
+ 0x12, 0x34, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x56, 0x78),
+ IPV6_VALID("1234:5678::abcd",
+ 0x12, 0x34, 0x56, 0x78,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xab, 0xcd),
+ IPV6_VALID("1234:5678:9abc:def0:1234:5678:9abc::",
+ 0x12, 0x34, 0x56, 0x78,
+ 0x9a, 0xbc, 0xde, 0xf0,
+ 0x12, 0x34, 0x56, 0x78,
+ 0x9a, 0xbc, 0x00, 0x00),
+
+ // Contraction in full IPv6 addresses not allowed
+ IPV6_INVALID("::1234:5678:9abc:def0:1234:5678:9abc:def0"), // start
+ IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:def0::"), // end
+ IPV6_INVALID("1234:5678::9abc:def0:1234:5678:9abc:def0"), // interior
+
+ // Multiple contractions not allowed
+ IPV6_INVALID("::1::"),
+ IPV6_INVALID("::1::2"),
+ IPV6_INVALID("1::2::"),
+
+ // Colon madness!
+ IPV6_INVALID(":"),
+ IPV6_INVALID("::"),
+ IPV6_INVALID(":::"),
+ IPV6_INVALID("::::"),
+ IPV6_INVALID(":::1"),
+ IPV6_INVALID("::::1"),
+ IPV6_INVALID("1:::2"),
+ IPV6_INVALID("1::::2"),
+ IPV6_INVALID("1:2:::"),
+ IPV6_INVALID("1:2::::"),
+ IPV6_INVALID("::1234:"),
+ IPV6_INVALID(":1234::"),
+
+ IPV6_INVALID("01234::"), // too many digits, even if zero
+ IPV6_INVALID("12345678::"), // too many digits or missing colon
+
+ // uppercase
+ IPV6_VALID("ABCD:EFAB::",
+ 0xab, 0xcd, 0xef, 0xab,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00),
+
+ // miXeD CAse
+ IPV6_VALID("aBcd:eFAb::",
+ 0xab, 0xcd, 0xef, 0xab,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00),
+
+ // IPv4-style
+ IPV6_VALID("::2.3.4.5",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x03, 0x04, 0x05),
+ IPV6_VALID("1234::2.3.4.5",
+ 0x12, 0x34, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x03, 0x04, 0x05),
+ IPV6_VALID("::abcd:2.3.4.5",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xab, 0xcd,
+ 0x02, 0x03, 0x04, 0x05),
+ IPV6_VALID("1234:5678:9abc:def0:1234:5678:252.253.254.255",
+ 0x12, 0x34, 0x56, 0x78,
+ 0x9a, 0xbc, 0xde, 0xf0,
+ 0x12, 0x34, 0x56, 0x78,
+ 252, 253, 254, 255),
+ IPV6_VALID("1234:5678:9abc:def0:1234::252.253.254.255",
+ 0x12, 0x34, 0x56, 0x78,
+ 0x9a, 0xbc, 0xde, 0xf0,
+ 0x12, 0x34, 0x00, 0x00,
+ 252, 253, 254, 255),
+ IPV6_INVALID("1234::252.253.254"),
+ IPV6_INVALID("::252.253.254"),
+ IPV6_INVALID("::252.253.254.300"),
+ IPV6_INVALID("1234::252.253.254.255:"),
+ IPV6_INVALID("1234::252.253.254.255:5678"),
+
+ // Contractions that don't contract
+ IPV6_INVALID("::1234:5678:9abc:def0:1234:5678:9abc:def0"),
+ IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:def0::"),
+ IPV6_INVALID("1234:5678:9abc:def0::1234:5678:9abc:def0"),
+ IPV6_INVALID("1234:5678:9abc:def0:1234:5678::252.253.254.255"),
+
+ // With and without leading zeros
+ IPV6_VALID("::123",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x23),
+ IPV6_VALID("::0123",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x23),
+ IPV6_VALID("::012",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x12),
+ IPV6_VALID("::0012",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x12),
+ IPV6_VALID("::01",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01),
+ IPV6_VALID("::001",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01),
+ IPV6_VALID("::0001",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01),
+ IPV6_VALID("::0",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00),
+ IPV6_VALID("::00",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00),
+ IPV6_VALID("::000",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00),
+ IPV6_VALID("::0000",
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00),
+ IPV6_INVALID("::01234"),
+ IPV6_INVALID("::00123"),
+ IPV6_INVALID("::000123"),
+
+ // Trailing zero
+ IPV6_INVALID("::12340"),
+
+ // Whitespace
+ IPV6_INVALID(" 1234:5678:9abc:def0:1234:5678:9abc:def0"),
+ IPV6_INVALID("\t1234:5678:9abc:def0:1234:5678:9abc:def0"),
+ IPV6_INVALID("\t1234:5678:9abc:def0:1234:5678:9abc:def0\n"),
+ IPV6_INVALID("1234 :5678:9abc:def0:1234:5678:9abc:def0"),
+ IPV6_INVALID("1234: 5678:9abc:def0:1234:5678:9abc:def0"),
+ IPV6_INVALID(":: 2.3.4.5"),
+ IPV6_INVALID("1234::252.253.254.255 "),
+ IPV6_INVALID("1234::252.253.254.255\n"),
+ IPV6_INVALID("1234::252.253. 254.255"),
+
+ // Nulls
+ IPV6_INVALID("\0"),
+ IPV6_INVALID("::1\0:2"),
+ IPV6_INVALID("::1\0"),
+ IPV6_INVALID("::1.2.3.4\0"),
+ IPV6_INVALID("::1.2\02.3.4"),
+};
+
+class pkixnames_MatchPresentedDNSIDWithReferenceDNSID
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<PresentedMatchesReference>
+{
+public:
+ DefaultNameMatchingPolicy mNameMatchingPolicy;
+};
+
+TEST_P(pkixnames_MatchPresentedDNSIDWithReferenceDNSID,
+ MatchPresentedDNSIDWithReferenceDNSID)
+{
+ const PresentedMatchesReference& param(GetParam());
+ SCOPED_TRACE(param.presentedDNSID.c_str());
+ SCOPED_TRACE(param.referenceDNSID.c_str());
+ Input presented;
+ ASSERT_EQ(Success, presented.Init(param.presentedDNSID.data(),
+ param.presentedDNSID.length()));
+ Input reference;
+ ASSERT_EQ(Success, reference.Init(param.referenceDNSID.data(),
+ param.referenceDNSID.length()));
+
+ // sanity check that test makes sense
+ ASSERT_TRUE(IsValidReferenceDNSID(reference));
+
+ bool matches;
+ ASSERT_EQ(param.expectedResult,
+ MatchPresentedDNSIDWithReferenceDNSID(presented, reference,
+ matches));
+ if (param.expectedResult == Success) {
+ ASSERT_EQ(param.expectedMatches, matches);
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(pkixnames_MatchPresentedDNSIDWithReferenceDNSID,
+ pkixnames_MatchPresentedDNSIDWithReferenceDNSID,
+ testing::ValuesIn(DNSID_MATCH_PARAMS));
+
+class pkixnames_Turkish_I_Comparison
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<InputValidity>
+{
+public:
+ DefaultNameMatchingPolicy mNameMatchingPolicy;
+};
+
+TEST_P(pkixnames_Turkish_I_Comparison, MatchPresentedDNSIDWithReferenceDNSID)
+{
+ // Make sure we don't have the similar problems that strcasecmp and others
+ // have with the other kinds of "i" and "I" commonly used in Turkish locales.
+
+ const InputValidity& inputValidity(GetParam());
+ SCOPED_TRACE(inputValidity.input.c_str());
+ Input input;
+ ASSERT_EQ(Success, input.Init(inputValidity.input.data(),
+ inputValidity.input.length()));
+
+ bool isASCII = InputsAreEqual(LOWERCASE_I, input) ||
+ InputsAreEqual(UPPERCASE_I, input);
+ {
+ bool matches;
+ ASSERT_EQ(inputValidity.isValidPresentedID ? Success
+ : Result::ERROR_BAD_DER,
+ MatchPresentedDNSIDWithReferenceDNSID(input, LOWERCASE_I,
+ matches));
+ if (inputValidity.isValidPresentedID) {
+ ASSERT_EQ(isASCII, matches);
+ }
+ }
+ {
+ bool matches;
+ ASSERT_EQ(inputValidity.isValidPresentedID ? Success
+ : Result::ERROR_BAD_DER,
+ MatchPresentedDNSIDWithReferenceDNSID(input, UPPERCASE_I,
+ matches));
+ if (inputValidity.isValidPresentedID) {
+ ASSERT_EQ(isASCII, matches);
+ }
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(pkixnames_Turkish_I_Comparison,
+ pkixnames_Turkish_I_Comparison,
+ testing::ValuesIn(DNSNAMES_VALIDITY_TURKISH_I));
+
+class pkixnames_IsValidReferenceDNSID
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<InputValidity>
+{
+public:
+ DefaultNameMatchingPolicy mNameMatchingPolicy;
+};
+
+TEST_P(pkixnames_IsValidReferenceDNSID, IsValidReferenceDNSID)
+{
+ const InputValidity& inputValidity(GetParam());
+ SCOPED_TRACE(inputValidity.input.c_str());
+ Input input;
+ ASSERT_EQ(Success, input.Init(inputValidity.input.data(),
+ inputValidity.input.length()));
+ ASSERT_EQ(inputValidity.isValidReferenceID, IsValidReferenceDNSID(input));
+ ASSERT_EQ(inputValidity.isValidPresentedID, IsValidPresentedDNSID(input));
+}
+
+INSTANTIATE_TEST_CASE_P(pkixnames_IsValidReferenceDNSID,
+ pkixnames_IsValidReferenceDNSID,
+ testing::ValuesIn(DNSNAMES_VALIDITY));
+INSTANTIATE_TEST_CASE_P(pkixnames_IsValidReferenceDNSID_Turkish_I,
+ pkixnames_IsValidReferenceDNSID,
+ testing::ValuesIn(DNSNAMES_VALIDITY_TURKISH_I));
+
+class pkixnames_ParseIPv4Address
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<IPAddressParams<4>>
+{
+public:
+ DefaultNameMatchingPolicy mNameMatchingPolicy;
+};
+
+TEST_P(pkixnames_ParseIPv4Address, ParseIPv4Address)
+{
+ const IPAddressParams<4>& param(GetParam());
+ SCOPED_TRACE(param.input.c_str());
+ Input input;
+ ASSERT_EQ(Success, input.Init(param.input.data(),
+ param.input.length()));
+ uint8_t ipAddress[4];
+ ASSERT_EQ(param.isValid, ParseIPv4Address(input, ipAddress));
+ if (param.isValid) {
+ for (size_t i = 0; i < sizeof(ipAddress); ++i) {
+ ASSERT_EQ(param.expectedValueIfValid[i], ipAddress[i]);
+ }
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(pkixnames_ParseIPv4Address,
+ pkixnames_ParseIPv4Address,
+ testing::ValuesIn(IPV4_ADDRESSES));
+
+class pkixnames_ParseIPv6Address
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<IPAddressParams<16>>
+{
+public:
+ DefaultNameMatchingPolicy mNameMatchingPolicy;
+};
+
+TEST_P(pkixnames_ParseIPv6Address, ParseIPv6Address)
+{
+ const IPAddressParams<16>& param(GetParam());
+ SCOPED_TRACE(param.input.c_str());
+ Input input;
+ ASSERT_EQ(Success, input.Init(param.input.data(),
+ param.input.length()));
+ uint8_t ipAddress[16];
+ ASSERT_EQ(param.isValid, ParseIPv6Address(input, ipAddress));
+ if (param.isValid) {
+ for (size_t i = 0; i < sizeof(ipAddress); ++i) {
+ ASSERT_EQ(param.expectedValueIfValid[i], ipAddress[i]);
+ }
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(pkixnames_ParseIPv6Address,
+ pkixnames_ParseIPv6Address,
+ testing::ValuesIn(IPV6_ADDRESSES));
+
+// This is an arbitrary string that is used to indicate that no SAN extension
+// should be put into the generated certificate. It needs to be different from
+// "" or any other subjectAltName value that we actually want to test, but its
+// actual value does not matter. Note that this isn't a correctly-encoded SAN
+// extension value!
+static const ByteString
+ NO_SAN(reinterpret_cast<const uint8_t*>("I'm a bad, bad, certificate"));
+
+struct CheckCertHostnameParams
+{
+ ByteString hostname;
+ ByteString subject;
+ ByteString subjectAltName;
+ Result result;
+};
+
+class pkixnames_CheckCertHostname
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<CheckCertHostnameParams>
+{
+public:
+ DefaultNameMatchingPolicy mNameMatchingPolicy;
+};
+
+#define WITH_SAN(r, ps, psan, result) \
+ { \
+ ByteString(reinterpret_cast<const uint8_t*>(r), sizeof(r) - 1), \
+ ps, \
+ psan, \
+ result \
+ }
+
+#define WITHOUT_SAN(r, ps, result) \
+ { \
+ ByteString(reinterpret_cast<const uint8_t*>(r), sizeof(r) - 1), \
+ ps, \
+ NO_SAN, \
+ result \
+ }
+
+static const uint8_t example_com[] = {
+ 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm'
+};
+
+// Note that We avoid zero-valued bytes in these IP addresses so that we don't
+// get false negatives from anti-NULL-byte defenses in dNSName decoding.
+static const uint8_t ipv4_addr_bytes[] = {
+ 1, 2, 3, 4
+};
+static const uint8_t ipv4_addr_bytes_as_str[] = "\x01\x02\x03\x04";
+static const uint8_t ipv4_addr_str[] = "1.2.3.4";
+static const uint8_t ipv4_addr_bytes_FFFFFFFF[8] = {
+ 1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff
+};
+
+static const uint8_t ipv4_compatible_ipv6_addr_bytes[] = {
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 1, 2, 3, 4
+};
+static const uint8_t ipv4_compatible_ipv6_addr_str[] = "::1.2.3.4";
+
+static const uint8_t ipv4_mapped_ipv6_addr_bytes[] = {
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0xFF, 0xFF,
+ 1, 2, 3, 4
+};
+static const uint8_t ipv4_mapped_ipv6_addr_str[] = "::FFFF:1.2.3.4";
+
+static const uint8_t ipv6_addr_bytes[] = {
+ 0x11, 0x22, 0x33, 0x44,
+ 0x55, 0x66, 0x77, 0x88,
+ 0x99, 0xaa, 0xbb, 0xcc,
+ 0xdd, 0xee, 0xff, 0x11
+};
+static const uint8_t ipv6_addr_bytes_as_str[] =
+ "\x11\x22\x33\x44"
+ "\x55\x66\x77\x88"
+ "\x99\xaa\xbb\xcc"
+ "\xdd\xee\xff\x11";
+
+static const uint8_t ipv6_addr_str[] =
+ "1122:3344:5566:7788:99aa:bbcc:ddee:ff11";
+
+static const uint8_t ipv6_other_addr_bytes[] = {
+ 0xff, 0xee, 0xdd, 0xcc,
+ 0xbb, 0xaa, 0x99, 0x88,
+ 0x77, 0x66, 0x55, 0x44,
+ 0x33, 0x22, 0x11, 0x00,
+};
+
+static const uint8_t ipv4_other_addr_bytes[] = {
+ 5, 6, 7, 8
+};
+static const uint8_t ipv4_other_addr_bytes_FFFFFFFF[] = {
+ 5, 6, 7, 8, 0xff, 0xff, 0xff, 0xff
+};
+
+static const uint8_t ipv4_addr_00000000_bytes[] = {
+ 0, 0, 0, 0
+};
+static const uint8_t ipv4_addr_FFFFFFFF_bytes[] = {
+ 0, 0, 0, 0
+};
+
+static const uint8_t ipv4_constraint_all_zeros_bytes[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static const uint8_t ipv6_addr_all_zeros_bytes[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static const uint8_t ipv6_constraint_all_zeros_bytes[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static const uint8_t ipv4_constraint_CIDR_16_bytes[] = {
+ 1, 2, 0, 0, 0xff, 0xff, 0, 0
+};
+static const uint8_t ipv4_constraint_CIDR_17_bytes[] = {
+ 1, 2, 0, 0, 0xff, 0xff, 0x80, 0
+};
+
+// The subnet is 1.2.0.0/16 but it is specified as 1.2.3.0/16
+static const uint8_t ipv4_constraint_CIDR_16_bad_addr_bytes[] = {
+ 1, 2, 3, 0, 0xff, 0xff, 0, 0
+};
+
+// Masks are supposed to be of the form <ones><zeros>, but this one is of the
+// form <ones><zeros><ones><zeros>.
+static const uint8_t ipv4_constraint_bad_mask_bytes[] = {
+ 1, 2, 3, 0, 0xff, 0, 0xff, 0
+};
+
+static const uint8_t ipv6_constraint_CIDR_16_bytes[] = {
+ 0x11, 0x22, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0xff, 0xff, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+// The subnet is 1122::/16 but it is specified as 1122:3344::/16
+static const uint8_t ipv6_constraint_CIDR_16_bad_addr_bytes[] = {
+ 0x11, 0x22, 0x33, 0x44, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0xff, 0xff, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+// Masks are supposed to be of the form <ones><zeros>, but this one is of the
+// form <ones><zeros><ones><zeros>.
+static const uint8_t ipv6_constraint_bad_mask_bytes[] = {
+ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0xff, 0xff, 0, 0, 0xff, 0xff, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static const uint8_t ipv4_addr_truncated_bytes[] = {
+ 1, 2, 3
+};
+static const uint8_t ipv4_addr_overlong_bytes[] = {
+ 1, 2, 3, 4, 5
+};
+static const uint8_t ipv4_constraint_truncated_bytes[] = {
+ 0, 0, 0, 0,
+ 0, 0, 0,
+};
+static const uint8_t ipv4_constraint_overlong_bytes[] = {
+ 0, 0, 0, 0,
+ 0, 0, 0, 0, 0
+};
+
+static const uint8_t ipv6_addr_truncated_bytes[] = {
+ 0x11, 0x22, 0x33, 0x44,
+ 0x55, 0x66, 0x77, 0x88,
+ 0x99, 0xaa, 0xbb, 0xcc,
+ 0xdd, 0xee, 0xff
+};
+static const uint8_t ipv6_addr_overlong_bytes[] = {
+ 0x11, 0x22, 0x33, 0x44,
+ 0x55, 0x66, 0x77, 0x88,
+ 0x99, 0xaa, 0xbb, 0xcc,
+ 0xdd, 0xee, 0xff, 0x11, 0x00
+};
+static const uint8_t ipv6_constraint_truncated_bytes[] = {
+ 0x11, 0x22, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0xff, 0xff, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0
+};
+static const uint8_t ipv6_constraint_overlong_bytes[] = {
+ 0x11, 0x22, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0xff, 0xff, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+// Note that, for DNSNames, these test cases in CHECK_CERT_HOSTNAME_PARAMS are
+// mostly about testing different scenerios regarding the structure of entries
+// in the subjectAltName and subject of the certificate, than about the how
+// specific presented identifier values are matched against the reference
+// identifier values. This is because we also use the test cases in
+// DNSNAMES_VALIDITY to test CheckCertHostname. Consequently, tests about
+// whether specific presented DNSNames (including wildcards, in particular) are
+// matched against a reference DNSName only need to be added to
+// DNSNAMES_VALIDITY, and not here.
+static const CheckCertHostnameParams CHECK_CERT_HOSTNAME_PARAMS[] =
+{
+ // This is technically illegal. PrintableString is defined in such a way that
+ // '*' is not an allowed character, but there are many real-world certificates
+ // that are encoded this way.
+ WITHOUT_SAN("foo.example.com", RDN(CN("*.example.com", der::PrintableString)),
+ Success),
+ WITHOUT_SAN("foo.example.com", RDN(CN("*.example.com", der::UTF8String)),
+ Success),
+
+ // Many certificates use TeletexString when encoding wildcards in CN-IDs
+ // because PrintableString is defined as not allowing '*' and UTF8String was,
+ // at one point in history, considered too new to depend on for compatibility.
+ // We accept TeletexString-encoded CN-IDs when they don't contain any escape
+ // sequences. The reference I used for the escape codes was
+ // https://tools.ietf.org/html/rfc1468. The escaping mechanism is actually
+ // pretty complex and these tests don't even come close to testing all the
+ // possibilities.
+ WITHOUT_SAN("foo.example.com", RDN(CN("*.example.com", der::TeletexString)),
+ Success),
+ // "ESC ( B" ({0x1B,0x50,0x42}) is the escape code to switch to ASCII, which
+ // is redundant because it already the default.
+ WITHOUT_SAN("foo.example.com",
+ RDN(CN("\x1B(B*.example.com", der::TeletexString)),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ WITHOUT_SAN("foo.example.com",
+ RDN(CN("*.example\x1B(B.com", der::TeletexString)),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ WITHOUT_SAN("foo.example.com",
+ RDN(CN("*.example.com\x1B(B", der::TeletexString)),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ // "ESC $ B" ({0x1B,0x24,0x42}) is the escape code to switch to
+ // JIS X 0208-1983 (a Japanese character set).
+ WITHOUT_SAN("foo.example.com",
+ RDN(CN("\x1B$B*.example.com", der::TeletexString)),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ WITHOUT_SAN("foo.example.com",
+ RDN(CN("*.example.com\x1B$B", der::TeletexString)),
+ Result::ERROR_BAD_CERT_DOMAIN),
+
+ // Match a DNSName SAN entry with a redundant (ignored) matching CN-ID.
+ WITH_SAN("a", RDN(CN("a")), DNSName("a"), Success),
+ // Match a DNSName SAN entry when there is an CN-ID that doesn't match.
+ WITH_SAN("b", RDN(CN("a")), DNSName("b"), Success),
+ // Do not match a CN-ID when there is a valid DNSName SAN Entry.
+ WITH_SAN("a", RDN(CN("a")), DNSName("b"), Result::ERROR_BAD_CERT_DOMAIN),
+ // Do not match a CN-ID when there is a malformed DNSName SAN Entry.
+ WITH_SAN("a", RDN(CN("a")), DNSName("!"), Result::ERROR_BAD_DER),
+ // Do not match a matching CN-ID when there is a valid IPAddress SAN entry.
+ WITH_SAN("a", RDN(CN("a")), IPAddress(ipv4_addr_bytes),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ // Do not match a matching CN-ID when there is a malformed IPAddress SAN entry.
+ WITH_SAN("a", RDN(CN("a")), IPAddress(example_com),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ // Match a DNSName against a matching CN-ID when there is a SAN, but the SAN
+ // does not contain an DNSName or IPAddress entry.
+ WITH_SAN("a", RDN(CN("a")), RFC822Name("foo@example.com"), Success),
+ // Match a matching CN-ID when there is no SAN.
+ WITHOUT_SAN("a", RDN(CN("a")), Success),
+ // Do not match a mismatching CN-ID when there is no SAN.
+ WITHOUT_SAN("a", RDN(CN("b")), Result::ERROR_BAD_CERT_DOMAIN),
+
+ // The first DNSName matches.
+ WITH_SAN("a", RDN(CN("foo")), DNSName("a") + DNSName("b"), Success),
+ // The last DNSName matches.
+ WITH_SAN("b", RDN(CN("foo")), DNSName("a") + DNSName("b"), Success),
+ // The middle DNSName matches.
+ WITH_SAN("b", RDN(CN("foo")),
+ DNSName("a") + DNSName("b") + DNSName("c"), Success),
+ // After an IP address.
+ WITH_SAN("b", RDN(CN("foo")),
+ IPAddress(ipv4_addr_bytes) + DNSName("b"), Success),
+ // Before an IP address.
+ WITH_SAN("a", RDN(CN("foo")),
+ DNSName("a") + IPAddress(ipv4_addr_bytes), Success),
+ // Between an RFC822Name and an IP address.
+ WITH_SAN("b", RDN(CN("foo")),
+ RFC822Name("foo@example.com") + DNSName("b") +
+ IPAddress(ipv4_addr_bytes),
+ Success),
+ // Duplicate DNSName.
+ WITH_SAN("a", RDN(CN("foo")), DNSName("a") + DNSName("a"), Success),
+ // After an invalid DNSName.
+ WITH_SAN("b", RDN(CN("foo")), DNSName("!") + DNSName("b"),
+ Result::ERROR_BAD_DER),
+
+ // http://tools.ietf.org/html/rfc5280#section-4.2.1.6: "If the subjectAltName
+ // extension is present, the sequence MUST contain at least one entry."
+ // However, for compatibility reasons, this is not enforced. See bug 1143085.
+ // This case is treated as if the extension is not present (i.e. name
+ // matching falls back to the subject CN).
+ WITH_SAN("a", RDN(CN("a")), ByteString(), Success),
+ WITH_SAN("a", RDN(CN("b")), ByteString(), Result::ERROR_BAD_CERT_DOMAIN),
+
+ // http://tools.ietf.org/html/rfc5280#section-4.1.2.6 says "If subject naming
+ // information is present only in the subjectAltName extension (e.g., a key
+ // bound only to an email address or URI), then the subject name MUST be an
+ // empty sequence and the subjectAltName extension MUST be critical." So, we
+ // have to support an empty subject. We don't enforce that the SAN must be
+ // critical or even that there is a SAN when the subject is empty, though.
+ WITH_SAN("a", ByteString(), DNSName("a"), Success),
+ // Make sure we return ERROR_BAD_CERT_DOMAIN and not ERROR_BAD_DER.
+ WITHOUT_SAN("a", ByteString(), Result::ERROR_BAD_CERT_DOMAIN),
+
+ // Two CNs in the same RDN, both match.
+ WITHOUT_SAN("a", RDN(CN("a") + CN("a")), Success),
+ // Two CNs in the same RDN, both DNSNames, first one matches.
+ WITHOUT_SAN("a", RDN(CN("a") + CN("b")),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ // Two CNs in the same RDN, both DNSNames, last one matches.
+ WITHOUT_SAN("b", RDN(CN("a") + CN("b")), Success),
+ // Two CNs in the same RDN, first one matches, second isn't a DNSName.
+ WITHOUT_SAN("a", RDN(CN("a") + CN("Not a DNSName")),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ // Two CNs in the same RDN, first one not a DNSName, second matches.
+ WITHOUT_SAN("b", RDN(CN("Not a DNSName") + CN("b")), Success),
+
+ // Two CNs in separate RDNs, both match.
+ WITHOUT_SAN("a", RDN(CN("a")) + RDN(CN("a")), Success),
+ // Two CNs in separate RDNs, both DNSNames, first one matches.
+ WITHOUT_SAN("a", RDN(CN("a")) + RDN(CN("b")),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ // Two CNs in separate RDNs, both DNSNames, last one matches.
+ WITHOUT_SAN("b", RDN(CN("a")) + RDN(CN("b")), Success),
+ // Two CNs in separate RDNs, first one matches, second isn't a DNSName.
+ WITHOUT_SAN("a", RDN(CN("a")) + RDN(CN("Not a DNSName")),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ // Two CNs in separate RDNs, first one not a DNSName, second matches.
+ WITHOUT_SAN("b", RDN(CN("Not a DNSName")) + RDN(CN("b")), Success),
+
+ // One CN, one RDN, CN is the first AVA in the RDN, CN matches.
+ WITHOUT_SAN("a", RDN(CN("a") + OU("b")), Success),
+ // One CN, one RDN, CN is the first AVA in the RDN, CN does not match.
+ WITHOUT_SAN("b", RDN(CN("a") + OU("b")),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ // One CN, one RDN, CN is not the first AVA in the RDN, CN matches.
+ WITHOUT_SAN("b", RDN(OU("a") + CN("b")), Success),
+ // One CN, one RDN, CN is not the first AVA in the RDN, CN does not match.
+ WITHOUT_SAN("a", RDN(OU("a") + CN("b")),
+ Result::ERROR_BAD_CERT_DOMAIN),
+
+ // One CN, multiple RDNs, CN is in the first RDN, CN matches.
+ WITHOUT_SAN("a", RDN(CN("a")) + RDN(OU("b")), Success),
+ // One CN, multiple RDNs, CN is in the first RDN, CN does not match.
+ WITHOUT_SAN("b", RDN(CN("a")) + RDN(OU("b")), Result::ERROR_BAD_CERT_DOMAIN),
+ // One CN, multiple RDNs, CN is not in the first RDN, CN matches.
+ WITHOUT_SAN("b", RDN(OU("a")) + RDN(CN("b")), Success),
+ // One CN, multiple RDNs, CN is not in the first RDN, CN does not match.
+ WITHOUT_SAN("a", RDN(OU("a")) + RDN(CN("b")), Result::ERROR_BAD_CERT_DOMAIN),
+
+ // One CN, one RDN, CN is not in the first or last AVA, CN matches.
+ WITHOUT_SAN("b", RDN(OU("a") + CN("b") + OU("c")), Success),
+ // One CN, multiple RDNs, CN is not in the first or last RDN, CN matches.
+ WITHOUT_SAN("b", RDN(OU("a")) + RDN(CN("b")) + RDN(OU("c")), Success),
+
+ // Empty CN does not match.
+ WITHOUT_SAN("example.com", RDN(CN("")), Result::ERROR_BAD_CERT_DOMAIN),
+
+ WITHOUT_SAN("uses_underscore.example.com", RDN(CN("*.example.com")), Success),
+ WITHOUT_SAN("a.uses_underscore.example.com",
+ RDN(CN("*.uses_underscore.example.com")), Success),
+ WITH_SAN("uses_underscore.example.com", RDN(CN("foo")),
+ DNSName("*.example.com"), Success),
+ WITH_SAN("a.uses_underscore.example.com", RDN(CN("foo")),
+ DNSName("*.uses_underscore.example.com"), Success),
+
+ // Do not match a DNSName that is encoded in a malformed IPAddress.
+ WITH_SAN("example.com", RDN(CN("foo")), IPAddress(example_com),
+ Result::ERROR_BAD_CERT_DOMAIN),
+
+ // We skip over the malformed IPAddress and match the DNSName entry because
+ // we've heard reports of real-world certificates that have malformed
+ // IPAddress SANs.
+ WITH_SAN("example.org", RDN(CN("foo")),
+ IPAddress(example_com) + DNSName("example.org"), Success),
+
+ WITH_SAN("example.com", RDN(CN("foo")),
+ DNSName("!") + DNSName("example.com"), Result::ERROR_BAD_DER),
+
+ // Match a matching IPv4 address SAN entry.
+ WITH_SAN(ipv4_addr_str, RDN(CN("foo")), IPAddress(ipv4_addr_bytes),
+ Success),
+ // Match a matching IPv4 addresses in the CN when there is no SAN
+ WITHOUT_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)), Success),
+ // Do not match a matching IPv4 address in the CN when there is a SAN with
+ // a DNSName entry.
+ WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)),
+ DNSName("example.com"), Result::ERROR_BAD_CERT_DOMAIN),
+ // Do not match a matching IPv4 address in the CN when there is a SAN with
+ // a non-matching IPAddress entry.
+ WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)),
+ IPAddress(ipv6_addr_bytes), Result::ERROR_BAD_CERT_DOMAIN),
+ // Match a matching IPv4 address in the CN when there is a SAN with a
+ // non-IPAddress, non-DNSName entry.
+ WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)),
+ RFC822Name("foo@example.com"), Success),
+ // Do not match a matching IPv4 address in the CN when there is a SAN with a
+ // malformed IPAddress entry.
+ WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)),
+ IPAddress(example_com), Result::ERROR_BAD_CERT_DOMAIN),
+ // Do not match a matching IPv4 address in the CN when there is a SAN with a
+ // malformed DNSName entry.
+ WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)),
+ DNSName("!"), Result::ERROR_BAD_CERT_DOMAIN),
+
+ // We don't match IPv6 addresses in the CN, regardless of whether there is
+ // a SAN.
+ WITHOUT_SAN(ipv6_addr_str, RDN(CN(ipv6_addr_str)),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ WITH_SAN(ipv6_addr_str, RDN(CN(ipv6_addr_str)),
+ DNSName("example.com"), Result::ERROR_BAD_CERT_DOMAIN),
+ WITH_SAN(ipv6_addr_str, RDN(CN(ipv6_addr_str)),
+ IPAddress(ipv6_addr_bytes), Success),
+ WITH_SAN(ipv6_addr_str, RDN(CN("foo")), IPAddress(ipv6_addr_bytes),
+ Success),
+
+ // We don't match the binary encoding of the bytes of IP addresses in the
+ // CN.
+ WITHOUT_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_bytes_as_str)),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ WITHOUT_SAN(ipv6_addr_str, RDN(CN(ipv6_addr_bytes_as_str)),
+ Result::ERROR_BAD_CERT_DOMAIN),
+
+ // We don't match IP addresses with DNSName SANs.
+ WITH_SAN(ipv4_addr_str, RDN(CN("foo")),
+ DNSName(ipv4_addr_bytes_as_str), Result::ERROR_BAD_CERT_DOMAIN),
+ WITH_SAN(ipv4_addr_str, RDN(CN("foo")), DNSName(ipv4_addr_str),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ WITH_SAN(ipv6_addr_str, RDN(CN("foo")),
+ DNSName(ipv6_addr_bytes_as_str), Result::ERROR_BAD_CERT_DOMAIN),
+ WITH_SAN(ipv6_addr_str, RDN(CN("foo")), DNSName(ipv6_addr_str),
+ Result::ERROR_BAD_CERT_DOMAIN),
+
+ // Do not match an IPv4 reference ID against the equivalent IPv4-compatible
+ // IPv6 SAN entry.
+ WITH_SAN(ipv4_addr_str, RDN(CN("foo")),
+ IPAddress(ipv4_compatible_ipv6_addr_bytes),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ // Do not match an IPv4 reference ID against the equivalent IPv4-mapped IPv6
+ // SAN entry.
+ WITH_SAN(ipv4_addr_str, RDN(CN("foo")),
+ IPAddress(ipv4_mapped_ipv6_addr_bytes),
+ Result::ERROR_BAD_CERT_DOMAIN),
+ // Do not match an IPv4-compatible IPv6 reference ID against the equivalent
+ // IPv4 SAN entry.
+ WITH_SAN(ipv4_compatible_ipv6_addr_str, RDN(CN("foo")),
+ IPAddress(ipv4_addr_bytes), Result::ERROR_BAD_CERT_DOMAIN),
+ // Do not match an IPv4 reference ID against the equivalent IPv4-mapped IPv6
+ // SAN entry.
+ WITH_SAN(ipv4_mapped_ipv6_addr_str, RDN(CN("foo")),
+ IPAddress(ipv4_addr_bytes),
+ Result::ERROR_BAD_CERT_DOMAIN),
+
+ // Test that the presence of an otherName entry is handled appropriately.
+ // (The actual value of the otherName entry isn't important - that's not what
+ // we're testing here.)
+ WITH_SAN("example.com", ByteString(),
+ // The tag for otherName is CONTEXT_SPECIFIC | CONSTRUCTED | 0
+ TLV((2 << 6) | (1 << 5) | 0, ByteString()) + DNSName("example.com"),
+ Success),
+ WITH_SAN("example.com", ByteString(),
+ TLV((2 << 6) | (1 << 5) | 0, ByteString()),
+ Result::ERROR_BAD_CERT_DOMAIN),
+};
+
+ByteString
+CreateCert(const ByteString& subject, const ByteString& subjectAltName,
+ EndEntityOrCA endEntityOrCA = EndEntityOrCA::MustBeEndEntity)
+{
+ ByteString serialNumber(CreateEncodedSerialNumber(1));
+ EXPECT_FALSE(ENCODING_FAILED(serialNumber));
+
+ ByteString issuerDER(Name(RDN(CN("issuer"))));
+ EXPECT_FALSE(ENCODING_FAILED(issuerDER));
+
+ ByteString extensions[2];
+ if (subjectAltName != NO_SAN) {
+ extensions[0] = CreateEncodedSubjectAltName(subjectAltName);
+ EXPECT_FALSE(ENCODING_FAILED(extensions[0]));
+ }
+ if (endEntityOrCA == EndEntityOrCA::MustBeCA) {
+ // Currently, these tests assume that if we're creating a CA certificate, it
+ // will not have a subjectAlternativeName extension. If that assumption
+ // changes, this code will have to be updated. Ideally this would be
+ // ASSERT_EQ, but that inserts a 'return;', which doesn't match this
+ // function's return type.
+ EXPECT_EQ(subjectAltName, NO_SAN);
+ extensions[0] = CreateEncodedBasicConstraints(true, nullptr,
+ Critical::Yes);
+ EXPECT_FALSE(ENCODING_FAILED(extensions[0]));
+ }
+
+ ScopedTestKeyPair keyPair(CloneReusedKeyPair());
+ return CreateEncodedCertificate(
+ v3, sha256WithRSAEncryption(), serialNumber, issuerDER,
+ oneDayBeforeNow, oneDayAfterNow, Name(subject), *keyPair,
+ extensions, *keyPair, sha256WithRSAEncryption());
+}
+
+TEST_P(pkixnames_CheckCertHostname, CheckCertHostname)
+{
+ const CheckCertHostnameParams& param(GetParam());
+
+ ByteString cert(CreateCert(param.subject, param.subjectAltName));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
+
+ Input hostnameInput;
+ ASSERT_EQ(Success, hostnameInput.Init(param.hostname.data(),
+ param.hostname.length()));
+
+ ASSERT_EQ(param.result, CheckCertHostname(certInput, hostnameInput,
+ mNameMatchingPolicy));
+}
+
+INSTANTIATE_TEST_CASE_P(pkixnames_CheckCertHostname,
+ pkixnames_CheckCertHostname,
+ testing::ValuesIn(CHECK_CERT_HOSTNAME_PARAMS));
+
+TEST_F(pkixnames_CheckCertHostname, SANWithoutSequence)
+{
+ // A certificate with a truly empty SAN extension (one that doesn't even
+ // contain a SEQUENCE at all) is malformed. If we didn't treat this as
+ // malformed then we'd have to treat it like the CN_EmptySAN cases.
+
+ ByteString serialNumber(CreateEncodedSerialNumber(1));
+ EXPECT_FALSE(ENCODING_FAILED(serialNumber));
+
+ ByteString extensions[2];
+ extensions[0] = CreateEncodedEmptySubjectAltName();
+ ASSERT_FALSE(ENCODING_FAILED(extensions[0]));
+
+ ScopedTestKeyPair keyPair(CloneReusedKeyPair());
+ ByteString certDER(CreateEncodedCertificate(
+ v3, sha256WithRSAEncryption(), serialNumber,
+ Name(RDN(CN("issuer"))), oneDayBeforeNow, oneDayAfterNow,
+ Name(RDN(CN("a"))), *keyPair, extensions,
+ *keyPair, sha256WithRSAEncryption()));
+ ASSERT_FALSE(ENCODING_FAILED(certDER));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length()));
+
+ static const uint8_t a[] = { 'a' };
+ ASSERT_EQ(Result::ERROR_EXTENSION_VALUE_INVALID,
+ CheckCertHostname(certInput, Input(a), mNameMatchingPolicy));
+}
+
+class pkixnames_CheckCertHostname_PresentedMatchesReference
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<PresentedMatchesReference>
+{
+public:
+ DefaultNameMatchingPolicy mNameMatchingPolicy;
+};
+
+TEST_P(pkixnames_CheckCertHostname_PresentedMatchesReference, CN_NoSAN)
+{
+ // Since there is no SAN, a valid presented DNS ID in the subject CN field
+ // should result in a match.
+
+ const PresentedMatchesReference& param(GetParam());
+
+ ByteString cert(CreateCert(RDN(CN(param.presentedDNSID)), NO_SAN));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
+
+ Input hostnameInput;
+ ASSERT_EQ(Success, hostnameInput.Init(param.referenceDNSID.data(),
+ param.referenceDNSID.length()));
+
+ ASSERT_EQ(param.expectedMatches ? Success : Result::ERROR_BAD_CERT_DOMAIN,
+ CheckCertHostname(certInput, hostnameInput, mNameMatchingPolicy));
+}
+
+TEST_P(pkixnames_CheckCertHostname_PresentedMatchesReference,
+ SubjectAltName_CNNotDNSName)
+{
+ // A DNSName SAN entry should match, regardless of the contents of the
+ // subject CN.
+
+ const PresentedMatchesReference& param(GetParam());
+
+ ByteString cert(CreateCert(RDN(CN("Common Name")),
+ DNSName(param.presentedDNSID)));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
+
+ Input hostnameInput;
+ ASSERT_EQ(Success, hostnameInput.Init(param.referenceDNSID.data(),
+ param.referenceDNSID.length()));
+ Result expectedResult
+ = param.expectedResult != Success ? param.expectedResult
+ : param.expectedMatches ? Success
+ : Result::ERROR_BAD_CERT_DOMAIN;
+ ASSERT_EQ(expectedResult, CheckCertHostname(certInput, hostnameInput,
+ mNameMatchingPolicy));
+}
+
+INSTANTIATE_TEST_CASE_P(pkixnames_CheckCertHostname_DNSID_MATCH_PARAMS,
+ pkixnames_CheckCertHostname_PresentedMatchesReference,
+ testing::ValuesIn(DNSID_MATCH_PARAMS));
+
+TEST_P(pkixnames_Turkish_I_Comparison, CheckCertHostname_CN_NoSAN)
+{
+ // Make sure we don't have the similar problems that strcasecmp and others
+ // have with the other kinds of "i" and "I" commonly used in Turkish locales,
+ // when we're matching a CN due to lack of subjectAltName.
+
+ const InputValidity& param(GetParam());
+ SCOPED_TRACE(param.input.c_str());
+
+ Input input;
+ ASSERT_EQ(Success, input.Init(param.input.data(), param.input.length()));
+
+ ByteString cert(CreateCert(RDN(CN(param.input)), NO_SAN));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
+
+ Result expectedResult = (InputsAreEqual(LOWERCASE_I, input) ||
+ InputsAreEqual(UPPERCASE_I, input))
+ ? Success
+ : Result::ERROR_BAD_CERT_DOMAIN;
+
+ ASSERT_EQ(expectedResult, CheckCertHostname(certInput, UPPERCASE_I,
+ mNameMatchingPolicy));
+ ASSERT_EQ(expectedResult, CheckCertHostname(certInput, LOWERCASE_I,
+ mNameMatchingPolicy));
+}
+
+TEST_P(pkixnames_Turkish_I_Comparison, CheckCertHostname_SAN)
+{
+ // Make sure we don't have the similar problems that strcasecmp and others
+ // have with the other kinds of "i" and "I" commonly used in Turkish locales,
+ // when we're matching a dNSName in the SAN.
+
+ const InputValidity& param(GetParam());
+ SCOPED_TRACE(param.input.c_str());
+
+ Input input;
+ ASSERT_EQ(Success, input.Init(param.input.data(), param.input.length()));
+
+ ByteString cert(CreateCert(RDN(CN("Common Name")), DNSName(param.input)));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
+
+ Result expectedResult
+ = (!param.isValidPresentedID) ? Result::ERROR_BAD_DER
+ : (InputsAreEqual(LOWERCASE_I, input) ||
+ InputsAreEqual(UPPERCASE_I, input)) ? Success
+ : Result::ERROR_BAD_CERT_DOMAIN;
+
+ ASSERT_EQ(expectedResult, CheckCertHostname(certInput, UPPERCASE_I,
+ mNameMatchingPolicy));
+ ASSERT_EQ(expectedResult, CheckCertHostname(certInput, LOWERCASE_I,
+ mNameMatchingPolicy));
+}
+
+class pkixnames_CheckCertHostname_IPV4_Addresses
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<IPAddressParams<4>>
+{
+public:
+ DefaultNameMatchingPolicy mNameMatchingPolicy;
+};
+
+TEST_P(pkixnames_CheckCertHostname_IPV4_Addresses,
+ ValidIPv4AddressInIPAddressSAN)
+{
+ // When the reference hostname is a valid IPv4 address, a correctly-formed
+ // IPv4 Address SAN matches it.
+
+ const IPAddressParams<4>& param(GetParam());
+
+ ByteString cert(CreateCert(RDN(CN("Common Name")),
+ IPAddress(param.expectedValueIfValid)));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
+
+ Input hostnameInput;
+ ASSERT_EQ(Success, hostnameInput.Init(param.input.data(),
+ param.input.length()));
+
+ ASSERT_EQ(param.isValid ? Success : Result::ERROR_BAD_CERT_DOMAIN,
+ CheckCertHostname(certInput, hostnameInput, mNameMatchingPolicy));
+}
+
+TEST_P(pkixnames_CheckCertHostname_IPV4_Addresses,
+ ValidIPv4AddressInCN_NoSAN)
+{
+ // When the reference hostname is a valid IPv4 address, a correctly-formed
+ // IPv4 Address in the CN matches it when there is no SAN.
+
+ const IPAddressParams<4>& param(GetParam());
+
+ SCOPED_TRACE(param.input.c_str());
+
+ ByteString cert(CreateCert(RDN(CN(param.input)), NO_SAN));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length()));
+
+ Input hostnameInput;
+ ASSERT_EQ(Success, hostnameInput.Init(param.input.data(),
+ param.input.length()));
+
+ // Some of the invalid IPv4 addresses are valid DNS names!
+ Result expectedResult = (param.isValid || IsValidReferenceDNSID(hostnameInput))
+ ? Success
+ : Result::ERROR_BAD_CERT_DOMAIN;
+
+ ASSERT_EQ(expectedResult, CheckCertHostname(certInput, hostnameInput,
+ mNameMatchingPolicy));
+}
+
+INSTANTIATE_TEST_CASE_P(pkixnames_CheckCertHostname_IPV4_ADDRESSES,
+ pkixnames_CheckCertHostname_IPV4_Addresses,
+ testing::ValuesIn(IPV4_ADDRESSES));
+
+struct NameConstraintParams
+{
+ ByteString subject;
+ ByteString subjectAltName;
+ ByteString subtrees;
+ Result expectedPermittedSubtreesResult;
+ Result expectedExcludedSubtreesResult;
+};
+
+static ByteString
+PermittedSubtrees(const ByteString& generalSubtrees)
+{
+ return TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0,
+ generalSubtrees);
+}
+
+static ByteString
+ExcludedSubtrees(const ByteString& generalSubtrees)
+{
+ return TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1,
+ generalSubtrees);
+}
+
+// Does not encode min or max.
+static ByteString
+GeneralSubtree(const ByteString& base)
+{
+ return TLV(der::SEQUENCE, base);
+}
+
+static const NameConstraintParams NAME_CONSTRAINT_PARAMS[] =
+{
+ /////////////////////////////////////////////////////////////////////////////
+ // XXX: Malformed name constraints for supported types of names are ignored
+ // when there are no names of that type to constrain.
+ { ByteString(), NO_SAN,
+ GeneralSubtree(DNSName("!")),
+ Success, Success
+ },
+ { // DirectoryName constraints are an exception, because *every* certificate
+ // has at least one DirectoryName (tbsCertificate.subject).
+ ByteString(), NO_SAN,
+ GeneralSubtree(Name(ByteString(reinterpret_cast<const uint8_t*>("!"), 1))),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { ByteString(), NO_SAN,
+ GeneralSubtree(IPAddress(ipv4_constraint_truncated_bytes)),
+ Success, Success
+ },
+ { ByteString(), NO_SAN,
+ GeneralSubtree(IPAddress(ipv4_constraint_overlong_bytes)),
+ Success, Success
+ },
+ { ByteString(), NO_SAN,
+ GeneralSubtree(IPAddress(ipv6_constraint_truncated_bytes)),
+ Success, Success
+ },
+ { ByteString(), NO_SAN,
+ GeneralSubtree(IPAddress(ipv6_constraint_overlong_bytes)),
+ Success, Success
+ },
+ { ByteString(), NO_SAN,
+ GeneralSubtree(RFC822Name("!")),
+ Success, Success
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Edge cases of name constraint absolute vs. relative and subdomain matching
+ // that are not clearly explained in RFC 5280. (See the long comment above
+ // MatchPresentedDNSIDWithReferenceDNSID.)
+
+ // Q: Does a presented identifier equal (case insensitive) to the name
+ // constraint match the constraint? For example, does the presented
+ // ID "host.example.com" match a "host.example.com" constraint?
+ { ByteString(), DNSName("host.example.com"),
+ GeneralSubtree(DNSName("host.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // This test case is an example from RFC 5280.
+ ByteString(), DNSName("host1.example.com"),
+ GeneralSubtree(DNSName("host.example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { ByteString(), RFC822Name("a@host.example.com"),
+ GeneralSubtree(RFC822Name("host.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // This test case is an example from RFC 5280.
+ ByteString(), RFC822Name("a@host1.example.com"),
+ GeneralSubtree(RFC822Name("host.example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+
+ // Q: When the name constraint does not start with ".", do subdomain
+ // presented identifiers match it? For example, does the presented
+ // ID "www.host.example.com" match a "host.example.com" constraint?
+ { // This test case is an example from RFC 5280.
+ ByteString(), DNSName("www.host.example.com"),
+ GeneralSubtree(DNSName( "host.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // The subdomain matching rule for host names that do not start with "." is
+ // different for RFC822Names than for DNSNames!
+ ByteString(), RFC822Name("a@www.host.example.com"),
+ GeneralSubtree(RFC822Name( "host.example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE,
+ Success
+ },
+
+ // Q: When the name constraint does not start with ".", does a
+ // non-subdomain prefix match it? For example, does "bigfoo.bar.com"
+ // match "foo.bar.com"?
+ { ByteString(), DNSName("bigfoo.bar.com"),
+ GeneralSubtree(DNSName( "foo.bar.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { ByteString(), RFC822Name("a@bigfoo.bar.com"),
+ GeneralSubtree(RFC822Name( "foo.bar.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+
+ // Q: Is a name constraint that starts with "." valid, and if so, what
+ // semantics does it have? For example, does a presented ID of
+ // "www.example.com" match a constraint of ".example.com"? Does a
+ // presented ID of "example.com" match a constraint of ".example.com"?
+ { ByteString(), DNSName("www.example.com"),
+ GeneralSubtree(DNSName( ".example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // When there is no Local-part, an RFC822 name constraint's domain may
+ // start with '.', and the semantics are the same as for DNSNames.
+ ByteString(), RFC822Name("a@www.example.com"),
+ GeneralSubtree(RFC822Name( ".example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // When there is a Local-part, an RFC822 name constraint's domain must not
+ // start with '.'.
+ ByteString(), RFC822Name("a@www.example.com"),
+ GeneralSubtree(RFC822Name( "a@.example.com")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // Check that we only allow subdomains to match.
+ ByteString(), DNSName( "example.com"),
+ GeneralSubtree(DNSName(".example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // Check that we only allow subdomains to match.
+ ByteString(), RFC822Name("a@example.com"),
+ GeneralSubtree(RFC822Name(".example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // Check that we don't get confused and consider "b" == "."
+ ByteString(), DNSName("bexample.com"),
+ GeneralSubtree(DNSName(".example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // Check that we don't get confused and consider "b" == "."
+ ByteString(), RFC822Name("a@bexample.com"),
+ GeneralSubtree(RFC822Name( ".example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+
+ // Q: Is there a way to prevent subdomain matches?
+ // (This is tested in a different set of tests because it requires a
+ // combination of permittedSubtrees and excludedSubtrees.)
+
+ // Q: Are name constraints allowed to be specified as absolute names?
+ // For example, does a presented ID of "example.com" match a name
+ // constraint of "example.com." and vice versa?
+ //
+ { // The DNSName in the constraint is not valid because constraint DNS IDs
+ // are not allowed to be absolute.
+ ByteString(), DNSName("example.com"),
+ GeneralSubtree(DNSName("example.com.")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER,
+ },
+ { ByteString(), RFC822Name("a@example.com"),
+ GeneralSubtree(RFC822Name( "example.com.")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER,
+ },
+ { // The DNSName in the SAN is not valid because presented DNS IDs are not
+ // allowed to be absolute.
+ ByteString(), DNSName("example.com."),
+ GeneralSubtree(DNSName("example.com")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER,
+ },
+ { ByteString(), RFC822Name("a@example.com."),
+ GeneralSubtree(RFC822Name( "example.com")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER,
+ },
+ { // The presented DNSName is the same length as the constraint, because the
+ // subdomain is only one character long and because the constraint both
+ // begins and ends with ".". But, it doesn't matter because absolute names
+ // are not allowed for DNSName constraints.
+ ByteString(), DNSName("p.example.com"),
+ GeneralSubtree(DNSName(".example.com.")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER,
+ },
+ { // The presented DNSName is the same length as the constraint, because the
+ // subdomain is only one character long and because the constraint both
+ // begins and ends with ".".
+ ByteString(), RFC822Name("a@p.example.com"),
+ GeneralSubtree(RFC822Name( ".example.com.")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER,
+ },
+ { // Same as previous test case, but using a wildcard presented ID.
+ ByteString(), DNSName("*.example.com"),
+ GeneralSubtree(DNSName(".example.com.")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // Same as previous test case, but using a wildcard presented ID, which is
+ // invalid in an RFC822Name.
+ ByteString(), RFC822Name("a@*.example.com"),
+ GeneralSubtree(RFC822Name( ".example.com.")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+
+ // Q: Are "" and "." valid DNSName constraints? If so, what do they mean?
+ { ByteString(), DNSName("example.com"),
+ GeneralSubtree(DNSName("")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), RFC822Name("a@example.com"),
+ GeneralSubtree(RFC822Name("")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // The malformed (absolute) presented ID does not match.
+ ByteString(), DNSName("example.com."),
+ GeneralSubtree(DNSName("")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { ByteString(), RFC822Name("a@example.com."),
+ GeneralSubtree(RFC822Name("")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // Invalid syntax in name constraint
+ ByteString(), DNSName("example.com"),
+ GeneralSubtree(DNSName(".")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER,
+ },
+ { // Invalid syntax in name constraint
+ ByteString(), RFC822Name("a@example.com"),
+ GeneralSubtree(RFC822Name(".")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER,
+ },
+ { ByteString(), DNSName("example.com."),
+ GeneralSubtree(DNSName(".")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { ByteString(), RFC822Name("a@example.com."),
+ GeneralSubtree(RFC822Name(".")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Basic IP Address constraints (non-CN-ID)
+
+ // The Mozilla CA Policy says this means "no IPv4 addresses allowed."
+ { ByteString(), IPAddress(ipv4_addr_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), IPAddress(ipv4_addr_00000000_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), IPAddress(ipv4_addr_FFFFFFFF_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+
+ // The Mozilla CA Policy says this means "no IPv6 addresses allowed."
+ { ByteString(), IPAddress(ipv6_addr_bytes),
+ GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), IPAddress(ipv6_addr_all_zeros_bytes),
+ GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+
+ // RFC 5280 doesn't partition IP address constraints into separate IPv4 and
+ // IPv6 categories, so a IPv4 permittedSubtrees constraint excludes all IPv6
+ // addresses, and vice versa.
+ { ByteString(), IPAddress(ipv4_addr_bytes),
+ GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { ByteString(), IPAddress(ipv6_addr_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+
+ // IPv4 Subnets
+ { ByteString(), IPAddress(ipv4_addr_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_CIDR_16_bytes)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), IPAddress(ipv4_addr_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_CIDR_17_bytes)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), IPAddress(ipv4_other_addr_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_CIDR_16_bytes)),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // XXX(bug 1089430): We don't reject this even though it is weird.
+ ByteString(), IPAddress(ipv4_addr_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_CIDR_16_bad_addr_bytes)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // XXX(bug 1089430): We don't reject this even though it is weird.
+ ByteString(), IPAddress(ipv4_other_addr_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_bad_mask_bytes)),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+
+ // IPv6 Subnets
+ { ByteString(), IPAddress(ipv6_addr_bytes),
+ GeneralSubtree(IPAddress(ipv6_constraint_CIDR_16_bytes)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), IPAddress(ipv6_other_addr_bytes),
+ GeneralSubtree(IPAddress(ipv6_constraint_CIDR_16_bytes)),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // XXX(bug 1089430): We don't reject this even though it is weird.
+ ByteString(), IPAddress(ipv6_addr_bytes),
+ GeneralSubtree(IPAddress(ipv6_constraint_CIDR_16_bad_addr_bytes)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // XXX(bug 1089430): We don't reject this even though it is weird.
+ ByteString(), IPAddress(ipv6_other_addr_bytes),
+ GeneralSubtree(IPAddress(ipv6_constraint_bad_mask_bytes)),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+
+ // Malformed presented IP addresses and constraints
+
+ { // The presented IPv4 address is empty
+ ByteString(), IPAddress(),
+ GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // The presented IPv4 address is truncated
+ ByteString(), IPAddress(ipv4_addr_truncated_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // The presented IPv4 address is too long
+ ByteString(), IPAddress(ipv4_addr_overlong_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // The presented IPv4 constraint is empty
+ ByteString(), IPAddress(ipv4_addr_bytes),
+ GeneralSubtree(IPAddress()),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // The presented IPv4 constraint is truncated
+ ByteString(), IPAddress(ipv4_addr_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_truncated_bytes)),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // The presented IPv4 constraint is too long
+ ByteString(), IPAddress(ipv4_addr_bytes),
+ GeneralSubtree(IPAddress(ipv4_constraint_overlong_bytes)),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // The presented IPv6 address is empty
+ ByteString(), IPAddress(),
+ GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // The presented IPv6 address is truncated
+ ByteString(), IPAddress(ipv6_addr_truncated_bytes),
+ GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // The presented IPv6 address is too long
+ ByteString(), IPAddress(ipv6_addr_overlong_bytes),
+ GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // The presented IPv6 constraint is empty
+ ByteString(), IPAddress(ipv6_addr_bytes),
+ GeneralSubtree(IPAddress()),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // The presented IPv6 constraint is truncated
+ ByteString(), IPAddress(ipv6_addr_bytes),
+ GeneralSubtree(IPAddress(ipv6_constraint_truncated_bytes)),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ { // The presented IPv6 constraint is too long
+ ByteString(), IPAddress(ipv6_addr_bytes),
+ GeneralSubtree(IPAddress(ipv6_constraint_overlong_bytes)),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // XXX: We don't reject malformed name constraints when there are no names of
+ // that type.
+ { ByteString(), NO_SAN, GeneralSubtree(DNSName("!")),
+ Success, Success
+ },
+ { ByteString(), NO_SAN, GeneralSubtree(IPAddress(ipv4_addr_overlong_bytes)),
+ Success, Success
+ },
+ { ByteString(), NO_SAN, GeneralSubtree(IPAddress(ipv6_addr_overlong_bytes)),
+ Success, Success
+ },
+ { ByteString(), NO_SAN, GeneralSubtree(RFC822Name("\0")),
+ Success, Success
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Basic CN-ID DNSName constraint tests.
+
+ { // Empty Name is ignored for DNSName constraints.
+ ByteString(), NO_SAN, GeneralSubtree(DNSName("a.example.com")),
+ Success, Success
+ },
+ { // Empty CN is ignored for DNSName constraints because it isn't a
+ // syntactically-valid DNSName.
+ //
+ // NSS gives different results.
+ RDN(CN("")), NO_SAN, GeneralSubtree(DNSName("a.example.com")),
+ Success, Success
+ },
+ { // IP Address is ignored for DNSName constraints.
+ //
+ // NSS gives different results.
+ RDN(CN("1.2.3.4")), NO_SAN, GeneralSubtree(DNSName("a.example.com")),
+ Success, Success
+ },
+ { // OU has something that looks like a dNSName that matches.
+ RDN(OU("a.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")),
+ Success, Success
+ },
+ { // OU has something that looks like a dNSName that does not match.
+ RDN(OU("b.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")),
+ Success, Success
+ },
+ { // NSS gives different results.
+ RDN(CN("Not a DNSName")), NO_SAN, GeneralSubtree(DNSName("a.example.com")),
+ Success, Success
+ },
+ { RDN(CN("a.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { RDN(CN("b.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // DNSName CN-ID match is detected when there is a SAN w/o any DNSName or
+ // IPAddress
+ RDN(CN("a.example.com")), RFC822Name("foo@example.com"),
+ GeneralSubtree(DNSName("a.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // DNSName CN-ID mismatch is detected when there is a SAN w/o any DNSName
+ // or IPAddress
+ RDN(CN("a.example.com")), RFC822Name("foo@example.com"),
+ GeneralSubtree(DNSName("b.example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // DNSName CN-ID match not reported when there is a DNSName SAN
+ RDN(CN("a.example.com")), DNSName("b.example.com"),
+ GeneralSubtree(DNSName("a.example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // DNSName CN-ID mismatch not reported when there is a DNSName SAN
+ RDN(CN("a.example.com")), DNSName("b.example.com"),
+ GeneralSubtree(DNSName("b.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE,
+ },
+ { // DNSName CN-ID match not reported when there is an IPAddress SAN
+ RDN(CN("a.example.com")), IPAddress(ipv4_addr_bytes),
+ GeneralSubtree(DNSName("a.example.com")),
+ Success, Success
+ },
+ { // DNSName CN-ID mismatch not reported when there is an IPAddress SAN
+ RDN(CN("a.example.com")), IPAddress(ipv4_addr_bytes),
+ GeneralSubtree(DNSName("b.example.com")),
+ Success, Success
+ },
+
+ { // IPAddress CN-ID match is detected when there is a SAN w/o any DNSName or
+ // IPAddress
+ RDN(CN(ipv4_addr_str)), RFC822Name("foo@example.com"),
+ GeneralSubtree(IPAddress(ipv4_addr_bytes_FFFFFFFF)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // IPAddress CN-ID mismatch is detected when there is a SAN w/o any DNSName
+ // or IPAddress
+ RDN(CN(ipv4_addr_str)), RFC822Name("foo@example.com"),
+ GeneralSubtree(IPAddress(ipv4_other_addr_bytes_FFFFFFFF)),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // IPAddress CN-ID match not reported when there is a DNSName SAN
+ RDN(CN(ipv4_addr_str)), DNSName("b.example.com"),
+ GeneralSubtree(IPAddress(ipv4_addr_bytes_FFFFFFFF)),
+ Success, Success
+ },
+ { // IPAddress CN-ID mismatch not reported when there is a DNSName SAN
+ RDN(CN(ipv4_addr_str)), DNSName("b.example.com"),
+ GeneralSubtree(IPAddress(ipv4_addr_bytes_FFFFFFFF)),
+ Success, Success
+ },
+ { // IPAddress CN-ID match not reported when there is an IPAddress SAN
+ RDN(CN(ipv4_addr_str)), IPAddress(ipv4_other_addr_bytes),
+ GeneralSubtree(IPAddress(ipv4_addr_bytes_FFFFFFFF)),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // IPAddress CN-ID mismatch not reported when there is an IPAddress SAN
+ RDN(CN(ipv4_addr_str)), IPAddress(ipv4_other_addr_bytes),
+ GeneralSubtree(IPAddress(ipv4_other_addr_bytes_FFFFFFFF)),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Test that constraints are applied to the most specific (last) CN, and only
+ // that CN-ID.
+
+ { // Name constraint only matches a.example.com, but the most specific CN
+ // (i.e. the CN-ID) is b.example.com. (Two CNs in one RDN.)
+ RDN(CN("a.example.com") + CN("b.example.com")), NO_SAN,
+ GeneralSubtree(DNSName("a.example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // Name constraint only matches a.example.com, but the most specific CN
+ // (i.e. the CN-ID) is b.example.com. (Two CNs in separate RDNs.)
+ RDN(CN("a.example.com")) + RDN(CN("b.example.com")), NO_SAN,
+ GeneralSubtree(DNSName("a.example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success
+ },
+ { // Name constraint only permits b.example.com, and the most specific CN
+ // (i.e. the CN-ID) is b.example.com. (Two CNs in one RDN.)
+ RDN(CN("a.example.com") + CN("b.example.com")), NO_SAN,
+ GeneralSubtree(DNSName("b.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // Name constraint only permits b.example.com, and the most specific CN
+ // (i.e. the CN-ID) is b.example.com. (Two CNs in separate RDNs.)
+ RDN(CN("a.example.com")) + RDN(CN("b.example.com")), NO_SAN,
+ GeneralSubtree(DNSName("b.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Additional RFC822 name constraint tests. There are more tests regarding
+ // the DNSName part of the constraint mixed into the DNSName constraint
+ // tests.
+
+ { ByteString(), RFC822Name("a@example.com"),
+ GeneralSubtree(RFC822Name("a@example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+
+ // Bug 1056773: name constraints that omit Local-part but include '@' are
+ // invalid.
+ { ByteString(), RFC822Name("a@example.com"),
+ GeneralSubtree(RFC822Name("@example.com")),
+ Result::ERROR_BAD_DER,
+ Result::ERROR_BAD_DER
+ },
+ { ByteString(), RFC822Name("@example.com"),
+ GeneralSubtree(RFC822Name("@example.com")),
+ Result::ERROR_BAD_DER,
+ Result::ERROR_BAD_DER
+ },
+ { ByteString(), RFC822Name("example.com"),
+ GeneralSubtree(RFC822Name("@example.com")),
+ Result::ERROR_BAD_DER,
+ Result::ERROR_BAD_DER
+ },
+ { ByteString(), RFC822Name("a@mail.example.com"),
+ GeneralSubtree(RFC822Name("a@*.example.com")),
+ Result::ERROR_BAD_DER,
+ Result::ERROR_BAD_DER
+ },
+ { ByteString(), RFC822Name("a@*.example.com"),
+ GeneralSubtree(RFC822Name(".example.com")),
+ Result::ERROR_BAD_DER,
+ Result::ERROR_BAD_DER
+ },
+ { ByteString(), RFC822Name("@example.com"),
+ GeneralSubtree(RFC822Name(".example.com")),
+ Result::ERROR_BAD_DER,
+ Result::ERROR_BAD_DER
+ },
+ { ByteString(), RFC822Name("@a.example.com"),
+ GeneralSubtree(RFC822Name(".example.com")),
+ Result::ERROR_BAD_DER,
+ Result::ERROR_BAD_DER
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Test name constraints with underscores.
+ //
+ { ByteString(), DNSName("uses_underscore.example.com"),
+ GeneralSubtree(DNSName("uses_underscore.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), DNSName("uses_underscore.example.com"),
+ GeneralSubtree(DNSName("example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), DNSName("a.uses_underscore.example.com"),
+ GeneralSubtree(DNSName("uses_underscore.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), RFC822Name("a@uses_underscore.example.com"),
+ GeneralSubtree(RFC822Name("uses_underscore.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), RFC822Name("uses_underscore@example.com"),
+ GeneralSubtree(RFC822Name("example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), RFC822Name("a@a.uses_underscore.example.com"),
+ GeneralSubtree(RFC822Name(".uses_underscore.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Name constraint tests that relate to having an empty SAN. According to RFC
+ // 5280 this isn't valid, but we allow it for compatibility reasons (see bug
+ // 1143085).
+ { // For DNSNames, we fall back to the subject CN.
+ RDN(CN("a.example.com")), ByteString(),
+ GeneralSubtree(DNSName("a.example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // For RFC822Names, we do not fall back to the subject emailAddress.
+ // This new implementation seems to conform better to the standards for
+ // RFC822 name constraints, by only applying the name constraints to
+ // emailAddress names in the certificate subject if there is no
+ // subjectAltName extension in the cert.
+ // In this case, the presence of the (empty) SAN extension means that RFC822
+ // name constraints are not enforced on the emailAddress attributes of the
+ // subject.
+ RDN(emailAddress("a@example.com")), ByteString(),
+ GeneralSubtree(RFC822Name("a@example.com")),
+ Success, Success
+ },
+ { // Compare this to the case where there is no SAN (i.e. the name
+ // constraints are enforced, because the extension is not present at all).
+ RDN(emailAddress("a@example.com")), NO_SAN,
+ GeneralSubtree(RFC822Name("a@example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // DirectoryName name constraint tests
+
+ { // One AVA per RDN
+ RDN(OU("Example Organization")) + RDN(CN("example.com")), NO_SAN,
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization")) +
+ RDN(CN("example.com"))))),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // RDNs can have multiple AVAs.
+ RDN(OU("Example Organization") + CN("example.com")), NO_SAN,
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization") +
+ CN("example.com"))))),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // The constraint is a prefix of the subject DN.
+ RDN(OU("Example Organization")) + RDN(CN("example.com")), NO_SAN,
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // The name constraint is not a prefix of the subject DN.
+ // Note that for excludedSubtrees, we simply prohibit any non-empty
+ // directoryName constraint to ensure we are not being too lenient.
+ RDN(OU("Other Example Organization")) + RDN(CN("example.com")), NO_SAN,
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization")) +
+ RDN(CN("example.com"))))),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // Same as the previous one, but one RDN with multiple AVAs.
+ RDN(OU("Other Example Organization") + CN("example.com")), NO_SAN,
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization") +
+ CN("example.com"))))),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // With multiple AVAs per RDN in the subject DN, the constraint is not a
+ // prefix of the subject DN.
+ RDN(OU("Example Organization") + CN("example.com")), NO_SAN,
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // The subject DN RDN has multiple AVAs, but the name constraint has only
+ // one AVA per RDN.
+ RDN(OU("Example Organization") + CN("example.com")), NO_SAN,
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization")) +
+ RDN(CN("example.com"))))),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // The name constraint RDN has multiple AVAs, but the subject DN has only
+ // one AVA per RDN.
+ RDN(OU("Example Organization")) + RDN(CN("example.com")), NO_SAN,
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization") +
+ CN("example.com"))))),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // In this case, the constraint uses a different encoding from the subject.
+ // We consider them to match because we allow UTF8String and
+ // PrintableString to compare equal when their contents are equal.
+ RDN(OU("Example Organization", der::UTF8String)) + RDN(CN("example.com")),
+ NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization",
+ der::PrintableString)) +
+ RDN(CN("example.com"))))),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // Same as above, but with UTF8String/PrintableString switched.
+ RDN(OU("Example Organization", der::PrintableString)) + RDN(CN("example.com")),
+ NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization",
+ der::UTF8String)) +
+ RDN(CN("example.com"))))),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // If the contents aren't the same, then they shouldn't match.
+ RDN(OU("Other Example Organization", der::UTF8String)) + RDN(CN("example.com")),
+ NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization",
+ der::PrintableString)) +
+ RDN(CN("example.com"))))),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { // Only UTF8String and PrintableString are considered equivalent.
+ RDN(OU("Example Organization", der::PrintableString)) + RDN(CN("example.com")),
+ NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization",
+ der::TeletexString)) +
+ RDN(CN("example.com"))))),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ // Some additional tests for completeness:
+ // Ensure that wildcards are handled:
+ { RDN(CN("*.example.com")), NO_SAN, GeneralSubtree(DNSName("example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), DNSName("*.example.com"),
+ GeneralSubtree(DNSName("example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), DNSName("www.example.com"),
+ GeneralSubtree(DNSName("*.example.com")),
+ Result::ERROR_BAD_DER, Result::ERROR_BAD_DER
+ },
+ // Handle multiple name constraint entries:
+ { RDN(CN("example.com")), NO_SAN,
+ GeneralSubtree(DNSName("example.org")) +
+ GeneralSubtree(DNSName("example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { ByteString(), DNSName("example.com"),
+ GeneralSubtree(DNSName("example.org")) +
+ GeneralSubtree(DNSName("example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ // Handle multiple names in subject alternative name extension:
+ { ByteString(), DNSName("example.com") + DNSName("example.org"),
+ GeneralSubtree(DNSName("example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ // Handle a mix of DNSName and DirectoryName:
+ { RDN(OU("Example Organization")), DNSName("example.com"),
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))) +
+ GeneralSubtree(DNSName("example.com")),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { RDN(OU("Other Example Organization")), DNSName("example.com"),
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))) +
+ GeneralSubtree(DNSName("example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ { RDN(OU("Example Organization")), DNSName("example.org"),
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))) +
+ GeneralSubtree(DNSName("example.com")),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ // Handle a certificate with no DirectoryName:
+ { ByteString(), DNSName("example.com"),
+ GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+};
+
+class pkixnames_CheckNameConstraints
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<NameConstraintParams>
+{
+public:
+ DefaultNameMatchingPolicy mNameMatchingPolicy;
+};
+
+TEST_P(pkixnames_CheckNameConstraints,
+ NameConstraintsEnforcedForDirectlyIssuedEndEntity)
+{
+ // Test that name constraints are enforced on a certificate directly issued by
+ // a certificate with the given name constraints.
+
+ const NameConstraintParams& param(GetParam());
+
+ ByteString certDER(CreateCert(param.subject, param.subjectAltName));
+ ASSERT_FALSE(ENCODING_FAILED(certDER));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length()));
+ BackCert cert(certInput, EndEntityOrCA::MustBeEndEntity, nullptr);
+ ASSERT_EQ(Success, cert.Init());
+
+ {
+ ByteString nameConstraintsDER(TLV(der::SEQUENCE,
+ PermittedSubtrees(param.subtrees)));
+ Input nameConstraints;
+ ASSERT_EQ(Success,
+ nameConstraints.Init(nameConstraintsDER.data(),
+ nameConstraintsDER.length()));
+ ASSERT_EQ(param.expectedPermittedSubtreesResult,
+ CheckNameConstraints(nameConstraints, cert,
+ KeyPurposeId::id_kp_serverAuth));
+ }
+ {
+ ByteString nameConstraintsDER(TLV(der::SEQUENCE,
+ ExcludedSubtrees(param.subtrees)));
+ Input nameConstraints;
+ ASSERT_EQ(Success,
+ nameConstraints.Init(nameConstraintsDER.data(),
+ nameConstraintsDER.length()));
+ ASSERT_EQ(param.expectedExcludedSubtreesResult,
+ CheckNameConstraints(nameConstraints, cert,
+ KeyPurposeId::id_kp_serverAuth));
+ }
+ {
+ ByteString nameConstraintsDER(TLV(der::SEQUENCE,
+ PermittedSubtrees(param.subtrees) +
+ ExcludedSubtrees(param.subtrees)));
+ Input nameConstraints;
+ ASSERT_EQ(Success,
+ nameConstraints.Init(nameConstraintsDER.data(),
+ nameConstraintsDER.length()));
+ ASSERT_EQ((param.expectedPermittedSubtreesResult ==
+ param.expectedExcludedSubtreesResult)
+ ? param.expectedExcludedSubtreesResult
+ : Result::ERROR_CERT_NOT_IN_NAME_SPACE,
+ CheckNameConstraints(nameConstraints, cert,
+ KeyPurposeId::id_kp_serverAuth));
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(pkixnames_CheckNameConstraints,
+ pkixnames_CheckNameConstraints,
+ testing::ValuesIn(NAME_CONSTRAINT_PARAMS));
+
+// The |subjectAltName| param is not used for these test cases (hence the use of
+// "NO_SAN").
+static const NameConstraintParams NO_FALLBACK_NAME_CONSTRAINT_PARAMS[] =
+{
+ // The only difference between end-entities being verified for serverAuth and
+ // intermediates or end-entities being verified for other uses is that for
+ // the latter cases, there is no fallback matching of DNSName entries to the
+ // subject common name.
+ { RDN(CN("Not a DNSName")), NO_SAN, GeneralSubtree(DNSName("a.example.com")),
+ Success, Success
+ },
+ { RDN(CN("a.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")),
+ Success, Success
+ },
+ { RDN(CN("b.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")),
+ Success, Success
+ },
+ // Sanity-check that name constraints are in fact enforced in these cases.
+ { RDN(CN("Example Name")), NO_SAN,
+ GeneralSubtree(DirectoryName(Name(RDN(CN("Example Name"))))),
+ Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+ // (In this implementation, if a DirectoryName is in excludedSubtrees, nothing
+ // is considered to be in the name space.)
+ { RDN(CN("Other Example Name")), NO_SAN,
+ GeneralSubtree(DirectoryName(Name(RDN(CN("Example Name"))))),
+ Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE
+ },
+};
+
+class pkixnames_CheckNameConstraintsOnIntermediate
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<NameConstraintParams>
+{
+};
+
+TEST_P(pkixnames_CheckNameConstraintsOnIntermediate,
+ NameConstraintsEnforcedOnIntermediate)
+{
+ // Test that name constraints are enforced on an intermediate certificate
+ // directly issued by a certificate with the given name constraints.
+
+ const NameConstraintParams& param(GetParam());
+
+ ByteString certDER(CreateCert(param.subject, NO_SAN,
+ EndEntityOrCA::MustBeCA));
+ ASSERT_FALSE(ENCODING_FAILED(certDER));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length()));
+ BackCert cert(certInput, EndEntityOrCA::MustBeCA, nullptr);
+ ASSERT_EQ(Success, cert.Init());
+
+ {
+ ByteString nameConstraintsDER(TLV(der::SEQUENCE,
+ PermittedSubtrees(param.subtrees)));
+ Input nameConstraints;
+ ASSERT_EQ(Success,
+ nameConstraints.Init(nameConstraintsDER.data(),
+ nameConstraintsDER.length()));
+ ASSERT_EQ(param.expectedPermittedSubtreesResult,
+ CheckNameConstraints(nameConstraints, cert,
+ KeyPurposeId::id_kp_serverAuth));
+ }
+ {
+ ByteString nameConstraintsDER(TLV(der::SEQUENCE,
+ ExcludedSubtrees(param.subtrees)));
+ Input nameConstraints;
+ ASSERT_EQ(Success,
+ nameConstraints.Init(nameConstraintsDER.data(),
+ nameConstraintsDER.length()));
+ ASSERT_EQ(param.expectedExcludedSubtreesResult,
+ CheckNameConstraints(nameConstraints, cert,
+ KeyPurposeId::id_kp_serverAuth));
+ }
+ {
+ ByteString nameConstraintsDER(TLV(der::SEQUENCE,
+ PermittedSubtrees(param.subtrees) +
+ ExcludedSubtrees(param.subtrees)));
+ Input nameConstraints;
+ ASSERT_EQ(Success,
+ nameConstraints.Init(nameConstraintsDER.data(),
+ nameConstraintsDER.length()));
+ ASSERT_EQ(param.expectedExcludedSubtreesResult,
+ CheckNameConstraints(nameConstraints, cert,
+ KeyPurposeId::id_kp_serverAuth));
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(pkixnames_CheckNameConstraintsOnIntermediate,
+ pkixnames_CheckNameConstraintsOnIntermediate,
+ testing::ValuesIn(NO_FALLBACK_NAME_CONSTRAINT_PARAMS));
+
+class pkixnames_CheckNameConstraintsForNonServerAuthUsage
+ : public ::testing::Test
+ , public ::testing::WithParamInterface<NameConstraintParams>
+{
+};
+
+TEST_P(pkixnames_CheckNameConstraintsForNonServerAuthUsage,
+ NameConstraintsEnforcedForNonServerAuthUsage)
+{
+ // Test that for key purposes other than serverAuth, fallback to the subject
+ // common name does not occur.
+
+ const NameConstraintParams& param(GetParam());
+
+ ByteString certDER(CreateCert(param.subject, NO_SAN));
+ ASSERT_FALSE(ENCODING_FAILED(certDER));
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length()));
+ BackCert cert(certInput, EndEntityOrCA::MustBeEndEntity, nullptr);
+ ASSERT_EQ(Success, cert.Init());
+
+ {
+ ByteString nameConstraintsDER(TLV(der::SEQUENCE,
+ PermittedSubtrees(param.subtrees)));
+ Input nameConstraints;
+ ASSERT_EQ(Success,
+ nameConstraints.Init(nameConstraintsDER.data(),
+ nameConstraintsDER.length()));
+ ASSERT_EQ(param.expectedPermittedSubtreesResult,
+ CheckNameConstraints(nameConstraints, cert,
+ KeyPurposeId::id_kp_clientAuth));
+ }
+ {
+ ByteString nameConstraintsDER(TLV(der::SEQUENCE,
+ ExcludedSubtrees(param.subtrees)));
+ Input nameConstraints;
+ ASSERT_EQ(Success,
+ nameConstraints.Init(nameConstraintsDER.data(),
+ nameConstraintsDER.length()));
+ ASSERT_EQ(param.expectedExcludedSubtreesResult,
+ CheckNameConstraints(nameConstraints, cert,
+ KeyPurposeId::id_kp_clientAuth));
+ }
+ {
+ ByteString nameConstraintsDER(TLV(der::SEQUENCE,
+ PermittedSubtrees(param.subtrees) +
+ ExcludedSubtrees(param.subtrees)));
+ Input nameConstraints;
+ ASSERT_EQ(Success,
+ nameConstraints.Init(nameConstraintsDER.data(),
+ nameConstraintsDER.length()));
+ ASSERT_EQ(param.expectedExcludedSubtreesResult,
+ CheckNameConstraints(nameConstraints, cert,
+ KeyPurposeId::id_kp_clientAuth));
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(pkixnames_CheckNameConstraintsForNonServerAuthUsage,
+ pkixnames_CheckNameConstraintsForNonServerAuthUsage,
+ testing::ValuesIn(NO_FALLBACK_NAME_CONSTRAINT_PARAMS));
diff --git a/security/pkix/test/gtest/pkixocsp_CreateEncodedOCSPRequest_tests.cpp b/security/pkix/test/gtest/pkixocsp_CreateEncodedOCSPRequest_tests.cpp
new file mode 100644
index 000000000..ffc987c86
--- /dev/null
+++ b/security/pkix/test/gtest/pkixocsp_CreateEncodedOCSPRequest_tests.cpp
@@ -0,0 +1,145 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2013 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pkixgtest.h"
+#include "pkixder.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+class CreateEncodedOCSPRequestTrustDomain final
+ : public EverythingFailsByDefaultTrustDomain
+{
+private:
+ Result DigestBuf(Input item, DigestAlgorithm digestAlg,
+ /*out*/ uint8_t *digestBuf, size_t digestBufLen)
+ override
+ {
+ return TestDigestBuf(item, digestAlg, digestBuf, digestBufLen);
+ }
+
+ Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, unsigned int)
+ override
+ {
+ return Success;
+ }
+};
+
+class pkixocsp_CreateEncodedOCSPRequest : public ::testing::Test
+{
+protected:
+ void MakeIssuerCertIDComponents(const char* issuerASCII,
+ /*out*/ ByteString& issuerDER,
+ /*out*/ ByteString& issuerSPKI)
+ {
+ issuerDER = CNToDERName(issuerASCII);
+ ASSERT_FALSE(ENCODING_FAILED(issuerDER));
+
+ ScopedTestKeyPair keyPair(GenerateKeyPair());
+ ASSERT_TRUE(keyPair.get());
+ issuerSPKI = keyPair->subjectPublicKeyInfo;
+ }
+
+ CreateEncodedOCSPRequestTrustDomain trustDomain;
+};
+
+// Test that the large length of the child serial number causes
+// CreateEncodedOCSPRequest to fail.
+TEST_F(pkixocsp_CreateEncodedOCSPRequest, ChildCertLongSerialNumberTest)
+{
+ static const uint8_t UNSUPPORTED_LEN = 128; // must be larger than 127
+
+ ByteString serialNumberString;
+ // tag + length + value is 1 + 2 + UNSUPPORTED_LEN
+ // Encoding the length takes two bytes: one byte to indicate that a
+ // second byte follows, and the second byte to indicate the length.
+ serialNumberString.push_back(0x80 + 1);
+ serialNumberString.push_back(UNSUPPORTED_LEN);
+ // value is 0x010000...00
+ serialNumberString.push_back(0x01);
+ for (size_t i = 1; i < UNSUPPORTED_LEN; ++i) {
+ serialNumberString.push_back(0x00);
+ }
+
+ ByteString issuerDER;
+ ByteString issuerSPKI;
+ ASSERT_NO_FATAL_FAILURE(MakeIssuerCertIDComponents("CA", issuerDER,
+ issuerSPKI));
+
+ Input issuer;
+ ASSERT_EQ(Success, issuer.Init(issuerDER.data(), issuerDER.length()));
+
+ Input spki;
+ ASSERT_EQ(Success, spki.Init(issuerSPKI.data(), issuerSPKI.length()));
+
+ Input serialNumber;
+ ASSERT_EQ(Success, serialNumber.Init(serialNumberString.data(),
+ serialNumberString.length()));
+
+ uint8_t ocspRequest[OCSP_REQUEST_MAX_LENGTH];
+ size_t ocspRequestLength;
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ CreateEncodedOCSPRequest(trustDomain,
+ CertID(issuer, spki, serialNumber),
+ ocspRequest, ocspRequestLength));
+}
+
+// Test that CreateEncodedOCSPRequest handles the longest serial number that
+// it's required to support (i.e. 20 octets).
+TEST_F(pkixocsp_CreateEncodedOCSPRequest, LongestSupportedSerialNumberTest)
+{
+ static const uint8_t LONGEST_REQUIRED_LEN = 20;
+
+ ByteString serialNumberString;
+ // tag + length + value is 1 + 1 + LONGEST_REQUIRED_LEN
+ serialNumberString.push_back(der::INTEGER);
+ serialNumberString.push_back(LONGEST_REQUIRED_LEN);
+ serialNumberString.push_back(0x01);
+ // value is 0x010000...00
+ for (size_t i = 1; i < LONGEST_REQUIRED_LEN; ++i) {
+ serialNumberString.push_back(0x00);
+ }
+
+ ByteString issuerDER;
+ ByteString issuerSPKI;
+ ASSERT_NO_FATAL_FAILURE(MakeIssuerCertIDComponents("CA", issuerDER,
+ issuerSPKI));
+
+ Input issuer;
+ ASSERT_EQ(Success, issuer.Init(issuerDER.data(), issuerDER.length()));
+
+ Input spki;
+ ASSERT_EQ(Success, spki.Init(issuerSPKI.data(), issuerSPKI.length()));
+
+ Input serialNumber;
+ ASSERT_EQ(Success, serialNumber.Init(serialNumberString.data(),
+ serialNumberString.length()));
+
+ uint8_t ocspRequest[OCSP_REQUEST_MAX_LENGTH];
+ size_t ocspRequestLength;
+ ASSERT_EQ(Success,
+ CreateEncodedOCSPRequest(trustDomain,
+ CertID(issuer, spki, serialNumber),
+ ocspRequest, ocspRequestLength));
+}
diff --git a/security/pkix/test/gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp b/security/pkix/test/gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp
new file mode 100644
index 000000000..d7dab09d9
--- /dev/null
+++ b/security/pkix/test/gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp
@@ -0,0 +1,1046 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2014 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pkixder.h"
+#include "pkixgtest.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+const uint16_t END_ENTITY_MAX_LIFETIME_IN_DAYS = 10;
+
+// Note that CheckRevocation is never called for OCSP signing certificates.
+class OCSPTestTrustDomain : public DefaultCryptoTrustDomain
+{
+public:
+ OCSPTestTrustDomain() { }
+
+ Result GetCertTrust(EndEntityOrCA endEntityOrCA, const CertPolicyId&,
+ Input, /*out*/ TrustLevel& trustLevel)
+ /*non-final*/ override
+ {
+ EXPECT_EQ(endEntityOrCA, EndEntityOrCA::MustBeEndEntity);
+ trustLevel = TrustLevel::InheritsTrust;
+ return Success;
+ }
+
+ virtual void NoteAuxiliaryExtension(AuxiliaryExtension extension,
+ Input extensionData) override
+ {
+ if (extension == AuxiliaryExtension::SCTListFromOCSPResponse) {
+ signedCertificateTimestamps = InputToByteString(extensionData);
+ } else {
+ // We do not currently expect to receive any other extension here.
+ ADD_FAILURE();
+ }
+ }
+
+ ByteString signedCertificateTimestamps;
+};
+
+namespace {
+char const* const rootName = "Test CA 1";
+void deleteCertID(CertID* certID) { delete certID; }
+} // namespace
+
+class pkixocsp_VerifyEncodedResponse : public ::testing::Test
+{
+public:
+ static void SetUpTestCase()
+ {
+ rootKeyPair.reset(GenerateKeyPair());
+ if (!rootKeyPair) {
+ abort();
+ }
+ }
+
+ void SetUp()
+ {
+ rootNameDER = CNToDERName(rootName);
+ if (ENCODING_FAILED(rootNameDER)) {
+ abort();
+ }
+ Input rootNameDERInput;
+ if (rootNameDERInput.Init(rootNameDER.data(), rootNameDER.length())
+ != Success) {
+ abort();
+ }
+
+ serialNumberDER =
+ CreateEncodedSerialNumber(static_cast<long>(++rootIssuedCount));
+ if (ENCODING_FAILED(serialNumberDER)) {
+ abort();
+ }
+ Input serialNumberDERInput;
+ if (serialNumberDERInput.Init(serialNumberDER.data(),
+ serialNumberDER.length()) != Success) {
+ abort();
+ }
+
+ Input rootSPKIDER;
+ if (rootSPKIDER.Init(rootKeyPair->subjectPublicKeyInfo.data(),
+ rootKeyPair->subjectPublicKeyInfo.length())
+ != Success) {
+ abort();
+ }
+ endEntityCertID.reset(new (std::nothrow) CertID(rootNameDERInput, rootSPKIDER,
+ serialNumberDERInput));
+ if (!endEntityCertID) {
+ abort();
+ }
+ }
+
+ static ScopedTestKeyPair rootKeyPair;
+ static uint32_t rootIssuedCount;
+ OCSPTestTrustDomain trustDomain;
+
+ // endEntityCertID references rootKeyPair, rootNameDER, and serialNumberDER.
+ ByteString rootNameDER;
+ ByteString serialNumberDER;
+ // endEntityCertID references rootKeyPair, rootNameDER, and serialNumberDER.
+ ScopedPtr<CertID, deleteCertID> endEntityCertID;
+};
+
+/*static*/ ScopedTestKeyPair pkixocsp_VerifyEncodedResponse::rootKeyPair;
+/*static*/ uint32_t pkixocsp_VerifyEncodedResponse::rootIssuedCount = 0;
+
+///////////////////////////////////////////////////////////////////////////////
+// responseStatus
+
+struct WithoutResponseBytes
+{
+ uint8_t responseStatus;
+ Result expectedError;
+};
+
+static const WithoutResponseBytes WITHOUT_RESPONSEBYTES[] = {
+ { OCSPResponseContext::successful, Result::ERROR_OCSP_MALFORMED_RESPONSE },
+ { OCSPResponseContext::malformedRequest, Result::ERROR_OCSP_MALFORMED_REQUEST },
+ { OCSPResponseContext::internalError, Result::ERROR_OCSP_SERVER_ERROR },
+ { OCSPResponseContext::tryLater, Result::ERROR_OCSP_TRY_SERVER_LATER },
+ { 4/*unused*/, Result::ERROR_OCSP_UNKNOWN_RESPONSE_STATUS },
+ { OCSPResponseContext::sigRequired, Result::ERROR_OCSP_REQUEST_NEEDS_SIG },
+ { OCSPResponseContext::unauthorized, Result::ERROR_OCSP_UNAUTHORIZED_REQUEST },
+ { OCSPResponseContext::unauthorized + 1,
+ Result::ERROR_OCSP_UNKNOWN_RESPONSE_STATUS
+ },
+};
+
+class pkixocsp_VerifyEncodedResponse_WithoutResponseBytes
+ : public pkixocsp_VerifyEncodedResponse
+ , public ::testing::WithParamInterface<WithoutResponseBytes>
+{
+protected:
+ ByteString CreateEncodedOCSPErrorResponse(uint8_t status)
+ {
+ static const Input EMPTY;
+ OCSPResponseContext context(CertID(EMPTY, EMPTY, EMPTY),
+ oneDayBeforeNow);
+ context.responseStatus = status;
+ context.skipResponseBytes = true;
+ return CreateEncodedOCSPResponse(context);
+ }
+};
+
+TEST_P(pkixocsp_VerifyEncodedResponse_WithoutResponseBytes, CorrectErrorCode)
+{
+ ByteString
+ responseString(CreateEncodedOCSPErrorResponse(GetParam().responseStatus));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(GetParam().expectedError,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+}
+
+INSTANTIATE_TEST_CASE_P(pkixocsp_VerifyEncodedResponse_WithoutResponseBytes,
+ pkixocsp_VerifyEncodedResponse_WithoutResponseBytes,
+ testing::ValuesIn(WITHOUT_RESPONSEBYTES));
+
+///////////////////////////////////////////////////////////////////////////////
+// "successful" responses
+
+namespace {
+
+// Alias for nullptr to aid readability in the code below.
+static const char* byKey = nullptr;
+
+} // namespace
+
+class pkixocsp_VerifyEncodedResponse_successful
+ : public pkixocsp_VerifyEncodedResponse
+{
+public:
+ void SetUp()
+ {
+ pkixocsp_VerifyEncodedResponse::SetUp();
+ }
+
+ static void SetUpTestCase()
+ {
+ pkixocsp_VerifyEncodedResponse::SetUpTestCase();
+ }
+
+ ByteString CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::CertStatus certStatus,
+ const CertID& certID,
+ /*optional*/ const char* signerName,
+ const TestKeyPair& signerKeyPair,
+ time_t producedAt, time_t thisUpdate,
+ /*optional*/ const time_t* nextUpdate,
+ const TestSignatureAlgorithm& signatureAlgorithm,
+ /*optional*/ const ByteString* certs = nullptr,
+ /*optional*/ OCSPResponseExtension* singleExtensions = nullptr,
+ /*optional*/ OCSPResponseExtension* responseExtensions = nullptr)
+ {
+ OCSPResponseContext context(certID, producedAt);
+ if (signerName) {
+ context.signerNameDER = CNToDERName(signerName);
+ EXPECT_FALSE(ENCODING_FAILED(context.signerNameDER));
+ }
+ context.signerKeyPair.reset(signerKeyPair.Clone());
+ EXPECT_TRUE(context.signerKeyPair.get());
+ context.responseStatus = OCSPResponseContext::successful;
+ context.producedAt = producedAt;
+ context.signatureAlgorithm = signatureAlgorithm;
+ context.certs = certs;
+ context.singleExtensions = singleExtensions;
+ context.responseExtensions = responseExtensions;
+
+ context.certStatus = static_cast<uint8_t>(certStatus);
+ context.thisUpdate = thisUpdate;
+ context.nextUpdate = nextUpdate ? *nextUpdate : 0;
+ context.includeNextUpdate = nextUpdate != nullptr;
+
+ return CreateEncodedOCSPResponse(context);
+ }
+};
+
+TEST_F(pkixocsp_VerifyEncodedResponse_successful, good_byKey)
+{
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID, byKey,
+ *rootKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ sha256WithRSAEncryption()));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Success,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID,
+ Now(), END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_successful, good_byName)
+{
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID, rootName,
+ *rootKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ sha256WithRSAEncryption()));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Success,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_successful, good_byKey_without_nextUpdate)
+{
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID, byKey,
+ *rootKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, nullptr,
+ sha256WithRSAEncryption()));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Success,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_successful, revoked)
+{
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::revoked, *endEntityCertID, byKey,
+ *rootKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ sha256WithRSAEncryption()));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_REVOKED_CERTIFICATE,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_successful, unknown)
+{
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::unknown, *endEntityCertID, byKey,
+ *rootKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ sha256WithRSAEncryption()));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_UNKNOWN_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_successful,
+ good_unsupportedSignatureAlgorithm)
+{
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID, byKey,
+ *rootKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ md5WithRSAEncryption()));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID,
+ Now(), END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+// Added for bug 1079436. The output variable validThrough represents the
+// latest time for which VerifyEncodedOCSPResponse will succeed, which is
+// different from the nextUpdate time in the OCSP response due to the slop we
+// add for time comparisons to deal with clock skew.
+TEST_F(pkixocsp_VerifyEncodedResponse_successful, check_validThrough)
+{
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID, byKey,
+ *rootKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ sha256WithRSAEncryption()));
+ Time validThrough(Time::uninitialized);
+ {
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Success,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID,
+ Now(), END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired, nullptr,
+ &validThrough));
+ ASSERT_FALSE(expired);
+ // The response was created to be valid until one day after now, so the
+ // value we got for validThrough should be after that.
+ Time oneDayAfterNowAsPKIXTime(
+ TimeFromEpochInSeconds(static_cast<uint64_t>(oneDayAfterNow)));
+ ASSERT_TRUE(validThrough > oneDayAfterNowAsPKIXTime);
+ }
+ {
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ // Given validThrough from a previous verification, this response should be
+ // valid through that time.
+ ASSERT_EQ(Success,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID,
+ validThrough, END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+ }
+ {
+ Time noLongerValid(validThrough);
+ ASSERT_EQ(Success, noLongerValid.AddSeconds(1));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ // The verification time is now after when the response will be considered
+ // valid.
+ ASSERT_EQ(Result::ERROR_OCSP_OLD_RESPONSE,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID,
+ noLongerValid, END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_TRUE(expired);
+ }
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_successful, ct_extension)
+{
+ // python DottedOIDToCode.py --tlv
+ // id_ocsp_singleExtensionSctList 1.3.6.1.4.1.11129.2.4.5
+ static const uint8_t tlv_id_ocsp_singleExtensionSctList[] = {
+ 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x05
+ };
+ static const uint8_t dummySctList[] = {
+ 0x01, 0x02, 0x03, 0x04, 0x05
+ };
+
+ OCSPResponseExtension ctExtension;
+ ctExtension.id = BytesToByteString(tlv_id_ocsp_singleExtensionSctList);
+ // SignedCertificateTimestampList structure is encoded as an OCTET STRING
+ // within the extension value (see RFC 6962 section 3.3).
+ // pkix decodes it internally and returns the actual structure.
+ ctExtension.value = TLV(der::OCTET_STRING, BytesToByteString(dummySctList));
+
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID, byKey,
+ *rootKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ sha256WithRSAEncryption(),
+ /*certs*/ nullptr,
+ &ctExtension));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+
+ bool expired;
+ ASSERT_EQ(Success,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID,
+ Now(), END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+ ASSERT_EQ(BytesToByteString(dummySctList),
+ trustDomain.signedCertificateTimestamps);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// indirect responses (signed by a delegated OCSP responder cert)
+
+class pkixocsp_VerifyEncodedResponse_DelegatedResponder
+ : public pkixocsp_VerifyEncodedResponse_successful
+{
+protected:
+ // certSubjectName should be unique for each call. This way, we avoid any
+ // issues with NSS caching the certificates internally. For the same reason,
+ // we generate a new keypair on each call. Either one of these should be
+ // sufficient to avoid issues with the NSS cache, but we do both to be
+ // cautious.
+ //
+ // signerName should be byKey to use the byKey ResponderID construction, or
+ // another value (usually equal to certSubjectName) to use the byName
+ // ResponderID construction.
+ //
+ // certSignatureAlgorithm specifies the signature algorithm that the
+ // certificate will be signed with, not the OCSP response.
+ //
+ // If signerEKU is omitted, then the certificate will have the
+ // id-kp-OCSPSigning EKU. If signerEKU is SEC_OID_UNKNOWN then it will not
+ // have any EKU extension. Otherwise, the certificate will have the given
+ // EKU.
+ ByteString CreateEncodedIndirectOCSPSuccessfulResponse(
+ const char* certSubjectName,
+ OCSPResponseContext::CertStatus certStatus,
+ const char* signerName,
+ const TestSignatureAlgorithm& certSignatureAlgorithm,
+ /*optional*/ const Input* signerEKUDER = &OCSPSigningEKUDER,
+ /*optional, out*/ ByteString* signerDEROut = nullptr)
+ {
+ assert(certSubjectName);
+
+ const ByteString extensions[] = {
+ signerEKUDER
+ ? CreateEncodedEKUExtension(*signerEKUDER, Critical::No)
+ : ByteString(),
+ ByteString()
+ };
+ ScopedTestKeyPair signerKeyPair(GenerateKeyPair());
+ ByteString signerDER(CreateEncodedCertificate(
+ ++rootIssuedCount, certSignatureAlgorithm,
+ rootName, oneDayBeforeNow, oneDayAfterNow,
+ certSubjectName, *signerKeyPair,
+ signerEKUDER ? extensions : nullptr,
+ *rootKeyPair));
+ EXPECT_FALSE(ENCODING_FAILED(signerDER));
+ if (signerDEROut) {
+ *signerDEROut = signerDER;
+ }
+
+ ByteString signerNameDER;
+ if (signerName) {
+ signerNameDER = CNToDERName(signerName);
+ EXPECT_FALSE(ENCODING_FAILED(signerNameDER));
+ }
+ ByteString certs[] = { signerDER, ByteString() };
+ return CreateEncodedOCSPSuccessfulResponse(certStatus, *endEntityCertID,
+ signerName, *signerKeyPair,
+ oneDayBeforeNow,
+ oneDayBeforeNow,
+ &oneDayAfterNow,
+ sha256WithRSAEncryption(),
+ certs);
+ }
+
+ static ByteString CreateEncodedCertificate(uint32_t serialNumber,
+ const TestSignatureAlgorithm& signatureAlg,
+ const char* issuer,
+ time_t notBefore,
+ time_t notAfter,
+ const char* subject,
+ const TestKeyPair& subjectKeyPair,
+ /*optional*/ const ByteString* extensions,
+ const TestKeyPair& signerKeyPair)
+ {
+ ByteString serialNumberDER(CreateEncodedSerialNumber(
+ static_cast<long>(serialNumber)));
+ if (ENCODING_FAILED(serialNumberDER)) {
+ return ByteString();
+ }
+ ByteString issuerDER(CNToDERName(issuer));
+ if (ENCODING_FAILED(issuerDER)) {
+ return ByteString();
+ }
+ ByteString subjectDER(CNToDERName(subject));
+ if (ENCODING_FAILED(subjectDER)) {
+ return ByteString();
+ }
+ return ::mozilla::pkix::test::CreateEncodedCertificate(
+ v3, signatureAlg, serialNumberDER,
+ issuerDER, notBefore, notAfter,
+ subjectDER, subjectKeyPair, extensions,
+ signerKeyPair, signatureAlg);
+ }
+
+ static const Input OCSPSigningEKUDER;
+};
+
+/*static*/ const Input pkixocsp_VerifyEncodedResponse_DelegatedResponder::
+ OCSPSigningEKUDER(tlv_id_kp_OCSPSigning);
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_byKey)
+{
+ ByteString responseString(
+ CreateEncodedIndirectOCSPSuccessfulResponse(
+ "good_indirect_byKey", OCSPResponseContext::good,
+ byKey, sha256WithRSAEncryption()));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Success,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_byName)
+{
+ ByteString responseString(
+ CreateEncodedIndirectOCSPSuccessfulResponse(
+ "good_indirect_byName", OCSPResponseContext::good,
+ "good_indirect_byName", sha256WithRSAEncryption()));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Success,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder,
+ good_byKey_missing_signer)
+{
+ ScopedTestKeyPair missingSignerKeyPair(GenerateKeyPair());
+ ASSERT_TRUE(missingSignerKeyPair.get());
+
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID, byKey,
+ *missingSignerKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, nullptr,
+ sha256WithRSAEncryption()));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder,
+ good_byName_missing_signer)
+{
+ ScopedTestKeyPair missingSignerKeyPair(GenerateKeyPair());
+ ASSERT_TRUE(missingSignerKeyPair.get());
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID,
+ "missing", *missingSignerKeyPair,
+ oneDayBeforeNow, oneDayBeforeNow, nullptr,
+ sha256WithRSAEncryption()));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_expired)
+{
+ static const char* signerName = "good_indirect_expired";
+
+ const ByteString extensions[] = {
+ CreateEncodedEKUExtension(OCSPSigningEKUDER, Critical::No),
+ ByteString()
+ };
+
+ ScopedTestKeyPair signerKeyPair(GenerateKeyPair());
+ ByteString signerDER(CreateEncodedCertificate(
+ ++rootIssuedCount, sha256WithRSAEncryption(),
+ rootName,
+ tenDaysBeforeNow,
+ twoDaysBeforeNow,
+ signerName, *signerKeyPair, extensions,
+ *rootKeyPair));
+ ASSERT_FALSE(ENCODING_FAILED(signerDER));
+
+ ByteString certs[] = { signerDER, ByteString() };
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID,
+ signerName, *signerKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ sha256WithRSAEncryption(), certs));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_future)
+{
+ static const char* signerName = "good_indirect_future";
+
+ const ByteString extensions[] = {
+ CreateEncodedEKUExtension(OCSPSigningEKUDER, Critical::No),
+ ByteString()
+ };
+
+ ScopedTestKeyPair signerKeyPair(GenerateKeyPair());
+ ByteString signerDER(CreateEncodedCertificate(
+ ++rootIssuedCount, sha256WithRSAEncryption(),
+ rootName,
+ twoDaysAfterNow,
+ tenDaysAfterNow,
+ signerName, *signerKeyPair, extensions,
+ *rootKeyPair));
+ ASSERT_FALSE(ENCODING_FAILED(signerDER));
+
+ ByteString certs[] = { signerDER, ByteString() };
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID,
+ signerName, *signerKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ sha256WithRSAEncryption(), certs));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_no_eku)
+{
+ ByteString responseString(
+ CreateEncodedIndirectOCSPSuccessfulResponse(
+ "good_indirect_wrong_eku",
+ OCSPResponseContext::good, byKey,
+ sha256WithRSAEncryption(), nullptr));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+static const Input serverAuthEKUDER(tlv_id_kp_serverAuth);
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder,
+ good_indirect_wrong_eku)
+{
+ ByteString responseString(
+ CreateEncodedIndirectOCSPSuccessfulResponse(
+ "good_indirect_wrong_eku",
+ OCSPResponseContext::good, byKey,
+ sha256WithRSAEncryption(), &serverAuthEKUDER));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+// Test that signature of OCSP response signer cert is verified
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_tampered_eku)
+{
+ ByteString tamperedResponse(
+ CreateEncodedIndirectOCSPSuccessfulResponse(
+ "good_indirect_tampered_eku",
+ OCSPResponseContext::good, byKey,
+ sha256WithRSAEncryption(), &serverAuthEKUDER));
+ ASSERT_EQ(Success,
+ TamperOnce(tamperedResponse,
+ ByteString(tlv_id_kp_serverAuth,
+ sizeof(tlv_id_kp_serverAuth)),
+ ByteString(tlv_id_kp_OCSPSigning,
+ sizeof(tlv_id_kp_OCSPSigning))));
+ Input tamperedResponseInput;
+ ASSERT_EQ(Success, tamperedResponseInput.Init(tamperedResponse.data(),
+ tamperedResponse.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ tamperedResponseInput, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_unknown_issuer)
+{
+ static const char* subCAName = "good_indirect_unknown_issuer sub-CA";
+ static const char* signerName = "good_indirect_unknown_issuer OCSP signer";
+
+ // unknown issuer
+ ScopedTestKeyPair unknownKeyPair(GenerateKeyPair());
+ ASSERT_TRUE(unknownKeyPair.get());
+
+ // Delegated responder cert signed by unknown issuer
+ const ByteString extensions[] = {
+ CreateEncodedEKUExtension(OCSPSigningEKUDER, Critical::No),
+ ByteString()
+ };
+ ScopedTestKeyPair signerKeyPair(GenerateKeyPair());
+ ByteString signerDER(CreateEncodedCertificate(
+ 1, sha256WithRSAEncryption(), subCAName,
+ oneDayBeforeNow, oneDayAfterNow, signerName,
+ *signerKeyPair, extensions, *unknownKeyPair));
+ ASSERT_FALSE(ENCODING_FAILED(signerDER));
+
+ // OCSP response signed by that delegated responder
+ ByteString certs[] = { signerDER, ByteString() };
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID,
+ signerName, *signerKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ sha256WithRSAEncryption(), certs));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+// The CA that issued the OCSP responder cert is a sub-CA of the issuer of
+// the certificate that the OCSP response is for. That sub-CA cert is included
+// in the OCSP response before the OCSP responder cert.
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder,
+ good_indirect_subca_1_first)
+{
+ static const char* subCAName = "good_indirect_subca_1_first sub-CA";
+ static const char* signerName = "good_indirect_subca_1_first OCSP signer";
+
+ // sub-CA of root (root is the direct issuer of endEntity)
+ const ByteString subCAExtensions[] = {
+ CreateEncodedBasicConstraints(true, 0, Critical::No),
+ ByteString()
+ };
+ ScopedTestKeyPair subCAKeyPair(GenerateKeyPair());
+ ByteString subCADER(CreateEncodedCertificate(
+ ++rootIssuedCount, sha256WithRSAEncryption(), rootName,
+ oneDayBeforeNow, oneDayAfterNow, subCAName,
+ *subCAKeyPair, subCAExtensions, *rootKeyPair));
+ ASSERT_FALSE(ENCODING_FAILED(subCADER));
+
+ // Delegated responder cert signed by that sub-CA
+ const ByteString extensions[] = {
+ CreateEncodedEKUExtension(OCSPSigningEKUDER, Critical::No),
+ ByteString(),
+ };
+ ScopedTestKeyPair signerKeyPair(GenerateKeyPair());
+ ByteString signerDER(CreateEncodedCertificate(
+ 1, sha256WithRSAEncryption(), subCAName,
+ oneDayBeforeNow, oneDayAfterNow, signerName,
+ *signerKeyPair, extensions, *subCAKeyPair));
+ ASSERT_FALSE(ENCODING_FAILED(signerDER));
+
+ // OCSP response signed by the delegated responder issued by the sub-CA
+ // that is trying to impersonate the root.
+ ByteString certs[] = { subCADER, signerDER, ByteString() };
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID,
+ signerName, *signerKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ sha256WithRSAEncryption(), certs));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+// The CA that issued the OCSP responder cert is a sub-CA of the issuer of
+// the certificate that the OCSP response is for. That sub-CA cert is included
+// in the OCSP response after the OCSP responder cert.
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder,
+ good_indirect_subca_1_second)
+{
+ static const char* subCAName = "good_indirect_subca_1_second sub-CA";
+ static const char* signerName = "good_indirect_subca_1_second OCSP signer";
+
+ // sub-CA of root (root is the direct issuer of endEntity)
+ const ByteString subCAExtensions[] = {
+ CreateEncodedBasicConstraints(true, 0, Critical::No),
+ ByteString()
+ };
+ ScopedTestKeyPair subCAKeyPair(GenerateKeyPair());
+ ByteString subCADER(CreateEncodedCertificate(++rootIssuedCount,
+ sha256WithRSAEncryption(),
+ rootName,
+ oneDayBeforeNow, oneDayAfterNow,
+ subCAName, *subCAKeyPair,
+ subCAExtensions, *rootKeyPair));
+ ASSERT_FALSE(ENCODING_FAILED(subCADER));
+
+ // Delegated responder cert signed by that sub-CA
+ const ByteString extensions[] = {
+ CreateEncodedEKUExtension(OCSPSigningEKUDER, Critical::No),
+ ByteString()
+ };
+ ScopedTestKeyPair signerKeyPair(GenerateKeyPair());
+ ByteString signerDER(CreateEncodedCertificate(
+ 1, sha256WithRSAEncryption(), subCAName,
+ oneDayBeforeNow, oneDayAfterNow, signerName,
+ *signerKeyPair, extensions, *subCAKeyPair));
+ ASSERT_FALSE(ENCODING_FAILED(signerDER));
+
+ // OCSP response signed by the delegated responder issued by the sub-CA
+ // that is trying to impersonate the root.
+ ByteString certs[] = { signerDER, subCADER, ByteString() };
+ ByteString responseString(
+ CreateEncodedOCSPSuccessfulResponse(
+ OCSPResponseContext::good, *endEntityCertID,
+ signerName, *signerKeyPair, oneDayBeforeNow,
+ oneDayBeforeNow, &oneDayAfterNow,
+ sha256WithRSAEncryption(), certs));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder,
+ good_unsupportedSignatureAlgorithmOnResponder)
+{
+ // Note that the algorithm ID (md5WithRSAEncryption) identifies the signature
+ // algorithm that will be used to sign the certificate that issues the OCSP
+ // responses, not the responses themselves.
+ ByteString responseString(
+ CreateEncodedIndirectOCSPSuccessfulResponse(
+ "good_indirect_unsupportedSignatureAlgorithm",
+ OCSPResponseContext::good, byKey,
+ md5WithRSAEncryption()));
+ Input response;
+ ASSERT_EQ(Success,
+ response.Init(responseString.data(), responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+}
+
+class pkixocsp_VerifyEncodedResponse_GetCertTrust
+ : public pkixocsp_VerifyEncodedResponse_DelegatedResponder {
+public:
+ void SetUp()
+ {
+ pkixocsp_VerifyEncodedResponse_DelegatedResponder::SetUp();
+
+ responseString =
+ CreateEncodedIndirectOCSPSuccessfulResponse(
+ "OCSPGetCertTrustTest Signer", OCSPResponseContext::good,
+ byKey, sha256WithRSAEncryption(), &OCSPSigningEKUDER,
+ &signerCertDER);
+ if (ENCODING_FAILED(responseString)) {
+ abort();
+ }
+ if (response.Init(responseString.data(), responseString.length())
+ != Success) {
+ abort();
+ }
+ if (signerCertDER.length() == 0) {
+ abort();
+ }
+ }
+
+ class TrustDomain final : public OCSPTestTrustDomain
+ {
+ public:
+ TrustDomain()
+ : certTrustLevel(TrustLevel::InheritsTrust)
+ {
+ }
+
+ bool SetCertTrust(const ByteString& certDER, TrustLevel certTrustLevel)
+ {
+ this->certDER = certDER;
+ this->certTrustLevel = certTrustLevel;
+ return true;
+ }
+ private:
+ Result GetCertTrust(EndEntityOrCA endEntityOrCA, const CertPolicyId&,
+ Input candidateCert, /*out*/ TrustLevel& trustLevel)
+ override
+ {
+ EXPECT_EQ(endEntityOrCA, EndEntityOrCA::MustBeEndEntity);
+ EXPECT_FALSE(certDER.empty());
+ Input certDERInput;
+ EXPECT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length()));
+ EXPECT_TRUE(InputsAreEqual(certDERInput, candidateCert));
+ trustLevel = certTrustLevel;
+ return Success;
+ }
+
+ ByteString certDER;
+ TrustLevel certTrustLevel;
+ };
+
+ TrustDomain trustDomain;
+ ByteString signerCertDER;
+ ByteString responseString;
+ Input response; // references data in responseString
+};
+
+TEST_F(pkixocsp_VerifyEncodedResponse_GetCertTrust, InheritTrust)
+{
+ ASSERT_TRUE(trustDomain.SetCertTrust(signerCertDER,
+ TrustLevel::InheritsTrust));
+ bool expired;
+ ASSERT_EQ(Success,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_GetCertTrust, TrustAnchor)
+{
+ ASSERT_TRUE(trustDomain.SetCertTrust(signerCertDER,
+ TrustLevel::TrustAnchor));
+ bool expired;
+ ASSERT_EQ(Success,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ response, expired));
+ ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_GetCertTrust, ActivelyDistrusted)
+{
+ ASSERT_TRUE(trustDomain.SetCertTrust(signerCertDER,
+ TrustLevel::ActivelyDistrusted));
+ Input responseInput;
+ ASSERT_EQ(Success,
+ responseInput.Init(responseString.data(),
+ responseString.length()));
+ bool expired;
+ ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT,
+ VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(),
+ END_ENTITY_MAX_LIFETIME_IN_DAYS,
+ responseInput, expired));
+ ASSERT_FALSE(expired);
+}
diff --git a/security/pkix/test/lib/moz.build b/security/pkix/test/lib/moz.build
new file mode 100644
index 000000000..c99aedf3a
--- /dev/null
+++ b/security/pkix/test/lib/moz.build
@@ -0,0 +1,39 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This code is made available to you under your choice of the following sets
+# of licensing terms:
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# Copyright 2013 Mozilla Contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+SOURCES += [
+ 'pkixtestalg.cpp',
+ 'pkixtestnss.cpp',
+ 'pkixtestutil.cpp',
+]
+
+Library('pkixtestutil')
+
+LOCAL_INCLUDES += [
+ '../../include',
+ '../../lib',
+]
+
+FINAL_LIBRARY = 'xul-gtest'
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/security/pkix/test/lib/pkixtestalg.cpp b/security/pkix/test/lib/pkixtestalg.cpp
new file mode 100644
index 000000000..a19fd26f3
--- /dev/null
+++ b/security/pkix/test/lib/pkixtestalg.cpp
@@ -0,0 +1,210 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2015 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pkixtestutil.h"
+
+#include "pkixder.h"
+
+// python DottedOIDToCode.py --prefixdefine PREFIX_1_2_840_10040 1.2.840.10040
+#define PREFIX_1_2_840_10040 0x2a, 0x86, 0x48, 0xce, 0x38
+
+// python DottedOIDToCode.py --prefixdefine PREFIX_1_2_840_10045 1.2.840.10045
+#define PREFIX_1_2_840_10045 0x2a, 0x86, 0x48, 0xce, 0x3d
+
+// python DottedOIDToCode.py --prefixdefine PREFIX_1_2_840_113549 1.2.840.113549
+#define PREFIX_1_2_840_113549 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d
+
+namespace mozilla { namespace pkix { namespace test {
+
+namespace {
+
+enum class NULLParam { NO, YES };
+
+template <size_t SIZE>
+ByteString
+OID(const uint8_t (&rawValue)[SIZE])
+{
+ return TLV(der::OIDTag, ByteString(rawValue, SIZE));
+}
+
+template <size_t SIZE>
+ByteString
+SimpleAlgID(const uint8_t (&rawValue)[SIZE],
+ NULLParam nullParam = NULLParam::NO)
+{
+ ByteString sequenceValue(OID(rawValue));
+ if (nullParam == NULLParam::YES) {
+ sequenceValue.append(TLV(der::NULLTag, ByteString()));
+ }
+ return TLV(der::SEQUENCE, sequenceValue);
+}
+
+template <size_t SIZE>
+ByteString
+DERInteger(const uint8_t (&rawValue)[SIZE])
+{
+ ByteString value(rawValue, SIZE);
+ if (value[0] & 0x80u) {
+ // Prefix with a leading zero to disambiguate this from a negative value.
+ value.insert(value.begin(), 0x00);
+ }
+ return TLV(der::INTEGER, value);
+}
+
+// Generated with "openssl dsaparam -C -noout 2048" and reformatted.
+// openssl 1.0 or later must be used so that a 256-bit Q value is
+// generated.
+static const uint8_t DSS_P_RAW[] =
+{
+ 0xB3,0xCD,0x29,0x44,0xF0,0x25,0xA7,0x73,0xFC,0x86,0x70,0xA2,
+ 0x69,0x5A,0x97,0x3F,0xBD,0x1C,0x6F,0xAA,0x4A,0x40,0x42,0x8E,
+ 0xCF,0xAE,0x62,0x12,0xED,0xB4,0xFD,0x05,0xC2,0xAE,0xB1,0x8C,
+ 0xFC,0xBE,0x38,0x90,0xBB,0x7C,0xFF,0x16,0xF4,0xED,0xCE,0x72,
+ 0x12,0x93,0x83,0xF0,0xA4,0xA1,0x71,0xDC,0x4B,0xF0,0x4E,0x3A,
+ 0x2B,0xFA,0x17,0xB7,0xB3,0x2A,0xCC,0x2C,0xD3,0xC8,0x21,0x49,
+ 0x7A,0x83,0x71,0x8B,0x3D,0x62,0x96,0xDC,0xAD,0xA8,0x03,0xBE,
+ 0x1D,0x33,0x11,0xF3,0xEB,0xD8,0x1B,0x8D,0xDB,0x62,0x79,0x83,
+ 0xF8,0x67,0x4E,0x62,0x21,0x2C,0x81,0x59,0xE8,0x73,0xD7,0xAF,
+ 0xB9,0x63,0x60,0xEA,0xAE,0xEC,0x68,0x6A,0xB4,0xB0,0x65,0xBA,
+ 0xA3,0x4C,0x09,0x99,0x29,0x6A,0x2E,0x2B,0xFC,0x6D,0x51,0xCA,
+ 0x30,0xA2,0x2F,0x7A,0x65,0x76,0xA7,0x55,0x13,0x11,0xA0,0x02,
+ 0xA2,0x59,0x4B,0xCE,0xA7,0x05,0xF6,0x07,0x35,0x9B,0x41,0xD7,
+ 0x11,0x5A,0x18,0x57,0xA7,0x78,0x88,0xC3,0xA8,0xE3,0x39,0xF5,
+ 0x47,0x3D,0x2E,0x18,0x54,0xB0,0xF0,0xBF,0x65,0x3F,0x77,0xC7,
+ 0x11,0xB8,0x0D,0x52,0xAD,0xC8,0xE8,0x6D,0xF6,0x7E,0x88,0x65,
+ 0x84,0x2B,0xF7,0xEF,0x8E,0xB5,0x7C,0xBD,0x2E,0x0D,0xF3,0xC6,
+ 0xDD,0x0B,0xB4,0xF2,0x23,0x1F,0xDA,0x55,0x05,0xF5,0xDC,0x53,
+ 0xA6,0x83,0xDA,0x5C,0xEF,0x29,0x02,0x78,0x68,0xD0,0xA4,0x39,
+ 0x09,0x7F,0xFA,0x49,0x18,0xD0,0xB5,0x19,0x35,0x31,0x8E,0xDE,
+ 0x43,0x35,0xA3,0xB9,0x6D,0xC1,0x70,0xC6,0x0D,0x18,0x24,0xEB,
+ 0x1E,0x4D,0x52,0xB7,
+};
+
+static const uint8_t DSS_Q_RAW[] =
+{
+ 0x8D,0x6B,0x86,0x89,0x9C,0x8D,0x30,0x91,0xCC,0x6E,0x34,0xF1,
+ 0xE8,0x9C,0x8A,0x5C,0xD6,0xAB,0x01,0x1E,0xC4,0xDB,0xFD,0x07,
+ 0xEB,0x5F,0x4E,0xE8,0xFA,0xFC,0x98,0x2D,
+};
+
+static const uint8_t DSS_G_RAW[] =
+{
+ 0x0E,0x2C,0x34,0xB2,0xE1,0x66,0x49,0xB6,0x9A,0x7D,0x67,0x3E,
+ 0xEE,0x98,0x35,0x18,0x28,0x35,0xFC,0x05,0x36,0x3B,0x94,0xE6,
+ 0x1E,0x1C,0x5B,0x05,0x3E,0x86,0x1B,0xE3,0xED,0xD2,0xE1,0xF3,
+ 0xF7,0xF7,0x60,0x6D,0x7D,0xA1,0xAF,0x9A,0xD1,0xDF,0xA2,0x9C,
+ 0xFC,0xA2,0xEB,0x90,0x8B,0x1C,0x82,0x92,0x45,0x7B,0x30,0x2A,
+ 0xFD,0x7A,0xE6,0x68,0x8F,0xEC,0x89,0x3A,0x9A,0xAD,0xFE,0x25,
+ 0x5E,0x51,0xC5,0x29,0x45,0x7F,0xAC,0xDE,0xFC,0xB4,0x1B,0x3A,
+ 0xDA,0xC7,0x21,0x68,0x87,0x27,0x8D,0x7B,0xB2,0xBB,0x41,0x60,
+ 0x46,0x42,0x5B,0x6B,0xE8,0x80,0xD2,0xE4,0xA3,0x30,0x8F,0xD5,
+ 0x71,0x07,0x8A,0x7B,0x32,0x56,0x84,0x41,0x1C,0xDF,0x69,0xE9,
+ 0xFD,0xBA,0x48,0xE0,0x43,0xA0,0x38,0x92,0x12,0xF3,0x52,0xA5,
+ 0x40,0x87,0xCB,0x34,0xBB,0x3E,0x25,0x29,0x3C,0xC6,0xA5,0x17,
+ 0xFD,0x58,0x47,0x89,0xDB,0x9B,0xB9,0xCF,0xE9,0xA8,0xF2,0xEC,
+ 0x55,0x76,0xF5,0xF1,0x9C,0x6E,0x0A,0x3F,0x16,0x5F,0x49,0x31,
+ 0x31,0x1C,0x43,0xA2,0x83,0xDA,0xDD,0x7F,0x1C,0xEA,0x05,0x36,
+ 0x7B,0xED,0x09,0xFB,0x6F,0x8A,0x2B,0x55,0xB9,0xBC,0x4A,0x8C,
+ 0x28,0xC1,0x4D,0x13,0x6E,0x47,0xF4,0xAD,0x79,0x00,0xE9,0x5A,
+ 0xB6,0xC7,0x73,0x28,0xA9,0x89,0xAD,0xE8,0x6E,0xC6,0x54,0xA5,
+ 0x56,0x2D,0xAA,0x81,0x83,0x9E,0xC1,0x13,0x79,0xA4,0x12,0xE0,
+ 0x76,0x1F,0x25,0x43,0xB6,0xDE,0x56,0xF7,0x52,0xCC,0x07,0xB8,
+ 0x37,0xE2,0x8C,0xC5,0x56,0x8C,0xDD,0x63,0xF5,0xB6,0xA3,0x46,
+ 0x62,0xF6,0x35,0x76,
+};
+
+} // namespace
+
+TestSignatureAlgorithm::TestSignatureAlgorithm(
+ const TestPublicKeyAlgorithm& publicKeyAlg,
+ TestDigestAlgorithmID digestAlg,
+ const ByteString& algorithmIdentifier,
+ bool accepted)
+ : publicKeyAlg(publicKeyAlg)
+ , digestAlg(digestAlg)
+ , algorithmIdentifier(algorithmIdentifier)
+ , accepted(accepted)
+{
+}
+
+ByteString DSS_P() { return ByteString(DSS_P_RAW, sizeof(DSS_P_RAW)); }
+ByteString DSS_Q() { return ByteString(DSS_Q_RAW, sizeof(DSS_Q_RAW)); }
+ByteString DSS_G() { return ByteString(DSS_G_RAW, sizeof(DSS_G_RAW)); }
+
+TestPublicKeyAlgorithm
+DSS()
+{
+ static const uint8_t oidValue[] = { PREFIX_1_2_840_10040, 4, 1 };
+
+ // RFC 3279 Section-2.3.2
+ return TestPublicKeyAlgorithm(
+ TLV(der::SEQUENCE,
+ OID(oidValue) +
+ TLV(der::SEQUENCE,
+ DERInteger(DSS_P_RAW) +
+ DERInteger(DSS_Q_RAW) +
+ DERInteger(DSS_G_RAW))));
+}
+
+// RFC 3279 Section 2.3.1
+TestPublicKeyAlgorithm
+RSA_PKCS1()
+{
+ static const uint8_t rsaEncryption[] = { PREFIX_1_2_840_113549, 1, 1, 1 };
+ return TestPublicKeyAlgorithm(SimpleAlgID(rsaEncryption, NULLParam::YES));
+}
+
+// RFC 3279 Section 2.2.1
+TestSignatureAlgorithm md2WithRSAEncryption()
+{
+ static const uint8_t oidValue[] = { PREFIX_1_2_840_113549, 1, 1, 2 };
+ return TestSignatureAlgorithm(RSA_PKCS1(), TestDigestAlgorithmID::MD2,
+ SimpleAlgID(oidValue), false);
+}
+
+// RFC 3279 Section 2.2.1
+TestSignatureAlgorithm md5WithRSAEncryption()
+{
+ static const uint8_t oidValue[] = { PREFIX_1_2_840_113549, 1, 1, 4 };
+ return TestSignatureAlgorithm(RSA_PKCS1(), TestDigestAlgorithmID::MD5,
+ SimpleAlgID(oidValue), false);
+}
+
+// RFC 3279 Section 2.2.1
+TestSignatureAlgorithm sha1WithRSAEncryption()
+{
+ static const uint8_t oidValue[] = { PREFIX_1_2_840_113549, 1, 1, 5 };
+ return TestSignatureAlgorithm(RSA_PKCS1(), TestDigestAlgorithmID::SHA1,
+ SimpleAlgID(oidValue), true);
+}
+
+// RFC 4055 Section 5
+TestSignatureAlgorithm sha256WithRSAEncryption()
+{
+ static const uint8_t oidValue[] = { PREFIX_1_2_840_113549, 1, 1, 11 };
+ return TestSignatureAlgorithm(RSA_PKCS1(), TestDigestAlgorithmID::SHA256,
+ SimpleAlgID(oidValue), true);
+}
+
+} } } // namespace mozilla::pkix
diff --git a/security/pkix/test/lib/pkixtestnss.cpp b/security/pkix/test/lib/pkixtestnss.cpp
new file mode 100644
index 000000000..4d49b9e31
--- /dev/null
+++ b/security/pkix/test/lib/pkixtestnss.cpp
@@ -0,0 +1,309 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2013 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pkixtestutil.h"
+
+#include <limits>
+
+#include "cryptohi.h"
+#include "keyhi.h"
+#include "nss.h"
+#include "pk11pqg.h"
+#include "pk11pub.h"
+#include "pkix/pkixnss.h"
+#include "pkixder.h"
+#include "pkixutil.h"
+#include "prinit.h"
+#include "secerr.h"
+#include "secitem.h"
+
+namespace mozilla { namespace pkix { namespace test {
+
+namespace {
+
+typedef ScopedPtr<SECKEYPublicKey, SECKEY_DestroyPublicKey>
+ ScopedSECKEYPublicKey;
+typedef ScopedPtr<SECKEYPrivateKey, SECKEY_DestroyPrivateKey>
+ ScopedSECKEYPrivateKey;
+
+inline void
+SECITEM_FreeItem_true(SECItem* item)
+{
+ SECITEM_FreeItem(item, true);
+}
+
+typedef mozilla::pkix::ScopedPtr<SECItem, SECITEM_FreeItem_true> ScopedSECItem;
+
+TestKeyPair* GenerateKeyPairInner();
+
+void
+InitNSSIfNeeded()
+{
+ if (NSS_NoDB_Init(nullptr) != SECSuccess) {
+ abort();
+ }
+}
+
+static ScopedTestKeyPair reusedKeyPair;
+
+PRStatus
+InitReusedKeyPair()
+{
+ InitNSSIfNeeded();
+ reusedKeyPair.reset(GenerateKeyPairInner());
+ return reusedKeyPair ? PR_SUCCESS : PR_FAILURE;
+}
+
+class NSSTestKeyPair final : public TestKeyPair
+{
+public:
+ // NSSTestKeyPair takes ownership of privateKey.
+ NSSTestKeyPair(const TestPublicKeyAlgorithm& publicKeyAlg,
+ const ByteString& spk,
+ SECKEYPrivateKey* privateKey)
+ : TestKeyPair(publicKeyAlg, spk)
+ , privateKey(privateKey)
+ {
+ }
+
+ Result SignData(const ByteString& tbs,
+ const TestSignatureAlgorithm& signatureAlgorithm,
+ /*out*/ ByteString& signature) const override
+ {
+ SECOidTag oidTag;
+ if (signatureAlgorithm.publicKeyAlg == RSA_PKCS1()) {
+ switch (signatureAlgorithm.digestAlg) {
+ case TestDigestAlgorithmID::MD2:
+ oidTag = SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION;
+ break;
+ case TestDigestAlgorithmID::MD5:
+ oidTag = SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION;
+ break;
+ case TestDigestAlgorithmID::SHA1:
+ oidTag = SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION;
+ break;
+ case TestDigestAlgorithmID::SHA224:
+ oidTag = SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION;
+ break;
+ case TestDigestAlgorithmID::SHA256:
+ oidTag = SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION;
+ break;
+ case TestDigestAlgorithmID::SHA384:
+ oidTag = SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION;
+ break;
+ case TestDigestAlgorithmID::SHA512:
+ oidTag = SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION;
+ break;
+ MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
+ }
+ } else {
+ abort();
+ }
+
+ SECItem signatureItem;
+ if (SEC_SignData(&signatureItem, tbs.data(),
+ static_cast<int>(tbs.length()),
+ privateKey.get(), oidTag) != SECSuccess) {
+ return MapPRErrorCodeToResult(PR_GetError());
+ }
+ signature.assign(signatureItem.data, signatureItem.len);
+ SECITEM_FreeItem(&signatureItem, false);
+ return Success;
+ }
+
+ TestKeyPair* Clone() const override
+ {
+ ScopedSECKEYPrivateKey
+ privateKeyCopy(SECKEY_CopyPrivateKey(privateKey.get()));
+ if (!privateKeyCopy) {
+ return nullptr;
+ }
+ return new (std::nothrow) NSSTestKeyPair(publicKeyAlg,
+ subjectPublicKey,
+ privateKeyCopy.release());
+ }
+
+private:
+ ScopedSECKEYPrivateKey privateKey;
+};
+
+} // namespace
+
+// This private function is also used by Gecko's PSM test framework
+// (OCSPCommon.cpp).
+//
+// Ownership of privateKey is transfered.
+TestKeyPair* CreateTestKeyPair(const TestPublicKeyAlgorithm publicKeyAlg,
+ const SECKEYPublicKey& publicKey,
+ SECKEYPrivateKey* privateKey)
+{
+ ScopedPtr<CERTSubjectPublicKeyInfo, SECKEY_DestroySubjectPublicKeyInfo>
+ spki(SECKEY_CreateSubjectPublicKeyInfo(&publicKey));
+ if (!spki) {
+ return nullptr;
+ }
+ SECItem spkDER = spki->subjectPublicKey;
+ DER_ConvertBitString(&spkDER); // bits to bytes
+ return new (std::nothrow) NSSTestKeyPair(publicKeyAlg,
+ ByteString(spkDER.data, spkDER.len),
+ privateKey);
+}
+
+namespace {
+
+TestKeyPair*
+GenerateKeyPairInner()
+{
+ ScopedPtr<PK11SlotInfo, PK11_FreeSlot> slot(PK11_GetInternalSlot());
+ if (!slot) {
+ abort();
+ }
+
+ // Bug 1012786: PK11_GenerateKeyPair can fail if there is insufficient
+ // entropy to generate a random key. Attempting to add some entropy and
+ // retrying appears to solve this issue.
+ for (uint32_t retries = 0; retries < 10; retries++) {
+ PK11RSAGenParams params;
+ params.keySizeInBits = 2048;
+ params.pe = 3;
+ SECKEYPublicKey* publicKeyTemp = nullptr;
+ ScopedSECKEYPrivateKey
+ privateKey(PK11_GenerateKeyPair(slot.get(), CKM_RSA_PKCS_KEY_PAIR_GEN,
+ &params, &publicKeyTemp, false, true,
+ nullptr));
+ ScopedSECKEYPublicKey publicKey(publicKeyTemp);
+ if (privateKey) {
+ return CreateTestKeyPair(RSA_PKCS1(), *publicKey, privateKey.release());
+ }
+
+ assert(!publicKeyTemp);
+
+ if (PR_GetError() != SEC_ERROR_PKCS11_FUNCTION_FAILED) {
+ break;
+ }
+
+ // Since these keys are only for testing, we don't need them to be good,
+ // random keys.
+ // https://xkcd.com/221/
+ static const uint8_t RANDOM_NUMBER[] = { 4, 4, 4, 4, 4, 4, 4, 4 };
+ if (PK11_RandomUpdate((void*) &RANDOM_NUMBER,
+ sizeof(RANDOM_NUMBER)) != SECSuccess) {
+ break;
+ }
+ }
+
+ abort();
+}
+
+} // namespace
+
+TestKeyPair*
+GenerateKeyPair()
+{
+ InitNSSIfNeeded();
+ return GenerateKeyPairInner();
+}
+
+TestKeyPair*
+CloneReusedKeyPair()
+{
+ static PRCallOnceType initCallOnce;
+ if (PR_CallOnce(&initCallOnce, InitReusedKeyPair) != PR_SUCCESS) {
+ abort();
+ }
+ assert(reusedKeyPair);
+ return reusedKeyPair->Clone();
+}
+
+TestKeyPair*
+GenerateDSSKeyPair()
+{
+ InitNSSIfNeeded();
+
+ ScopedPtr<PK11SlotInfo, PK11_FreeSlot> slot(PK11_GetInternalSlot());
+ if (!slot) {
+ return nullptr;
+ }
+
+ ByteString p(DSS_P());
+ ByteString q(DSS_Q());
+ ByteString g(DSS_G());
+
+ static const PQGParams PARAMS = {
+ nullptr,
+ { siBuffer,
+ const_cast<uint8_t*>(p.data()),
+ static_cast<unsigned int>(p.length())
+ },
+ { siBuffer,
+ const_cast<uint8_t*>(q.data()),
+ static_cast<unsigned int>(q.length())
+ },
+ { siBuffer,
+ const_cast<uint8_t*>(g.data()),
+ static_cast<unsigned int>(g.length())
+ }
+ };
+
+ SECKEYPublicKey* publicKeyTemp = nullptr;
+ ScopedSECKEYPrivateKey
+ privateKey(PK11_GenerateKeyPair(slot.get(), CKM_DSA_KEY_PAIR_GEN,
+ const_cast<PQGParams*>(&PARAMS),
+ &publicKeyTemp, false, true, nullptr));
+ if (!privateKey) {
+ return nullptr;
+ }
+ ScopedSECKEYPublicKey publicKey(publicKeyTemp);
+ return CreateTestKeyPair(DSS(), *publicKey, privateKey.release());
+}
+
+Result
+TestVerifyECDSASignedDigest(const SignedDigest& signedDigest,
+ Input subjectPublicKeyInfo)
+{
+ InitNSSIfNeeded();
+ return VerifyECDSASignedDigestNSS(signedDigest, subjectPublicKeyInfo,
+ nullptr);
+}
+
+Result
+TestVerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest,
+ Input subjectPublicKeyInfo)
+{
+ InitNSSIfNeeded();
+ return VerifyRSAPKCS1SignedDigestNSS(signedDigest, subjectPublicKeyInfo,
+ nullptr);
+}
+
+Result
+TestDigestBuf(Input item,
+ DigestAlgorithm digestAlg,
+ /*out*/ uint8_t* digestBuf,
+ size_t digestBufLen)
+{
+ InitNSSIfNeeded();
+ return DigestBufNSS(item, digestAlg, digestBuf, digestBufLen);
+}
+
+} } } // namespace mozilla::pkix::test
diff --git a/security/pkix/test/lib/pkixtestutil.cpp b/security/pkix/test/lib/pkixtestutil.cpp
new file mode 100644
index 000000000..decdee09a
--- /dev/null
+++ b/security/pkix/test/lib/pkixtestutil.cpp
@@ -0,0 +1,1153 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2013 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pkixtestutil.h"
+
+#include <cerrno>
+#include <cstdio>
+#include <limits>
+#include <new>
+#include <sstream>
+#include <cstdlib>
+
+#include "pkixder.h"
+#include "pkixutil.h"
+
+using namespace std;
+
+namespace mozilla { namespace pkix { namespace test {
+
+namespace {
+
+inline void
+fclose_void(FILE* file) {
+ (void) fclose(file);
+}
+
+typedef mozilla::pkix::ScopedPtr<FILE, fclose_void> ScopedFILE;
+
+FILE*
+OpenFile(const string& dir, const string& filename, const string& mode)
+{
+ string path = dir + '/' + filename;
+
+ ScopedFILE file;
+#ifdef _MSC_VER
+ {
+ FILE* rawFile;
+ errno_t error = fopen_s(&rawFile, path.c_str(), mode.c_str());
+ if (error) {
+ // TODO: map error to NSPR error code
+ rawFile = nullptr;
+ }
+ file.reset(rawFile);
+ }
+#else
+ file.reset(fopen(path.c_str(), mode.c_str()));
+#endif
+ return file.release();
+}
+
+} // namespace
+
+bool
+InputEqualsByteString(Input input, const ByteString& bs)
+{
+ Input bsInput;
+ if (bsInput.Init(bs.data(), bs.length()) != Success) {
+ // Init can only fail if it is given a bad pointer or if the input is too
+ // long, which won't ever happen. Plus, if it does, it is ok to call abort
+ // since this is only test code.
+ abort();
+ }
+ return InputsAreEqual(input, bsInput);
+}
+
+ByteString
+InputToByteString(Input input)
+{
+ ByteString result;
+ Reader reader(input);
+ for (;;) {
+ uint8_t b;
+ if (reader.Read(b) != Success) {
+ return result;
+ }
+ result.push_back(b);
+ }
+}
+
+Result
+TamperOnce(/*in/out*/ ByteString& item, const ByteString& from,
+ const ByteString& to)
+{
+ if (from.length() < 8) {
+ return Result::FATAL_ERROR_INVALID_ARGS;
+ }
+ if (from.length() != to.length()) {
+ return Result::FATAL_ERROR_INVALID_ARGS;
+ }
+ size_t pos = item.find(from);
+ if (pos == string::npos) {
+ return Result::FATAL_ERROR_INVALID_ARGS; // No matches.
+ }
+ if (item.find(from, pos + from.length()) != string::npos) {
+ return Result::FATAL_ERROR_INVALID_ARGS; // More than once match.
+ }
+ item.replace(pos, from.length(), to);
+ return Success;
+}
+
+// Given a tag and a value, generates a DER-encoded tag-length-value item.
+ByteString
+TLV(uint8_t tag, size_t length, const ByteString& value)
+{
+ ByteString result;
+ result.push_back(tag);
+
+ if (value.length() < 128) {
+ result.push_back(static_cast<uint8_t>(length));
+ } else if (value.length() < 256) {
+ result.push_back(0x81u);
+ result.push_back(static_cast<uint8_t>(length));
+ } else if (value.length() < 65536) {
+ result.push_back(0x82u);
+ result.push_back(static_cast<uint8_t>(length / 256));
+ result.push_back(static_cast<uint8_t>(length % 256));
+ } else {
+ // It is MUCH more convenient for TLV to be infallible than for it to have
+ // "proper" error handling.
+ abort();
+ }
+ result.append(value);
+ return result;
+}
+
+OCSPResponseExtension::OCSPResponseExtension()
+ : id()
+ , critical(false)
+ , value()
+ , next(nullptr)
+{
+}
+
+OCSPResponseContext::OCSPResponseContext(const CertID& certID, time_t time)
+ : certID(certID)
+ , responseStatus(successful)
+ , skipResponseBytes(false)
+ , producedAt(time)
+ , singleExtensions(nullptr)
+ , responseExtensions(nullptr)
+ , includeEmptyExtensions(false)
+ , signatureAlgorithm(sha256WithRSAEncryption())
+ , badSignature(false)
+ , certs(nullptr)
+
+ , certStatus(good)
+ , revocationTime(0)
+ , thisUpdate(time)
+ , nextUpdate(time + static_cast<time_t>(Time::ONE_DAY_IN_SECONDS))
+ , includeNextUpdate(true)
+{
+}
+
+static ByteString ResponseBytes(OCSPResponseContext& context);
+static ByteString BasicOCSPResponse(OCSPResponseContext& context);
+static ByteString ResponseData(OCSPResponseContext& context);
+static ByteString ResponderID(OCSPResponseContext& context);
+static ByteString KeyHash(const ByteString& subjectPublicKeyInfo);
+static ByteString SingleResponse(OCSPResponseContext& context);
+static ByteString CertID(OCSPResponseContext& context);
+static ByteString CertStatus(OCSPResponseContext& context);
+
+static ByteString
+SHA1(const ByteString& toHash)
+{
+ uint8_t digestBuf[20];
+ Input input;
+ if (input.Init(toHash.data(), toHash.length()) != Success) {
+ abort();
+ }
+ Result rv = TestDigestBuf(input, DigestAlgorithm::sha1, digestBuf,
+ sizeof(digestBuf));
+ if (rv != Success) {
+ abort();
+ }
+ return ByteString(digestBuf, sizeof(digestBuf));
+}
+
+static ByteString
+HashedOctetString(const ByteString& bytes)
+{
+ ByteString digest(SHA1(bytes));
+ if (ENCODING_FAILED(digest)) {
+ return ByteString();
+ }
+ return TLV(der::OCTET_STRING, digest);
+}
+
+static ByteString
+BitString(const ByteString& rawBytes, bool corrupt)
+{
+ ByteString prefixed;
+ // We have to add a byte at the beginning indicating no unused bits.
+ // TODO: add ability to have bit strings of bit length not divisible by 8,
+ // resulting in unused bits in the bitstring encoding
+ prefixed.push_back(0);
+ prefixed.append(rawBytes);
+ if (corrupt) {
+ assert(prefixed.length() > 8);
+ prefixed[8]++;
+ }
+ return TLV(der::BIT_STRING, prefixed);
+}
+
+ByteString
+Boolean(bool value)
+{
+ ByteString encodedValue;
+ encodedValue.push_back(value ? 0xffu : 0x00u);
+ return TLV(der::BOOLEAN, encodedValue);
+}
+
+ByteString
+Integer(long value)
+{
+ if (value < 0 || value > 127) {
+ // TODO: add encoding of larger values
+ // It is MUCH more convenient for Integer to be infallible than for it to
+ // have "proper" error handling.
+ abort();
+ }
+
+ ByteString encodedValue;
+ encodedValue.push_back(static_cast<uint8_t>(value));
+ return TLV(der::INTEGER, encodedValue);
+}
+
+enum TimeEncoding { UTCTime = 0, GeneralizedTime = 1 };
+
+// Windows doesn't provide gmtime_r, but it provides something very similar.
+#if defined(WIN32) && !defined(_POSIX_THREAD_SAFE_FUNCTIONS)
+static tm*
+gmtime_r(const time_t* t, /*out*/ tm* exploded)
+{
+ if (gmtime_s(exploded, t) != 0) {
+ return nullptr;
+ }
+ return exploded;
+}
+#endif
+
+// http://tools.ietf.org/html/rfc5280#section-4.1.2.5
+// UTCTime: YYMMDDHHMMSSZ (years 1950-2049 only)
+// GeneralizedTime: YYYYMMDDHHMMSSZ
+//
+// This assumes that time/time_t are POSIX-compliant in that time() returns
+// the number of seconds since the Unix epoch.
+static ByteString
+TimeToEncodedTime(time_t time, TimeEncoding encoding)
+{
+ assert(encoding == UTCTime || encoding == GeneralizedTime);
+
+ tm exploded;
+ if (!gmtime_r(&time, &exploded)) {
+ return ByteString();
+ }
+
+ if (exploded.tm_sec >= 60) {
+ // round down for leap seconds
+ exploded.tm_sec = 59;
+ }
+
+ // exploded.tm_year is the year offset by 1900.
+ int year = exploded.tm_year + 1900;
+
+ if (encoding == UTCTime && (year < 1950 || year >= 2050)) {
+ return ByteString();
+ }
+
+ ByteString value;
+
+ if (encoding == GeneralizedTime) {
+ value.push_back(static_cast<uint8_t>('0' + (year / 1000)));
+ value.push_back(static_cast<uint8_t>('0' + ((year % 1000) / 100)));
+ }
+
+ value.push_back(static_cast<uint8_t>('0' + ((year % 100) / 10)));
+ value.push_back(static_cast<uint8_t>('0' + (year % 10)));
+ value.push_back(static_cast<uint8_t>('0' + ((exploded.tm_mon + 1) / 10)));
+ value.push_back(static_cast<uint8_t>('0' + ((exploded.tm_mon + 1) % 10)));
+ value.push_back(static_cast<uint8_t>('0' + (exploded.tm_mday / 10)));
+ value.push_back(static_cast<uint8_t>('0' + (exploded.tm_mday % 10)));
+ value.push_back(static_cast<uint8_t>('0' + (exploded.tm_hour / 10)));
+ value.push_back(static_cast<uint8_t>('0' + (exploded.tm_hour % 10)));
+ value.push_back(static_cast<uint8_t>('0' + (exploded.tm_min / 10)));
+ value.push_back(static_cast<uint8_t>('0' + (exploded.tm_min % 10)));
+ value.push_back(static_cast<uint8_t>('0' + (exploded.tm_sec / 10)));
+ value.push_back(static_cast<uint8_t>('0' + (exploded.tm_sec % 10)));
+ value.push_back('Z');
+
+ return TLV(encoding == GeneralizedTime ? der::GENERALIZED_TIME : der::UTCTime,
+ value);
+}
+
+static ByteString
+TimeToGeneralizedTime(time_t time)
+{
+ return TimeToEncodedTime(time, GeneralizedTime);
+}
+
+// http://tools.ietf.org/html/rfc5280#section-4.1.2.5: "CAs conforming to this
+// profile MUST always encode certificate validity dates through the year 2049
+// as UTCTime; certificate validity dates in 2050 or later MUST be encoded as
+// GeneralizedTime." (This is a special case of the rule that we must always
+// use the shortest possible encoding.)
+static ByteString
+TimeToTimeChoice(time_t time)
+{
+ tm exploded;
+ if (!gmtime_r(&time, &exploded)) {
+ return ByteString();
+ }
+ TimeEncoding encoding = (exploded.tm_year + 1900 >= 1950 &&
+ exploded.tm_year + 1900 < 2050)
+ ? UTCTime
+ : GeneralizedTime;
+
+ return TimeToEncodedTime(time, encoding);
+}
+
+Time
+YMDHMS(uint16_t year, uint16_t month, uint16_t day,
+ uint16_t hour, uint16_t minutes, uint16_t seconds)
+{
+ assert(year <= 9999);
+ assert(month >= 1);
+ assert(month <= 12);
+ assert(day >= 1);
+ assert(hour < 24);
+ assert(minutes < 60);
+ assert(seconds < 60);
+
+ uint64_t days = DaysBeforeYear(year);
+
+ {
+ static const int16_t DAYS_IN_MONTH[] = {
+ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+ };
+
+ int16_t i = 1;
+ for (;;) {
+ int16_t daysInMonth = DAYS_IN_MONTH[i - 1];
+ if (i == 2 &&
+ ((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)))) {
+ // Add leap day
+ ++daysInMonth;
+ }
+ if (i == month) {
+ assert(day <= daysInMonth);
+ break;
+ }
+ days += daysInMonth;
+ ++i;
+ }
+ }
+
+ days += (day - 1);
+
+ uint64_t totalSeconds = days * Time::ONE_DAY_IN_SECONDS;
+ totalSeconds += hour * 60 * 60;
+ totalSeconds += minutes * 60;
+ totalSeconds += seconds;
+ return TimeFromElapsedSecondsAD(totalSeconds);
+}
+
+static ByteString
+SignedData(const ByteString& tbsData,
+ const TestKeyPair& keyPair,
+ const TestSignatureAlgorithm& signatureAlgorithm,
+ bool corrupt, /*optional*/ const ByteString* certs)
+{
+ ByteString signature;
+ if (keyPair.SignData(tbsData, signatureAlgorithm, signature) != Success) {
+ return ByteString();
+ }
+
+ // TODO: add ability to have signatures of bit length not divisible by 8,
+ // resulting in unused bits in the bitstring encoding
+ ByteString signatureNested(BitString(signature, corrupt));
+ if (ENCODING_FAILED(signatureNested)) {
+ return ByteString();
+ }
+
+ ByteString certsNested;
+ if (certs) {
+ ByteString certsSequenceValue;
+ while (!(*certs).empty()) {
+ certsSequenceValue.append(*certs);
+ ++certs;
+ }
+ ByteString certsSequence(TLV(der::SEQUENCE, certsSequenceValue));
+ certsNested = TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0,
+ certsSequence);
+ }
+
+ ByteString value;
+ value.append(tbsData);
+ value.append(signatureAlgorithm.algorithmIdentifier);
+ value.append(signatureNested);
+ value.append(certsNested);
+ return TLV(der::SEQUENCE, value);
+}
+
+// Extension ::= SEQUENCE {
+// extnID OBJECT IDENTIFIER,
+// critical BOOLEAN DEFAULT FALSE,
+// extnValue OCTET STRING
+// -- contains the DER encoding of an ASN.1 value
+// -- corresponding to the extension type identified
+// -- by extnID
+// }
+static ByteString
+Extension(Input extnID, Critical critical, const ByteString& extnValueBytes)
+{
+ ByteString encoded;
+
+ encoded.append(ByteString(extnID.UnsafeGetData(), extnID.GetLength()));
+
+ if (critical == Critical::Yes) {
+ encoded.append(Boolean(true));
+ }
+
+ ByteString extnValueSequence(TLV(der::SEQUENCE, extnValueBytes));
+ ByteString extnValue(TLV(der::OCTET_STRING, extnValueSequence));
+ encoded.append(extnValue);
+ return TLV(der::SEQUENCE, encoded);
+}
+
+static ByteString
+EmptyExtension(Input extnID, Critical critical)
+{
+ ByteString encoded(extnID.UnsafeGetData(), extnID.GetLength());
+
+ if (critical == Critical::Yes) {
+ encoded.append(Boolean(true));
+ }
+
+ ByteString extnValue(TLV(der::OCTET_STRING, ByteString()));
+ encoded.append(extnValue);
+ return TLV(der::SEQUENCE, encoded);
+}
+
+std::string
+GetEnv(const char* name)
+{
+ std::string result;
+
+#ifndef _MSC_VER
+ // XXX: Not thread safe.
+ const char* value = getenv(name);
+ if (value) {
+ result = value;
+ }
+#else
+ char* value = nullptr;
+ size_t valueLength = 0;
+ if (_dupenv_s(&value, &valueLength, name) != 0) {
+ abort();
+ }
+ if (value) {
+ result = value;
+ free(value);
+ }
+#endif
+ return result;
+}
+
+void
+MaybeLogOutput(const ByteString& result, const char* suffix)
+{
+ assert(suffix);
+
+ // This allows us to more easily debug the generated output, by creating a
+ // file in the directory given by MOZILLA_PKIX_TEST_LOG_DIR for each
+ // NOT THREAD-SAFE!!!
+ std::string logPath(GetEnv("MOZILLA_PKIX_TEST_LOG_DIR"));
+ if (!logPath.empty()) {
+ static int counter = 0;
+
+ std::ostringstream counterStream;
+ counterStream << counter;
+ if (!counterStream) {
+ assert(false);
+ return;
+ }
+ string filename = counterStream.str() + '-' + suffix + ".der";
+
+ ++counter;
+ ScopedFILE file(OpenFile(logPath, filename, "wb"));
+ if (file) {
+ (void) fwrite(result.data(), result.length(), 1, file.get());
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Certificates
+
+static ByteString TBSCertificate(long version, const ByteString& serialNumber,
+ const ByteString& signature,
+ const ByteString& issuer,
+ time_t notBefore, time_t notAfter,
+ const ByteString& subject,
+ const ByteString& subjectPublicKeyInfo,
+ /*optional*/ const ByteString* extensions);
+
+// Certificate ::= SEQUENCE {
+// tbsCertificate TBSCertificate,
+// signatureAlgorithm AlgorithmIdentifier,
+// signatureValue BIT STRING }
+ByteString
+CreateEncodedCertificate(long version,
+ const TestSignatureAlgorithm& signature,
+ const ByteString& serialNumber,
+ const ByteString& issuerNameDER,
+ time_t notBefore, time_t notAfter,
+ const ByteString& subjectNameDER,
+ const TestKeyPair& subjectKeyPair,
+ /*optional*/ const ByteString* extensions,
+ const TestKeyPair& issuerKeyPair,
+ const TestSignatureAlgorithm& signatureAlgorithm)
+{
+ ByteString tbsCertificate(TBSCertificate(version, serialNumber,
+ signature.algorithmIdentifier,
+ issuerNameDER, notBefore,
+ notAfter, subjectNameDER,
+ subjectKeyPair.subjectPublicKeyInfo,
+ extensions));
+ if (ENCODING_FAILED(tbsCertificate)) {
+ return ByteString();
+ }
+
+ ByteString result(SignedData(tbsCertificate, issuerKeyPair,
+ signatureAlgorithm, false, nullptr));
+ if (ENCODING_FAILED(result)) {
+ return ByteString();
+ }
+
+ MaybeLogOutput(result, "cert");
+
+ return result;
+}
+
+// TBSCertificate ::= SEQUENCE {
+// version [0] Version DEFAULT v1,
+// serialNumber CertificateSerialNumber,
+// signature AlgorithmIdentifier,
+// issuer Name,
+// validity Validity,
+// subject Name,
+// subjectPublicKeyInfo SubjectPublicKeyInfo,
+// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
+// -- If present, version MUST be v2 or v3
+// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
+// -- If present, version MUST be v2 or v3
+// extensions [3] Extensions OPTIONAL
+// -- If present, version MUST be v3 -- }
+static ByteString
+TBSCertificate(long versionValue,
+ const ByteString& serialNumber, const ByteString& signature,
+ const ByteString& issuer, time_t notBeforeTime,
+ time_t notAfterTime, const ByteString& subject,
+ const ByteString& subjectPublicKeyInfo,
+ /*optional*/ const ByteString* extensions)
+{
+ ByteString value;
+
+ if (versionValue != static_cast<long>(der::Version::v1)) {
+ ByteString versionInteger(Integer(versionValue));
+ ByteString version(TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0,
+ versionInteger));
+ value.append(version);
+ }
+
+ value.append(serialNumber);
+ value.append(signature);
+ value.append(issuer);
+
+ // Validity ::= SEQUENCE {
+ // notBefore Time,
+ // notAfter Time }
+ ByteString validity;
+ {
+ ByteString notBefore(TimeToTimeChoice(notBeforeTime));
+ if (ENCODING_FAILED(notBefore)) {
+ return ByteString();
+ }
+ ByteString notAfter(TimeToTimeChoice(notAfterTime));
+ if (ENCODING_FAILED(notAfter)) {
+ return ByteString();
+ }
+ ByteString validityValue;
+ validityValue.append(notBefore);
+ validityValue.append(notAfter);
+ validity = TLV(der::SEQUENCE, validityValue);
+ if (ENCODING_FAILED(validity)) {
+ return ByteString();
+ }
+ }
+ value.append(validity);
+
+ value.append(subject);
+
+ value.append(subjectPublicKeyInfo);
+
+ if (extensions) {
+ ByteString extensionsValue;
+ while (!(*extensions).empty()) {
+ extensionsValue.append(*extensions);
+ ++extensions;
+ }
+ ByteString extensionsSequence(TLV(der::SEQUENCE, extensionsValue));
+ if (ENCODING_FAILED(extensionsSequence)) {
+ return ByteString();
+ }
+ ByteString extensionsWrapped(
+ TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 3, extensionsSequence));
+ if (ENCODING_FAILED(extensionsWrapped)) {
+ return ByteString();
+ }
+ value.append(extensionsWrapped);
+ }
+
+ return TLV(der::SEQUENCE, value);
+}
+
+// AttributeTypeAndValue ::= SEQUENCE {
+// type AttributeType,
+// value AttributeValue }
+//
+// AttributeType ::= OBJECT IDENTIFIER
+//
+// AttributeValue ::= ANY -- DEFINED BY AttributeType
+//
+// DirectoryString ::= CHOICE {
+// teletexString TeletexString (SIZE (1..MAX)),
+// printableString PrintableString (SIZE (1..MAX)),
+// universalString UniversalString (SIZE (1..MAX)),
+// utf8String UTF8String (SIZE (1..MAX)),
+// bmpString BMPString (SIZE (1..MAX)) }
+template <size_t N>
+static ByteString
+AVA(const uint8_t (&type)[N], uint8_t directoryStringType,
+ const ByteString& value)
+{
+ ByteString wrappedValue(TLV(directoryStringType, value));
+ ByteString ava;
+ ava.append(type, N);
+ ava.append(wrappedValue);
+ return TLV(der::SEQUENCE, ava);
+}
+
+ByteString
+CN(const ByteString& value, uint8_t encodingTag)
+{
+ // id-at OBJECT IDENTIFIER ::= { joint-iso-ccitt(2) ds(5) 4 }
+ // id-at-commonName AttributeType ::= { id-at 3 }
+ // python DottedOIDToCode.py --tlv id-at-commonName 2.5.4.3
+ static const uint8_t tlv_id_at_commonName[] = {
+ 0x06, 0x03, 0x55, 0x04, 0x03
+ };
+ return AVA(tlv_id_at_commonName, encodingTag, value);
+}
+
+ByteString
+OU(const ByteString& value, uint8_t encodingTag)
+{
+ // id-at OBJECT IDENTIFIER ::= { joint-iso-ccitt(2) ds(5) 4 }
+ // id-at-organizationalUnitName AttributeType ::= { id-at 11 }
+ // python DottedOIDToCode.py --tlv id-at-organizationalUnitName 2.5.4.11
+ static const uint8_t tlv_id_at_organizationalUnitName[] = {
+ 0x06, 0x03, 0x55, 0x04, 0x0b
+ };
+
+ return AVA(tlv_id_at_organizationalUnitName, encodingTag, value);
+}
+
+ByteString
+emailAddress(const ByteString& value)
+{
+ // id-emailAddress AttributeType ::= { pkcs-9 1 }
+ // python DottedOIDToCode.py --tlv id-emailAddress 1.2.840.113549.1.9.1
+ static const uint8_t tlv_id_emailAddress[] = {
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01
+ };
+
+ return AVA(tlv_id_emailAddress, der::IA5String, value);
+}
+
+// RelativeDistinguishedName ::=
+// SET SIZE (1..MAX) OF AttributeTypeAndValue
+//
+ByteString
+RDN(const ByteString& avas)
+{
+ return TLV(der::SET, avas);
+}
+
+// Name ::= CHOICE { -- only one possibility for now --
+// rdnSequence RDNSequence }
+//
+// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
+//
+ByteString
+Name(const ByteString& rdns)
+{
+ return TLV(der::SEQUENCE, rdns);
+}
+
+ByteString
+CreateEncodedSerialNumber(long serialNumberValue)
+{
+ return Integer(serialNumberValue);
+}
+
+// BasicConstraints ::= SEQUENCE {
+// cA BOOLEAN DEFAULT FALSE,
+// pathLenConstraint INTEGER (0..MAX) OPTIONAL }
+ByteString
+CreateEncodedBasicConstraints(bool isCA,
+ /*optional*/ long* pathLenConstraintValue,
+ Critical critical)
+{
+ ByteString value;
+
+ if (isCA) {
+ ByteString cA(Boolean(true));
+ value.append(cA);
+ }
+
+ if (pathLenConstraintValue) {
+ ByteString pathLenConstraint(Integer(*pathLenConstraintValue));
+ value.append(pathLenConstraint);
+ }
+
+ // python DottedOIDToCode.py --tlv id-ce-basicConstraints 2.5.29.19
+ static const uint8_t tlv_id_ce_basicConstraints[] = {
+ 0x06, 0x03, 0x55, 0x1d, 0x13
+ };
+ return Extension(Input(tlv_id_ce_basicConstraints), critical, value);
+}
+
+// ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId
+// KeyPurposeId ::= OBJECT IDENTIFIER
+ByteString
+CreateEncodedEKUExtension(Input ekuOID, Critical critical)
+{
+ ByteString value(ekuOID.UnsafeGetData(), ekuOID.GetLength());
+
+ // python DottedOIDToCode.py --tlv id-ce-extKeyUsage 2.5.29.37
+ static const uint8_t tlv_id_ce_extKeyUsage[] = {
+ 0x06, 0x03, 0x55, 0x1d, 0x25
+ };
+
+ return Extension(Input(tlv_id_ce_extKeyUsage), critical, value);
+}
+
+// python DottedOIDToCode.py --tlv id-ce-subjectAltName 2.5.29.17
+static const uint8_t tlv_id_ce_subjectAltName[] = {
+ 0x06, 0x03, 0x55, 0x1d, 0x11
+};
+
+ByteString
+CreateEncodedSubjectAltName(const ByteString& names)
+{
+ return Extension(Input(tlv_id_ce_subjectAltName), Critical::No, names);
+}
+
+ByteString
+CreateEncodedEmptySubjectAltName()
+{
+ return EmptyExtension(Input(tlv_id_ce_subjectAltName), Critical::No);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// OCSP responses
+
+ByteString
+CreateEncodedOCSPResponse(OCSPResponseContext& context)
+{
+ if (!context.skipResponseBytes) {
+ if (!context.signerKeyPair) {
+ return ByteString();
+ }
+ }
+
+ // OCSPResponse ::= SEQUENCE {
+ // responseStatus OCSPResponseStatus,
+ // responseBytes [0] EXPLICIT ResponseBytes OPTIONAL }
+
+ // OCSPResponseStatus ::= ENUMERATED {
+ // successful (0), -- Response has valid confirmations
+ // malformedRequest (1), -- Illegal confirmation request
+ // internalError (2), -- Internal error in issuer
+ // tryLater (3), -- Try again later
+ // -- (4) is not used
+ // sigRequired (5), -- Must sign the request
+ // unauthorized (6) -- Request unauthorized
+ // }
+ ByteString reponseStatusValue;
+ reponseStatusValue.push_back(context.responseStatus);
+ ByteString responseStatus(TLV(der::ENUMERATED, reponseStatusValue));
+
+ ByteString responseBytesNested;
+ if (!context.skipResponseBytes) {
+ ByteString responseBytes(ResponseBytes(context));
+ if (ENCODING_FAILED(responseBytes)) {
+ return ByteString();
+ }
+
+ responseBytesNested = TLV(der::CONSTRUCTED | der::CONTEXT_SPECIFIC,
+ responseBytes);
+ }
+
+ ByteString value;
+ value.append(responseStatus);
+ value.append(responseBytesNested);
+ ByteString result(TLV(der::SEQUENCE, value));
+
+ MaybeLogOutput(result, "ocsp");
+
+ return result;
+}
+
+// ResponseBytes ::= SEQUENCE {
+// responseType OBJECT IDENTIFIER,
+// response OCTET STRING }
+ByteString
+ResponseBytes(OCSPResponseContext& context)
+{
+ // Includes tag and length
+ static const uint8_t id_pkix_ocsp_basic_encoded[] = {
+ 0x06, 0x09, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01
+ };
+ ByteString response(BasicOCSPResponse(context));
+ if (ENCODING_FAILED(response)) {
+ return ByteString();
+ }
+ ByteString responseNested = TLV(der::OCTET_STRING, response);
+
+ ByteString value;
+ value.append(id_pkix_ocsp_basic_encoded,
+ sizeof(id_pkix_ocsp_basic_encoded));
+ value.append(responseNested);
+ return TLV(der::SEQUENCE, value);
+}
+
+// BasicOCSPResponse ::= SEQUENCE {
+// tbsResponseData ResponseData,
+// signatureAlgorithm AlgorithmIdentifier,
+// signature BIT STRING,
+// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
+ByteString
+BasicOCSPResponse(OCSPResponseContext& context)
+{
+ ByteString tbsResponseData(ResponseData(context));
+ if (ENCODING_FAILED(tbsResponseData)) {
+ return ByteString();
+ }
+
+ return SignedData(tbsResponseData, *context.signerKeyPair,
+ context.signatureAlgorithm, context.badSignature,
+ context.certs);
+}
+
+// Extension ::= SEQUENCE {
+// id OBJECT IDENTIFIER,
+// critical BOOLEAN DEFAULT FALSE
+// value OCTET STRING
+// }
+static ByteString
+OCSPExtension(OCSPResponseExtension& extension)
+{
+ ByteString encoded;
+ encoded.append(extension.id);
+ if (extension.critical) {
+ encoded.append(Boolean(true));
+ }
+ ByteString value(TLV(der::OCTET_STRING, extension.value));
+ encoded.append(value);
+ return TLV(der::SEQUENCE, encoded);
+}
+
+// Extensions ::= [1] {
+// SEQUENCE OF Extension
+// }
+static ByteString
+OCSPExtensions(OCSPResponseExtension* extensions)
+{
+ ByteString value;
+ for (OCSPResponseExtension* extension = extensions;
+ extension; extension = extension->next) {
+ ByteString extensionEncoded(OCSPExtension(*extension));
+ if (ENCODING_FAILED(extensionEncoded)) {
+ return ByteString();
+ }
+ value.append(extensionEncoded);
+ }
+ ByteString sequence(TLV(der::SEQUENCE, value));
+ return TLV(der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 1, sequence);
+}
+
+// ResponseData ::= SEQUENCE {
+// version [0] EXPLICIT Version DEFAULT v1,
+// responderID ResponderID,
+// producedAt GeneralizedTime,
+// responses SEQUENCE OF SingleResponse,
+// responseExtensions [1] EXPLICIT Extensions OPTIONAL }
+ByteString
+ResponseData(OCSPResponseContext& context)
+{
+ ByteString responderID(ResponderID(context));
+ if (ENCODING_FAILED(responderID)) {
+ return ByteString();
+ }
+ ByteString producedAtEncoded(TimeToGeneralizedTime(context.producedAt));
+ if (ENCODING_FAILED(producedAtEncoded)) {
+ return ByteString();
+ }
+ ByteString response(SingleResponse(context));
+ if (ENCODING_FAILED(response)) {
+ return ByteString();
+ }
+ ByteString responses(TLV(der::SEQUENCE, response));
+ ByteString responseExtensions;
+ if (context.responseExtensions || context.includeEmptyExtensions) {
+ responseExtensions = OCSPExtensions(context.responseExtensions);
+ }
+
+ ByteString value;
+ value.append(responderID);
+ value.append(producedAtEncoded);
+ value.append(responses);
+ value.append(responseExtensions);
+ return TLV(der::SEQUENCE, value);
+}
+
+// ResponderID ::= CHOICE {
+// byName [1] Name,
+// byKey [2] KeyHash }
+// }
+ByteString
+ResponderID(OCSPResponseContext& context)
+{
+ ByteString contents;
+ uint8_t responderIDType;
+ if (!context.signerNameDER.empty()) {
+ contents = context.signerNameDER;
+ responderIDType = 1; // byName
+ } else {
+ contents = KeyHash(context.signerKeyPair->subjectPublicKey);
+ if (ENCODING_FAILED(contents)) {
+ return ByteString();
+ }
+ responderIDType = 2; // byKey
+ }
+
+ // XXX: MSVC 2015 wrongly warns about signed/unsigned conversion without the
+ // static_cast.
+ uint8_t tag = static_cast<uint8_t>(der::CONSTRUCTED | der::CONTEXT_SPECIFIC |
+ responderIDType);
+ return TLV(tag, contents);
+}
+
+// KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key
+// -- (i.e., the SHA-1 hash of the value of the
+// -- BIT STRING subjectPublicKey [excluding
+// -- the tag, length, and number of unused
+// -- bits] in the responder's certificate)
+ByteString
+KeyHash(const ByteString& subjectPublicKey)
+{
+ return HashedOctetString(subjectPublicKey);
+}
+
+// SingleResponse ::= SEQUENCE {
+// certID CertID,
+// certStatus CertStatus,
+// thisUpdate GeneralizedTime,
+// nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL,
+// singleExtensions [1] EXPLICIT Extensions OPTIONAL }
+ByteString
+SingleResponse(OCSPResponseContext& context)
+{
+ ByteString certID(CertID(context));
+ if (ENCODING_FAILED(certID)) {
+ return ByteString();
+ }
+ ByteString certStatus(CertStatus(context));
+ if (ENCODING_FAILED(certStatus)) {
+ return ByteString();
+ }
+ ByteString thisUpdateEncoded(TimeToGeneralizedTime(context.thisUpdate));
+ if (ENCODING_FAILED(thisUpdateEncoded)) {
+ return ByteString();
+ }
+ ByteString nextUpdateEncodedNested;
+ if (context.includeNextUpdate) {
+ ByteString nextUpdateEncoded(TimeToGeneralizedTime(context.nextUpdate));
+ if (ENCODING_FAILED(nextUpdateEncoded)) {
+ return ByteString();
+ }
+ nextUpdateEncodedNested = TLV(der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 0,
+ nextUpdateEncoded);
+ }
+ ByteString singleExtensions;
+ if (context.singleExtensions || context.includeEmptyExtensions) {
+ singleExtensions = OCSPExtensions(context.singleExtensions);
+ }
+
+ ByteString value;
+ value.append(certID);
+ value.append(certStatus);
+ value.append(thisUpdateEncoded);
+ value.append(nextUpdateEncodedNested);
+ value.append(singleExtensions);
+ return TLV(der::SEQUENCE, value);
+}
+
+// CertID ::= SEQUENCE {
+// hashAlgorithm AlgorithmIdentifier,
+// issuerNameHash OCTET STRING, -- Hash of issuer's DN
+// issuerKeyHash OCTET STRING, -- Hash of issuer's public key
+// serialNumber CertificateSerialNumber }
+ByteString
+CertID(OCSPResponseContext& context)
+{
+ ByteString issuerName(context.certID.issuer.UnsafeGetData(),
+ context.certID.issuer.GetLength());
+ ByteString issuerNameHash(HashedOctetString(issuerName));
+ if (ENCODING_FAILED(issuerNameHash)) {
+ return ByteString();
+ }
+
+ ByteString issuerKeyHash;
+ {
+ // context.certID.issuerSubjectPublicKeyInfo is the entire
+ // SubjectPublicKeyInfo structure, but we need just the subjectPublicKey
+ // part.
+ Reader input(context.certID.issuerSubjectPublicKeyInfo);
+ Reader contents;
+ if (der::ExpectTagAndGetValue(input, der::SEQUENCE, contents) != Success) {
+ return ByteString();
+ }
+ // Skip AlgorithmIdentifier
+ if (der::ExpectTagAndSkipValue(contents, der::SEQUENCE) != Success) {
+ return ByteString();
+ }
+ Input subjectPublicKey;
+ if (der::BitStringWithNoUnusedBits(contents, subjectPublicKey)
+ != Success) {
+ return ByteString();
+ }
+ issuerKeyHash = KeyHash(ByteString(subjectPublicKey.UnsafeGetData(),
+ subjectPublicKey.GetLength()));
+ if (ENCODING_FAILED(issuerKeyHash)) {
+ return ByteString();
+ }
+ }
+
+ ByteString serialNumberValue(context.certID.serialNumber.UnsafeGetData(),
+ context.certID.serialNumber.GetLength());
+ ByteString serialNumber(TLV(der::INTEGER, serialNumberValue));
+
+ // python DottedOIDToCode.py --alg id-sha1 1.3.14.3.2.26
+ static const uint8_t alg_id_sha1[] = {
+ 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a
+ };
+
+ ByteString value;
+ value.append(alg_id_sha1, sizeof(alg_id_sha1));
+ value.append(issuerNameHash);
+ value.append(issuerKeyHash);
+ value.append(serialNumber);
+ return TLV(der::SEQUENCE, value);
+}
+
+// CertStatus ::= CHOICE {
+// good [0] IMPLICIT NULL,
+// revoked [1] IMPLICIT RevokedInfo,
+// unknown [2] IMPLICIT UnknownInfo }
+//
+// RevokedInfo ::= SEQUENCE {
+// revocationTime GeneralizedTime,
+// revocationReason [0] EXPLICIT CRLReason OPTIONAL }
+//
+// UnknownInfo ::= NULL
+//
+ByteString
+CertStatus(OCSPResponseContext& context)
+{
+ switch (context.certStatus) {
+ // Both good and unknown are ultimately represented as NULL - the only
+ // difference is in the tag that identifies them.
+ case 0:
+ case 2:
+ {
+ // XXX: MSVC 2015 wrongly warns about signed/unsigned conversion without
+ // the static cast.
+ return TLV(static_cast<uint8_t>(der::CONTEXT_SPECIFIC |
+ context.certStatus), ByteString());
+ }
+ case 1:
+ {
+ ByteString revocationTime(TimeToGeneralizedTime(context.revocationTime));
+ if (ENCODING_FAILED(revocationTime)) {
+ return ByteString();
+ }
+ // TODO(bug 980536): add support for revocationReason
+ return TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1, revocationTime);
+ }
+ default:
+ assert(false);
+ // fall through
+ }
+ return ByteString();
+}
+
+static const ByteString NO_UNUSED_BITS(1, 0x00);
+
+// The SubjectPublicKeyInfo syntax is specified in RFC 5280 Section 4.1.
+TestKeyPair::TestKeyPair(const TestPublicKeyAlgorithm& publicKeyAlg,
+ const ByteString& spk)
+ : publicKeyAlg(publicKeyAlg)
+ , subjectPublicKeyInfo(TLV(der::SEQUENCE,
+ publicKeyAlg.algorithmIdentifier +
+ TLV(der::BIT_STRING, NO_UNUSED_BITS + spk)))
+ , subjectPublicKey(spk)
+{
+}
+
+} } } // namespace mozilla::pkix::test
diff --git a/security/pkix/test/lib/pkixtestutil.h b/security/pkix/test/lib/pkixtestutil.h
new file mode 100644
index 000000000..b36f1f8ad
--- /dev/null
+++ b/security/pkix/test/lib/pkixtestutil.h
@@ -0,0 +1,448 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+/* Copyright 2013 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mozilla_pkix_test_pkixtestutils_h
+#define mozilla_pkix_test_pkixtestutils_h
+
+#include <ctime>
+#include <stdint.h> // Some Mozilla-supported compilers lack <cstdint>
+#include <string>
+#include <cstring>
+
+#include "pkix/pkixtypes.h"
+#include "../../lib/ScopedPtr.h"
+
+namespace mozilla { namespace pkix { namespace test {
+
+typedef std::basic_string<uint8_t> ByteString;
+
+inline bool ENCODING_FAILED(const ByteString& bs) { return bs.empty(); }
+
+template <size_t L>
+inline ByteString
+BytesToByteString(const uint8_t (&bytes)[L])
+{
+ return ByteString(bytes, L);
+}
+
+// XXX: Ideally, we should define this instead:
+//
+// template <typename T, std::size_t N>
+// constexpr inline std::size_t
+// ArrayLength(T (&)[N])
+// {
+// return N;
+// }
+//
+// However, we don't because not all supported compilers support constexpr,
+// and we need to calculate array lengths in static_assert sometimes.
+//
+// XXX: Evaluates its argument twice
+#define MOZILLA_PKIX_ARRAY_LENGTH(x) (sizeof(x) / sizeof((x)[0]))
+
+bool InputEqualsByteString(Input input, const ByteString& bs);
+ByteString InputToByteString(Input input);
+
+// python DottedOIDToCode.py --tlv id-kp-OCSPSigning 1.3.6.1.5.5.7.3.9
+static const uint8_t tlv_id_kp_OCSPSigning[] = {
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x09
+};
+
+// python DottedOIDToCode.py --tlv id-kp-serverAuth 1.3.6.1.5.5.7.3.1
+static const uint8_t tlv_id_kp_serverAuth[] = {
+ 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01
+};
+
+enum class TestDigestAlgorithmID
+{
+ MD2,
+ MD5,
+ SHA1,
+ SHA224,
+ SHA256,
+ SHA384,
+ SHA512,
+};
+
+struct TestPublicKeyAlgorithm
+{
+ explicit TestPublicKeyAlgorithm(const ByteString& algorithmIdentifier)
+ : algorithmIdentifier(algorithmIdentifier) { }
+ bool operator==(const TestPublicKeyAlgorithm& other) const
+ {
+ return algorithmIdentifier == other.algorithmIdentifier;
+ }
+ ByteString algorithmIdentifier;
+};
+
+ByteString DSS_P();
+ByteString DSS_Q();
+ByteString DSS_G();
+
+TestPublicKeyAlgorithm DSS();
+TestPublicKeyAlgorithm RSA_PKCS1();
+
+struct TestSignatureAlgorithm
+{
+ TestSignatureAlgorithm(const TestPublicKeyAlgorithm& publicKeyAlg,
+ TestDigestAlgorithmID digestAlg,
+ const ByteString& algorithmIdentifier,
+ bool accepted);
+
+ TestPublicKeyAlgorithm publicKeyAlg;
+ TestDigestAlgorithmID digestAlg;
+ ByteString algorithmIdentifier;
+ bool accepted;
+};
+
+TestSignatureAlgorithm md2WithRSAEncryption();
+TestSignatureAlgorithm md5WithRSAEncryption();
+TestSignatureAlgorithm sha1WithRSAEncryption();
+TestSignatureAlgorithm sha256WithRSAEncryption();
+
+// e.g. YMDHMS(2016, 12, 31, 1, 23, 45) => 2016-12-31:01:23:45 (GMT)
+mozilla::pkix::Time YMDHMS(uint16_t year, uint16_t month, uint16_t day,
+ uint16_t hour, uint16_t minutes, uint16_t seconds);
+
+ByteString TLV(uint8_t tag, size_t length, const ByteString& value);
+
+inline ByteString
+TLV(uint8_t tag, const ByteString& value)
+{
+ return TLV(tag, value.length(), value);
+}
+
+// Although we can't enforce it without relying on Cuser-defined literals,
+// which aren't supported by all of our compilers yet, you should only pass
+// string literals as the last parameter to the following two functions.
+
+template <size_t N>
+inline ByteString
+TLV(uint8_t tag, const char(&value)[N])
+{
+ static_assert(N > 0, "cannot have string literal of size 0");
+ assert(value[N - 1] == 0);
+ return TLV(tag, ByteString(reinterpret_cast<const uint8_t*>(&value), N - 1));
+}
+
+template <size_t N>
+inline ByteString
+TLV(uint8_t tag, size_t length, const char(&value)[N])
+{
+ static_assert(N > 0, "cannot have string literal of size 0");
+ assert(value[N - 1] == 0);
+ return TLV(tag, length,
+ ByteString(reinterpret_cast<const uint8_t*>(&value), N - 1));
+}
+
+ByteString Boolean(bool value);
+ByteString Integer(long value);
+
+ByteString CN(const ByteString&, uint8_t encodingTag = 0x0c /*UTF8String*/);
+
+inline ByteString
+CN(const char* value, uint8_t encodingTag = 0x0c /*UTF8String*/)
+{
+ return CN(ByteString(reinterpret_cast<const uint8_t*>(value),
+ std::strlen(value)), encodingTag);
+}
+
+ByteString OU(const ByteString&, uint8_t encodingTag = 0x0c /*UTF8String*/);
+
+inline ByteString
+OU(const char* value, uint8_t encodingTag = 0x0c /*UTF8String*/)
+{
+ return OU(ByteString(reinterpret_cast<const uint8_t*>(value),
+ std::strlen(value)), encodingTag);
+}
+
+ByteString emailAddress(const ByteString&);
+
+inline ByteString
+emailAddress(const char* value)
+{
+ return emailAddress(ByteString(reinterpret_cast<const uint8_t*>(value),
+ std::strlen(value)));
+}
+
+// RelativeDistinguishedName ::=
+// SET SIZE (1..MAX) OF AttributeTypeAndValue
+//
+ByteString RDN(const ByteString& avas);
+
+// Name ::= CHOICE { -- only one possibility for now --
+// rdnSequence RDNSequence }
+//
+// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
+//
+ByteString Name(const ByteString& rdns);
+
+inline ByteString
+CNToDERName(const ByteString& cn)
+{
+ return Name(RDN(CN(cn)));
+}
+
+inline ByteString
+CNToDERName(const char* cn)
+{
+ return Name(RDN(CN(cn)));
+}
+
+// GeneralName ::= CHOICE {
+// otherName [0] OtherName,
+// rfc822Name [1] IA5String,
+// dNSName [2] IA5String,
+// x400Address [3] ORAddress,
+// directoryName [4] Name,
+// ediPartyName [5] EDIPartyName,
+// uniformResourceIdentifier [6] IA5String,
+// iPAddress [7] OCTET STRING,
+// registeredID [8] OBJECT IDENTIFIER }
+
+inline ByteString
+RFC822Name(const ByteString& name)
+{
+ // (2 << 6) means "context-specific", 1 is the GeneralName tag.
+ return TLV((2 << 6) | 1, name);
+}
+
+template <size_t L>
+inline ByteString
+RFC822Name(const char (&bytes)[L])
+{
+ return RFC822Name(ByteString(reinterpret_cast<const uint8_t*>(&bytes),
+ L - 1));
+}
+
+inline ByteString
+DNSName(const ByteString& name)
+{
+ // (2 << 6) means "context-specific", 2 is the GeneralName tag.
+ return TLV((2 << 6) | 2, name);
+}
+
+template <size_t L>
+inline ByteString
+DNSName(const char (&bytes)[L])
+{
+ return DNSName(ByteString(reinterpret_cast<const uint8_t*>(&bytes),
+ L - 1));
+}
+
+inline ByteString
+DirectoryName(const ByteString& name)
+{
+ // (2 << 6) means "context-specific", (1 << 5) means "constructed", and 4 is
+ // the DirectoryName tag.
+ return TLV((2 << 6) | (1 << 5) | 4, name);
+}
+
+inline ByteString
+IPAddress()
+{
+ // (2 << 6) means "context-specific", 7 is the GeneralName tag.
+ return TLV((2 << 6) | 7, ByteString());
+}
+
+template <size_t L>
+inline ByteString
+IPAddress(const uint8_t (&bytes)[L])
+{
+ // (2 << 6) means "context-specific", 7 is the GeneralName tag.
+ return TLV((2 << 6) | 7, ByteString(bytes, L));
+}
+
+// Names should be zero or more GeneralNames, like DNSName and IPAddress return,
+// concatenated together.
+//
+// CreatedEncodedSubjectAltName(ByteString()) results in a SAN with an empty
+// sequence. CreateEmptyEncodedSubjectName() results in a SAN without any
+// sequence.
+ByteString CreateEncodedSubjectAltName(const ByteString& names);
+ByteString CreateEncodedEmptySubjectAltName();
+
+class TestKeyPair
+{
+public:
+ virtual ~TestKeyPair() { }
+
+ const TestPublicKeyAlgorithm publicKeyAlg;
+
+ // The DER encoding of the entire SubjectPublicKeyInfo structure. This is
+ // what is encoded in certificates.
+ const ByteString subjectPublicKeyInfo;
+
+ // The DER encoding of subjectPublicKeyInfo.subjectPublicKey. This is what is
+ // hashed to create CertIDs for OCSP.
+ const ByteString subjectPublicKey;
+
+ virtual Result SignData(const ByteString& tbs,
+ const TestSignatureAlgorithm& signatureAlgorithm,
+ /*out*/ ByteString& signature) const = 0;
+
+ virtual TestKeyPair* Clone() const = 0;
+protected:
+ TestKeyPair(const TestPublicKeyAlgorithm& publicKeyAlg, const ByteString& spk);
+ TestKeyPair(const TestKeyPair&) = delete;
+ void operator=(const TestKeyPair&) = delete;
+};
+
+TestKeyPair* CloneReusedKeyPair();
+TestKeyPair* GenerateKeyPair();
+TestKeyPair* GenerateDSSKeyPair();
+inline void DeleteTestKeyPair(TestKeyPair* keyPair) { delete keyPair; }
+typedef ScopedPtr<TestKeyPair, DeleteTestKeyPair> ScopedTestKeyPair;
+
+Result TestVerifyECDSASignedDigest(const SignedDigest& signedDigest,
+ Input subjectPublicKeyInfo);
+Result TestVerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest,
+ Input subjectPublicKeyInfo);
+Result TestDigestBuf(Input item, DigestAlgorithm digestAlg,
+ /*out*/ uint8_t* digestBuf, size_t digestBufLen);
+
+// Replace one substring in item with another of the same length, but only if
+// the substring was found exactly once. The "same length" restriction is
+// useful for avoiding invalidating lengths encoded within the item. The
+// "only once" restriction is helpful for avoiding making accidental changes.
+//
+// The string to search for must be 8 or more bytes long so that it is
+// extremely unlikely that there will ever be any false positive matches
+// in digital signatures, keys, hashes, etc.
+Result TamperOnce(/*in/out*/ ByteString& item, const ByteString& from,
+ const ByteString& to);
+
+///////////////////////////////////////////////////////////////////////////////
+// Encode Certificates
+
+enum Version { v1 = 0, v2 = 1, v3 = 2 };
+
+// signature is assumed to be the DER encoding of an AlgorithmIdentifer. It is
+// put into the signature field of the TBSCertificate. In most cases, it will
+// be the same as signatureAlgorithm, which is the algorithm actually used
+// to sign the certificate.
+// serialNumber is assumed to be the DER encoding of an INTEGER.
+//
+// If extensions is null, then no extensions will be encoded. Otherwise,
+// extensions must point to an array of ByteStrings, terminated with an empty
+// ByteString. (If the first item of the array is empty then an empty
+// Extensions sequence will be encoded.)
+ByteString CreateEncodedCertificate(long version,
+ const TestSignatureAlgorithm& signature,
+ const ByteString& serialNumber,
+ const ByteString& issuerNameDER,
+ time_t notBefore, time_t notAfter,
+ const ByteString& subjectNameDER,
+ const TestKeyPair& subjectKeyPair,
+ /*optional*/ const ByteString* extensions,
+ const TestKeyPair& issuerKeyPair,
+ const TestSignatureAlgorithm& signatureAlgorithm);
+
+ByteString CreateEncodedSerialNumber(long value);
+
+enum class Critical { No = 0, Yes = 1 };
+
+ByteString CreateEncodedBasicConstraints(bool isCA,
+ /*optional*/ long* pathLenConstraint,
+ Critical critical);
+
+// Creates a DER-encoded extKeyUsage extension with one EKU OID.
+ByteString CreateEncodedEKUExtension(Input eku, Critical critical);
+
+///////////////////////////////////////////////////////////////////////////////
+// Encode OCSP responses
+
+class OCSPResponseExtension final
+{
+public:
+ OCSPResponseExtension();
+
+ ByteString id;
+ bool critical;
+ ByteString value;
+ OCSPResponseExtension* next;
+};
+
+class OCSPResponseContext final
+{
+public:
+ OCSPResponseContext(const CertID& certID, std::time_t time);
+
+ const CertID& certID;
+ // TODO(bug 980538): add a way to specify what certificates are included.
+
+ // The fields below are in the order that they appear in an OCSP response.
+
+ enum OCSPResponseStatus
+ {
+ successful = 0,
+ malformedRequest = 1,
+ internalError = 2,
+ tryLater = 3,
+ // 4 is not used
+ sigRequired = 5,
+ unauthorized = 6,
+ };
+ uint8_t responseStatus; // an OCSPResponseStatus or an invalid value
+ bool skipResponseBytes; // If true, don't include responseBytes
+
+ // responderID
+ ByteString signerNameDER; // If set, responderID will use the byName
+ // form; otherwise responderID will use the
+ // byKeyHash form.
+
+ std::time_t producedAt;
+
+ // SingleResponse extensions (for the certID given in the constructor).
+ OCSPResponseExtension* singleExtensions;
+ // ResponseData extensions.
+ OCSPResponseExtension* responseExtensions;
+ bool includeEmptyExtensions; // If true, include the extension wrapper
+ // regardless of if there are any actual
+ // extensions.
+ ScopedTestKeyPair signerKeyPair;
+ TestSignatureAlgorithm signatureAlgorithm;
+ bool badSignature; // If true, alter the signature to fail verification
+ const ByteString* certs; // optional; array terminated by an empty string
+
+ // The following fields are on a per-SingleResponse basis. In the future we
+ // may support including multiple SingleResponses per response.
+ enum CertStatus
+ {
+ good = 0,
+ revoked = 1,
+ unknown = 2,
+ };
+ uint8_t certStatus; // CertStatus or an invalid value
+ std::time_t revocationTime; // For certStatus == revoked
+ std::time_t thisUpdate;
+ std::time_t nextUpdate;
+ bool includeNextUpdate;
+};
+
+ByteString CreateEncodedOCSPResponse(OCSPResponseContext& context);
+
+} } } // namespace mozilla::pkix::test
+
+#endif // mozilla_pkix_test_pkixtestutils_h