summaryrefslogtreecommitdiffstats
path: root/security/nss/gtests/mozpkix_gtest
diff options
context:
space:
mode:
Diffstat (limited to 'security/nss/gtests/mozpkix_gtest')
-rw-r--r--security/nss/gtests/mozpkix_gtest/README.txt61
-rw-r--r--security/nss/gtests/mozpkix_gtest/mozpkix_gtest.gyp71
-rw-r--r--security/nss/gtests/mozpkix_gtest/pkixbuild_tests.cpp894
-rw-r--r--security/nss/gtests/mozpkix_gtest/pkixcert_extension_tests.cpp276
-rw-r--r--security/nss/gtests/mozpkix_gtest/pkixcert_signature_algorithm_tests.cpp259
-rw-r--r--security/nss/gtests/mozpkix_gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp722
-rw-r--r--security/nss/gtests/mozpkix_gtest/pkixcheck_CheckIssuer_tests.cpp63
-rw-r--r--security/nss/gtests/mozpkix_gtest/pkixcheck_CheckKeyUsage_tests.cpp284
-rw-r--r--security/nss/gtests/mozpkix_gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp367
-rw-r--r--security/nss/gtests/mozpkix_gtest/pkixcheck_CheckValidity_tests.cpp128
-rw-r--r--security/nss/gtests/mozpkix_gtest/pkixcheck_ParseValidity_tests.cpp84
-rw-r--r--security/nss/gtests/mozpkix_gtest/pkixcheck_TLSFeaturesSatisfiedInternal_tests.cpp120
-rw-r--r--security/nss/gtests/mozpkix_gtest/pkixder_input_tests.cpp920
-rw-r--r--security/nss/gtests/mozpkix_gtest/pkixder_pki_types_tests.cpp480
-rw-r--r--security/nss/gtests/mozpkix_gtest/pkixder_universal_types_tests.cpp1226
-rw-r--r--security/nss/gtests/mozpkix_gtest/pkixgtest.cpp46
-rw-r--r--security/nss/gtests/mozpkix_gtest/pkixgtest.h229
-rw-r--r--security/nss/gtests/mozpkix_gtest/pkixnames_tests.cpp2838
-rw-r--r--security/nss/gtests/mozpkix_gtest/pkixocsp_CreateEncodedOCSPRequest_tests.cpp146
-rw-r--r--security/nss/gtests/mozpkix_gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp1064
20 files changed, 10278 insertions, 0 deletions
diff --git a/security/nss/gtests/mozpkix_gtest/README.txt b/security/nss/gtests/mozpkix_gtest/README.txt
new file mode 100644
index 000000000..5d3484a21
--- /dev/null
+++ b/security/nss/gtests/mozpkix_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/nss/gtests/mozpkix_gtest/mozpkix_gtest.gyp b/security/nss/gtests/mozpkix_gtest/mozpkix_gtest.gyp
new file mode 100644
index 000000000..899b849fc
--- /dev/null
+++ b/security/nss/gtests/mozpkix_gtest/mozpkix_gtest.gyp
@@ -0,0 +1,71 @@
+# 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/.
+{
+ 'includes': [
+ '../../coreconf/config.gypi',
+ '../common/gtest.gypi',
+ ],
+ 'targets': [
+ {
+ 'target_name': 'mozpkix_gtest',
+ 'type': 'executable',
+ 'sources': [
+ '<(DEPTH)/gtests/common/gtests.cc',
+ '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',
+ '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',
+ ],
+ 'dependencies': [
+ '<(DEPTH)/exports.gyp:nss_exports',
+ '<(DEPTH)/gtests/google_test/google_test.gyp:gtest',
+ '<(DEPTH)/lib/util/util.gyp:nssutil',
+ '<(DEPTH)/lib/ssl/ssl.gyp:ssl',
+ '<(DEPTH)/lib/nss/nss.gyp:nss_static',
+ '<(DEPTH)/lib/pk11wrap/pk11wrap.gyp:pk11wrap_static',
+ '<(DEPTH)/lib/cryptohi/cryptohi.gyp:cryptohi',
+ '<(DEPTH)/lib/certhigh/certhigh.gyp:certhi',
+ '<(DEPTH)/lib/certdb/certdb.gyp:certdb',
+ '<(DEPTH)/lib/base/base.gyp:nssb',
+ '<(DEPTH)/lib/dev/dev.gyp:nssdev',
+ '<(DEPTH)/lib/pki/pki.gyp:nsspki',
+ '<(DEPTH)/lib/mozpkix/mozpkix.gyp:mozpkix',
+ '<(DEPTH)/lib/mozpkix/mozpkix.gyp:mozpkix-testlib',
+ ],
+ 'include_dirs': [
+ '<(DEPTH)/lib/mozpkix/',
+ '<(DEPTH)/lib/mozpkix/lib',
+ '<(DEPTH)/lib/mozpkix/include/',
+ '<(DEPTH)/lib/mozpkix/include/pkix-test/',
+ ],
+ 'conditions': [
+ [ 'OS=="win"', {
+ 'libraries': [
+ 'advapi32.lib',
+ ],
+ }],
+ ],
+ 'defines': [
+ 'NSS_USE_STATIC_LIBS'
+ ],
+ }
+ ],
+ 'variables': {
+ 'module': 'nss',
+ 'use_static_libs': 1,
+ }
+}
diff --git a/security/nss/gtests/mozpkix_gtest/pkixbuild_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixbuild_tests.cpp
new file mode 100644
index 000000000..e17321075
--- /dev/null
+++ b/security/nss/gtests/mozpkix_gtest/pkixbuild_tests.cpp
@@ -0,0 +1,894 @@
+/* -*- 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 "pkixgtest.h"
+
+#include "mozpkix/pkixder.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,
+ /*optional*/ const TestKeyPair* issuerKeyPair = nullptr,
+ /*optional*/ const TestKeyPair* subjectKeyPair = 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,
+ subjectKeyPair ? *subjectKeyPair : *reusedKey,
+ extensions.data(),
+ issuerKeyPair ? *issuerKeyPair : *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, const CertPolicyId&) 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 aRootDER)
+ : rootDER(aRootDER)
+ {
+ }
+
+ // 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, const CertPolicyId&) 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 aRootDER)
+ : SingleRootTrustDomain(aRootDER)
+ {
+ }
+
+ 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& aIssuer, bool aExpectedKeepGoing)
+ : issuer(aIssuer)
+ , expectedKeepGoing(aExpectedKeepGoing)
+ {
+ }
+
+ 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, const CertPolicyId&) 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 aRootDER)
+ : SingleRootTrustDomain(aRootDER)
+ {
+ }
+
+ 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);
+}
+
+// This TrustDomain implements a hierarchy like so:
+//
+// A B
+// | |
+// C D
+// \ /
+// E
+//
+// where A is a trust anchor, B is not a trust anchor and has no known issuer, C
+// and D are intermediates with the same subject and subject public key, and E
+// is an end-entity (in practice, the end-entity will be generated by the test
+// functions using this trust domain).
+class MultiplePathTrustDomain: public DefaultCryptoTrustDomain
+{
+public:
+ void SetUpCerts()
+ {
+ ASSERT_FALSE(ENCODING_FAILED(CreateCert("UntrustedRoot", "UntrustedRoot",
+ EndEntityOrCA::MustBeCA,
+ &subjectDERToCertDER)));
+ // The subject DER -> cert DER mapping would be overwritten for subject
+ // "Intermediate" when we create the second "Intermediate" certificate, so
+ // we keep a copy of this "Intermediate".
+ intermediateSignedByUntrustedRootCertDER =
+ CreateCert("UntrustedRoot", "Intermediate", EndEntityOrCA::MustBeCA);
+ ASSERT_FALSE(ENCODING_FAILED(intermediateSignedByUntrustedRootCertDER));
+ rootCACertDER = CreateCert("TrustedRoot", "TrustedRoot",
+ EndEntityOrCA::MustBeCA, &subjectDERToCertDER);
+ ASSERT_FALSE(ENCODING_FAILED(rootCACertDER));
+ ASSERT_FALSE(ENCODING_FAILED(CreateCert("TrustedRoot", "Intermediate",
+ EndEntityOrCA::MustBeCA,
+ &subjectDERToCertDER)));
+ }
+
+private:
+ Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert,
+ /*out*/ TrustLevel& trustLevel) override
+ {
+ trustLevel = InputEqualsByteString(candidateCert, rootCACertDER)
+ ? TrustLevel::TrustAnchor
+ : TrustLevel::InheritsTrust;
+ return Success;
+ }
+
+ Result CheckCert(ByteString& certDER, IssuerChecker& checker, bool& keepGoing)
+ {
+ Input derCert;
+ Result rv = derCert.Init(certDER.data(), certDER.length());
+ if (rv != Success) {
+ return rv;
+ }
+ return checker.Check(derCert, nullptr/*additionalNameConstraints*/,
+ keepGoing);
+ }
+
+ Result FindIssuer(Input encodedIssuerName, IssuerChecker& checker, Time)
+ override
+ {
+ ByteString subjectDER(InputToByteString(encodedIssuerName));
+ ByteString certDER(subjectDERToCertDER[subjectDER]);
+ assert(!ENCODING_FAILED(certDER));
+ bool keepGoing;
+ Result rv = CheckCert(certDER, checker, keepGoing);
+ if (rv != Success) {
+ return rv;
+ }
+ // Also try the other intermediate.
+ if (keepGoing) {
+ rv = CheckCert(intermediateSignedByUntrustedRootCertDER, checker,
+ 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, const CertPolicyId&) override
+ {
+ return Success;
+ }
+
+ std::map<ByteString, ByteString> subjectDERToCertDER;
+ ByteString rootCACertDER;
+ ByteString intermediateSignedByUntrustedRootCertDER;
+};
+
+TEST_F(pkixbuild, BadEmbeddedSCTWithMultiplePaths)
+{
+ MultiplePathTrustDomain localTrustDomain;
+ localTrustDomain.SetUpCerts();
+
+ // 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) +
+ // The contents of the OCTET STRING are supposed to consist of an OCTET
+ // STRING of useful data. We're testing what happens if it isn't, so shove
+ // some bogus (non-OCTET STRING) data in there.
+ TLV(der::OCTET_STRING, BytesToByteString(dummySctList)));
+ ByteString certDER(CreateCert("Intermediate", "Cert with bogus SCT list",
+ EndEntityOrCA::MustBeEndEntity,
+ nullptr, /*subjectDERToCertDER*/
+ &ctExtension));
+ ASSERT_FALSE(ENCODING_FAILED(certDER));
+ Input certDERInput;
+ ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length()));
+ ASSERT_EQ(Result::ERROR_BAD_DER,
+ BuildCertChain(localTrustDomain, certDERInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+}
+
+// Same as a MultiplePathTrustDomain, but the end-entity is revoked.
+class RevokedEndEntityTrustDomain final : public MultiplePathTrustDomain
+{
+public:
+ Result CheckRevocation(EndEntityOrCA endEntityOrCA, const CertID&, Time,
+ Duration, /*optional*/ const Input*,
+ /*optional*/ const Input*) override
+ {
+ if (endEntityOrCA == EndEntityOrCA::MustBeEndEntity) {
+ return Result::ERROR_REVOKED_CERTIFICATE;
+ }
+ return Success;
+ }
+};
+
+TEST_F(pkixbuild, RevokedEndEntityWithMultiplePaths)
+{
+ RevokedEndEntityTrustDomain localTrustDomain;
+ localTrustDomain.SetUpCerts();
+ ByteString certDER(CreateCert("Intermediate", "RevokedEndEntity",
+ EndEntityOrCA::MustBeEndEntity));
+ ASSERT_FALSE(ENCODING_FAILED(certDER));
+ Input certDERInput;
+ ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length()));
+ ASSERT_EQ(Result::ERROR_REVOKED_CERTIFICATE,
+ BuildCertChain(localTrustDomain, certDERInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+}
+
+// This represents a collection of different certificates that all have the same
+// subject and issuer distinguished name.
+class SelfIssuedCertificatesTrustDomain final : public DefaultCryptoTrustDomain
+{
+public:
+ void SetUpCerts(size_t totalCerts)
+ {
+ ASSERT_TRUE(totalCerts > 0);
+ // First we generate a trust anchor.
+ ScopedTestKeyPair rootKeyPair(GenerateKeyPair());
+ rootCACertDER = CreateCert("DN", "DN", EndEntityOrCA::MustBeCA, nullptr,
+ nullptr, rootKeyPair.get(), rootKeyPair.get());
+ ASSERT_FALSE(ENCODING_FAILED(rootCACertDER));
+ certs.push_back(rootCACertDER);
+ ScopedTestKeyPair issuerKeyPair(rootKeyPair.release());
+ size_t subCAsGenerated;
+ // Then we generate 6 sub-CAs (given that we were requested to generate at
+ // least that many).
+ for (subCAsGenerated = 0;
+ subCAsGenerated < totalCerts - 1 && subCAsGenerated < 6;
+ subCAsGenerated++) {
+ // Each certificate has to have a unique SPKI (mozilla::pkix does loop
+ // detection and stops searching if it encounters two certificates in a
+ // path with the same subject and SPKI).
+ ScopedTestKeyPair keyPair(GenerateKeyPair());
+ ByteString cert(CreateCert("DN", "DN", EndEntityOrCA::MustBeCA, nullptr,
+ nullptr, issuerKeyPair.get(), keyPair.get()));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ certs.push_back(cert);
+ issuerKeyPair.reset(keyPair.release());
+ }
+ // We set firstIssuerKey here because we can't end up with a path that has
+ // more than 7 CAs in it (because mozilla::pkix limits the path length).
+ firstIssuerKey.reset(issuerKeyPair.release());
+ // For any more sub CAs we generate, it doesn't matter what their keys are
+ // as long as they're different.
+ for (; subCAsGenerated < totalCerts - 1; subCAsGenerated++) {
+ ScopedTestKeyPair keyPair(GenerateKeyPair());
+ ByteString cert(CreateCert("DN", "DN", EndEntityOrCA::MustBeCA, nullptr,
+ nullptr, keyPair.get(), keyPair.get()));
+ ASSERT_FALSE(ENCODING_FAILED(cert));
+ certs.insert(certs.begin(), cert);
+ }
+ }
+
+ const TestKeyPair* GetFirstIssuerKey()
+ {
+ return firstIssuerKey.get();
+ }
+
+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, IssuerChecker& checker, Time) override
+ {
+ bool keepGoing;
+ for (auto& cert: certs) {
+ Input certInput;
+ Result rv = certInput.Init(cert.data(), cert.length());
+ if (rv != Success) {
+ return rv;
+ }
+ rv = checker.Check(certInput, nullptr, keepGoing);
+ if (rv != Success || !keepGoing) {
+ 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, const CertPolicyId&) override
+ {
+ return Success;
+ }
+
+ std::vector<ByteString> certs;
+ ByteString rootCACertDER;
+ ScopedTestKeyPair firstIssuerKey;
+};
+
+TEST_F(pkixbuild, AvoidUnboundedPathSearchingFailure)
+{
+ SelfIssuedCertificatesTrustDomain localTrustDomain;
+ // This creates a few hundred million potential paths of length 8 (end entity
+ // + 6 sub-CAs + root). It would be prohibitively expensive to enumerate all
+ // of these, so we give mozilla::pkix a budget that is spent when searching
+ // paths. If the budget is exhausted, it simply returns an unknown issuer
+ // error. In the future it might be nice to return a specific error that would
+ // give the front-end a hint that maybe it shouldn't have so many certificates
+ // that all have the same subject and issuer DN but different SPKIs.
+ localTrustDomain.SetUpCerts(18);
+ ByteString certDER(CreateCert("DN", "DN", EndEntityOrCA::MustBeEndEntity,
+ nullptr, nullptr,
+ localTrustDomain.GetFirstIssuerKey()));
+ ASSERT_FALSE(ENCODING_FAILED(certDER));
+ Input certDERInput;
+ ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length()));
+ ASSERT_EQ(Result::ERROR_UNKNOWN_ISSUER,
+ BuildCertChain(localTrustDomain, certDERInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+}
+
+TEST_F(pkixbuild, AvoidUnboundedPathSearchingSuccess)
+{
+ SelfIssuedCertificatesTrustDomain localTrustDomain;
+ // This creates a few hundred thousand possible potential paths of length 8
+ // (end entity + 6 sub-CAs + root). This will nearly exhaust mozilla::pkix's
+ // search budget, so this should succeed.
+ localTrustDomain.SetUpCerts(10);
+ ByteString certDER(CreateCert("DN", "DN", EndEntityOrCA::MustBeEndEntity,
+ nullptr, nullptr,
+ localTrustDomain.GetFirstIssuerKey()));
+ ASSERT_FALSE(ENCODING_FAILED(certDER));
+ Input certDERInput;
+ ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length()));
+ ASSERT_EQ(Success,
+ BuildCertChain(localTrustDomain, certDERInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::id_kp_serverAuth,
+ CertPolicyId::anyPolicy,
+ nullptr/*stapledOCSPResponse*/));
+}
diff --git a/security/nss/gtests/mozpkix_gtest/pkixcert_extension_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixcert_extension_tests.cpp
new file mode 100644
index 000000000..762fac146
--- /dev/null
+++ b/security/nss/gtests/mozpkix_gtest/pkixcert_extension_tests.cpp
@@ -0,0 +1,276 @@
+/* -*- 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 "mozpkix/pkixder.h"
+#include "mozpkix/test/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, const CertPolicyId&) 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;
+};
+
+::std::ostream& operator<<(::std::ostream& os, const ExtensionTestcase&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+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/nss/gtests/mozpkix_gtest/pkixcert_signature_algorithm_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixcert_signature_algorithm_tests.cpp
new file mode 100644
index 000000000..00ccffb04
--- /dev/null
+++ b/security/nss/gtests/mozpkix_gtest/pkixcert_signature_algorithm_tests.cpp
@@ -0,0 +1,259 @@
+/* -*- 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 "pkixgtest.h"
+
+#include "mozpkix/pkixder.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& aRootDER,
+ const ByteString& aRootSubjectDER,
+ /*optional*/ const ByteString& aIntDER,
+ /*optional*/ const ByteString& aIntSubjectDER)
+ : rootDER(aRootDER)
+ , rootSubjectDER(aRootSubjectDER)
+ , intDER(aIntDER)
+ , intSubjectDER(aIntSubjectDER)
+ {
+ }
+
+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, const CertPolicyId&) 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& aEndEntitySignatureAlgorithm,
+ const TestSignatureAlgorithm& aOptionalIntSignatureAlgorithm,
+ const TestSignatureAlgorithm& aRootSignatureAlgorithm,
+ bool aIsValid)
+ : endEntitySignatureAlgorithm(aEndEntitySignatureAlgorithm)
+ , optionalIntermediateSignatureAlgorithm(aOptionalIntSignatureAlgorithm)
+ , rootSignatureAlgorithm(aRootSignatureAlgorithm)
+ , isValid(aIsValid)
+ { }
+
+ // 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>
+{
+};
+
+::std::ostream& operator<<(::std::ostream& os,
+ const pkixcert_IsValidChainForAlgorithm&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+::std::ostream& operator<<(::std::ostream& os, const ChainValidity&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+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/nss/gtests/mozpkix_gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp
new file mode 100644
index 000000000..0aef3d5c1
--- /dev/null
+++ b/security/nss/gtests/mozpkix_gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp
@@ -0,0 +1,722 @@
+/* -*- 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 "pkixgtest.h"
+
+#include "mozpkix/pkixder.h"
+#include "mozpkix/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;
+};
+
+::std::ostream& operator<<(::std::ostream& os, const EKUTestcase&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+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;
+};
+
+::std::ostream& operator<<(::std::ostream& os, const EKUChainTestcase&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+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, const CertPolicyId&) 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/nss/gtests/mozpkix_gtest/pkixcheck_CheckIssuer_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixcheck_CheckIssuer_tests.cpp
new file mode 100644
index 000000000..bcc2c1198
--- /dev/null
+++ b/security/nss/gtests/mozpkix_gtest/pkixcheck_CheckIssuer_tests.cpp
@@ -0,0 +1,63 @@
+/* -*- 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 "pkixgtest.h"
+
+#include "mozpkix/pkixcheck.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/nss/gtests/mozpkix_gtest/pkixcheck_CheckKeyUsage_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixcheck_CheckKeyUsage_tests.cpp
new file mode 100644
index 000000000..136f8719a
--- /dev/null
+++ b/security/nss/gtests/mozpkix_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/nss/gtests/mozpkix_gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp
new file mode 100644
index 000000000..70e6fd410
--- /dev/null
+++ b/security/nss/gtests/mozpkix_gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp
@@ -0,0 +1,367 @@
+/* -*- 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 "pkixgtest.h"
+
+#include "mozpkix/pkixder.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;
+};
+
+::std::ostream& operator<<(::std::ostream& os,
+ const CheckSignatureAlgorithmTestParams&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+#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 aPublicKeySizeInBits)
+ : publicKeySizeInBits(aPublicKeySizeInBits)
+ , 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& aIssuer)
+ : issuer(aIssuer)
+ {
+ }
+
+ 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, const CertPolicyId&) 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/nss/gtests/mozpkix_gtest/pkixcheck_CheckValidity_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixcheck_CheckValidity_tests.cpp
new file mode 100644
index 000000000..a1a6f998b
--- /dev/null
+++ b/security/nss/gtests/mozpkix_gtest/pkixcheck_CheckValidity_tests.cpp
@@ -0,0 +1,128 @@
+/* -*- 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 "pkixgtest.h"
+
+#include "mozpkix/pkixcheck.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/nss/gtests/mozpkix_gtest/pkixcheck_ParseValidity_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixcheck_ParseValidity_tests.cpp
new file mode 100644
index 000000000..7255bb5df
--- /dev/null
+++ b/security/nss/gtests/mozpkix_gtest/pkixcheck_ParseValidity_tests.cpp
@@ -0,0 +1,84 @@
+/* -*- 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 "pkixgtest.h"
+
+#include "mozpkix/pkixcheck.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/nss/gtests/mozpkix_gtest/pkixcheck_TLSFeaturesSatisfiedInternal_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixcheck_TLSFeaturesSatisfiedInternal_tests.cpp
new file mode 100644
index 000000000..b7809cc60
--- /dev/null
+++ b/security/nss/gtests/mozpkix_gtest/pkixcheck_TLSFeaturesSatisfiedInternal_tests.cpp
@@ -0,0 +1,120 @@
+/* -*- 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 "pkixgtest.h"
+
+#include "mozpkix/pkixder.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;
+};
+
+::std::ostream& operator<<(::std::ostream& os, const TLSFeaturesTestParams&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+#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/nss/gtests/mozpkix_gtest/pkixder_input_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixder_input_tests.cpp
new file mode 100644
index 000000000..cf91fa2c6
--- /dev/null
+++ b/security/nss/gtests/mozpkix_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 "mozpkix/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/nss/gtests/mozpkix_gtest/pkixder_pki_types_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixder_pki_types_tests.cpp
new file mode 100644
index 000000000..989f3d296
--- /dev/null
+++ b/security/nss/gtests/mozpkix_gtest/pkixder_pki_types_tests.cpp
@@ -0,0 +1,480 @@
+/* -*- 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 "mozpkix/pkixtypes.h"
+#include "mozpkix/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/nss/gtests/mozpkix_gtest/pkixder_universal_types_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixder_universal_types_tests.cpp
new file mode 100644
index 000000000..260c735ec
--- /dev/null
+++ b/security/nss/gtests/mozpkix_gtest/pkixder_universal_types_tests.cpp
@@ -0,0 +1,1226 @@
+/* -*- 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 "pkixgtest.h"
+
+#include "mozpkix/pkixder.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>
+{
+};
+
+::std::ostream& operator<<(::std::ostream& os, const IntegerTestParams&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+#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/nss/gtests/mozpkix_gtest/pkixgtest.cpp b/security/nss/gtests/mozpkix_gtest/pkixgtest.cpp
new file mode 100644
index 000000000..45932731b
--- /dev/null
+++ b/security/nss/gtests/mozpkix_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 "mozpkix/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/nss/gtests/mozpkix_gtest/pkixgtest.h b/security/nss/gtests/mozpkix_gtest/pkixgtest.h
new file mode 100644
index 000000000..bb3491d44
--- /dev/null
+++ b/security/nss/gtests/mozpkix_gtest/pkixgtest.h
@@ -0,0 +1,229 @@
+/* -*- 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 "mozpkix/pkix.h"
+#include "mozpkix/test/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, const CertPolicyId&) 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/nss/gtests/mozpkix_gtest/pkixnames_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixnames_tests.cpp
new file mode 100644
index 000000000..2169db9db
--- /dev/null
+++ b/security/nss/gtests/mozpkix_gtest/pkixnames_tests.cpp
@@ -0,0 +1,2838 @@
+/* -*- 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 "pkixgtest.h"
+
+#include "mozpkix/pkixcheck.h"
+#include "mozpkix/pkixder.h"
+#include "mozpkix/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
+};
+
+::std::ostream& operator<<(::std::ostream& os, const PresentedMatchesReference&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+#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;
+};
+
+::std::ostream& operator<<(::std::ostream& os, const InputValidity&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+// 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];
+};
+
+template <unsigned int L>
+::std::ostream& operator<<(::std::ostream& os, const IPAddressParams<L>&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+#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;
+};
+
+::std::ostream& operator<<(::std::ostream& os, const CheckCertHostnameParams&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+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;
+};
+
+::std::ostream& operator<<(::std::ostream& os, const NameConstraintParams&)
+{
+ return os << "TODO (bug 1318770)";
+}
+
+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/nss/gtests/mozpkix_gtest/pkixocsp_CreateEncodedOCSPRequest_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixocsp_CreateEncodedOCSPRequest_tests.cpp
new file mode 100644
index 000000000..ff154e7ec
--- /dev/null
+++ b/security/nss/gtests/mozpkix_gtest/pkixocsp_CreateEncodedOCSPRequest_tests.cpp
@@ -0,0 +1,146 @@
+/* -*- 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 "mozpkix/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/nss/gtests/mozpkix_gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp b/security/nss/gtests/mozpkix_gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp
new file mode 100644
index 000000000..3fe4e7b5a
--- /dev/null
+++ b/security/nss/gtests/mozpkix_gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp
@@ -0,0 +1,1064 @@
+/* -*- 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 "pkixgtest.h"
+
+#include "mozpkix/pkixder.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";
+} // 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.
+ ScopedCertID 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";
+ static const long zero = 0;
+
+ // sub-CA of root (root is the direct issuer of endEntity)
+ const ByteString subCAExtensions[] = {
+ CreateEncodedBasicConstraints(true, &zero, 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";
+ static const long zero = 0;
+
+ // sub-CA of root (root is the direct issuer of endEntity)
+ const ByteString subCAExtensions[] = {
+ CreateEncodedBasicConstraints(true, &zero, 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& aCertDER, TrustLevel aCertTrustLevel)
+ {
+ this->certDER = aCertDER;
+ this->certTrustLevel = aCertTrustLevel;
+ 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 deliberately shadows the inherited field so that it isn't used
+// by accident. See bug 1339921.
+// Unfortunately GCC can't parse __has_warning("-Wshadow-field") even if it's
+// the latter part of a conjunction that would evaluate to false, so we have to
+// wrap it in a separate preprocessor conditional rather than using &&.
+#if defined(__clang__)
+ #if __has_warning("-Wshadow-field")
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wshadow-field"
+ #endif
+#endif
+ TrustDomain trustDomain;
+#if defined(__clang__)
+ #if __has_warning("-Wshadow-field")
+ #pragma clang diagnostic pop
+ #endif
+#endif
+ 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);
+}