diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /security/pkix/test | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'security/pkix/test')
25 files changed, 12050 insertions, 0 deletions
diff --git a/security/pkix/test/gtest/README.txt b/security/pkix/test/gtest/README.txt new file mode 100644 index 000000000..5d3484a21 --- /dev/null +++ b/security/pkix/test/gtest/README.txt @@ -0,0 +1,61 @@ +------------- +Running Tests +------------- + +Because of the rules below, you can run all the unit tests in this directory, +and only these tests, with: + + mach gtest "pkix*" + +You can run just the tests for functions defined in filename pkixfoo.cpp with: + + mach gtest "pkixfoo*" + +If you run "mach gtest" then you'll end up running every gtest in Gecko. + + + +------------ +Naming Files +------------ + +Name files containing tests according to one of the following patterns: + + * <filename>_tests.cpp + * <filename>_<Function>_tests.cpp + * <filename>_<category>_tests.cpp + + <filename> is the name of the file containing the definitions of the + function(s) being tested by every test. + <Function> is the name of the function that is being tested by every + test. + <category> describes the group of related functions that are being + tested by every test. + + + +------------------------------------------------ +Always Use a Fixture Class: TEST_F(), not TEST() +------------------------------------------------ + +Many tests don't technically need a fixture, and so TEST() could technically +be used to define the test. However, when you use TEST_F() instead of TEST(), +the compiler will not allow you to make any typos in the test case name, but +if you use TEST() then the name of the test case is not checked. + +See https://code.google.com/p/googletest/wiki/Primer#Test_Fixtures:_Using_the_Same_Data_Configuration_for_Multiple_Te +to learn more about test fixtures. + +--------------- +Naming Fixtures +--------------- + +When all tests in a file use the same fixture, use the base name of the file +without the "_tests" suffix as the name of the fixture class; e.g. tests in +"pkixocsp.cpp" should use a fixture "class pkixocsp" by default. + +Sometimes tests in a file need separate fixtures. In this case, name the +fixture class according to the pattern <fixture_base>_<fixture_suffix>, where +<fixture_base> is the base name of the file without the "_tests" suffix, and +<fixture_suffix> is a descriptive name for the fixture class, e.g. +"class pkixocsp_DelegatedResponder". diff --git a/security/pkix/test/gtest/moz.build b/security/pkix/test/gtest/moz.build new file mode 100644 index 000000000..50d7ae966 --- /dev/null +++ b/security/pkix/test/gtest/moz.build @@ -0,0 +1,66 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SOURCES += [ + 'pkixbuild_tests.cpp', + 'pkixcert_extension_tests.cpp', + 'pkixcert_signature_algorithm_tests.cpp', + 'pkixcheck_CheckExtendedKeyUsage_tests.cpp', + 'pkixcheck_CheckIssuer_tests.cpp', + 'pkixcheck_CheckKeyUsage_tests.cpp', + 'pkixcheck_CheckSignatureAlgorithm_tests.cpp', + 'pkixcheck_CheckValidity_tests.cpp', + 'pkixcheck_ParseValidity_tests.cpp', + 'pkixcheck_TLSFeaturesSatisfiedInternal_tests.cpp', + + # The naming conventions are described in ./README.txt. + + 'pkixder_input_tests.cpp', + 'pkixder_pki_types_tests.cpp', + 'pkixder_universal_types_tests.cpp', + 'pkixgtest.cpp', + 'pkixnames_tests.cpp', + 'pkixocsp_CreateEncodedOCSPRequest_tests.cpp', + 'pkixocsp_VerifyEncodedOCSPResponse.cpp', +] + +LOCAL_INCLUDES += [ + '../../include', + '../../lib', + '../lib', +] + +FINAL_LIBRARY = 'xul-gtest' + +include('../../warnings.mozbuild') + +# These warnings are disabled in order to minimize the amount of boilerplate +# required to implement tests, and/or because they originate in the GTest +# framework in a way we cannot otherwise work around. +if CONFIG['GNU_CXX']: + CXXFLAGS += [ + '-Wno-error=shadow', + '-Wno-old-style-cast', + ] + if CONFIG['CLANG_CXX']: + CXXFLAGS += [ + '-Wno-exit-time-destructors', + '-Wno-global-constructors', + '-Wno-used-but-marked-unused', + ] +elif CONFIG['_MSC_VER']: + CXXFLAGS += [ + '-wd4350', # behavior change: 'std::_Wrap_alloc<std::allocator<_Ty>>::... + '-wd4275', # non dll-interface class used as base for dll-interface class + '-wd4548', # Expression before comma has no effect + '-wd4625', # copy constructor could not be generated. + '-wd4626', # assugment operator could not be generated. + '-wd4640', # construction of local static object is not thread safe. + + # This is intended as a temporary hack to support building with VS2015. + # declaration of '*' hides class member + '-wd4458', + ] diff --git a/security/pkix/test/gtest/pkixbuild_tests.cpp b/security/pkix/test/gtest/pkixbuild_tests.cpp new file mode 100644 index 000000000..6ddd88823 --- /dev/null +++ b/security/pkix/test/gtest/pkixbuild_tests.cpp @@ -0,0 +1,584 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2013 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1900 +// When building with -D_HAS_EXCEPTIONS=0, MSVC's <xtree> header triggers +// warning C4702: unreachable code. +// https://connect.microsoft.com/VisualStudio/feedback/details/809962 +#pragma warning(push) +#pragma warning(disable: 4702) +#endif + +#include <map> +#include <vector> + +#if defined(_MSC_VER) && _MSC_VER < 1900 +#pragma warning(pop) +#endif + +#include "pkixder.h" +#include "pkixgtest.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +static ByteString +CreateCert(const char* issuerCN, // null means "empty name" + const char* subjectCN, // null means "empty name" + EndEntityOrCA endEntityOrCA, + /*optional modified*/ std::map<ByteString, ByteString>* + subjectDERToCertDER = nullptr, + /*optional*/ const ByteString* extension = nullptr) +{ + static long serialNumberValue = 0; + ++serialNumberValue; + ByteString serialNumber(CreateEncodedSerialNumber(serialNumberValue)); + EXPECT_FALSE(ENCODING_FAILED(serialNumber)); + + ByteString issuerDER(issuerCN ? CNToDERName(issuerCN) : Name(ByteString())); + ByteString subjectDER(subjectCN ? CNToDERName(subjectCN) : Name(ByteString())); + + std::vector<ByteString> extensions; + if (endEntityOrCA == EndEntityOrCA::MustBeCA) { + ByteString basicConstraints = + CreateEncodedBasicConstraints(true, nullptr, Critical::Yes); + EXPECT_FALSE(ENCODING_FAILED(basicConstraints)); + extensions.push_back(basicConstraints); + } + if (extension) { + extensions.push_back(*extension); + } + extensions.push_back(ByteString()); // marks the end of the list + + ScopedTestKeyPair reusedKey(CloneReusedKeyPair()); + ByteString certDER(CreateEncodedCertificate( + v3, sha256WithRSAEncryption(), serialNumber, issuerDER, + oneDayBeforeNow, oneDayAfterNow, subjectDER, + *reusedKey, extensions.data(), *reusedKey, + sha256WithRSAEncryption())); + EXPECT_FALSE(ENCODING_FAILED(certDER)); + + if (subjectDERToCertDER) { + (*subjectDERToCertDER)[subjectDER] = certDER; + } + + return certDER; +} + +class TestTrustDomain final : public DefaultCryptoTrustDomain +{ +public: + // The "cert chain tail" is a longish chain of certificates that is used by + // all of the tests here. We share this chain across all the tests in order + // to speed up the tests (generating keypairs for the certs is very slow). + bool SetUpCertChainTail() + { + static char const* const names[] = { + "CA1 (Root)", "CA2", "CA3", "CA4", "CA5", "CA6", "CA7" + }; + + for (size_t i = 0; i < MOZILLA_PKIX_ARRAY_LENGTH(names); ++i) { + const char* issuerName = i == 0 ? names[0] : names[i-1]; + CreateCACert(issuerName, names[i]); + if (i == 0) { + rootCACertDER = leafCACertDER; + } + } + + return true; + } + + void CreateCACert(const char* issuerName, const char* subjectName) + { + leafCACertDER = CreateCert(issuerName, subjectName, + EndEntityOrCA::MustBeCA, &subjectDERToCertDER); + assert(!ENCODING_FAILED(leafCACertDER)); + } + + ByteString GetLeafCACertDER() const { return leafCACertDER; } + +private: + Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert, + /*out*/ TrustLevel& trustLevel) override + { + trustLevel = InputEqualsByteString(candidateCert, rootCACertDER) + ? TrustLevel::TrustAnchor + : TrustLevel::InheritsTrust; + return Success; + } + + Result FindIssuer(Input encodedIssuerName, IssuerChecker& checker, Time) + override + { + ByteString subjectDER(InputToByteString(encodedIssuerName)); + ByteString certDER(subjectDERToCertDER[subjectDER]); + Input derCert; + Result rv = derCert.Init(certDER.data(), certDER.length()); + if (rv != Success) { + return rv; + } + bool keepGoing; + rv = checker.Check(derCert, nullptr/*additionalNameConstraints*/, + keepGoing); + if (rv != Success) { + return rv; + } + return Success; + } + + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, + /*optional*/ const Input*, /*optional*/ const Input*) + override + { + return Success; + } + + Result IsChainValid(const DERArray&, Time) override + { + return Success; + } + + std::map<ByteString, ByteString> subjectDERToCertDER; + ByteString leafCACertDER; + ByteString rootCACertDER; +}; + +class pkixbuild : public ::testing::Test +{ +public: + static void SetUpTestCase() + { + if (!trustDomain.SetUpCertChainTail()) { + abort(); + } + } + +protected: + + static TestTrustDomain trustDomain; +}; + +/*static*/ TestTrustDomain pkixbuild::trustDomain; + +TEST_F(pkixbuild, MaxAcceptableCertChainLength) +{ + { + ByteString leafCACert(trustDomain.GetLeafCACertDER()); + Input certDER; + ASSERT_EQ(Success, certDER.Init(leafCACert.data(), leafCACert.length())); + ASSERT_EQ(Success, + BuildCertChain(trustDomain, certDER, Now(), + EndEntityOrCA::MustBeCA, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::id_kp_serverAuth, + CertPolicyId::anyPolicy, + nullptr/*stapledOCSPResponse*/)); + } + + { + ByteString certDER(CreateCert("CA7", "Direct End-Entity", + EndEntityOrCA::MustBeEndEntity)); + ASSERT_FALSE(ENCODING_FAILED(certDER)); + Input certDERInput; + ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length())); + ASSERT_EQ(Success, + BuildCertChain(trustDomain, certDERInput, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::id_kp_serverAuth, + CertPolicyId::anyPolicy, + nullptr/*stapledOCSPResponse*/)); + } +} + +TEST_F(pkixbuild, BeyondMaxAcceptableCertChainLength) +{ + static char const* const caCertName = "CA Too Far"; + + trustDomain.CreateCACert("CA7", caCertName); + + { + ByteString certDER(trustDomain.GetLeafCACertDER()); + Input certDERInput; + ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length())); + ASSERT_EQ(Result::ERROR_UNKNOWN_ISSUER, + BuildCertChain(trustDomain, certDERInput, Now(), + EndEntityOrCA::MustBeCA, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::id_kp_serverAuth, + CertPolicyId::anyPolicy, + nullptr/*stapledOCSPResponse*/)); + } + + { + ByteString certDER(CreateCert(caCertName, "End-Entity Too Far", + EndEntityOrCA::MustBeEndEntity)); + ASSERT_FALSE(ENCODING_FAILED(certDER)); + Input certDERInput; + ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length())); + ASSERT_EQ(Result::ERROR_UNKNOWN_ISSUER, + BuildCertChain(trustDomain, certDERInput, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::id_kp_serverAuth, + CertPolicyId::anyPolicy, + nullptr/*stapledOCSPResponse*/)); + } +} + +// A TrustDomain that checks certificates against a given root certificate. +// It is initialized with the DER encoding of a root certificate that +// is treated as a trust anchor and is assumed to have issued all certificates +// (i.e. FindIssuer always attempts to build the next step in the chain with +// it). +class SingleRootTrustDomain : public DefaultCryptoTrustDomain +{ +public: + explicit SingleRootTrustDomain(ByteString rootDER) + : rootDER(rootDER) + { + } + + // The CertPolicyId argument is unused because we don't care about EV. + Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert, + /*out*/ TrustLevel& trustLevel) override + { + Input rootCert; + Result rv = rootCert.Init(rootDER.data(), rootDER.length()); + if (rv != Success) { + return rv; + } + if (InputsAreEqual(candidateCert, rootCert)) { + trustLevel = TrustLevel::TrustAnchor; + } else { + trustLevel = TrustLevel::InheritsTrust; + } + return Success; + } + + Result FindIssuer(Input, IssuerChecker& checker, Time) override + { + // keepGoing is an out parameter from IssuerChecker.Check. It would tell us + // whether or not to continue attempting other potential issuers. We only + // know of one potential issuer, however, so we ignore it. + bool keepGoing; + Input rootCert; + Result rv = rootCert.Init(rootDER.data(), rootDER.length()); + if (rv != Success) { + return rv; + } + return checker.Check(rootCert, nullptr, keepGoing); + } + + Result IsChainValid(const DERArray&, Time) override + { + return Success; + } + + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, + /*optional*/ const Input*, /*optional*/ const Input*) + override + { + return Success; + } + +private: + ByteString rootDER; +}; + +// A TrustDomain that explicitly fails if CheckRevocation is called. +class ExpiredCertTrustDomain final : public SingleRootTrustDomain +{ +public: + explicit ExpiredCertTrustDomain(ByteString rootDER) + : SingleRootTrustDomain(rootDER) + { + } + + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, + /*optional*/ const Input*, /*optional*/ const Input*) + override + { + ADD_FAILURE(); + return NotReached("CheckRevocation should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } +}; + +TEST_F(pkixbuild, NoRevocationCheckingForExpiredCert) +{ + const char* rootCN = "Root CA"; + ByteString rootDER(CreateCert(rootCN, rootCN, EndEntityOrCA::MustBeCA, + nullptr)); + EXPECT_FALSE(ENCODING_FAILED(rootDER)); + ExpiredCertTrustDomain expiredCertTrustDomain(rootDER); + + ByteString serialNumber(CreateEncodedSerialNumber(100)); + EXPECT_FALSE(ENCODING_FAILED(serialNumber)); + ByteString issuerDER(CNToDERName(rootCN)); + ByteString subjectDER(CNToDERName("Expired End-Entity Cert")); + ScopedTestKeyPair reusedKey(CloneReusedKeyPair()); + ByteString certDER(CreateEncodedCertificate( + v3, sha256WithRSAEncryption(), + serialNumber, issuerDER, + twoDaysBeforeNow, + oneDayBeforeNow, + subjectDER, *reusedKey, nullptr, *reusedKey, + sha256WithRSAEncryption())); + EXPECT_FALSE(ENCODING_FAILED(certDER)); + + Input cert; + ASSERT_EQ(Success, cert.Init(certDER.data(), certDER.length())); + ASSERT_EQ(Result::ERROR_EXPIRED_CERTIFICATE, + BuildCertChain(expiredCertTrustDomain, cert, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::id_kp_serverAuth, + CertPolicyId::anyPolicy, + nullptr)); +} + +class DSSTrustDomain final : public EverythingFailsByDefaultTrustDomain +{ +public: + Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, + Input, /*out*/ TrustLevel& trustLevel) override + { + trustLevel = TrustLevel::TrustAnchor; + return Success; + } +}; + +class pkixbuild_DSS : public ::testing::Test { }; + +TEST_F(pkixbuild_DSS, DSSEndEntityKeyNotAccepted) +{ + DSSTrustDomain trustDomain; + + ByteString serialNumber(CreateEncodedSerialNumber(1)); + ASSERT_FALSE(ENCODING_FAILED(serialNumber)); + + ByteString subjectDER(CNToDERName("DSS")); + ASSERT_FALSE(ENCODING_FAILED(subjectDER)); + ScopedTestKeyPair subjectKey(GenerateDSSKeyPair()); + ASSERT_TRUE(subjectKey.get()); + + ByteString issuerDER(CNToDERName("RSA")); + ASSERT_FALSE(ENCODING_FAILED(issuerDER)); + ScopedTestKeyPair issuerKey(CloneReusedKeyPair()); + ASSERT_TRUE(issuerKey.get()); + + ByteString cert(CreateEncodedCertificate(v3, sha256WithRSAEncryption(), + serialNumber, issuerDER, + oneDayBeforeNow, oneDayAfterNow, + subjectDER, *subjectKey, nullptr, + *issuerKey, sha256WithRSAEncryption())); + ASSERT_FALSE(ENCODING_FAILED(cert)); + Input certDER; + ASSERT_EQ(Success, certDER.Init(cert.data(), cert.length())); + + ASSERT_EQ(Result::ERROR_UNSUPPORTED_KEYALG, + BuildCertChain(trustDomain, certDER, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::id_kp_serverAuth, + CertPolicyId::anyPolicy, + nullptr/*stapledOCSPResponse*/)); +} + +class IssuerNameCheckTrustDomain final : public DefaultCryptoTrustDomain +{ +public: + IssuerNameCheckTrustDomain(const ByteString& issuer, bool expectedKeepGoing) + : issuer(issuer) + , expectedKeepGoing(expectedKeepGoing) + { + } + + Result GetCertTrust(EndEntityOrCA endEntityOrCA, const CertPolicyId&, Input, + /*out*/ TrustLevel& trustLevel) override + { + trustLevel = endEntityOrCA == EndEntityOrCA::MustBeCA + ? TrustLevel::TrustAnchor + : TrustLevel::InheritsTrust; + return Success; + } + + Result FindIssuer(Input, IssuerChecker& checker, Time) override + { + Input issuerInput; + EXPECT_EQ(Success, issuerInput.Init(issuer.data(), issuer.length())); + bool keepGoing; + EXPECT_EQ(Success, + checker.Check(issuerInput, nullptr /*additionalNameConstraints*/, + keepGoing)); + EXPECT_EQ(expectedKeepGoing, keepGoing); + return Success; + } + + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, + /*optional*/ const Input*, /*optional*/ const Input*) + override + { + return Success; + } + + Result IsChainValid(const DERArray&, Time) override + { + return Success; + } + +private: + const ByteString issuer; + const bool expectedKeepGoing; +}; + +struct IssuerNameCheckParams +{ + const char* subjectIssuerCN; // null means "empty name" + const char* issuerSubjectCN; // null means "empty name" + bool matches; + Result expectedError; +}; + +static const IssuerNameCheckParams ISSUER_NAME_CHECK_PARAMS[] = +{ + { "foo", "foo", true, Success }, + { "foo", "bar", false, Result::ERROR_UNKNOWN_ISSUER }, + { "f", "foo", false, Result::ERROR_UNKNOWN_ISSUER }, // prefix + { "foo", "f", false, Result::ERROR_UNKNOWN_ISSUER }, // prefix + { "foo", "Foo", false, Result::ERROR_UNKNOWN_ISSUER }, // case sensitive + { "", "", true, Success }, + { nullptr, nullptr, false, Result::ERROR_EMPTY_ISSUER_NAME }, // empty issuer + + // check that certificate-related errors are deferred and superseded by + // ERROR_UNKNOWN_ISSUER when a chain can't be built due to name mismatches + { "foo", nullptr, false, Result::ERROR_UNKNOWN_ISSUER }, + { nullptr, "foo", false, Result::ERROR_UNKNOWN_ISSUER } +}; + +class pkixbuild_IssuerNameCheck + : public ::testing::Test + , public ::testing::WithParamInterface<IssuerNameCheckParams> +{ +}; + +TEST_P(pkixbuild_IssuerNameCheck, MatchingName) +{ + const IssuerNameCheckParams& params(GetParam()); + + ByteString issuerCertDER(CreateCert(params.issuerSubjectCN, + params.issuerSubjectCN, + EndEntityOrCA::MustBeCA, nullptr)); + ASSERT_FALSE(ENCODING_FAILED(issuerCertDER)); + + ByteString subjectCertDER(CreateCert(params.subjectIssuerCN, "end-entity", + EndEntityOrCA::MustBeEndEntity, + nullptr)); + ASSERT_FALSE(ENCODING_FAILED(subjectCertDER)); + + Input subjectCertDERInput; + ASSERT_EQ(Success, subjectCertDERInput.Init(subjectCertDER.data(), + subjectCertDER.length())); + + IssuerNameCheckTrustDomain trustDomain(issuerCertDER, !params.matches); + ASSERT_EQ(params.expectedError, + BuildCertChain(trustDomain, subjectCertDERInput, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::id_kp_serverAuth, + CertPolicyId::anyPolicy, + nullptr/*stapledOCSPResponse*/)); +} + +INSTANTIATE_TEST_CASE_P(pkixbuild_IssuerNameCheck, pkixbuild_IssuerNameCheck, + testing::ValuesIn(ISSUER_NAME_CHECK_PARAMS)); + + +// Records the embedded SCT list extension for later examination. +class EmbeddedSCTListTestTrustDomain final : public SingleRootTrustDomain +{ +public: + explicit EmbeddedSCTListTestTrustDomain(ByteString rootDER) + : SingleRootTrustDomain(rootDER) + { + } + + virtual void NoteAuxiliaryExtension(AuxiliaryExtension extension, + Input extensionData) override + { + if (extension == AuxiliaryExtension::EmbeddedSCTList) { + signedCertificateTimestamps = InputToByteString(extensionData); + } else { + ADD_FAILURE(); + } + } + + ByteString signedCertificateTimestamps; +}; + +TEST_F(pkixbuild, CertificateTransparencyExtension) +{ + // python security/pkix/tools/DottedOIDToCode.py --tlv + // id-embeddedSctList 1.3.6.1.4.1.11129.2.4.2 + static const uint8_t tlv_id_embeddedSctList[] = { + 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x02 + }; + static const uint8_t dummySctList[] = { + 0x01, 0x02, 0x03, 0x04, 0x05 + }; + + ByteString ctExtension = TLV(der::SEQUENCE, + BytesToByteString(tlv_id_embeddedSctList) + + Boolean(false) + + TLV(der::OCTET_STRING, + // SignedCertificateTimestampList structure is encoded as an OCTET STRING + // within the X.509v3 extension (see RFC 6962 section 3.3). + // pkix decodes it internally and returns the actual structure. + TLV(der::OCTET_STRING, BytesToByteString(dummySctList)))); + + const char* rootCN = "Root CA"; + ByteString rootDER(CreateCert(rootCN, rootCN, EndEntityOrCA::MustBeCA)); + ASSERT_FALSE(ENCODING_FAILED(rootDER)); + + ByteString certDER(CreateCert(rootCN, "Cert with SCT list", + EndEntityOrCA::MustBeEndEntity, + nullptr, /*subjectDERToCertDER*/ + &ctExtension)); + ASSERT_FALSE(ENCODING_FAILED(certDER)); + + Input certInput; + ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length())); + + EmbeddedSCTListTestTrustDomain extTrustDomain(rootDER); + ASSERT_EQ(Success, + BuildCertChain(extTrustDomain, certInput, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::anyExtendedKeyUsage, + CertPolicyId::anyPolicy, + nullptr /*stapledOCSPResponse*/)); + ASSERT_EQ(BytesToByteString(dummySctList), + extTrustDomain.signedCertificateTimestamps); +} diff --git a/security/pkix/test/gtest/pkixcert_extension_tests.cpp b/security/pkix/test/gtest/pkixcert_extension_tests.cpp new file mode 100644 index 000000000..464643134 --- /dev/null +++ b/security/pkix/test/gtest/pkixcert_extension_tests.cpp @@ -0,0 +1,270 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2013 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixder.h" +#include "pkixgtest.h" +#include "pkixtestutil.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +// Creates a self-signed certificate with the given extension. +static ByteString +CreateCertWithExtensions(const char* subjectCN, + const ByteString* extensions) +{ + static long serialNumberValue = 0; + ++serialNumberValue; + ByteString serialNumber(CreateEncodedSerialNumber(serialNumberValue)); + EXPECT_FALSE(ENCODING_FAILED(serialNumber)); + ByteString issuerDER(CNToDERName(subjectCN)); + EXPECT_FALSE(ENCODING_FAILED(issuerDER)); + ByteString subjectDER(CNToDERName(subjectCN)); + EXPECT_FALSE(ENCODING_FAILED(subjectDER)); + ScopedTestKeyPair subjectKey(CloneReusedKeyPair()); + return CreateEncodedCertificate(v3, sha256WithRSAEncryption(), + serialNumber, issuerDER, + oneDayBeforeNow, oneDayAfterNow, + subjectDER, *subjectKey, extensions, + *subjectKey, + sha256WithRSAEncryption()); +} + +// Creates a self-signed certificate with the given extension. +static ByteString +CreateCertWithOneExtension(const char* subjectStr, const ByteString& extension) +{ + const ByteString extensions[] = { extension, ByteString() }; + return CreateCertWithExtensions(subjectStr, extensions); +} + +class TrustEverythingTrustDomain final : public DefaultCryptoTrustDomain +{ +private: + Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input, + /*out*/ TrustLevel& trustLevel) override + { + trustLevel = TrustLevel::TrustAnchor; + return Success; + } + + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, + /*optional*/ const Input*, /*optional*/ const Input*) + override + { + return Success; + } + + Result IsChainValid(const DERArray&, Time) override + { + return Success; + } +}; + +// python DottedOIDToCode.py --tlv unknownExtensionOID 1.3.6.1.4.1.13769.666.666.666.1.500.9.3 +static const uint8_t tlv_unknownExtensionOID[] = { + 0x06, 0x12, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xeb, 0x49, 0x85, 0x1a, 0x85, 0x1a, + 0x85, 0x1a, 0x01, 0x83, 0x74, 0x09, 0x03 +}; + +// python DottedOIDToCode.py --tlv id-pe-authorityInformationAccess 1.3.6.1.5.5.7.1.1 +static const uint8_t tlv_id_pe_authorityInformationAccess[] = { + 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01 +}; + +// python DottedOIDToCode.py --tlv wrongExtensionOID 1.3.6.6.1.5.5.7.1.1 +// (there is an extra "6" that shouldn't be in this OID) +static const uint8_t tlv_wrongExtensionOID[] = { + 0x06, 0x09, 0x2b, 0x06, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01 +}; + +// python DottedOIDToCode.py --tlv id-ce-unknown 2.5.29.55 +// (this is a made-up OID for testing "id-ce"-prefixed OIDs that mozilla::pkix +// doesn't handle) +static const uint8_t tlv_id_ce_unknown[] = { + 0x06, 0x03, 0x55, 0x1d, 0x37 +}; + +// python DottedOIDToCode.py --tlv id-ce-inhibitAnyPolicy 2.5.29.54 +static const uint8_t tlv_id_ce_inhibitAnyPolicy[] = { + 0x06, 0x03, 0x55, 0x1d, 0x36 +}; + +// python DottedOIDToCode.py --tlv id-pkix-ocsp-nocheck 1.3.6.1.5.5.7.48.1.5 +static const uint8_t tlv_id_pkix_ocsp_nocheck[] = { + 0x06, 0x09, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x05 +}; + +struct ExtensionTestcase +{ + ByteString extension; + Result expectedResult; +}; + +static const ExtensionTestcase EXTENSION_TESTCASES[] = +{ + // Tests that a non-critical extension not in the id-ce or id-pe arcs (which + // is thus unknown to us) verifies successfully even if empty (extensions we + // know about aren't normally allowed to be empty). + { TLV(der::SEQUENCE, + BytesToByteString(tlv_unknownExtensionOID) + + TLV(der::OCTET_STRING, ByteString())), + Success + }, + + // Tests that a critical extension not in the id-ce or id-pe arcs (which is + // thus unknown to us) is detected and that verification fails with the + // appropriate error. + { TLV(der::SEQUENCE, + BytesToByteString(tlv_unknownExtensionOID) + + Boolean(true) + + TLV(der::OCTET_STRING, ByteString())), + Result::ERROR_UNKNOWN_CRITICAL_EXTENSION + }, + + // Tests that a id-pe-authorityInformationAccess critical extension + // is detected and that verification succeeds. + // XXX: According to RFC 5280 an AIA that consists of an empty sequence is + // not legal, but we accept it and that is not what we're testing here. + { TLV(der::SEQUENCE, + BytesToByteString(tlv_id_pe_authorityInformationAccess) + + Boolean(true) + + TLV(der::OCTET_STRING, TLV(der::SEQUENCE, ByteString()))), + Success + }, + + // Tests that an incorrect OID for id-pe-authorityInformationAccess + // (when marked critical) is detected and that verification fails. + // (Until bug 1020993 was fixed, this wrong value was used for + // id-pe-authorityInformationAccess.) + { TLV(der::SEQUENCE, + BytesToByteString(tlv_wrongExtensionOID) + + Boolean(true) + + TLV(der::OCTET_STRING, ByteString())), + Result::ERROR_UNKNOWN_CRITICAL_EXTENSION + }, + + // We know about some id-ce extensions (OID arc 2.5.29), but not all of them. + // Tests that an unknown id-ce extension is detected and that verification + // fails. + { TLV(der::SEQUENCE, + BytesToByteString(tlv_id_ce_unknown) + + Boolean(true) + + TLV(der::OCTET_STRING, ByteString())), + Result::ERROR_UNKNOWN_CRITICAL_EXTENSION + }, + + // Tests that a certificate with a known critical id-ce extension (in this + // case, OID 2.5.29.54, which is id-ce-inhibitAnyPolicy), verifies + // successfully. + { TLV(der::SEQUENCE, + BytesToByteString(tlv_id_ce_inhibitAnyPolicy) + + Boolean(true) + + TLV(der::OCTET_STRING, Integer(0))), + Success + }, + + // Tests that a certificate with the id-pkix-ocsp-nocheck extension (marked + // critical) verifies successfully. + // RFC 6960: + // ext-ocsp-nocheck EXTENSION ::= { SYNTAX NULL IDENTIFIED + // BY id-pkix-ocsp-nocheck } + { TLV(der::SEQUENCE, + BytesToByteString(tlv_id_pkix_ocsp_nocheck) + + Boolean(true) + + TLV(der::OCTET_STRING, TLV(der::NULLTag, ByteString()))), + Success + }, + + // Tests that a certificate with another representation of the + // id-pkix-ocsp-nocheck extension (marked critical) verifies successfully. + // According to http://comments.gmane.org/gmane.ietf.x509/30947, + // some code creates certificates where value of the extension is + // an empty OCTET STRING. + { TLV(der::SEQUENCE, + BytesToByteString(tlv_id_pkix_ocsp_nocheck) + + Boolean(true) + + TLV(der::OCTET_STRING, ByteString())), + Success + }, +}; + +class pkixcert_extension + : public ::testing::Test + , public ::testing::WithParamInterface<ExtensionTestcase> +{ +protected: + static TrustEverythingTrustDomain trustDomain; +}; + +/*static*/ TrustEverythingTrustDomain pkixcert_extension::trustDomain; + +TEST_P(pkixcert_extension, ExtensionHandledProperly) +{ + const ExtensionTestcase& testcase(GetParam()); + const char* cn = "Cert Extension Test"; + ByteString cert(CreateCertWithOneExtension(cn, testcase.extension)); + ASSERT_FALSE(ENCODING_FAILED(cert)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); + ASSERT_EQ(testcase.expectedResult, + BuildCertChain(trustDomain, certInput, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::anyExtendedKeyUsage, + CertPolicyId::anyPolicy, + nullptr/*stapledOCSPResponse*/)); +} + +INSTANTIATE_TEST_CASE_P(pkixcert_extension, + pkixcert_extension, + testing::ValuesIn(EXTENSION_TESTCASES)); + +// Two subjectAltNames must result in an error. +TEST_F(pkixcert_extension, DuplicateSubjectAltName) +{ + // python DottedOIDToCode.py --tlv id-ce-subjectAltName 2.5.29.17 + static const uint8_t tlv_id_ce_subjectAltName[] = { + 0x06, 0x03, 0x55, 0x1d, 0x11 + }; + + ByteString subjectAltName( + TLV(der::SEQUENCE, + BytesToByteString(tlv_id_ce_subjectAltName) + + TLV(der::OCTET_STRING, TLV(der::SEQUENCE, DNSName("example.com"))))); + static const ByteString extensions[] = { subjectAltName, subjectAltName, + ByteString() }; + static const char* certCN = "Cert With Duplicate subjectAltName"; + ByteString cert(CreateCertWithExtensions(certCN, extensions)); + ASSERT_FALSE(ENCODING_FAILED(cert)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); + ASSERT_EQ(Result::ERROR_EXTENSION_VALUE_INVALID, + BuildCertChain(trustDomain, certInput, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::anyExtendedKeyUsage, + CertPolicyId::anyPolicy, + nullptr/*stapledOCSPResponse*/)); +} diff --git a/security/pkix/test/gtest/pkixcert_signature_algorithm_tests.cpp b/security/pkix/test/gtest/pkixcert_signature_algorithm_tests.cpp new file mode 100644 index 000000000..924c2b053 --- /dev/null +++ b/security/pkix/test/gtest/pkixcert_signature_algorithm_tests.cpp @@ -0,0 +1,247 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +#include "pkixder.h" +#include "pkixgtest.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +static ByteString +CreateCert(const char* issuerCN, + const char* subjectCN, + EndEntityOrCA endEntityOrCA, + const TestSignatureAlgorithm& signatureAlgorithm, + /*out*/ ByteString& subjectDER) +{ + static long serialNumberValue = 0; + ++serialNumberValue; + ByteString serialNumber(CreateEncodedSerialNumber(serialNumberValue)); + EXPECT_FALSE(ENCODING_FAILED(serialNumber)); + + ByteString issuerDER(CNToDERName(issuerCN)); + EXPECT_FALSE(ENCODING_FAILED(issuerDER)); + subjectDER = CNToDERName(subjectCN); + EXPECT_FALSE(ENCODING_FAILED(subjectDER)); + + ByteString extensions[2]; + if (endEntityOrCA == EndEntityOrCA::MustBeCA) { + extensions[0] = + CreateEncodedBasicConstraints(true, nullptr, Critical::Yes); + EXPECT_FALSE(ENCODING_FAILED(extensions[0])); + } + + ScopedTestKeyPair reusedKey(CloneReusedKeyPair()); + ByteString certDER(CreateEncodedCertificate(v3, signatureAlgorithm, + serialNumber, issuerDER, + oneDayBeforeNow, oneDayAfterNow, + subjectDER, *reusedKey, + extensions, *reusedKey, + signatureAlgorithm)); + EXPECT_FALSE(ENCODING_FAILED(certDER)); + return certDER; +} + +class AlgorithmTestsTrustDomain final : public DefaultCryptoTrustDomain +{ +public: + AlgorithmTestsTrustDomain(const ByteString& rootDER, + const ByteString& rootSubjectDER, + /*optional*/ const ByteString& intDER, + /*optional*/ const ByteString& intSubjectDER) + : rootDER(rootDER) + , rootSubjectDER(rootSubjectDER) + , intDER(intDER) + , intSubjectDER(intSubjectDER) + { + } + +private: + Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert, + /*out*/ TrustLevel& trustLevel) override + { + if (InputEqualsByteString(candidateCert, rootDER)) { + trustLevel = TrustLevel::TrustAnchor; + } else { + trustLevel = TrustLevel::InheritsTrust; + } + return Success; + } + + Result FindIssuer(Input encodedIssuerName, IssuerChecker& checker, Time) + override + { + ByteString* issuerDER = nullptr; + if (InputEqualsByteString(encodedIssuerName, rootSubjectDER)) { + issuerDER = &rootDER; + } else if (InputEqualsByteString(encodedIssuerName, intSubjectDER)) { + issuerDER = &intDER; + } else { + // FindIssuer just returns success if it can't find a potential issuer. + return Success; + } + Input issuerCert; + Result rv = issuerCert.Init(issuerDER->data(), issuerDER->length()); + if (rv != Success) { + return rv; + } + bool keepGoing; + return checker.Check(issuerCert, nullptr, keepGoing); + } + + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, + const Input*, const Input*) override + { + return Success; + } + + Result IsChainValid(const DERArray&, Time) override + { + return Success; + } + + ByteString rootDER; + ByteString rootSubjectDER; + ByteString intDER; + ByteString intSubjectDER; +}; + +static const TestSignatureAlgorithm NO_INTERMEDIATE +{ + TestPublicKeyAlgorithm(ByteString()), + TestDigestAlgorithmID::MD2, + ByteString(), + false +}; + +struct ChainValidity final +{ + ChainValidity(const TestSignatureAlgorithm& endEntitySignatureAlgorithm, + const TestSignatureAlgorithm& optionalIntSignatureAlgorithm, + const TestSignatureAlgorithm& rootSignatureAlgorithm, + bool isValid) + : endEntitySignatureAlgorithm(endEntitySignatureAlgorithm) + , optionalIntermediateSignatureAlgorithm(optionalIntSignatureAlgorithm) + , rootSignatureAlgorithm(rootSignatureAlgorithm) + , isValid(isValid) + { } + + // In general, a certificate is generated for each of these. However, if + // optionalIntermediateSignatureAlgorithm is NO_INTERMEDIATE, then only 2 + // certificates are generated. + // The certificate generated for the given rootSignatureAlgorithm is the + // trust anchor. + TestSignatureAlgorithm endEntitySignatureAlgorithm; + TestSignatureAlgorithm optionalIntermediateSignatureAlgorithm; + TestSignatureAlgorithm rootSignatureAlgorithm; + bool isValid; +}; + +static const ChainValidity CHAIN_VALIDITY[] = +{ + // The trust anchor may have a signature with an unsupported signature + // algorithm. + ChainValidity(sha256WithRSAEncryption(), + NO_INTERMEDIATE, + md5WithRSAEncryption(), + true), + ChainValidity(sha256WithRSAEncryption(), + NO_INTERMEDIATE, + md2WithRSAEncryption(), + true), + + // Certificates that are not trust anchors must not have a signature with an + // unsupported signature algorithm. + ChainValidity(md5WithRSAEncryption(), + NO_INTERMEDIATE, + sha256WithRSAEncryption(), + false), + ChainValidity(md2WithRSAEncryption(), + NO_INTERMEDIATE, + sha256WithRSAEncryption(), + false), + ChainValidity(md2WithRSAEncryption(), + NO_INTERMEDIATE, + md5WithRSAEncryption(), + false), + ChainValidity(sha256WithRSAEncryption(), + md5WithRSAEncryption(), + sha256WithRSAEncryption(), + false), + ChainValidity(sha256WithRSAEncryption(), + md2WithRSAEncryption(), + sha256WithRSAEncryption(), + false), + ChainValidity(sha256WithRSAEncryption(), + md2WithRSAEncryption(), + md5WithRSAEncryption(), + false), +}; + +class pkixcert_IsValidChainForAlgorithm + : public ::testing::Test + , public ::testing::WithParamInterface<ChainValidity> +{ +}; + +TEST_P(pkixcert_IsValidChainForAlgorithm, IsValidChainForAlgorithm) +{ + const ChainValidity& chainValidity(GetParam()); + const char* rootCN = "CN=Root"; + ByteString rootSubjectDER; + ByteString rootEncoded( + CreateCert(rootCN, rootCN, EndEntityOrCA::MustBeCA, + chainValidity.rootSignatureAlgorithm, rootSubjectDER)); + EXPECT_FALSE(ENCODING_FAILED(rootEncoded)); + EXPECT_FALSE(ENCODING_FAILED(rootSubjectDER)); + + const char* issuerCN = rootCN; + + const char* intermediateCN = "CN=Intermediate"; + ByteString intermediateSubjectDER; + ByteString intermediateEncoded; + + // If the the algorithmIdentifier is empty, then it's NO_INTERMEDIATE. + if (!chainValidity.optionalIntermediateSignatureAlgorithm + .algorithmIdentifier.empty()) { + intermediateEncoded = + CreateCert(rootCN, intermediateCN, EndEntityOrCA::MustBeCA, + chainValidity.optionalIntermediateSignatureAlgorithm, + intermediateSubjectDER); + EXPECT_FALSE(ENCODING_FAILED(intermediateEncoded)); + EXPECT_FALSE(ENCODING_FAILED(intermediateSubjectDER)); + issuerCN = intermediateCN; + } + + AlgorithmTestsTrustDomain trustDomain(rootEncoded, rootSubjectDER, + intermediateEncoded, + intermediateSubjectDER); + + const char* endEntityCN = "CN=End Entity"; + ByteString endEntitySubjectDER; + ByteString endEntityEncoded( + CreateCert(issuerCN, endEntityCN, EndEntityOrCA::MustBeEndEntity, + chainValidity.endEntitySignatureAlgorithm, + endEntitySubjectDER)); + EXPECT_FALSE(ENCODING_FAILED(endEntityEncoded)); + EXPECT_FALSE(ENCODING_FAILED(endEntitySubjectDER)); + + Input endEntity; + ASSERT_EQ(Success, endEntity.Init(endEntityEncoded.data(), + endEntityEncoded.length())); + Result expectedResult = chainValidity.isValid + ? Success + : Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED; + ASSERT_EQ(expectedResult, + BuildCertChain(trustDomain, endEntity, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::id_kp_serverAuth, + CertPolicyId::anyPolicy, nullptr)); +} + +INSTANTIATE_TEST_CASE_P(pkixcert_IsValidChainForAlgorithm, + pkixcert_IsValidChainForAlgorithm, + testing::ValuesIn(CHAIN_VALIDITY)); diff --git a/security/pkix/test/gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp b/security/pkix/test/gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp new file mode 100644 index 000000000..4bfbd2265 --- /dev/null +++ b/security/pkix/test/gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp @@ -0,0 +1,711 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2016 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixder.h" +#include "pkixgtest.h" +#include "pkixutil.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +namespace mozilla { namespace pkix { + +extern Result CheckExtendedKeyUsage(EndEntityOrCA endEntityOrCA, + const Input* encodedExtendedKeyUsage, + KeyPurposeId requiredEKU, + TrustDomain& trustDomain, Time notBefore); + +} } // namespace mozilla::pkix + +class pkixcheck_CheckExtendedKeyUsage : public ::testing::Test +{ +protected: + DefaultCryptoTrustDomain mTrustDomain; +}; + +#define ASSERT_BAD(x) ASSERT_EQ(Result::ERROR_INADEQUATE_CERT_TYPE, x) + +// tlv_id_kp_OCSPSigning and tlv_id_kp_serverAuth are defined in pkixtestutil.h + +// python DottedOIDToCode.py --tlv id-kp-clientAuth 1.3.6.1.5.5.7.3.2 +static const uint8_t tlv_id_kp_clientAuth[] = { + 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02 +}; + +// python DottedOIDToCode.py --tlv id-kp-codeSigning 1.3.6.1.5.5.7.3.3 +static const uint8_t tlv_id_kp_codeSigning[] = { + 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x03 +}; + +// python DottedOIDToCode.py --tlv id_kp_emailProtection 1.3.6.1.5.5.7.3.4 +static const uint8_t tlv_id_kp_emailProtection[] = { + 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x04 +}; + +// python DottedOIDToCode.py --tlv id-Netscape-stepUp 2.16.840.1.113730.4.1 +static const uint8_t tlv_id_Netscape_stepUp[] = { + 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01 +}; + +// python DottedOIDToCode.py --tlv unknownOID 1.3.6.1.4.1.13769.666.666.666.1.500.9.3 +static const uint8_t tlv_unknownOID[] = { + 0x06, 0x12, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xeb, 0x49, 0x85, 0x1a, 0x85, 0x1a, + 0x85, 0x1a, 0x01, 0x83, 0x74, 0x09, 0x03 +}; + +// python DottedOIDToCode.py --tlv anyExtendedKeyUsage 2.5.29.37.0 +static const uint8_t tlv_anyExtendedKeyUsage[] = { + 0x06, 0x04, 0x55, 0x1d, 0x25, 0x00 +}; + +TEST_F(pkixcheck_CheckExtendedKeyUsage, none) +{ + // The input Input is nullptr. This means the cert had no extended key usage + // extension. This is always valid except for when the certificate is an + // end-entity and the required usage is id-kp-OCSPSigning. + + ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, + nullptr, + KeyPurposeId::anyExtendedKeyUsage, + mTrustDomain, Now())); + ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr, + KeyPurposeId::anyExtendedKeyUsage, + mTrustDomain, Now())); + ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, + nullptr, + KeyPurposeId::id_kp_serverAuth, + mTrustDomain, Now())); + ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr, + KeyPurposeId::id_kp_serverAuth, + mTrustDomain, Now())); + ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, + nullptr, + KeyPurposeId::id_kp_clientAuth, + mTrustDomain, Now())); + ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr, + KeyPurposeId::id_kp_clientAuth, + mTrustDomain, Now())); + ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, + nullptr, + KeyPurposeId::id_kp_codeSigning, + mTrustDomain, Now())); + ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr, + KeyPurposeId::id_kp_codeSigning, + mTrustDomain, Now())); + ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, + nullptr, + KeyPurposeId::id_kp_emailProtection, + mTrustDomain, Now())); + ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr, + KeyPurposeId::id_kp_emailProtection, + mTrustDomain, Now())); + ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr, + KeyPurposeId::id_kp_OCSPSigning, + mTrustDomain, Now())); + ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr, + KeyPurposeId::id_kp_OCSPSigning, + mTrustDomain, Now())); +} + +static const Input empty_null; + +TEST_F(pkixcheck_CheckExtendedKeyUsage, empty) +{ + // The input Input is empty. The cert has an empty extended key usage + // extension, which is syntactically invalid. + ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, &empty_null, + KeyPurposeId::id_kp_serverAuth, + mTrustDomain, Now())); + ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, &empty_null, + KeyPurposeId::id_kp_serverAuth, + mTrustDomain, Now())); + + static const uint8_t dummy = 0x00; + Input empty_nonnull; + ASSERT_EQ(Success, empty_nonnull.Init(&dummy, 0)); + ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, &empty_nonnull, + KeyPurposeId::id_kp_serverAuth, + mTrustDomain, Now())); + ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, &empty_nonnull, + KeyPurposeId::id_kp_serverAuth, + mTrustDomain, Now())); +} + +struct EKUTestcase +{ + ByteString ekuSEQUENCE; + KeyPurposeId keyPurposeId; + Result expectedResultEndEntity; + Result expectedResultCA; +}; + +class CheckExtendedKeyUsageTest + : public ::testing::Test + , public ::testing::WithParamInterface<EKUTestcase> +{ +protected: + DefaultCryptoTrustDomain mTrustDomain; +}; + +TEST_P(CheckExtendedKeyUsageTest, EKUTestcase) +{ + const EKUTestcase& param(GetParam()); + Input encodedEKU; + ASSERT_EQ(Success, encodedEKU.Init(param.ekuSEQUENCE.data(), + param.ekuSEQUENCE.length())); + ASSERT_EQ(param.expectedResultEndEntity, + CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, &encodedEKU, + param.keyPurposeId, + mTrustDomain, Now())); + ASSERT_EQ(param.expectedResultCA, + CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, &encodedEKU, + param.keyPurposeId, + mTrustDomain, Now())); +} + +#define SINGLE_EKU_SUCCESS(oidBytes, keyPurposeId) \ + { TLV(der::SEQUENCE, BytesToByteString(oidBytes)), keyPurposeId, \ + Success, Success } +#define SINGLE_EKU_SUCCESS_CA(oidBytes, keyPurposeId) \ + { TLV(der::SEQUENCE, BytesToByteString(oidBytes)), keyPurposeId, \ + Result::ERROR_INADEQUATE_CERT_TYPE, Success } +#define SINGLE_EKU_FAILURE(oidBytes, keyPurposeId) \ + { TLV(der::SEQUENCE, BytesToByteString(oidBytes)), keyPurposeId, \ + Result::ERROR_INADEQUATE_CERT_TYPE, Result::ERROR_INADEQUATE_CERT_TYPE } +#define DOUBLE_EKU_SUCCESS(oidBytes1, oidBytes2, keyPurposeId) \ + { TLV(der::SEQUENCE, \ + BytesToByteString(oidBytes1) + BytesToByteString(oidBytes2)), \ + keyPurposeId, \ + Success, Success } +#define DOUBLE_EKU_SUCCESS_CA(oidBytes1, oidBytes2, keyPurposeId) \ + { TLV(der::SEQUENCE, \ + BytesToByteString(oidBytes1) + BytesToByteString(oidBytes2)), \ + keyPurposeId, \ + Result::ERROR_INADEQUATE_CERT_TYPE, Success } +#define DOUBLE_EKU_FAILURE(oidBytes1, oidBytes2, keyPurposeId) \ + { TLV(der::SEQUENCE, \ + BytesToByteString(oidBytes1) + BytesToByteString(oidBytes2)), \ + keyPurposeId, \ + Result::ERROR_INADEQUATE_CERT_TYPE, Result::ERROR_INADEQUATE_CERT_TYPE } + +static const EKUTestcase EKU_TESTCASES[] = +{ + SINGLE_EKU_SUCCESS(tlv_id_kp_serverAuth, KeyPurposeId::anyExtendedKeyUsage), + SINGLE_EKU_SUCCESS(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_serverAuth), + SINGLE_EKU_FAILURE(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_clientAuth), + SINGLE_EKU_FAILURE(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_codeSigning), + SINGLE_EKU_FAILURE(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_emailProtection), + SINGLE_EKU_FAILURE(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_OCSPSigning), + + SINGLE_EKU_SUCCESS(tlv_id_kp_clientAuth, KeyPurposeId::anyExtendedKeyUsage), + SINGLE_EKU_FAILURE(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_serverAuth), + SINGLE_EKU_SUCCESS(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_clientAuth), + SINGLE_EKU_FAILURE(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_codeSigning), + SINGLE_EKU_FAILURE(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_emailProtection), + SINGLE_EKU_FAILURE(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_OCSPSigning), + + SINGLE_EKU_SUCCESS(tlv_id_kp_codeSigning, KeyPurposeId::anyExtendedKeyUsage), + SINGLE_EKU_FAILURE(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_serverAuth), + SINGLE_EKU_FAILURE(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_clientAuth), + SINGLE_EKU_SUCCESS(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_codeSigning), + SINGLE_EKU_FAILURE(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_emailProtection), + SINGLE_EKU_FAILURE(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_OCSPSigning), + + SINGLE_EKU_SUCCESS(tlv_id_kp_emailProtection, KeyPurposeId::anyExtendedKeyUsage), + SINGLE_EKU_FAILURE(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_serverAuth), + SINGLE_EKU_FAILURE(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_clientAuth), + SINGLE_EKU_FAILURE(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_codeSigning), + SINGLE_EKU_SUCCESS(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_emailProtection), + SINGLE_EKU_FAILURE(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_OCSPSigning), + + // For end-entities, if id-kp-OCSPSigning is present, no usage is allowed + // except OCSPSigning. + SINGLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage), + SINGLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth), + SINGLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth), + SINGLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning), + SINGLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection), + SINGLE_EKU_SUCCESS(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning), + + SINGLE_EKU_SUCCESS(tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage), + // For compatibility, id-Netscape-stepUp is treated as equivalent to + // id-kp-serverAuth for CAs. + SINGLE_EKU_SUCCESS_CA(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth), + SINGLE_EKU_FAILURE(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth), + SINGLE_EKU_FAILURE(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning), + SINGLE_EKU_FAILURE(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection), + SINGLE_EKU_FAILURE(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning), + + SINGLE_EKU_SUCCESS(tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage), + SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_serverAuth), + SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_clientAuth), + SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_codeSigning), + SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_emailProtection), + SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning), + + SINGLE_EKU_SUCCESS(tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage), + SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth), + SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth), + SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning), + SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection), + SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_SUCCESS(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_SUCCESS(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_SUCCESS(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS_CA(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS_CA(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning), +}; + +INSTANTIATE_TEST_CASE_P(pkixcheck_CheckExtendedKeyUsage, + CheckExtendedKeyUsageTest, + ::testing::ValuesIn(EKU_TESTCASES)); + +struct EKUChainTestcase +{ + ByteString ekuExtensionEE; + ByteString ekuExtensionCA; + KeyPurposeId keyPurposeId; + Result expectedResult; +}; + +class CheckExtendedKeyUsageChainTest + : public ::testing::Test + , public ::testing::WithParamInterface<EKUChainTestcase> +{ +}; + +static ByteString +CreateCert(const char* issuerCN, const char* subjectCN, + EndEntityOrCA endEntityOrCA, ByteString encodedEKU) +{ + static long serialNumberValue = 0; + ++serialNumberValue; + ByteString serialNumber(CreateEncodedSerialNumber(serialNumberValue)); + EXPECT_FALSE(ENCODING_FAILED(serialNumber)); + + ByteString issuerDER(CNToDERName(issuerCN)); + ByteString subjectDER(CNToDERName(subjectCN)); + + ByteString extensions[3]; + extensions[0] = + CreateEncodedBasicConstraints(endEntityOrCA == EndEntityOrCA::MustBeCA, + nullptr, Critical::Yes); + EXPECT_FALSE(ENCODING_FAILED(extensions[0])); + if (encodedEKU.length() > 0) { + extensions[1] = encodedEKU; + } + + ScopedTestKeyPair reusedKey(CloneReusedKeyPair()); + ByteString certDER(CreateEncodedCertificate( + v3, sha256WithRSAEncryption(), serialNumber, issuerDER, + oneDayBeforeNow, oneDayAfterNow, subjectDER, + *reusedKey, extensions, *reusedKey, + sha256WithRSAEncryption())); + EXPECT_FALSE(ENCODING_FAILED(certDER)); + + return certDER; +} + +class EKUTrustDomain final : public DefaultCryptoTrustDomain +{ +public: + explicit EKUTrustDomain(ByteString issuerCertDER) + : mIssuerCertDER(issuerCertDER) + { + } + +private: + Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert, + TrustLevel& trustLevel) override + { + trustLevel = InputEqualsByteString(candidateCert, mIssuerCertDER) + ? TrustLevel::TrustAnchor + : TrustLevel::InheritsTrust; + return Success; + } + + Result FindIssuer(Input, IssuerChecker& checker, Time) override + { + Input derCert; + Result rv = derCert.Init(mIssuerCertDER.data(), mIssuerCertDER.length()); + if (rv != Success) { + return rv; + } + bool keepGoing; + return checker.Check(derCert, nullptr, keepGoing); + } + + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, + const Input*, const Input*) override + { + return Success; + } + + Result IsChainValid(const DERArray&, Time) override + { + return Success; + } + + ByteString mIssuerCertDER; +}; + +TEST_P(CheckExtendedKeyUsageChainTest, EKUChainTestcase) +{ + const EKUChainTestcase& param(GetParam()); + ByteString issuerCertDER(CreateCert("CA", "CA", EndEntityOrCA::MustBeCA, + param.ekuExtensionCA)); + ByteString subjectCertDER(CreateCert("CA", "EE", + EndEntityOrCA::MustBeEndEntity, + param.ekuExtensionEE)); + + EKUTrustDomain trustDomain(issuerCertDER); + + Input subjectCertDERInput; + ASSERT_EQ(Success, subjectCertDERInput.Init(subjectCertDER.data(), + subjectCertDER.length())); + ASSERT_EQ(param.expectedResult, + BuildCertChain(trustDomain, subjectCertDERInput, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + param.keyPurposeId, + CertPolicyId::anyPolicy, + nullptr)); +} + +// python DottedOIDToCode.py --tlv id-ce-extKeyUsage 2.5.29.37 +static const uint8_t tlv_id_ce_extKeyUsage[] = { + 0x06, 0x03, 0x55, 0x1d, 0x25 +}; + +static inline ByteString +CreateEKUExtension(ByteString ekuOIDs) +{ + return TLV(der::SEQUENCE, + BytesToByteString(tlv_id_ce_extKeyUsage) + + TLV(der::OCTET_STRING, TLV(der::SEQUENCE, ekuOIDs))); +} + +static const EKUChainTestcase EKU_CHAIN_TESTCASES[] = +{ + { + // Both end-entity and CA have id-kp-serverAuth => should succeed + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)), + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)), + KeyPurposeId::id_kp_serverAuth, + Success + }, + { + // CA has no EKU extension => should succeed + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)), + ByteString(), + KeyPurposeId::id_kp_serverAuth, + Success + }, + { + // End-entity has no EKU extension => should succeed + ByteString(), + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)), + KeyPurposeId::id_kp_serverAuth, + Success + }, + { + // No EKU extensions at all => should succeed + ByteString(), + ByteString(), + KeyPurposeId::id_kp_serverAuth, + Success + }, + { + // CA has EKU without id-kp-serverAuth => should fail + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)), + CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)), + KeyPurposeId::id_kp_serverAuth, + Result::ERROR_INADEQUATE_CERT_TYPE + }, + { + // End-entity has EKU without id-kp-serverAuth => should fail + CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)), + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)), + KeyPurposeId::id_kp_serverAuth, + Result::ERROR_INADEQUATE_CERT_TYPE + }, + { + // Both end-entity and CA have EKU without id-kp-serverAuth => should fail + CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)), + CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)), + KeyPurposeId::id_kp_serverAuth, + Result::ERROR_INADEQUATE_CERT_TYPE + }, + { + // End-entity has no EKU, CA doesn't have id-kp-serverAuth => should fail + ByteString(), + CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)), + KeyPurposeId::id_kp_serverAuth, + Result::ERROR_INADEQUATE_CERT_TYPE + }, + { + // End-entity doesn't have id-kp-serverAuth, CA has no EKU => should fail + CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)), + ByteString(), + KeyPurposeId::id_kp_serverAuth, + Result::ERROR_INADEQUATE_CERT_TYPE + }, + { + // CA has id-Netscape-stepUp => should succeed + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)), + CreateEKUExtension(BytesToByteString(tlv_id_Netscape_stepUp)), + KeyPurposeId::id_kp_serverAuth, + Success + }, + { + // End-entity has id-Netscape-stepUp => should fail + CreateEKUExtension(BytesToByteString(tlv_id_Netscape_stepUp)), + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)), + KeyPurposeId::id_kp_serverAuth, + Result::ERROR_INADEQUATE_CERT_TYPE + }, + { + // End-entity and CA have id-kp-serverAuth and id-kp-clientAuth => should + // succeed + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) + + BytesToByteString(tlv_id_kp_clientAuth)), + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) + + BytesToByteString(tlv_id_kp_clientAuth)), + KeyPurposeId::id_kp_serverAuth, + Success + }, + { + // End-entity has id-kp-serverAuth and id-kp-OCSPSigning => should fail + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) + + BytesToByteString(tlv_id_kp_OCSPSigning)), + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) + + BytesToByteString(tlv_id_kp_clientAuth)), + KeyPurposeId::id_kp_serverAuth, + Result::ERROR_INADEQUATE_CERT_TYPE + }, + { + // CA has id-kp-serverAuth and id-kp-OCSPSigning => should succeed + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) + + BytesToByteString(tlv_id_kp_clientAuth)), + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) + + BytesToByteString(tlv_id_kp_OCSPSigning)), + KeyPurposeId::id_kp_serverAuth, + Success + }, +}; + +INSTANTIATE_TEST_CASE_P(pkixcheck_CheckExtendedKeyUsage, + CheckExtendedKeyUsageChainTest, + ::testing::ValuesIn(EKU_CHAIN_TESTCASES)); diff --git a/security/pkix/test/gtest/pkixcheck_CheckIssuer_tests.cpp b/security/pkix/test/gtest/pkixcheck_CheckIssuer_tests.cpp new file mode 100644 index 000000000..d7fcfb210 --- /dev/null +++ b/security/pkix/test/gtest/pkixcheck_CheckIssuer_tests.cpp @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2016 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixcheck.h" +#include "pkixgtest.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +class pkixcheck_CheckIssuer : public ::testing::Test { }; + +static const uint8_t EMPTY_NAME_DATA[] = { + 0x30, 0x00 /* tag, length */ +}; +static const Input EMPTY_NAME(EMPTY_NAME_DATA); + +static const uint8_t VALID_NAME_DATA[] = { + /* From https://www.example.com/: C=US, O=DigiCert Inc, OU=www.digicert.com, + * CN=DigiCert SHA2 High Assurance Server CA */ + 0x30, 0x70, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, + 0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0A, + 0x13, 0x0C, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, + 0x6E, 0x63, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0B, 0x13, + 0x10, 0x77, 0x77, 0x77, 0x2E, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, + 0x74, 0x2E, 0x63, 0x6F, 0x6D, 0x31, 0x2F, 0x30, 0x2D, 0x06, 0x03, 0x55, + 0x04, 0x03, 0x13, 0x26, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, + 0x20, 0x53, 0x48, 0x41, 0x32, 0x20, 0x48, 0x69, 0x67, 0x68, 0x20, 0x41, + 0x73, 0x73, 0x75, 0x72, 0x61, 0x6E, 0x63, 0x65, 0x20, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x20, 0x43, 0x41 +}; +static const Input VALID_NAME(VALID_NAME_DATA); + +TEST_F(pkixcheck_CheckIssuer, ValidIssuer) +{ + ASSERT_EQ(Success, CheckIssuer(VALID_NAME)); +} + +TEST_F(pkixcheck_CheckIssuer, EmptyIssuer) +{ + ASSERT_EQ(Result::ERROR_EMPTY_ISSUER_NAME, CheckIssuer(EMPTY_NAME)); +} diff --git a/security/pkix/test/gtest/pkixcheck_CheckKeyUsage_tests.cpp b/security/pkix/test/gtest/pkixcheck_CheckKeyUsage_tests.cpp new file mode 100644 index 000000000..136f8719a --- /dev/null +++ b/security/pkix/test/gtest/pkixcheck_CheckKeyUsage_tests.cpp @@ -0,0 +1,284 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2013 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixgtest.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +namespace mozilla { namespace pkix { + +extern Result CheckKeyUsage(EndEntityOrCA endEntityOrCA, + const Input* encodedKeyUsage, + KeyUsage requiredKeyUsageIfPresent); + +} } // namespace mozilla::pkix + +class pkixcheck_CheckKeyUsage : public ::testing::Test { }; + +#define ASSERT_BAD(x) ASSERT_EQ(Result::ERROR_INADEQUATE_KEY_USAGE, x) + +// Make it easy to define test data for the common, simplest cases. +#define NAMED_SIMPLE_KU(name, unusedBits, bits) \ + const uint8_t name##_bytes[4] = { \ + 0x03/*BIT STRING*/, 0x02/*LENGTH=2*/, unusedBits, bits \ + }; \ + const Input name(name##_bytes); + +static const Input empty_null; + +// Note that keyCertSign is really the only interesting case for CA +// certificates since we don't support cRLSign. + +TEST_F(pkixcheck_CheckKeyUsage, EE_none) +{ + // The input Input is nullptr. This means the cert had no keyUsage + // extension. This is always valid because no key usage in an end-entity + // means that there are no key usage restrictions. + + ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr, + KeyUsage::noParticularKeyUsageRequired)); + ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr, + KeyUsage::digitalSignature)); + ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr, + KeyUsage::nonRepudiation)); + ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr, + KeyUsage::keyEncipherment)); + ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr, + KeyUsage::dataEncipherment)); + ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr, + KeyUsage::keyAgreement)); +} + +TEST_F(pkixcheck_CheckKeyUsage, EE_empty) +{ + // The input Input is empty. The cert had an empty keyUsage extension, + // which is syntactically invalid. + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &empty_null, + KeyUsage::digitalSignature)); + static const uint8_t dummy = 0x00; + Input empty_nonnull; + ASSERT_EQ(Success, empty_nonnull.Init(&dummy, 0)); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &empty_nonnull, + KeyUsage::digitalSignature)); +} + +TEST_F(pkixcheck_CheckKeyUsage, CA_none) +{ + // A CA certificate does not have a KU extension. + ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeCA, nullptr, + KeyUsage::keyCertSign)); +} + +TEST_F(pkixcheck_CheckKeyUsage, CA_empty) +{ + // A CA certificate has an empty KU extension. + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &empty_null, + KeyUsage::keyCertSign)); + static const uint8_t dummy = 0x00; + Input empty_nonnull; + ASSERT_EQ(Success, empty_nonnull.Init(&dummy, 0)); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &empty_nonnull, + KeyUsage::keyCertSign)); +} + +TEST_F(pkixcheck_CheckKeyUsage, maxUnusedBits) +{ + NAMED_SIMPLE_KU(encoded, 7, 0x80); + ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &encoded, + KeyUsage::digitalSignature)); +} + +TEST_F(pkixcheck_CheckKeyUsage, tooManyUnusedBits) +{ + static uint8_t oneValueByteData[] = { + 0x03/*BIT STRING*/, 0x02/*LENGTH=2*/, 8/*unused bits*/, 0x80 + }; + static const Input oneValueByte(oneValueByteData); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &oneValueByte, + KeyUsage::digitalSignature)); + + static uint8_t twoValueBytesData[] = { + 0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, 8/*unused bits*/, 0x01, 0x00 + }; + static const Input twoValueBytes(twoValueBytesData); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &twoValueBytes, + KeyUsage::digitalSignature)); +} + +TEST_F(pkixcheck_CheckKeyUsage, NoValueBytes_NoPaddingBits) +{ + static const uint8_t DER_BYTES[] = { + 0x03/*BIT STRING*/, 0x01/*LENGTH=1*/, 0/*unused bits*/ + }; + static const Input DER(DER_BYTES); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &DER, + KeyUsage::digitalSignature)); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &DER, + KeyUsage::keyCertSign)); +} + +TEST_F(pkixcheck_CheckKeyUsage, NoValueBytes_7PaddingBits) +{ + static const uint8_t DER_BYTES[] = { + 0x03/*BIT STRING*/, 0x01/*LENGTH=1*/, 7/*unused bits*/ + }; + static const Input DER(DER_BYTES); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &DER, + KeyUsage::digitalSignature)); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &DER, + KeyUsage::keyCertSign)); +} + +void ASSERT_SimpleCase(uint8_t unusedBits, uint8_t bits, KeyUsage usage) +{ + // Test that only the right bit is accepted for the usage for both EE and CA + // certs. + NAMED_SIMPLE_KU(good, unusedBits, bits); + ASSERT_EQ(Success, + CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &good, usage)); + ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeCA, &good, usage)); + + // We use (~bits >> unusedBits) << unusedBits) instead of using the same + // calculation that is in CheckKeyUsage to validate that the calculation in + // CheckKeyUsage is correct. + + // Test that none of the other non-padding bits are mistaken for the given + // key usage in the single-byte value case. + NAMED_SIMPLE_KU(notGood, unusedBits, + static_cast<uint8_t>((~bits >> unusedBits) << unusedBits)); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, ¬Good, usage)); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, ¬Good, 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, ¬Good, + KeyUsage::keyCertSign)); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, ¬Good, + KeyUsage::keyCertSign)); + + // Test that none of the other non-padding bits are mistaken for the given + // key usage in the two-byte value case. + static uint8_t twoByteNotGoodData[] = { + 0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, 2/*unused bits*/, 0xFBu, 0xFCu + }; + static const Input twoByteNotGood(twoByteNotGoodData); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &twoByteNotGood, + KeyUsage::keyCertSign)); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &twoByteNotGood, + KeyUsage::keyCertSign)); + + // If an end-entity certificate does assert keyCertSign, this is allowed + // as long as that isn't the required key usage. + NAMED_SIMPLE_KU(digitalSignatureAndKeyCertSign, 2, 0x84); + ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, + &digitalSignatureAndKeyCertSign, + KeyUsage::digitalSignature)); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, + &digitalSignatureAndKeyCertSign, + KeyUsage::keyCertSign)); +} + +TEST_F(pkixcheck_CheckKeyUsage, unusedBitNotZero) +{ + // single byte control case + static uint8_t controlOneValueByteData[] = { + 0x03/*BIT STRING*/, 0x02/*LENGTH=2*/, 7/*unused bits*/, 0x80 + }; + static const Input controlOneValueByte(controlOneValueByteData); + ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, + &controlOneValueByte, + KeyUsage::digitalSignature)); + ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeCA, + &controlOneValueByte, + KeyUsage::digitalSignature)); + + // single-byte test case + static uint8_t oneValueByteData[] = { + 0x03/*BIT STRING*/, 0x02/*LENGTH=2*/, 7/*unused bits*/, 0x80 | 0x01 + }; + static const Input oneValueByte(oneValueByteData); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &oneValueByte, + KeyUsage::digitalSignature)); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &oneValueByte, + KeyUsage::digitalSignature)); + + // two-byte control case + static uint8_t controlTwoValueBytesData[] = { + 0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, 7/*unused bits*/, + 0x80 | 0x01, 0x80 + }; + static const Input controlTwoValueBytes(controlTwoValueBytesData); + ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, + &controlTwoValueBytes, + KeyUsage::digitalSignature)); + ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeCA, + &controlTwoValueBytes, + KeyUsage::digitalSignature)); + + // two-byte test case + static uint8_t twoValueBytesData[] = { + 0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, 7/*unused bits*/, + 0x80 | 0x01, 0x80 | 0x01 + }; + static const Input twoValueBytes(twoValueBytesData); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &twoValueBytes, + KeyUsage::digitalSignature)); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &twoValueBytes, + KeyUsage::digitalSignature)); +} diff --git a/security/pkix/test/gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp b/security/pkix/test/gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp new file mode 100644 index 000000000..7ab9d095a --- /dev/null +++ b/security/pkix/test/gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp @@ -0,0 +1,360 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2015 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixder.h" +#include "pkixgtest.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +namespace mozilla { namespace pkix { + +extern Result CheckSignatureAlgorithm( + TrustDomain& trustDomain, EndEntityOrCA endEntityOrCA, + Time notBefore, + const der::SignedDataWithSignature& signedData, + Input signatureValue); + +} } // namespace mozilla::pkix + +struct CheckSignatureAlgorithmTestParams +{ + ByteString signatureAlgorithmValue; + ByteString signatureValue; + unsigned int signatureLengthInBytes; + Result expectedResult; +}; + +#define BS(s) ByteString(s, MOZILLA_PKIX_ARRAY_LENGTH(s)) + +// python DottedOIDToCode.py --tlv sha256WithRSAEncryption 1.2.840.113549.1.1.11 +static const uint8_t tlv_sha256WithRSAEncryption[] = { + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b +}; + +// Same as tlv_sha256WithRSAEncryption, except one without the "0x0b" and with +// the DER length decreased accordingly. +static const uint8_t tlv_sha256WithRSAEncryption_truncated[] = { + 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01 +}; + +// python DottedOIDToCode.py --tlv sha-1WithRSAEncryption 1.2.840.113549.1.1.5 +static const uint8_t tlv_sha_1WithRSAEncryption[] = { + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05 +}; + +// python DottedOIDToCode.py --tlv sha1WithRSASignature 1.3.14.3.2.29 +static const uint8_t tlv_sha1WithRSASignature[] = { + 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1d +}; + +// python DottedOIDToCode.py --tlv md5WithRSAEncryption 1.2.840.113549.1.1.4 +static const uint8_t tlv_md5WithRSAEncryption[] = { + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x04 +}; + +static const CheckSignatureAlgorithmTestParams + CHECKSIGNATUREALGORITHM_TEST_PARAMS[] = +{ + { // Both algorithm IDs are empty + ByteString(), + ByteString(), + 2048 / 8, + Result::ERROR_BAD_DER, + }, + { // signatureAlgorithm is empty, signature is supported. + ByteString(), + BS(tlv_sha256WithRSAEncryption), + 2048 / 8, + Result::ERROR_BAD_DER, + }, + { // signatureAlgorithm is supported, signature is empty. + BS(tlv_sha256WithRSAEncryption), + ByteString(), + 2048 / 8, + Result::ERROR_BAD_DER, + }, + { // Algorithms match, both are supported. + BS(tlv_sha256WithRSAEncryption), + BS(tlv_sha256WithRSAEncryption), + 2048 / 8, + Success + }, + { // Algorithms do not match because signatureAlgorithm is truncated. + BS(tlv_sha256WithRSAEncryption_truncated), + BS(tlv_sha256WithRSAEncryption), + 2048 / 8, + Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED + }, + { // Algorithms do not match because signature is truncated. + BS(tlv_sha256WithRSAEncryption), + BS(tlv_sha256WithRSAEncryption_truncated), + 2048 / 8, + Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED + }, + { // Algorithms do not match, both are supported. + BS(tlv_sha_1WithRSAEncryption), + BS(tlv_sha256WithRSAEncryption), + 2048 / 8, + Result::ERROR_SIGNATURE_ALGORITHM_MISMATCH, + }, + { // Algorithms do not match, both are supported. + BS(tlv_sha256WithRSAEncryption), + BS(tlv_sha_1WithRSAEncryption), + 2048 / 8, + Result::ERROR_SIGNATURE_ALGORITHM_MISMATCH, + }, + { // Algorithms match, both are unsupported. + BS(tlv_md5WithRSAEncryption), + BS(tlv_md5WithRSAEncryption), + 2048 / 8, + Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED + }, + { // signatureAlgorithm is unsupported, signature is supported. + BS(tlv_md5WithRSAEncryption), + BS(tlv_sha256WithRSAEncryption), + 2048 / 8, + Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED + }, + { // signatureAlgorithm is supported, signature is unsupported. + BS(tlv_sha256WithRSAEncryption), + BS(tlv_md5WithRSAEncryption), + 2048 / 8, + Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED + }, + { // Both have the optional NULL parameter. + BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()), + BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()), + 2048 / 8, + Success + }, + { // signatureAlgorithm has the optional NULL parameter, signature doesn't. + BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()), + BS(tlv_sha256WithRSAEncryption), + 2048 / 8, + Success + }, + { // signatureAlgorithm does not have the optional NULL parameter, signature + // does. + BS(tlv_sha256WithRSAEncryption), + BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()), + 2048 / 8, + Success + }, + { // The different OIDs for RSA-with-SHA1 we support are semantically + // equivalent. + BS(tlv_sha1WithRSASignature), + BS(tlv_sha_1WithRSAEncryption), + 2048 / 8, + Success, + }, + { // The different OIDs for RSA-with-SHA1 we support are semantically + // equivalent (opposite order). + BS(tlv_sha_1WithRSAEncryption), + BS(tlv_sha1WithRSASignature), + 2048 / 8, + Success, + }, + { // Algorithms match, both are supported, key size is not a multile of 128 + // bits. This test verifies that we're not wrongly rounding up the + // signature size like we did in the original patch for bug 1131767. + BS(tlv_sha256WithRSAEncryption), + BS(tlv_sha256WithRSAEncryption), + (2048 / 8) - 1, + Success + }, +}; + +class pkixcheck_CheckSignatureAlgorithm + : public ::testing::Test + , public ::testing::WithParamInterface<CheckSignatureAlgorithmTestParams> +{ +}; + +class pkixcheck_CheckSignatureAlgorithm_TrustDomain final + : public EverythingFailsByDefaultTrustDomain +{ +public: + explicit pkixcheck_CheckSignatureAlgorithm_TrustDomain( + unsigned int publicKeySizeInBits) + : publicKeySizeInBits(publicKeySizeInBits) + , checkedDigestAlgorithm(false) + , checkedModulusSizeInBits(false) + { + } + + Result CheckSignatureDigestAlgorithm(DigestAlgorithm, EndEntityOrCA, Time) + override + { + checkedDigestAlgorithm = true; + return Success; + } + + Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA endEntityOrCA, + unsigned int modulusSizeInBits) + override + { + EXPECT_EQ(EndEntityOrCA::MustBeEndEntity, endEntityOrCA); + EXPECT_EQ(publicKeySizeInBits, modulusSizeInBits); + checkedModulusSizeInBits = true; + return Success; + } + + const unsigned int publicKeySizeInBits; + bool checkedDigestAlgorithm; + bool checkedModulusSizeInBits; +}; + +TEST_P(pkixcheck_CheckSignatureAlgorithm, CheckSignatureAlgorithm) +{ + const Time now(Now()); + const CheckSignatureAlgorithmTestParams& params(GetParam()); + + Input signatureValueInput; + ASSERT_EQ(Success, + signatureValueInput.Init(params.signatureValue.data(), + params.signatureValue.length())); + + pkixcheck_CheckSignatureAlgorithm_TrustDomain + trustDomain(params.signatureLengthInBytes * 8); + + der::SignedDataWithSignature signedData; + ASSERT_EQ(Success, + signedData.algorithm.Init(params.signatureAlgorithmValue.data(), + params.signatureAlgorithmValue.length())); + + ByteString dummySignature(params.signatureLengthInBytes, 0xDE); + ASSERT_EQ(Success, + signedData.signature.Init(dummySignature.data(), + dummySignature.length())); + + ASSERT_EQ(params.expectedResult, + CheckSignatureAlgorithm(trustDomain, EndEntityOrCA::MustBeEndEntity, + now, signedData, signatureValueInput)); + ASSERT_EQ(params.expectedResult == Success, + trustDomain.checkedDigestAlgorithm); + ASSERT_EQ(params.expectedResult == Success, + trustDomain.checkedModulusSizeInBits); +} + +INSTANTIATE_TEST_CASE_P( + pkixcheck_CheckSignatureAlgorithm, pkixcheck_CheckSignatureAlgorithm, + testing::ValuesIn(CHECKSIGNATUREALGORITHM_TEST_PARAMS)); + +class pkixcheck_CheckSignatureAlgorithm_BuildCertChain_TrustDomain + : public DefaultCryptoTrustDomain +{ +public: + explicit pkixcheck_CheckSignatureAlgorithm_BuildCertChain_TrustDomain( + const ByteString& issuer) + : issuer(issuer) + { + } + + Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, + Input cert, /*out*/ TrustLevel& trustLevel) override + { + trustLevel = InputEqualsByteString(cert, issuer) + ? TrustLevel::TrustAnchor + : TrustLevel::InheritsTrust; + return Success; + } + + Result FindIssuer(Input, IssuerChecker& checker, Time) override + { + EXPECT_FALSE(ENCODING_FAILED(issuer)); + + Input issuerInput; + EXPECT_EQ(Success, issuerInput.Init(issuer.data(), issuer.length())); + + bool keepGoing; + EXPECT_EQ(Success, checker.Check(issuerInput, nullptr, keepGoing)); + EXPECT_FALSE(keepGoing); + + return Success; + } + + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, + /*optional*/ const Input*, + /*optional*/ const Input*) override + { + return Success; + } + + Result IsChainValid(const DERArray&, Time) override + { + return Success; + } + + ByteString issuer; +}; + +// Test that CheckSignatureAlgorithm actually gets called at some point when +// BuildCertChain is called. +TEST_F(pkixcheck_CheckSignatureAlgorithm, BuildCertChain) +{ + ScopedTestKeyPair keyPair(CloneReusedKeyPair()); + ASSERT_TRUE(keyPair.get()); + + ByteString issuerExtensions[2]; + issuerExtensions[0] = CreateEncodedBasicConstraints(true, nullptr, + Critical::No); + ASSERT_FALSE(ENCODING_FAILED(issuerExtensions[0])); + + ByteString issuer(CreateEncodedCertificate(3, + sha256WithRSAEncryption(), + CreateEncodedSerialNumber(1), + CNToDERName("issuer"), + oneDayBeforeNow, oneDayAfterNow, + CNToDERName("issuer"), + *keyPair, + issuerExtensions, + *keyPair, + sha256WithRSAEncryption())); + ASSERT_FALSE(ENCODING_FAILED(issuer)); + + ByteString subject(CreateEncodedCertificate(3, + sha1WithRSAEncryption(), + CreateEncodedSerialNumber(2), + CNToDERName("issuer"), + oneDayBeforeNow, oneDayAfterNow, + CNToDERName("subject"), + *keyPair, + nullptr, + *keyPair, + sha256WithRSAEncryption())); + ASSERT_FALSE(ENCODING_FAILED(subject)); + + Input subjectInput; + ASSERT_EQ(Success, subjectInput.Init(subject.data(), subject.length())); + pkixcheck_CheckSignatureAlgorithm_BuildCertChain_TrustDomain + trustDomain(issuer); + Result rv = BuildCertChain(trustDomain, subjectInput, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::anyExtendedKeyUsage, + CertPolicyId::anyPolicy, + nullptr); + ASSERT_EQ(Result::ERROR_SIGNATURE_ALGORITHM_MISMATCH, rv); +} diff --git a/security/pkix/test/gtest/pkixcheck_CheckValidity_tests.cpp b/security/pkix/test/gtest/pkixcheck_CheckValidity_tests.cpp new file mode 100644 index 000000000..a77a2f47c --- /dev/null +++ b/security/pkix/test/gtest/pkixcheck_CheckValidity_tests.cpp @@ -0,0 +1,127 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2014 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixcheck.h" +#include "pkixgtest.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +static const Time PAST_TIME(YMDHMS(1998, 12, 31, 12, 23, 56)); + +#define OLDER_GENERALIZEDTIME \ + 0x18, 15, /* tag, length */ \ + '1', '9', '9', '9', '0', '1', '0', '1', /* 1999-01-01 */ \ + '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */ + +#define OLDER_UTCTIME \ + 0x17, 13, /* tag, length */ \ + '9', '9', '0', '1', '0', '1', /* (19)99-01-01 */ \ + '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */ + +static const Time NOW(YMDHMS(2016, 12, 31, 12, 23, 56)); + +#define NEWER_GENERALIZEDTIME \ + 0x18, 15, /* tag, length */ \ + '2', '0', '2', '1', '0', '1', '0', '1', /* 2021-01-01 */ \ + '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */ + +#define NEWER_UTCTIME \ + 0x17, 13, /* tag, length */ \ + '2', '1', '0', '1', '0', '1', /* 2021-01-01 */ \ + '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */ + +static const Time FUTURE_TIME(YMDHMS(2025, 12, 31, 12, 23, 56)); + +class pkixcheck_CheckValidity : public ::testing::Test { }; + +static const uint8_t OLDER_UTCTIME_NEWER_UTCTIME_DATA[] = { + OLDER_UTCTIME, + NEWER_UTCTIME, +}; +static const Input +OLDER_UTCTIME_NEWER_UTCTIME(OLDER_UTCTIME_NEWER_UTCTIME_DATA); + +TEST_F(pkixcheck_CheckValidity, Valid_UTCTIME_UTCTIME) +{ + static Time notBefore(Time::uninitialized); + static Time notAfter(Time::uninitialized); + ASSERT_EQ(Success, ParseValidity(OLDER_UTCTIME_NEWER_UTCTIME, ¬Before, ¬After)); + 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, ¬Before, ¬After)); + 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, ¬Before, ¬After)); + 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, ¬Before, ¬After)); + 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, ¬Before, ¬After)); + 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, ¬Before, ¬After)); + ASSERT_EQ(Result::ERROR_EXPIRED_CERTIFICATE, CheckValidity(FUTURE_TIME, notBefore, notAfter)); +} diff --git a/security/pkix/test/gtest/pkixcheck_ParseValidity_tests.cpp b/security/pkix/test/gtest/pkixcheck_ParseValidity_tests.cpp new file mode 100644 index 000000000..5206ce14f --- /dev/null +++ b/security/pkix/test/gtest/pkixcheck_ParseValidity_tests.cpp @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2014 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixcheck.h" +#include "pkixgtest.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +#define OLDER_UTCTIME \ + 0x17, 13, /* tag, length */ \ + '9', '9', '0', '1', '0', '1', /* (19)99-01-01 */ \ + '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */ + +#define NEWER_UTCTIME \ + 0x17, 13, /* tag, length */ \ + '2', '1', '0', '1', '0', '1', /* 2021-01-01 */ \ + '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */ + +static const Time FUTURE_TIME(YMDHMS(2025, 12, 31, 12, 23, 56)); + +class pkixcheck_ParseValidity : public ::testing::Test { }; + +TEST_F(pkixcheck_ParseValidity, BothEmptyNull) +{ + static const uint8_t DER[] = { + 0x17/*UTCTime*/, 0/*length*/, + 0x17/*UTCTime*/, 0/*length*/, + }; + static const Input validity(DER); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity)); +} + +TEST_F(pkixcheck_ParseValidity, NotBeforeEmptyNull) +{ + static const uint8_t DER[] = { + 0x17/*UTCTime*/, 0x00/*length*/, + NEWER_UTCTIME + }; + static const Input validity(DER); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity)); +} + +TEST_F(pkixcheck_ParseValidity, NotAfterEmptyNull) +{ + static const uint8_t DER[] = { + NEWER_UTCTIME, + 0x17/*UTCTime*/, 0x00/*length*/, + }; + static const Input validity(DER); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity)); +} + +TEST_F(pkixcheck_ParseValidity, InvalidNotAfterBeforeNotBefore) +{ + static const uint8_t DER[] = { + NEWER_UTCTIME, + OLDER_UTCTIME, + }; + static const Input validity(DER); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity)); +} diff --git a/security/pkix/test/gtest/pkixcheck_TLSFeaturesSatisfiedInternal_tests.cpp b/security/pkix/test/gtest/pkixcheck_TLSFeaturesSatisfiedInternal_tests.cpp new file mode 100644 index 000000000..28db4d150 --- /dev/null +++ b/security/pkix/test/gtest/pkixcheck_TLSFeaturesSatisfiedInternal_tests.cpp @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2015 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixder.h" +#include "pkixgtest.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +namespace mozilla { namespace pkix { + extern Result TLSFeaturesSatisfiedInternal(const Input* requiredTLSFeatures, + const Input* stapledOCSPResponse); +} } // namespace mozilla::pkix + +struct TLSFeaturesTestParams +{ + ByteString requiredTLSFeatures; + Result expectedResultWithResponse; + Result expectedResultWithoutResponse; +}; + +#define BS(s) ByteString(s, MOZILLA_PKIX_ARRAY_LENGTH(s)) +static const uint8_t statusRequest[] = { + 0x30, 0x03, 0x02, 0x01, 0x05 +}; + +static const uint8_t unknown[] = { + 0x30, 0x03, 0x02, 0x01, 0x06 +}; + +static const uint8_t statusRequestAndUnknown[] = { + 0x30, 0x06, 0x02, 0x01, 0x05, 0x02, 0x01, 0x06 +}; + +static const uint8_t duplicateStatusRequest[] = { + 0x30, 0x06, 0x02, 0x01, 0x05, 0x02, 0x01, 0x05 +}; + +static const uint8_t twoByteUnknown[] = { + 0x30, 0x04, 0x02, 0x02, 0x05, 0x05 +}; + +static const uint8_t zeroByteInteger[] = { + 0x30, 0x02, 0x02, 0x00 +}; + +static const TLSFeaturesTestParams + TLSFEATURESSATISFIED_TEST_PARAMS[] = +{ + // some tests with checks enforced + { ByteString(), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, + { BS(statusRequest), Success, Result::ERROR_REQUIRED_TLS_FEATURE_MISSING }, + { BS(unknown), Result::ERROR_REQUIRED_TLS_FEATURE_MISSING, + Result::ERROR_REQUIRED_TLS_FEATURE_MISSING }, + { BS(statusRequestAndUnknown), Result::ERROR_REQUIRED_TLS_FEATURE_MISSING, + Result::ERROR_REQUIRED_TLS_FEATURE_MISSING }, + { BS(duplicateStatusRequest), Success, + Result::ERROR_REQUIRED_TLS_FEATURE_MISSING }, + { BS(twoByteUnknown), Result::ERROR_REQUIRED_TLS_FEATURE_MISSING, + Result::ERROR_REQUIRED_TLS_FEATURE_MISSING }, + { BS(zeroByteInteger), Result::ERROR_REQUIRED_TLS_FEATURE_MISSING, + Result::ERROR_REQUIRED_TLS_FEATURE_MISSING }, +}; + +class pkixcheck_TLSFeaturesSatisfiedInternal + : public ::testing::Test + , public ::testing::WithParamInterface<TLSFeaturesTestParams> +{ +}; + +TEST_P(pkixcheck_TLSFeaturesSatisfiedInternal, TLSFeaturesSatisfiedInternal) { + const TLSFeaturesTestParams& params(GetParam()); + + Input featuresInput; + ASSERT_EQ(Success, featuresInput.Init(params.requiredTLSFeatures.data(), + params.requiredTLSFeatures.length())); + Input responseInput; + // just create an input with any data in it + ByteString stapledOCSPResponse = BS(statusRequest); + ASSERT_EQ(Success, responseInput.Init(stapledOCSPResponse.data(), + stapledOCSPResponse.length())); + // first we omit the response + ASSERT_EQ(params.expectedResultWithoutResponse, + TLSFeaturesSatisfiedInternal(&featuresInput, nullptr)); + // then we try again with the response + ASSERT_EQ(params.expectedResultWithResponse, + TLSFeaturesSatisfiedInternal(&featuresInput, &responseInput)); +} + +INSTANTIATE_TEST_CASE_P( + pkixcheck_TLSFeaturesSatisfiedInternal, + pkixcheck_TLSFeaturesSatisfiedInternal, + testing::ValuesIn(TLSFEATURESSATISFIED_TEST_PARAMS)); diff --git a/security/pkix/test/gtest/pkixder_input_tests.cpp b/security/pkix/test/gtest/pkixder_input_tests.cpp new file mode 100644 index 000000000..b0a6c8bf0 --- /dev/null +++ b/security/pkix/test/gtest/pkixder_input_tests.cpp @@ -0,0 +1,920 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2013 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <functional> +#include <vector> +#include "pkixgtest.h" + +#include "pkixder.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::der; + +namespace { + +class pkixder_input_tests : public ::testing::Test { }; + +static const uint8_t DER_SEQUENCE_EMPTY[] = { + 0x30, // SEQUENCE + 0x00, // length +}; + +static const uint8_t DER_SEQUENCE_NOT_EMPTY[] = { + 0x30, // SEQUENCE + 0x01, // length + 'X', // value +}; + +static const uint8_t DER_SEQUENCE_NOT_EMPTY_VALUE[] = { + 'X', // value +}; + +static const uint8_t DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED[] = { + 0x30, // SEQUENCE + 0x01, // length +}; + +const uint8_t DER_SEQUENCE_OF_INT8[] = { + 0x30, // SEQUENCE + 0x09, // length + 0x02, 0x01, 0x01, // INTEGER length 1 value 0x01 + 0x02, 0x01, 0x02, // INTEGER length 1 value 0x02 + 0x02, 0x01, 0x03 // INTEGER length 1 value 0x03 +}; + +const uint8_t DER_TRUNCATED_SEQUENCE_OF_INT8[] = { + 0x30, // SEQUENCE + 0x09, // length + 0x02, 0x01, 0x01, // INTEGER length 1 value 0x01 + 0x02, 0x01, 0x02 // INTEGER length 1 value 0x02 + // MISSING DATA HERE ON PURPOSE +}; + +const uint8_t DER_OVERRUN_SEQUENCE_OF_INT8[] = { + 0x30, // SEQUENCE + 0x09, // length + 0x02, 0x01, 0x01, // INTEGER length 1 value 0x01 + 0x02, 0x01, 0x02, // INTEGER length 1 value 0x02 + 0x02, 0x02, 0xFF, 0x03 // INTEGER length 2 value 0xFF03 +}; + +const uint8_t DER_INT16[] = { + 0x02, // INTEGER + 0x02, // length + 0x12, 0x34 // 0x1234 +}; + +static const Input EMPTY_INPUT; + +TEST_F(pkixder_input_tests, InputInit) +{ + Input buf; + ASSERT_EQ(Success, + buf.Init(DER_SEQUENCE_OF_INT8, sizeof DER_SEQUENCE_OF_INT8)); +} + +TEST_F(pkixder_input_tests, InputInitWithNullPointerOrZeroLength) +{ + Input buf; + ASSERT_EQ(Result::ERROR_BAD_DER, buf.Init(nullptr, 0)); + + ASSERT_EQ(Result::ERROR_BAD_DER, buf.Init(nullptr, 100)); + + // Though it seems odd to initialize with zero-length and non-null ptr, this + // is working as intended. The Reader class was intended to protect against + // buffer overflows, and there's no risk with the current behavior. See bug + // 1000354. + ASSERT_EQ(Success, buf.Init((const uint8_t*) "hello", 0)); + ASSERT_TRUE(buf.GetLength() == 0); +} + +TEST_F(pkixder_input_tests, InputInitWithLargeData) +{ + Input buf; + // Data argument length does not matter, it is not touched, just + // needs to be non-null + ASSERT_EQ(Result::ERROR_BAD_DER, buf.Init((const uint8_t*) "", 0xffff+1)); + + ASSERT_EQ(Success, buf.Init((const uint8_t*) "", 0xffff)); +} + +TEST_F(pkixder_input_tests, InputInitMultipleTimes) +{ + Input buf; + + ASSERT_EQ(Success, + buf.Init(DER_SEQUENCE_OF_INT8, sizeof DER_SEQUENCE_OF_INT8)); + + ASSERT_EQ(Result::FATAL_ERROR_INVALID_ARGS, + buf.Init(DER_SEQUENCE_OF_INT8, sizeof DER_SEQUENCE_OF_INT8)); +} + +TEST_F(pkixder_input_tests, PeekWithinBounds) +{ + const uint8_t der[] = { 0x11, 0x11 }; + Input buf(der); + Reader input(buf); + ASSERT_TRUE(input.Peek(0x11)); + ASSERT_FALSE(input.Peek(0x22)); +} + +TEST_F(pkixder_input_tests, PeekPastBounds) +{ + const uint8_t der[] = { 0x11, 0x22 }; + Input buf; + ASSERT_EQ(Success, buf.Init(der, 1)); + Reader input(buf); + + uint8_t readByte; + ASSERT_EQ(Success, input.Read(readByte)); + ASSERT_EQ(0x11, readByte); + ASSERT_FALSE(input.Peek(0x22)); +} + +TEST_F(pkixder_input_tests, ReadByte) +{ + const uint8_t der[] = { 0x11, 0x22 }; + Input buf(der); + Reader input(buf); + + uint8_t readByte1; + ASSERT_EQ(Success, input.Read(readByte1)); + ASSERT_EQ(0x11, readByte1); + + uint8_t readByte2; + ASSERT_EQ(Success, input.Read(readByte2)); + ASSERT_EQ(0x22, readByte2); +} + +TEST_F(pkixder_input_tests, ReadBytePastEnd) +{ + const uint8_t der[] = { 0x11, 0x22 }; + Input buf; + ASSERT_EQ(Success, buf.Init(der, 1)); + Reader input(buf); + + uint8_t readByte1 = 0; + ASSERT_EQ(Success, input.Read(readByte1)); + ASSERT_EQ(0x11, readByte1); + + uint8_t readByte2 = 0; + ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(readByte2)); + ASSERT_NE(0x22, readByte2); +} + +TEST_F(pkixder_input_tests, ReadByteWrapAroundPointer) +{ + // The original implementation of our buffer read overflow checks was + // susceptible to integer overflows which could make the checks ineffective. + // This attempts to verify that we've fixed that. Unfortunately, decrementing + // a null pointer is undefined behavior according to the C++ language spec., + // but this should catch the problem on at least some compilers, if not all of + // them. + const uint8_t* der = nullptr; + --der; + Input buf; + ASSERT_EQ(Success, buf.Init(der, 0)); + Reader input(buf); + + uint8_t b; + ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(b)); +} + +TEST_F(pkixder_input_tests, ReadWord) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + + uint16_t readWord1 = 0; + ASSERT_EQ(Success, input.Read(readWord1)); + ASSERT_EQ(0x1122, readWord1); + + uint16_t readWord2 = 0; + ASSERT_EQ(Success, input.Read(readWord2)); + ASSERT_EQ(0x3344, readWord2); +} + +TEST_F(pkixder_input_tests, ReadWordPastEnd) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf; + ASSERT_EQ(Success, buf.Init(der, 2)); // Initialize with too-short length + Reader input(buf); + + uint16_t readWord1 = 0; + ASSERT_EQ(Success, input.Read(readWord1)); + ASSERT_EQ(0x1122, readWord1); + + uint16_t readWord2 = 0; + ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(readWord2)); + ASSERT_NE(0x3344, readWord2); +} + +TEST_F(pkixder_input_tests, ReadWordWithInsufficentData) +{ + const uint8_t der[] = { 0x11, 0x22 }; + Input buf; + ASSERT_EQ(Success, buf.Init(der, 1)); + Reader input(buf); + + uint16_t readWord1 = 0; + ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(readWord1)); + ASSERT_NE(0x1122, readWord1); +} + +TEST_F(pkixder_input_tests, ReadWordWrapAroundPointer) +{ + // The original implementation of our buffer read overflow checks was + // susceptible to integer overflows which could make the checks ineffective. + // This attempts to verify that we've fixed that. Unfortunately, decrementing + // a null pointer is undefined behavior according to the C++ language spec., + // but this should catch the problem on at least some compilers, if not all of + // them. + const uint8_t* der = nullptr; + --der; + Input buf; + ASSERT_EQ(Success, buf.Init(der, 0)); + Reader input(buf); + uint16_t b; + ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(b)); +} + +TEST_F(pkixder_input_tests, Skip) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + + ASSERT_EQ(Success, input.Skip(1)); + + uint8_t readByte1 = 0; + ASSERT_EQ(Success, input.Read(readByte1)); + ASSERT_EQ(0x22, readByte1); + + ASSERT_EQ(Success, input.Skip(1)); + + uint8_t readByte2 = 0; + ASSERT_EQ(Success, input.Read(readByte2)); + ASSERT_EQ(0x44, readByte2); +} + +TEST_F(pkixder_input_tests, Skip_ToEnd) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + ASSERT_EQ(Success, input.Skip(sizeof der)); + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, Skip_PastEnd) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + + ASSERT_EQ(Result::ERROR_BAD_DER, input.Skip(sizeof der + 1)); +} + +TEST_F(pkixder_input_tests, Skip_ToNewInput) +{ + const uint8_t der[] = { 0x01, 0x02, 0x03, 0x04 }; + Input buf(der); + Reader input(buf); + + Reader skippedInput; + ASSERT_EQ(Success, input.Skip(3, skippedInput)); + + uint8_t readByte1 = 0; + ASSERT_EQ(Success, input.Read(readByte1)); + ASSERT_EQ(0x04, readByte1); + + ASSERT_TRUE(input.AtEnd()); + + // Reader has no Remaining() or Length() so we simply read the bytes + // and then expect to be at the end. + + for (uint8_t i = 1; i <= 3; ++i) { + uint8_t readByte = 0; + ASSERT_EQ(Success, skippedInput.Read(readByte)); + ASSERT_EQ(i, readByte); + } + + ASSERT_TRUE(skippedInput.AtEnd()); +} + +TEST_F(pkixder_input_tests, Skip_ToNewInputPastEnd) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + + Reader skippedInput; + ASSERT_EQ(Result::ERROR_BAD_DER, input.Skip(sizeof der * 2, skippedInput)); +} + +TEST_F(pkixder_input_tests, Skip_ToInput) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + + const uint8_t expectedItemData[] = { 0x11, 0x22, 0x33 }; + + Input item; + ASSERT_EQ(Success, input.Skip(sizeof expectedItemData, item)); + + Input expected(expectedItemData); + ASSERT_TRUE(InputsAreEqual(expected, item)); +} + +TEST_F(pkixder_input_tests, Skip_WrapAroundPointer) +{ + // The original implementation of our buffer read overflow checks was + // susceptible to integer overflows which could make the checks ineffective. + // This attempts to verify that we've fixed that. Unfortunately, decrementing + // a null pointer is undefined behavior according to the C++ language spec., + // but this should catch the problem on at least some compilers, if not all of + // them. + const uint8_t* der = nullptr; + --der; + Input buf; + ASSERT_EQ(Success, buf.Init(der, 0)); + Reader input(buf); + ASSERT_EQ(Result::ERROR_BAD_DER, input.Skip(1)); +} + +TEST_F(pkixder_input_tests, Skip_ToInputPastEnd) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + + Input skipped; + ASSERT_EQ(Result::ERROR_BAD_DER, input.Skip(sizeof der + 1, skipped)); +} + +TEST_F(pkixder_input_tests, SkipToEnd_ToInput) +{ + static const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + + Input skipped; + ASSERT_EQ(Success, input.SkipToEnd(skipped)); +} + +TEST_F(pkixder_input_tests, SkipToEnd_ToInput_InputAlreadyInited) +{ + static const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + + static const uint8_t initialValue[] = { 0x01, 0x02, 0x03 }; + Input x(initialValue); + // Fails because skipped was already initialized once, and Inputs are not + // allowed to be Init()d multiple times. + ASSERT_EQ(Result::FATAL_ERROR_INVALID_ARGS, input.SkipToEnd(x)); + ASSERT_TRUE(InputsAreEqual(x, Input(initialValue))); +} + +TEST_F(pkixder_input_tests, ExpectTagAndSkipValue) +{ + Input buf(DER_SEQUENCE_OF_INT8); + Reader input(buf); + + ASSERT_EQ(Success, ExpectTagAndSkipValue(input, SEQUENCE)); + ASSERT_EQ(Success, End(input)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndSkipValueWithTruncatedData) +{ + Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8); + Reader input(buf); + + ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndSkipValue(input, SEQUENCE)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndSkipValueWithOverrunData) +{ + Input buf(DER_OVERRUN_SEQUENCE_OF_INT8); + Reader input(buf); + ASSERT_EQ(Success, ExpectTagAndSkipValue(input, SEQUENCE)); + ASSERT_EQ(Result::ERROR_BAD_DER, End(input)); +} + +TEST_F(pkixder_input_tests, AtEndOnUnInitializedInput) +{ + Reader input; + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, AtEndAtBeginning) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + ASSERT_FALSE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, AtEndAtEnd) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + ASSERT_EQ(Success, input.Skip(sizeof der)); + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, MarkAndGetInput) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + + Reader::Mark mark = input.GetMark(); + + const uint8_t expectedItemData[] = { 0x11, 0x22, 0x33 }; + + ASSERT_EQ(Success, input.Skip(sizeof expectedItemData)); + + Input item; + ASSERT_EQ(Success, input.GetInput(mark, item)); + Input expected(expectedItemData); + ASSERT_TRUE(InputsAreEqual(expected, item)); +} + +// Cannot run this test on debug builds because of the NotReached +#ifdef NDEBUG +TEST_F(pkixder_input_tests, MarkAndGetInputDifferentInput) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + + Reader another; + Reader::Mark mark = another.GetMark(); + + ASSERT_EQ(Success, input.Skip(3)); + + Input item; + ASSERT_EQ(Result::FATAL_ERROR_INVALID_ARGS, input.GetInput(mark, item)); +} +#endif + +TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_AtEnd) +{ + Reader input(EMPTY_INPUT); + uint8_t tag; + Input value; + ASSERT_EQ(Result::ERROR_BAD_DER, ReadTagAndGetValue(input, tag, value)); +} + +TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_TruncatedAfterTag) +{ + static const uint8_t DER[] = { SEQUENCE }; + Input buf(DER); + Reader input(buf); + uint8_t tag; + Input value; + ASSERT_EQ(Result::ERROR_BAD_DER, ReadTagAndGetValue(input, tag, value)); +} + +TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_ValidEmpty) +{ + Input buf(DER_SEQUENCE_EMPTY); + Reader input(buf); + uint8_t tag = 0; + Input value; + ASSERT_EQ(Success, ReadTagAndGetValue(input, tag, value)); + ASSERT_EQ(SEQUENCE, tag); + ASSERT_EQ(0u, value.GetLength()); + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_ValidNotEmpty) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY); + Reader input(buf); + uint8_t tag = 0; + Input value; + ASSERT_EQ(Success, ReadTagAndGetValue(input, tag, value)); + ASSERT_EQ(SEQUENCE, tag); + Input expected(DER_SEQUENCE_NOT_EMPTY_VALUE); + ASSERT_TRUE(InputsAreEqual(expected, value)); + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, + ReadTagAndGetValue_Input_InvalidNotEmptyValueTruncated) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED); + Reader input(buf); + uint8_t tag; + Input value; + ASSERT_EQ(Result::ERROR_BAD_DER, ReadTagAndGetValue(input, tag, value)); +} + +TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_InvalidWrongLength) +{ + Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8); + Reader input(buf); + uint8_t tag; + Input value; + ASSERT_EQ(Result::ERROR_BAD_DER, + ReadTagAndGetValue(input, tag, value)); +} + +TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_InvalidHighTagNumberForm1) +{ + // High tag number form is not allowed (illegal 1 byte tag) + // + // If the decoder treats 0x1F as a valid low tag number tag, then it will + // treat the actual tag (1) as a length, and then it will return Success + // with value == { 0x00 } and tag == 0x1f. + // + // It is illegal to encode tag 1 in the high tag number form because it isn't + // the shortest encoding (the low tag number form is). + static const uint8_t DER[] = { + 0x1F, // high tag number form indicator + 1, // tag 1 (not legal!) + 0 // length zero + }; + Input buf(DER); + Reader input(buf); + uint8_t tag; + Input value; + ASSERT_EQ(Result::ERROR_BAD_DER, + ReadTagAndGetValue(input, tag, value)); +} + +TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_InvalidHighTagNumberForm2) +{ + // High tag number form is not allowed (legal 1 byte tag). + // + // ReadTagAndGetValue's check to prohibit the high tag number form has no + // effect on whether this test passes or fails, because ReadTagAndGetValue + // will interpret the second byte (31) as a length, and the input doesn't + // have 31 bytes following it. This test is here to guard against the case + // where somebody actually implements high tag number form parsing, to remind + // that person that they need to add tests here, including in particular + // tests for overly-long encodings. + static const uint8_t DER[] = { + 0x1F, // high tag number form indicator + 31, // tag 31 + 0 // length zero + }; + Input buf(DER); + Reader input(buf); + uint8_t tag; + Input value; + ASSERT_EQ(Result::ERROR_BAD_DER, + ReadTagAndGetValue(input, tag, value)); +} + +TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_InvalidHighTagNumberForm3) +{ + // High tag number form is not allowed (2 byte legal tag) + // + // ReadTagAndGetValue's check to prohibit the high tag number form has no + // effect on whether this test passes or fails, because ReadTagAndGetValue + // will interpret the second byte as a length, and the input doesn't have + // that many bytes following it. This test is here to guard against the case + // where somebody actually implements high tag number form parsing, to remind + // that person that they need to add tests here, including in particular + // tests for overly-long encodings. + static const uint8_t DER[] = { + 0x1F, // high tag number form indicator + 0x80 | 0x01, 0x00, // tag 0x100 (256) + 0 // length zero + }; + Input buf(DER); + Reader input(buf); + uint8_t tag; + Input value; + ASSERT_EQ(Result::ERROR_BAD_DER, + ReadTagAndGetValue(input, tag, value)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Reader_ValidEmpty) +{ + Input buf(DER_SEQUENCE_EMPTY); + Reader input(buf); + Reader value; + ASSERT_EQ(Success, ExpectTagAndGetValue(input, SEQUENCE, value)); + ASSERT_TRUE(value.AtEnd()); + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Reader_ValidNotEmpty) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY); + Reader input(buf); + Reader value; + ASSERT_EQ(Success, ExpectTagAndGetValue(input, SEQUENCE, value)); + ASSERT_TRUE(value.MatchRest(DER_SEQUENCE_NOT_EMPTY_VALUE)); + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, + ExpectTagAndGetValue_Reader_InvalidNotEmptyValueTruncated) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED); + Reader input(buf); + Reader value; + ASSERT_EQ(Result::ERROR_BAD_DER, + ExpectTagAndGetValue(input, SEQUENCE, value)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Reader_InvalidWrongLength) +{ + Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8); + Reader input(buf); + Reader value; + ASSERT_EQ(Result::ERROR_BAD_DER, + ExpectTagAndGetValue(input, SEQUENCE, value)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Reader_InvalidWrongTag) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY); + Reader input(buf); + Reader value; + ASSERT_EQ(Result::ERROR_BAD_DER, + ExpectTagAndGetValue(input, INTEGER, value)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Input_ValidEmpty) +{ + Input buf(DER_SEQUENCE_EMPTY); + Reader input(buf); + Input value; + ASSERT_EQ(Success, ExpectTagAndGetValue(input, SEQUENCE, value)); + ASSERT_EQ(0u, value.GetLength()); + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Input_ValidNotEmpty) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY); + Reader input(buf); + Input value; + ASSERT_EQ(Success, ExpectTagAndGetValue(input, SEQUENCE, value)); + Input expected(DER_SEQUENCE_NOT_EMPTY_VALUE); + ASSERT_TRUE(InputsAreEqual(expected, value)); + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, + ExpectTagAndGetValue_Input_InvalidNotEmptyValueTruncated) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED); + Reader input(buf); + Input value; + ASSERT_EQ(Result::ERROR_BAD_DER, + ExpectTagAndGetValue(input, SEQUENCE, value)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Input_InvalidWrongLength) +{ + Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8); + Reader input(buf); + Input value; + ASSERT_EQ(Result::ERROR_BAD_DER, + ExpectTagAndGetValue(input, SEQUENCE, value)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Input_InvalidWrongTag) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY); + Reader input(buf); + Input value; + ASSERT_EQ(Result::ERROR_BAD_DER, + ExpectTagAndGetValue(input, INTEGER, value)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndEmptyValue_ValidEmpty) +{ + Input buf(DER_SEQUENCE_EMPTY); + Reader input(buf); + ASSERT_EQ(Success, ExpectTagAndEmptyValue(input, SEQUENCE)); + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, ExpectTagAndEmptyValue_InValidNotEmpty) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY); + Reader input(buf); + ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndEmptyValue(input, SEQUENCE)); +} + +TEST_F(pkixder_input_tests, + ExpectTagAndEmptyValue_Input_InvalidNotEmptyValueTruncated) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED); + Reader input(buf); + ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndEmptyValue(input, SEQUENCE)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndEmptyValue_InvalidWrongLength) +{ + Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8); + Reader input(buf); + ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndEmptyValue(input, SEQUENCE)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndEmptyValue_InvalidWrongTag) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY); + Reader input(buf); + ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndEmptyValue(input, INTEGER)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetTLV_Input_ValidEmpty) +{ + Input buf(DER_SEQUENCE_EMPTY); + Reader input(buf); + Input tlv; + ASSERT_EQ(Success, ExpectTagAndGetTLV(input, SEQUENCE, tlv)); + Input expected(DER_SEQUENCE_EMPTY); + ASSERT_TRUE(InputsAreEqual(expected, tlv)); + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetTLV_Input_ValidNotEmpty) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY); + Reader input(buf); + Input tlv; + ASSERT_EQ(Success, ExpectTagAndGetTLV(input, SEQUENCE, tlv)); + Input expected(DER_SEQUENCE_NOT_EMPTY); + ASSERT_TRUE(InputsAreEqual(expected, tlv)); + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, + ExpectTagAndGetTLV_Input_InvalidNotEmptyValueTruncated) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED); + Reader input(buf); + Input tlv; + ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndGetTLV(input, SEQUENCE, tlv)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetTLV_Input_InvalidWrongLength) +{ + Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8); + Reader input(buf); + Input tlv; + ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndGetTLV(input, SEQUENCE, tlv)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetTLV_Input_InvalidWrongTag) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY); + Reader input(buf); + Input tlv; + ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndGetTLV(input, INTEGER, tlv)); +} + +TEST_F(pkixder_input_tests, EndAtEnd) +{ + Input buf(DER_INT16); + Reader input(buf); + ASSERT_EQ(Success, input.Skip(4)); + ASSERT_EQ(Success, End(input)); +} + +TEST_F(pkixder_input_tests, EndBeforeEnd) +{ + Input buf(DER_INT16); + Reader input(buf); + ASSERT_EQ(Success, input.Skip(2)); + ASSERT_EQ(Result::ERROR_BAD_DER, End(input)); +} + +TEST_F(pkixder_input_tests, EndAtBeginning) +{ + Input buf(DER_INT16); + Reader input(buf); + ASSERT_EQ(Result::ERROR_BAD_DER, End(input)); +} + +// TODO: Need tests for Nested too? + +Result NestedOfHelper(Reader& input, std::vector<uint8_t>& readValues) +{ + uint8_t value = 0; + Result rv = input.Read(value); + EXPECT_EQ(Success, rv); + if (rv != Success) { + return rv; + } + readValues.push_back(value); + return Success; +} + +TEST_F(pkixder_input_tests, NestedOf) +{ + Input buf(DER_SEQUENCE_OF_INT8); + Reader input(buf); + + std::vector<uint8_t> readValues; + ASSERT_EQ(Success, + NestedOf(input, SEQUENCE, INTEGER, EmptyAllowed::No, + [&readValues](Reader& r) { + return NestedOfHelper(r, readValues); + })); + ASSERT_EQ(3u, readValues.size()); + ASSERT_EQ(0x01, readValues[0]); + ASSERT_EQ(0x02, readValues[1]); + ASSERT_EQ(0x03, readValues[2]); + ASSERT_EQ(Success, End(input)); +} + +TEST_F(pkixder_input_tests, NestedOfWithTruncatedData) +{ + Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8); + Reader input(buf); + + std::vector<uint8_t> readValues; + ASSERT_EQ(Result::ERROR_BAD_DER, + NestedOf(input, SEQUENCE, INTEGER, EmptyAllowed::No, + [&readValues](Reader& r) { + return NestedOfHelper(r, readValues); + })); + ASSERT_EQ(0u, readValues.size()); +} + +TEST_F(pkixder_input_tests, MatchRestAtEnd) +{ + static const uint8_t der[1] = { }; + Input buf; + ASSERT_EQ(Success, buf.Init(der, 0)); + Reader input(buf); + ASSERT_TRUE(input.AtEnd()); + static const uint8_t toMatch[] = { 1 }; + ASSERT_FALSE(input.MatchRest(toMatch)); +} + +TEST_F(pkixder_input_tests, MatchRest1Match) +{ + static const uint8_t der[] = { 1 }; + Input buf(der); + Reader input(buf); + ASSERT_FALSE(input.AtEnd()); + ASSERT_TRUE(input.MatchRest(der)); +} + +TEST_F(pkixder_input_tests, MatchRest1Mismatch) +{ + static const uint8_t der[] = { 1 }; + Input buf(der); + Reader input(buf); + static const uint8_t toMatch[] = { 2 }; + ASSERT_FALSE(input.MatchRest(toMatch)); + ASSERT_FALSE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, MatchRest2WithTrailingByte) +{ + static const uint8_t der[] = { 1, 2, 3 }; + Input buf(der); + Reader input(buf); + static const uint8_t toMatch[] = { 1, 2 }; + ASSERT_FALSE(input.MatchRest(toMatch)); +} + +TEST_F(pkixder_input_tests, MatchRest2Mismatch) +{ + static const uint8_t der[] = { 1, 2, 3 }; + Input buf(der); + Reader input(buf); + static const uint8_t toMatchMismatch[] = { 1, 3 }; + ASSERT_FALSE(input.MatchRest(toMatchMismatch)); + ASSERT_TRUE(input.MatchRest(der)); +} + +} // namespace diff --git a/security/pkix/test/gtest/pkixder_pki_types_tests.cpp b/security/pkix/test/gtest/pkixder_pki_types_tests.cpp new file mode 100644 index 000000000..e40c2a4c3 --- /dev/null +++ b/security/pkix/test/gtest/pkixder_pki_types_tests.cpp @@ -0,0 +1,479 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2013 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <functional> +#include <vector> + +#include "pkixgtest.h" +#include "pkix/pkixtypes.h" +#include "pkixder.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::der; + +class pkixder_pki_types_tests : public ::testing::Test { }; + +TEST_F(pkixder_pki_types_tests, CertificateSerialNumber) +{ + const uint8_t DER_CERT_SERIAL[] = { + 0x02, // INTEGER + 8, // length + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef + }; + Input input(DER_CERT_SERIAL); + Reader reader(input); + + Input item; + ASSERT_EQ(Success, CertificateSerialNumber(reader, item)); + + Input expected; + ASSERT_EQ(Success, + expected.Init(DER_CERT_SERIAL + 2, sizeof DER_CERT_SERIAL - 2)); + ASSERT_TRUE(InputsAreEqual(expected, item)); +} + +TEST_F(pkixder_pki_types_tests, CertificateSerialNumberLongest) +{ + const uint8_t DER_CERT_SERIAL_LONGEST[] = { + 0x02, // INTEGER + 20, // length + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 + }; + Input input(DER_CERT_SERIAL_LONGEST); + Reader reader(input); + + Input item; + ASSERT_EQ(Success, CertificateSerialNumber(reader, item)); + + Input expected; + ASSERT_EQ(Success, + expected.Init(DER_CERT_SERIAL_LONGEST + 2, + sizeof DER_CERT_SERIAL_LONGEST - 2)); + ASSERT_TRUE(InputsAreEqual(expected, item)); +} + +TEST_F(pkixder_pki_types_tests, CertificateSerialNumberCrazyLong) +{ + const uint8_t DER_CERT_SERIAL_CRAZY_LONG[] = { + 0x02, // INTEGER + 32, // length + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 + }; + Input input(DER_CERT_SERIAL_CRAZY_LONG); + Reader reader(input); + + Input item; + ASSERT_EQ(Success, CertificateSerialNumber(reader, item)); +} + +TEST_F(pkixder_pki_types_tests, CertificateSerialNumberZeroLength) +{ + const uint8_t DER_CERT_SERIAL_ZERO_LENGTH[] = { + 0x02, // INTEGER + 0x00 // length + }; + Input input(DER_CERT_SERIAL_ZERO_LENGTH); + Reader reader(input); + + Input item; + ASSERT_EQ(Result::ERROR_INVALID_INTEGER_ENCODING, + CertificateSerialNumber(reader, item)); +} + +TEST_F(pkixder_pki_types_tests, OptionalVersionV1ExplicitEncodingAllowed) +{ + const uint8_t DER_OPTIONAL_VERSION_V1[] = { + 0xa0, 0x03, // context specific 0 + 0x02, 0x01, 0x00 // INTEGER(0) + }; + Input input(DER_OPTIONAL_VERSION_V1); + Reader reader(input); + + // XXX(bug 1031093): We shouldn't accept an explicit encoding of v1, but we + // do here for compatibility reasons. + // Version version; + // ASSERT_EQ(Result::ERROR_BAD_DER, OptionalVersion(reader, version)); + der::Version version = der::Version::v3; + ASSERT_EQ(Success, OptionalVersion(reader, version)); + ASSERT_EQ(der::Version::v1, version); +} + +TEST_F(pkixder_pki_types_tests, OptionalVersionV2) +{ + const uint8_t DER_OPTIONAL_VERSION_V2[] = { + 0xa0, 0x03, // context specific 0 + 0x02, 0x01, 0x01 // INTEGER(1) + }; + Input input(DER_OPTIONAL_VERSION_V2); + Reader reader(input); + + der::Version version = der::Version::v1; + ASSERT_EQ(Success, OptionalVersion(reader, version)); + ASSERT_EQ(der::Version::v2, version); +} + +TEST_F(pkixder_pki_types_tests, OptionalVersionV3) +{ + const uint8_t DER_OPTIONAL_VERSION_V3[] = { + 0xa0, 0x03, // context specific 0 + 0x02, 0x01, 0x02 // INTEGER(2) + }; + Input input(DER_OPTIONAL_VERSION_V3); + Reader reader(input); + + der::Version version = der::Version::v1; + ASSERT_EQ(Success, OptionalVersion(reader, version)); + ASSERT_EQ(der::Version::v3, version); +} + +TEST_F(pkixder_pki_types_tests, OptionalVersionUnknown) +{ + const uint8_t DER_OPTIONAL_VERSION_INVALID[] = { + 0xa0, 0x03, // context specific 0 + 0x02, 0x01, 0x42 // INTEGER(0x42) + }; + Input input(DER_OPTIONAL_VERSION_INVALID); + Reader reader(input); + + der::Version version = der::Version::v1; + ASSERT_EQ(Result::ERROR_BAD_DER, OptionalVersion(reader, version)); +} + +TEST_F(pkixder_pki_types_tests, OptionalVersionInvalidTooLong) +{ + const uint8_t DER_OPTIONAL_VERSION_INVALID_TOO_LONG[] = { + 0xa0, 0x03, // context specific 0 + 0x02, 0x02, 0x12, 0x34 // INTEGER(0x1234) + }; + Input input(DER_OPTIONAL_VERSION_INVALID_TOO_LONG); + Reader reader(input); + + der::Version version; + ASSERT_EQ(Result::ERROR_BAD_DER, OptionalVersion(reader, version)); +} + +TEST_F(pkixder_pki_types_tests, OptionalVersionMissing) +{ + const uint8_t DER_OPTIONAL_VERSION_MISSING[] = { + 0x02, 0x11, 0x22 // INTEGER + }; + Input input(DER_OPTIONAL_VERSION_MISSING); + Reader reader(input); + + der::Version version = der::Version::v3; + ASSERT_EQ(Success, OptionalVersion(reader, version)); + ASSERT_EQ(der::Version::v1, version); +} + +static const size_t MAX_ALGORITHM_OID_DER_LENGTH = 13; + +struct InvalidAlgorithmIdentifierTestInfo +{ + uint8_t der[MAX_ALGORITHM_OID_DER_LENGTH]; + size_t derLength; +}; + +struct ValidDigestAlgorithmIdentifierTestInfo +{ + DigestAlgorithm algorithm; + uint8_t der[MAX_ALGORITHM_OID_DER_LENGTH]; + size_t derLength; +}; + +class pkixder_DigestAlgorithmIdentifier_Valid + : public ::testing::Test + , public ::testing::WithParamInterface<ValidDigestAlgorithmIdentifierTestInfo> +{ +}; + +static const ValidDigestAlgorithmIdentifierTestInfo + VALID_DIGEST_ALGORITHM_TEST_INFO[] = +{ + { DigestAlgorithm::sha512, + { 0x30, 0x0b, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03 }, + 13 + }, + { DigestAlgorithm::sha384, + { 0x30, 0x0b, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02 }, + 13 + }, + { DigestAlgorithm::sha256, + { 0x30, 0x0b, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 }, + 13 + }, + { DigestAlgorithm::sha1, + { 0x30, 0x07, 0x06, 0x05, + 0x2b, 0x0e, 0x03, 0x02, 0x1a }, + 9 + }, +}; + +TEST_P(pkixder_DigestAlgorithmIdentifier_Valid, Valid) +{ + const ValidDigestAlgorithmIdentifierTestInfo& param(GetParam()); + + { + Input input; + ASSERT_EQ(Success, input.Init(param.der, param.derLength)); + Reader reader(input); + DigestAlgorithm alg; + ASSERT_EQ(Success, DigestAlgorithmIdentifier(reader, alg)); + ASSERT_EQ(param.algorithm, alg); + ASSERT_EQ(Success, End(reader)); + } + + { + uint8_t derWithNullParam[MAX_ALGORITHM_OID_DER_LENGTH + 2]; + memcpy(derWithNullParam, param.der, param.derLength); + derWithNullParam[1] += 2; // we're going to expand the value by 2 bytes + derWithNullParam[param.derLength] = 0x05; // NULL tag + derWithNullParam[param.derLength + 1] = 0x00; // length zero + + Input input; + ASSERT_EQ(Success, input.Init(derWithNullParam, param.derLength + 2)); + Reader reader(input); + DigestAlgorithm alg; + ASSERT_EQ(Success, DigestAlgorithmIdentifier(reader, alg)); + ASSERT_EQ(param.algorithm, alg); + ASSERT_EQ(Success, End(reader)); + } +} + +INSTANTIATE_TEST_CASE_P(pkixder_DigestAlgorithmIdentifier_Valid, + pkixder_DigestAlgorithmIdentifier_Valid, + testing::ValuesIn(VALID_DIGEST_ALGORITHM_TEST_INFO)); + +class pkixder_DigestAlgorithmIdentifier_Invalid + : public ::testing::Test + , public ::testing::WithParamInterface<InvalidAlgorithmIdentifierTestInfo> +{ +}; + +static const InvalidAlgorithmIdentifierTestInfo + INVALID_DIGEST_ALGORITHM_TEST_INFO[] = +{ + { // MD5 + { 0x30, 0x0a, 0x06, 0x08, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05 }, + 12, + }, + { // ecdsa-with-SHA256 (1.2.840.10045.4.3.2) (not a hash algorithm) + { 0x30, 0x0a, 0x06, 0x08, + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02 }, + 12, + }, +}; + +TEST_P(pkixder_DigestAlgorithmIdentifier_Invalid, Invalid) +{ + const InvalidAlgorithmIdentifierTestInfo& param(GetParam()); + Input input; + ASSERT_EQ(Success, input.Init(param.der, param.derLength)); + Reader reader(input); + DigestAlgorithm alg; + ASSERT_EQ(Result::ERROR_INVALID_ALGORITHM, + DigestAlgorithmIdentifier(reader, alg)); +} + +INSTANTIATE_TEST_CASE_P(pkixder_DigestAlgorithmIdentifier_Invalid, + pkixder_DigestAlgorithmIdentifier_Invalid, + testing::ValuesIn(INVALID_DIGEST_ALGORITHM_TEST_INFO)); + +struct ValidSignatureAlgorithmIdentifierValueTestInfo +{ + PublicKeyAlgorithm publicKeyAlg; + DigestAlgorithm digestAlg; + uint8_t der[MAX_ALGORITHM_OID_DER_LENGTH]; + size_t derLength; +}; + +static const ValidSignatureAlgorithmIdentifierValueTestInfo + VALID_SIGNATURE_ALGORITHM_VALUE_TEST_INFO[] = +{ + // ECDSA + { PublicKeyAlgorithm::ECDSA, + DigestAlgorithm::sha512, + { 0x06, 0x08, + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04 }, + 10, + }, + { PublicKeyAlgorithm::ECDSA, + DigestAlgorithm::sha384, + { 0x06, 0x08, + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03 }, + 10, + }, + { PublicKeyAlgorithm::ECDSA, + DigestAlgorithm::sha256, + { 0x06, 0x08, + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02 }, + 10, + }, + { PublicKeyAlgorithm::ECDSA, + DigestAlgorithm::sha1, + { 0x06, 0x07, + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x01 }, + 9, + }, + + // RSA + { PublicKeyAlgorithm::RSA_PKCS1, + DigestAlgorithm::sha512, + { 0x06, 0x09, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0d }, + 11, + }, + { PublicKeyAlgorithm::RSA_PKCS1, + DigestAlgorithm::sha384, + { 0x06, 0x09, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c }, + 11, + }, + { PublicKeyAlgorithm::RSA_PKCS1, + DigestAlgorithm::sha256, + { 0x06, 0x09, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b }, + 11, + }, + { PublicKeyAlgorithm::RSA_PKCS1, + DigestAlgorithm::sha1, + // IETF Standard OID + { 0x06, 0x09, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05 }, + 11, + }, + { PublicKeyAlgorithm::RSA_PKCS1, + DigestAlgorithm::sha1, + // Legacy OIW OID (bug 1042479) + { 0x06, 0x05, + 0x2b, 0x0e, 0x03, 0x02, 0x1d }, + 7, + }, +}; + +class pkixder_SignatureAlgorithmIdentifierValue_Valid + : public ::testing::Test + , public ::testing::WithParamInterface< + ValidSignatureAlgorithmIdentifierValueTestInfo> +{ +}; + +TEST_P(pkixder_SignatureAlgorithmIdentifierValue_Valid, Valid) +{ + const ValidSignatureAlgorithmIdentifierValueTestInfo& param(GetParam()); + + { + Input input; + ASSERT_EQ(Success, input.Init(param.der, param.derLength)); + Reader reader(input); + PublicKeyAlgorithm publicKeyAlg; + DigestAlgorithm digestAlg; + ASSERT_EQ(Success, + SignatureAlgorithmIdentifierValue(reader, publicKeyAlg, + digestAlg)); + ASSERT_EQ(param.publicKeyAlg, publicKeyAlg); + ASSERT_EQ(param.digestAlg, digestAlg); + ASSERT_EQ(Success, End(reader)); + } + + { + uint8_t derWithNullParam[MAX_ALGORITHM_OID_DER_LENGTH + 2]; + memcpy(derWithNullParam, param.der, param.derLength); + derWithNullParam[param.derLength] = 0x05; // NULL tag + derWithNullParam[param.derLength + 1] = 0x00; // length zero + + Input input; + ASSERT_EQ(Success, input.Init(derWithNullParam, param.derLength + 2)); + Reader reader(input); + PublicKeyAlgorithm publicKeyAlg; + DigestAlgorithm digestAlg; + ASSERT_EQ(Success, + SignatureAlgorithmIdentifierValue(reader, publicKeyAlg, + digestAlg)); + ASSERT_EQ(param.publicKeyAlg, publicKeyAlg); + ASSERT_EQ(param.digestAlg, digestAlg); + ASSERT_EQ(Success, End(reader)); + } +} + +INSTANTIATE_TEST_CASE_P( + pkixder_SignatureAlgorithmIdentifierValue_Valid, + pkixder_SignatureAlgorithmIdentifierValue_Valid, + testing::ValuesIn(VALID_SIGNATURE_ALGORITHM_VALUE_TEST_INFO)); + +static const InvalidAlgorithmIdentifierTestInfo + INVALID_SIGNATURE_ALGORITHM_VALUE_TEST_INFO[] = +{ + // id-dsa-with-sha256 (2.16.840.1.101.3.4.3.2) + { { 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x02 }, + 11, + }, + + // id-dsa-with-sha1 (1.2.840.10040.4.3) + { { 0x06, 0x07, + 0x2a, 0x86, 0x48, 0xce, 0x38, 0x04, 0x03 }, + 9, + }, + + // RSA-with-MD5 (1.2.840.113549.1.1.4) + { { 0x06, 0x09, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x04 }, + 11, + }, + + // id-sha256 (2.16.840.1.101.3.4.2.1). It is invalid because SHA-256 is not + // a signature algorithm. + { { 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 }, + 11, + }, +}; + +class pkixder_SignatureAlgorithmIdentifier_Invalid + : public ::testing::Test + , public ::testing::WithParamInterface<InvalidAlgorithmIdentifierTestInfo> +{ +}; + +TEST_P(pkixder_SignatureAlgorithmIdentifier_Invalid, Invalid) +{ + const InvalidAlgorithmIdentifierTestInfo& param(GetParam()); + Input input; + ASSERT_EQ(Success, input.Init(param.der, param.derLength)); + Reader reader(input); + der::PublicKeyAlgorithm publicKeyAlg; + DigestAlgorithm digestAlg; + ASSERT_EQ(Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED, + SignatureAlgorithmIdentifierValue(reader, publicKeyAlg, digestAlg)); +} + +INSTANTIATE_TEST_CASE_P( + pkixder_SignatureAlgorithmIdentifier_Invalid, + pkixder_SignatureAlgorithmIdentifier_Invalid, + testing::ValuesIn(INVALID_SIGNATURE_ALGORITHM_VALUE_TEST_INFO)); diff --git a/security/pkix/test/gtest/pkixder_universal_types_tests.cpp b/security/pkix/test/gtest/pkixder_universal_types_tests.cpp new file mode 100644 index 000000000..bf4175bac --- /dev/null +++ b/security/pkix/test/gtest/pkixder_universal_types_tests.cpp @@ -0,0 +1,1220 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2013 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <limits> +#include <stdint.h> +#include <vector> + +#include "pkixder.h" +#include "pkixgtest.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::der; +using namespace mozilla::pkix::test; +using namespace std; + +class pkixder_universal_types_tests : public ::testing::Test { }; + +TEST_F(pkixder_universal_types_tests, BooleanTrue01) +{ + const uint8_t DER_BOOLEAN_TRUE_01[] = { + 0x01, // BOOLEAN + 0x01, // length + 0x01 // invalid + }; + Input input(DER_BOOLEAN_TRUE_01); + Reader reader(input); + bool value = false; + ASSERT_EQ(Result::ERROR_BAD_DER, Boolean(reader, value)); +} + +TEST_F(pkixder_universal_types_tests, BooleanTrue42) +{ + const uint8_t DER_BOOLEAN_TRUE_42[] = { + 0x01, // BOOLEAN + 0x01, // length + 0x42 // invalid + }; + Input input(DER_BOOLEAN_TRUE_42); + Reader reader(input); + bool value = false; + ASSERT_EQ(Result::ERROR_BAD_DER, Boolean(reader, value)); +} + +static const uint8_t DER_BOOLEAN_TRUE[] = { + 0x01, // BOOLEAN + 0x01, // length + 0xff // true +}; + +TEST_F(pkixder_universal_types_tests, BooleanTrueFF) +{ + Input input(DER_BOOLEAN_TRUE); + Reader reader(input); + bool value = false; + ASSERT_EQ(Success, Boolean(reader, value)); + ASSERT_TRUE(value); +} + +TEST_F(pkixder_universal_types_tests, BooleanFalse) +{ + const uint8_t DER_BOOLEAN_FALSE[] = { + 0x01, // BOOLEAN + 0x01, // length + 0x00 // false + }; + Input input(DER_BOOLEAN_FALSE); + Reader reader(input); + + bool value = true; + ASSERT_EQ(Success, Boolean(reader, value)); + ASSERT_FALSE(value); +} + +TEST_F(pkixder_universal_types_tests, BooleanInvalidLength) +{ + const uint8_t DER_BOOLEAN_INVALID_LENGTH[] = { + 0x01, // BOOLEAN + 0x02, // length + 0x42, 0x42 // invalid + }; + Input input(DER_BOOLEAN_INVALID_LENGTH); + Reader reader(input); + + bool value = true; + ASSERT_EQ(Result::ERROR_BAD_DER, Boolean(reader, value)); +} + +TEST_F(pkixder_universal_types_tests, BooleanInvalidZeroLength) +{ + const uint8_t DER_BOOLEAN_INVALID_ZERO_LENGTH[] = { + 0x01, // BOOLEAN + 0x00 // length + }; + Input input(DER_BOOLEAN_INVALID_ZERO_LENGTH); + Reader reader(input); + + bool value = true; + ASSERT_EQ(Result::ERROR_BAD_DER, Boolean(reader, value)); +} + +// OptionalBoolean implements decoding of OPTIONAL BOOLEAN DEFAULT FALSE. +// If the field is present, it must be a valid encoding of a BOOLEAN with +// value TRUE. If the field is not present, it defaults to FALSE. For +// compatibility reasons, OptionalBoolean also accepts encodings where the field +// is present with value FALSE (this is technically not a valid DER encoding). +TEST_F(pkixder_universal_types_tests, OptionalBooleanValidEncodings) +{ + { + const uint8_t DER_OPTIONAL_BOOLEAN_PRESENT_TRUE[] = { + 0x01, // BOOLEAN + 0x01, // length + 0xff // true + }; + Input input(DER_OPTIONAL_BOOLEAN_PRESENT_TRUE); + Reader reader(input); + bool value = false; + ASSERT_EQ(Success, OptionalBoolean(reader, value)) << + "Should accept the only valid encoding of a present OPTIONAL BOOLEAN"; + ASSERT_TRUE(value); + ASSERT_TRUE(reader.AtEnd()); + } + + { + // The OPTIONAL BOOLEAN is omitted in this data. + const uint8_t DER_INTEGER_05[] = { + 0x02, // INTEGER + 0x01, // length + 0x05 + }; + Input input(DER_INTEGER_05); + Reader reader(input); + bool value = true; + ASSERT_EQ(Success, OptionalBoolean(reader, value)) << + "Should accept a valid encoding of an omitted OPTIONAL BOOLEAN"; + ASSERT_FALSE(value); + ASSERT_FALSE(reader.AtEnd()); + } + + { + Input input; + ASSERT_EQ(Success, input.Init(reinterpret_cast<const uint8_t*>(""), 0)); + Reader reader(input); + bool value = true; + ASSERT_EQ(Success, OptionalBoolean(reader, value)) << + "Should accept another valid encoding of an omitted OPTIONAL BOOLEAN"; + ASSERT_FALSE(value); + ASSERT_TRUE(reader.AtEnd()); + } +} + +TEST_F(pkixder_universal_types_tests, OptionalBooleanInvalidEncodings) +{ + const uint8_t DER_OPTIONAL_BOOLEAN_PRESENT_FALSE[] = { + 0x01, // BOOLEAN + 0x01, // length + 0x00 // false + }; + + { + Input input(DER_OPTIONAL_BOOLEAN_PRESENT_FALSE); + Reader reader(input); + bool value = true; + ASSERT_EQ(Success, OptionalBoolean(reader, value)) << + "Should accept an invalid, default-value encoding of OPTIONAL BOOLEAN"; + ASSERT_FALSE(value); + ASSERT_TRUE(reader.AtEnd()); + } + + const uint8_t DER_OPTIONAL_BOOLEAN_PRESENT_42[] = { + 0x01, // BOOLEAN + 0x01, // length + 0x42 // (invalid value for a BOOLEAN) + }; + + { + Input input(DER_OPTIONAL_BOOLEAN_PRESENT_42); + Reader reader(input); + bool value; + ASSERT_EQ(Result::ERROR_BAD_DER, OptionalBoolean(reader, value)) << + "Should reject an invalid-valued encoding of OPTIONAL BOOLEAN"; + } +} + +TEST_F(pkixder_universal_types_tests, Enumerated) +{ + const uint8_t DER_ENUMERATED[] = { + 0x0a, // ENUMERATED + 0x01, // length + 0x42 // value + }; + Input input(DER_ENUMERATED); + Reader reader(input); + + uint8_t value = 0; + ASSERT_EQ(Success, Enumerated(reader, value)); + ASSERT_EQ(0x42, value); +} + +TEST_F(pkixder_universal_types_tests, EnumeratedNotShortestPossibleDER) +{ + const uint8_t DER_ENUMERATED[] = { + 0x0a, // ENUMERATED + 0x02, // length + 0x00, 0x01 // value + }; + Input input(DER_ENUMERATED); + Reader reader(input); + + uint8_t value = 0; + ASSERT_EQ(Result::ERROR_INVALID_INTEGER_ENCODING, Enumerated(reader, value)); +} + +TEST_F(pkixder_universal_types_tests, EnumeratedOutOfAcceptedRange) +{ + // Although this is a valid ENUMERATED value according to ASN.1, we + // intentionally don't support these large values because there are no + // ENUMERATED values in X.509 certs or OCSP this large, and we're trying to + // keep the parser simple and fast. + const uint8_t DER_ENUMERATED_INVALID_LENGTH[] = { + 0x0a, // ENUMERATED + 0x02, // length + 0x12, 0x34 // value + }; + Input input(DER_ENUMERATED_INVALID_LENGTH); + Reader reader(input); + + uint8_t value = 0; + ASSERT_EQ(Result::ERROR_INVALID_INTEGER_ENCODING, Enumerated(reader, value)); +} + +TEST_F(pkixder_universal_types_tests, EnumeratedInvalidZeroLength) +{ + const uint8_t DER_ENUMERATED_INVALID_ZERO_LENGTH[] = { + 0x0a, // ENUMERATED + 0x00 // length + }; + Input input(DER_ENUMERATED_INVALID_ZERO_LENGTH); + Reader reader(input); + + uint8_t value = 0; + ASSERT_EQ(Result::ERROR_INVALID_INTEGER_ENCODING, Enumerated(reader, value)); +} + +//////////////////////////////////////// +// GeneralizedTime and TimeChoice +// +// From RFC 5280 section 4.1.2.5.2 +// +// For the purposes of this profile, GeneralizedTime values MUST be +// expressed in Greenwich Mean Time (Zulu) and MUST include seconds +// (i.e., times are YYYYMMDDHHMMSSZ), even where the number of seconds +// is zero. GeneralizedTime values MUST NOT include fractional seconds. +// +// And from from RFC 6960 (OCSP) section 4.2.2.1: +// +// Responses can contain four times -- thisUpdate, nextUpdate, +// producedAt, and revocationTime. The semantics of these fields are +// defined in Section 2.4. The format for GeneralizedTime is as +// specified in Section 4.1.2.5.2 of [RFC5280]. +// +// So while we can could accept other ASN1 (ITU-T X.680) encodings for +// GeneralizedTime we should not accept them, and breaking reading of these +// other encodings is actually encouraged. + +// e.g. TWO_CHARS(53) => '5', '3' +#define TWO_CHARS(t) \ + static_cast<uint8_t>('0' + (static_cast<uint8_t>(t) / 10u)), \ + static_cast<uint8_t>('0' + (static_cast<uint8_t>(t) % 10u)) + +// Calls TimeChoice on the UTCTime variant of the given generalized time. +template <uint16_t LENGTH> +Result +TimeChoiceForEquivalentUTCTime(const uint8_t (&generalizedTimeDER)[LENGTH], + /*out*/ Time& value) +{ + static_assert(LENGTH >= 4, + "TimeChoiceForEquivalentUTCTime input too small"); + uint8_t utcTimeDER[LENGTH - 2]; + utcTimeDER[0] = 0x17; // tag UTCTime + utcTimeDER[1] = LENGTH - 1/*tag*/ - 1/*value*/ - 2/*century*/; + // Copy the value except for the first two digits of the year + for (size_t i = 2; i < LENGTH - 2; ++i) { + utcTimeDER[i] = generalizedTimeDER[i + 2]; + } + + Input input(utcTimeDER); + Reader reader(input); + return TimeChoice(reader, value); +} + +template <uint16_t LENGTH> +void +ExpectGoodTime(Time expectedValue, + const uint8_t (&generalizedTimeDER)[LENGTH]) +{ + // GeneralizedTime + { + Input input(generalizedTimeDER); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Success, GeneralizedTime(reader, value)); + EXPECT_EQ(expectedValue, value); + } + + // TimeChoice: GeneralizedTime + { + Input input(generalizedTimeDER); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Success, TimeChoice(reader, value)); + EXPECT_EQ(expectedValue, value); + } + + // TimeChoice: UTCTime + { + Time value(Time::uninitialized); + ASSERT_EQ(Success, + TimeChoiceForEquivalentUTCTime(generalizedTimeDER, value)); + EXPECT_EQ(expectedValue, value); + } +} + +template <uint16_t LENGTH> +void +ExpectBadTime(const uint8_t (&generalizedTimeDER)[LENGTH]) +{ + // GeneralizedTime + { + Input input(generalizedTimeDER); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, GeneralizedTime(reader, value)); + } + + // TimeChoice: GeneralizedTime + { + Input input(generalizedTimeDER); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, TimeChoice(reader, value)); + } + + // TimeChoice: UTCTime + { + Time value(Time::uninitialized); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, + TimeChoiceForEquivalentUTCTime(generalizedTimeDER, value)); + } +} + +// Control value: a valid time +TEST_F(pkixder_universal_types_tests, ValidControl) +{ + const uint8_t GT_DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '1', '9', '9', '1', '0', '5', '0', '6', '1', '6', '4', '5', '4', '0', 'Z' + }; + ExpectGoodTime(YMDHMS(1991, 5, 6, 16, 45, 40), GT_DER); +} + +TEST_F(pkixder_universal_types_tests, TimeTimeZoneOffset) +{ + const uint8_t DER_GENERALIZED_TIME_OFFSET[] = { + 0x18, // Generalized Time + 19, // Length = 19 + '1', '9', '9', '1', '0', '5', '0', '6', '1', '6', '4', '5', '4', '0', '-', + '0', '7', '0', '0' + }; + ExpectBadTime(DER_GENERALIZED_TIME_OFFSET); +} + +TEST_F(pkixder_universal_types_tests, TimeInvalidZeroLength) +{ + const uint8_t DER_GENERALIZED_TIME_INVALID_ZERO_LENGTH[] = { + 0x18, // GeneralizedTime + 0x00 // Length = 0 + }; + + Time value(Time::uninitialized); + + // GeneralizedTime + Input gtBuf(DER_GENERALIZED_TIME_INVALID_ZERO_LENGTH); + Reader gt(gtBuf); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, GeneralizedTime(gt, value)); + + // TimeChoice: GeneralizedTime + Input tc_gt_buf(DER_GENERALIZED_TIME_INVALID_ZERO_LENGTH); + Reader tc_gt(tc_gt_buf); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, TimeChoice(tc_gt, value)); + + // TimeChoice: UTCTime + const uint8_t DER_UTCTIME_INVALID_ZERO_LENGTH[] = { + 0x17, // UTCTime + 0x00 // Length = 0 + }; + Input tc_utc_buf(DER_UTCTIME_INVALID_ZERO_LENGTH); + Reader tc_utc(tc_utc_buf); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, TimeChoice(tc_utc, value)); +} + +// A non zulu time should fail +TEST_F(pkixder_universal_types_tests, TimeInvalidLocal) +{ + const uint8_t DER_GENERALIZED_TIME_INVALID_LOCAL[] = { + 0x18, // Generalized Time + 14, // Length = 14 + '1', '9', '9', '1', '0', '5', '0', '6', '1', '6', '4', '5', '4', '0' + }; + ExpectBadTime(DER_GENERALIZED_TIME_INVALID_LOCAL); +} + +// A time missing seconds and zulu should fail +TEST_F(pkixder_universal_types_tests, TimeInvalidTruncated) +{ + const uint8_t DER_GENERALIZED_TIME_INVALID_TRUNCATED[] = { + 0x18, // Generalized Time + 12, // Length = 12 + '1', '9', '9', '1', '0', '5', '0', '6', '1', '6', '4', '5' + }; + ExpectBadTime(DER_GENERALIZED_TIME_INVALID_TRUNCATED); +} + +TEST_F(pkixder_universal_types_tests, TimeNoSeconds) +{ + const uint8_t DER_GENERALIZED_TIME_NO_SECONDS[] = { + 0x18, // Generalized Time + 13, // Length = 13 + '1', '9', '9', '1', '0', '5', '0', '6', '1', '6', '4', '5', 'Z' + }; + ExpectBadTime(DER_GENERALIZED_TIME_NO_SECONDS); +} + +TEST_F(pkixder_universal_types_tests, TimeInvalidPrefixedYear) +{ + const uint8_t DER_GENERALIZED_TIME_INVALID_PREFIXED_YEAR[] = { + 0x18, // Generalized Time + 16, // Length = 16 + ' ', '1', '9', '9', '1', '0', '1', '0', '1', '0', '1', '0', '1', '0', '1', 'Z' + }; + ExpectBadTime(DER_GENERALIZED_TIME_INVALID_PREFIXED_YEAR); +} + +TEST_F(pkixder_universal_types_tests, TimeTooManyDigits) +{ + const uint8_t DER_GENERALIZED_TIME_TOO_MANY_DIGITS[] = { + 0x18, // Generalized Time + 16, // Length = 16 + '1', '1', '1', '1', '1', '0', '1', '0', '1', '0', '1', '0', '1', '0', '1', 'Z' + }; + ExpectBadTime(DER_GENERALIZED_TIME_TOO_MANY_DIGITS); +} + +// In order to ensure we we don't run into any trouble with conversions to and +// from time_t we only accept times from 1970 onwards. +TEST_F(pkixder_universal_types_tests, GeneralizedTimeYearValidRange) +{ + // Note that by using the last second of the last day of the year, we're also + // effectively testing all the accumulated conversions from Gregorian to to + // Julian time, including in particular the effects of leap years. + + for (uint16_t i = 1970; i <= 9999; ++i) { + const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + TWO_CHARS(i / 100), TWO_CHARS(i % 100), // YYYY + '1', '2', '3', '1', // 12-31 + '2', '3', '5', '9', '5', '9', 'Z' // 23:59:59Z + }; + + Time expectedValue = YMDHMS(i, 12, 31, 23, 59, 59); + + // We have to test GeneralizedTime separately from UTCTime instead of using + // ExpectGooDtime because the range of UTCTime is less than the range of + // GeneralizedTime. + + // GeneralizedTime + { + Input input(DER); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Success, GeneralizedTime(reader, value)); + EXPECT_EQ(expectedValue, value); + } + + // TimeChoice: GeneralizedTime + { + Input input(DER); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Success, TimeChoice(reader, value)); + EXPECT_EQ(expectedValue, value); + } + + // TimeChoice: UTCTime, which is limited to years less than 2049. + if (i <= 2049) { + Time value(Time::uninitialized); + ASSERT_EQ(Success, TimeChoiceForEquivalentUTCTime(DER, value)); + EXPECT_EQ(expectedValue, value); + } + } +} + +// In order to ensure we we don't run into any trouble with conversions to and +// from time_t we only accept times from 1970 onwards. +TEST_F(pkixder_universal_types_tests, TimeYearInvalid1969) +{ + static const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '1', '9', '6', '9', '1', '2', '3', '1', // !!!1969!!!-12-31 + '2', '3', '5', '9', '5', '9', 'Z' // 23:59:59Z + }; + ExpectBadTime(DER); +} + +static const uint8_t DAYS_IN_MONTH[] = { + 0, // unused + 31, // January + 28, // February (leap years tested separately) + 31, // March + 30, // April + 31, // May + 30, // Jun + 31, // July + 31, // August + 30, // September + 31, // October + 30, // November + 31, // December +}; + +TEST_F(pkixder_universal_types_tests, TimeMonthDaysValidRange) +{ + for (uint16_t month = 1; month <= 12; ++month) { + for (uint8_t day = 1; day <= DAYS_IN_MONTH[month]; ++day) { + const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '5', TWO_CHARS(month), TWO_CHARS(day), // (2015-mm-dd) + '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40 + }; + ExpectGoodTime(YMDHMS(2015, month, day, 16, 45, 40), DER); + } + } +} + +TEST_F(pkixder_universal_types_tests, TimeMonthInvalid0) +{ + static const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '5', '0', '0', '1', '5', // 2015-!!!00!!!-15 + '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40 + }; + ExpectBadTime(DER); +} + +TEST_F(pkixder_universal_types_tests, TimeMonthInvalid13) +{ + const uint8_t DER_GENERALIZED_TIME_13TH_MONTH[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '1', '9', '9', '1', //YYYY (1991) + '1', '3', //MM 13th month of the year + '0', '6', '1', '6', '4', '5', '4', '0', 'Z' + }; + ExpectBadTime(DER_GENERALIZED_TIME_13TH_MONTH); +} + +TEST_F(pkixder_universal_types_tests, TimeDayInvalid0) +{ + static const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '5', '0', '1', '0', '0', // 2015-01-!!!00!!! + '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40 + }; + ExpectBadTime(DER); +} + +TEST_F(pkixder_universal_types_tests, TimeMonthDayInvalidPastEndOfMonth) +{ + for (int16_t month = 1; month <= 12; ++month) { + const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '1', '9', '9', '1', // YYYY 1991 + TWO_CHARS(month), // MM + TWO_CHARS(1 + (month == 2 ? 29 : DAYS_IN_MONTH[month])), // !!!DD!!! + '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40 + }; + ExpectBadTime(DER); + } +} + +TEST_F(pkixder_universal_types_tests, TimeMonthFebLeapYear2016) +{ + static const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '6', '0', '2', '2', '9', // 2016-02-29 + '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40 + }; + ExpectGoodTime(YMDHMS(2016, 2, 29, 16, 45, 40), DER); +} + +TEST_F(pkixder_universal_types_tests, TimeMonthFebLeapYear2000) +{ + static const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '0', '0', '0', '2', '2', '9', // 2000-02-29 + '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40 + }; + ExpectGoodTime(YMDHMS(2000, 2, 29, 16, 45, 40), DER); +} + +TEST_F(pkixder_universal_types_tests, TimeMonthFebLeapYear2400) +{ + static const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '4', '0', '0', '0', '2', '2', '9', // 2400-02-29 + '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40 + }; + + // We don't use ExpectGoodTime here because UTCTime can't represent 2400. + + Time expectedValue = YMDHMS(2400, 2, 29, 16, 45, 40); + + // GeneralizedTime + { + Input input(DER); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Success, GeneralizedTime(reader, value)); + EXPECT_EQ(expectedValue, value); + } + + // TimeChoice: GeneralizedTime + { + Input input(DER); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Success, TimeChoice(reader, value)); + EXPECT_EQ(expectedValue, value); + } +} + +TEST_F(pkixder_universal_types_tests, TimeMonthFebNotLeapYear2014) +{ + static const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '4', '0', '2', '2', '9', // 2014-02-29 + '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40 + }; + ExpectBadTime(DER); +} + +TEST_F(pkixder_universal_types_tests, TimeMonthFebNotLeapYear2100) +{ + static const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '1', '0', '0', '0', '2', '2', '9', // 2100-02-29 + '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40 + }; + + // We don't use ExpectBadTime here because UTCTime can't represent 2100. + + // GeneralizedTime + { + Input input(DER); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, GeneralizedTime(reader, value)); + } + + // TimeChoice: GeneralizedTime + { + Input input(DER); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, TimeChoice(reader, value)); + } +} + +TEST_F(pkixder_universal_types_tests, TimeHoursValidRange) +{ + for (uint8_t i = 0; i <= 23; ++i) { + const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30) + TWO_CHARS(i), '5', '9', '0', '1', 'Z' // HHMMSSZ (!!!!ii!!!!:59:01 Zulu) + }; + ExpectGoodTime(YMDHMS(2012, 6, 30, i, 59, 1), DER); + } +} + +TEST_F(pkixder_universal_types_tests, TimeHoursInvalid_24_00_00) +{ + static const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30) + '2', '4', '0', '0', '0', '0', 'Z' // HHMMSSZ (!!24!!:00:00 Zulu) + }; + ExpectBadTime(DER); +} + +TEST_F(pkixder_universal_types_tests, TimeMinutesValidRange) +{ + for (uint8_t i = 0; i <= 59; ++i) { + const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30) + '2', '3', TWO_CHARS(i), '0', '1', 'Z' // HHMMSSZ (23:!!!!ii!!!!:01 Zulu) + }; + ExpectGoodTime(YMDHMS(2012, 6, 30, 23, i, 1), DER); + } +} + +TEST_F(pkixder_universal_types_tests, TimeMinutesInvalid60) +{ + const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30) + '2', '3', '6', '0', '5', '9', 'Z' // HHMMSSZ (23:!!!60!!!:01 Zulu) + }; + ExpectBadTime(DER); +} + +TEST_F(pkixder_universal_types_tests, TimeSecondsValidRange) +{ + for (uint8_t i = 0; i <= 59; ++i) { + const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30) + '2', '3', '5', '9', TWO_CHARS(i), 'Z' // HHMMSSZ (23:59:!!!!ii!!!! Zulu) + }; + ExpectGoodTime(YMDHMS(2012, 6, 30, 23, 59, i), DER); + } +} + +// No Leap Seconds (60) +TEST_F(pkixder_universal_types_tests, TimeSecondsInvalid60) +{ + static const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30) + '2', '3', '5', '9', '6', '0', 'Z' // HHMMSSZ (23:59:!!!!60!!!! Zulu) + }; + ExpectBadTime(DER); +} + +// No Leap Seconds (61) +TEST_F(pkixder_universal_types_tests, TimeSecondsInvalid61) +{ + static const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30) + '2', '3', '5', '9', '6', '1', 'Z' // HHMMSSZ (23:59:!!!!61!!!! Zulu) + }; + ExpectBadTime(DER); +} + +TEST_F(pkixder_universal_types_tests, TimeInvalidZulu) +{ + const uint8_t DER_GENERALIZED_TIME_INVALID_ZULU[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30) + '2', '3', '5', '9', '5', '9', 'z' // HHMMSSZ (23:59:59 !!!z!!!) should be Z + }; + ExpectBadTime(DER_GENERALIZED_TIME_INVALID_ZULU); +} + +TEST_F(pkixder_universal_types_tests, TimeInvalidExtraData) +{ + const uint8_t DER_GENERALIZED_TIME_INVALID_EXTRA_DATA[] = { + 0x18, // Generalized Time + 16, // Length = 16 + '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30) + '2', '3', '5', '9', '5', '9', 'Z', // HHMMSSZ (23:59:59Z) + 0 // Extra null character + }; + ExpectBadTime(DER_GENERALIZED_TIME_INVALID_EXTRA_DATA); +} + +TEST_F(pkixder_universal_types_tests, TimeInvalidCenturyChar) +{ + const uint8_t DER_GENERALIZED_TIME_INVALID_CENTURY_CHAR[] = { + 0x18, // Generalized Time + 15, // Length = 15 + 'X', '9', '9', '1', '1', '2', '0', '6', // YYYYMMDD (X991-12-06) + '1', '6', '4', '5', '4', '0', 'Z' // HHMMSSZ (16:45:40Z) + }; + + // We can't use ExpectBadTime here, because ExpectBadTime requires + // consistent results for GeneralizedTime and UTCTime, but the results + // for this input are different. + + // GeneralizedTime + { + Input input(DER_GENERALIZED_TIME_INVALID_CENTURY_CHAR); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, GeneralizedTime(reader, value)); + } + + // TimeChoice: GeneralizedTime + { + Input input(DER_GENERALIZED_TIME_INVALID_CENTURY_CHAR); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, TimeChoice(reader, value)); + } + + // This test is not applicable to TimeChoice: UTCTime +} + +TEST_F(pkixder_universal_types_tests, TimeInvalidYearChar) +{ + const uint8_t DER_GENERALIZED_TIME_INVALID_YEAR_CHAR[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '1', '9', '9', 'I', '0', '1', '0', '6', // YYYYMMDD (199I-12-06) + '1', '6', '4', '5', '4', '0', 'Z' // HHMMSSZ (16:45:40Z) + }; + ExpectBadTime(DER_GENERALIZED_TIME_INVALID_YEAR_CHAR); +} + +TEST_F(pkixder_universal_types_tests, GeneralizedTimeInvalidMonthChar) +{ + const uint8_t DER_GENERALIZED_TIME_INVALID_MONTH_CHAR[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '1', '9', '9', '1', '0', 'I', '0', '6', // YYYYMMDD (1991-0I-06) + '1', '6', '4', '5', '4', '0', 'Z' // HHMMSSZ (16:45:40Z) + }; + ExpectBadTime(DER_GENERALIZED_TIME_INVALID_MONTH_CHAR); +} + +TEST_F(pkixder_universal_types_tests, TimeInvalidDayChar) +{ + const uint8_t DER_GENERALIZED_TIME_INVALID_DAY_CHAR[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '1', '9', '9', '1', '0', '1', '0', 'S', // YYYYMMDD (1991-01-0S) + '1', '6', '4', '5', '4', '0', 'Z' // HHMMSSZ (16:45:40Z) + }; + ExpectBadTime(DER_GENERALIZED_TIME_INVALID_DAY_CHAR); +} + +TEST_F(pkixder_universal_types_tests, TimeInvalidFractionalSeconds) +{ + const uint8_t DER_GENERALIZED_TIME_INVALID_FRACTIONAL_SECONDS[] = { + 0x18, // Generalized Time + 17, // Length = 17 + '1', '9', '9', '1', '0', '1', '0', '1', // YYYYMMDD (1991-01-01) + '1', '6', '4', '5', '4', '0', '.', '3', 'Z' // HHMMSS.FFF (16:45:40.3Z) + }; + ExpectBadTime(DER_GENERALIZED_TIME_INVALID_FRACTIONAL_SECONDS); +} + +struct IntegerTestParams +{ + ByteString encoded; + struct PositiveIntegerParams + { + Result expectedResult; + Input::size_type significantBytesIfValid; + } positiveInteger; + struct SmallNonnegativeIntegerParams + { + Result expectedResult; + uint8_t valueIfValid; + } smallNonnegativeInteger; +}; + +class pkixder_universal_types_tests_Integer + : public ::testing::Test + , public ::testing::WithParamInterface<IntegerTestParams> +{ +}; + +#define INVALID 0xFF + +static const IntegerTestParams INTEGER_TEST_PARAMS[] = +{ + // Zero is encoded with one value byte of 0x00. + { TLV(2, ByteString()), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\x00"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Success, 0 } }, + + // Positive single-byte values + { TLV(2, "\x01"), { Success, 1 }, { Success, 1} }, + { TLV(2, "\x02"), { Success, 1 }, { Success, 2} }, + { TLV(2, "\x7e"), { Success, 1 }, { Success, 0x7e} }, + { TLV(2, "\x7f"), { Success, 1 }, { Success, 0x7f} }, + + // Negative single-byte values + { TLV(2, "\x80"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\x81"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\xFE"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\xFF"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + + // Positive two-byte values not starting with 0x00 + { TLV(2, "\x7F\x00"), + { Success, 2 }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\x01\x00"), + { Success, 2 }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\x01\x02"), + { Success, 2 }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + + // Negative two-byte values not starting with 0xFF + { TLV(2, "\x80\x00"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\x80\x7F"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\x80\x80"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\x80\xFF"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + + // The leading zero is necessary. + { TLV(2, "\x00\x80"), + { Success, 1}, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\x00\x81"), + { Success, 1}, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\x00\xFF"), + { Success, 1}, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + + // The leading zero is unnecessary. + { TLV(2, "\x00\x01"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\x00\x7F"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + + // The leading 0xFF is necessary. + { TLV(2, "\xFF\x00"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\xFF\x7F"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + + // The leading 0xFF is unnecessary. + { TLV(2, "\xFF\x80"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\xFF\xFF"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + + // Truncated values + { TLV(2, 1, ByteString(/*missing value*/)), + { Result::ERROR_BAD_DER, INVALID }, + { Result::ERROR_BAD_DER, INVALID } }, + { TLV(2, 3, "\x11\x22" /*truncated*/), + { Result::ERROR_BAD_DER, INVALID }, + { Result::ERROR_BAD_DER, INVALID } }, + { TLV(2, 4, "\x11\x22" /*truncated*/), + { Result::ERROR_BAD_DER, INVALID }, + { Result::ERROR_BAD_DER, INVALID } }, + { TLV(2, 2, "\x00" /*truncated*/), + { Result::ERROR_BAD_DER, INVALID }, + { Result::ERROR_BAD_DER, INVALID } }, + { TLV(2, 2, "\xFF" /*truncated*/), + { Result::ERROR_BAD_DER, INVALID }, + { Result::ERROR_BAD_DER, INVALID } }, + { TLV(2, 3, "\x00\x80" /*truncated*/), + { Result::ERROR_BAD_DER, INVALID }, + { Result::ERROR_BAD_DER, INVALID } }, + { TLV(2, 3, "\xFF\x00" /*truncated*/), + { Result::ERROR_BAD_DER, INVALID }, + { Result::ERROR_BAD_DER, INVALID } }, + + // Misc. larger values + { TLV(2, 4, "\x11\x22\x33\x44"), + { Success, 4 }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"), + { Success, 256 }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, +}; + +TEST_P(pkixder_universal_types_tests_Integer, Integer) +{ + const IntegerTestParams& params(GetParam()); + Input input; + ASSERT_EQ(Success, input.Init(params.encoded.data(), + params.encoded.length())); + Reader reader(input); + Result expectedResult = params.smallNonnegativeInteger.expectedResult; + uint8_t value; + ASSERT_EQ(expectedResult, der::Integer(reader, value)); + if (expectedResult == Success) { + ASSERT_EQ(params.smallNonnegativeInteger.valueIfValid, value); + ASSERT_TRUE(reader.AtEnd()); + } +} + +TEST_P(pkixder_universal_types_tests_Integer, + PositiveInteger_without_significantBytes) +{ + const IntegerTestParams& params(GetParam()); + Input input; + ASSERT_EQ(Success, input.Init(params.encoded.data(), + params.encoded.length())); + Reader reader(input); + Result expectedResult = params.positiveInteger.expectedResult; + Input value; + ASSERT_EQ(expectedResult, der::PositiveInteger(reader, value)); + if (expectedResult == Success) { + Reader anotherReader(input); + Input expectedValue; + ASSERT_EQ(Success, ExpectTagAndGetValue(anotherReader, + der::INTEGER, expectedValue)); + ASSERT_TRUE(InputsAreEqual(expectedValue, value)); + ASSERT_TRUE(reader.AtEnd()); + } +} + +TEST_P(pkixder_universal_types_tests_Integer, + PositiveInteger_with_significantBytes) +{ + const IntegerTestParams& params(GetParam()); + Input input; + ASSERT_EQ(Success, input.Init(params.encoded.data(), + params.encoded.length())); + Reader reader(input); + Result expectedResult = params.positiveInteger.expectedResult; + Input value; + Input::size_type significantBytes = INVALID; + ASSERT_EQ(expectedResult, der::PositiveInteger(reader, value, + &significantBytes)); + if (expectedResult == Success) { + ASSERT_NE(INVALID, params.positiveInteger.significantBytesIfValid); + ASSERT_EQ(params.positiveInteger.significantBytesIfValid, + significantBytes); + + Reader anotherReader(input); + Input expectedValue; + ASSERT_EQ(Success, ExpectTagAndGetValue(anotherReader, + der::INTEGER, expectedValue)); + ASSERT_TRUE(InputsAreEqual(expectedValue, value)); + ASSERT_TRUE(reader.AtEnd()); + } +} + +#undef INVALID + +INSTANTIATE_TEST_CASE_P(pkixder_universal_types_tests_Integer, + pkixder_universal_types_tests_Integer, + testing::ValuesIn(INTEGER_TEST_PARAMS)); + +TEST_F(pkixder_universal_types_tests, OptionalIntegerSupportedDefault) +{ + // The input is a BOOLEAN and not INTEGER for the input so we'll not parse + // anything and instead use the default value. + Input input(DER_BOOLEAN_TRUE); + Reader reader(input); + + long value = 1; + ASSERT_EQ(Success, OptionalInteger(reader, -1, value)); + ASSERT_EQ(-1, value); + bool boolValue; + ASSERT_EQ(Success, Boolean(reader, boolValue)); +} + +TEST_F(pkixder_universal_types_tests, OptionalIntegerUnsupportedDefault) +{ + // The same as the previous test, except with an unsupported default value + // passed in. + Input input(DER_BOOLEAN_TRUE); + Reader reader(input); + + long value; + ASSERT_EQ(Result::FATAL_ERROR_INVALID_ARGS, OptionalInteger(reader, 0, value)); +} + +TEST_F(pkixder_universal_types_tests, OptionalIntegerSupportedDefaultAtEnd) +{ + static const uint8_t dummy = 1; + Input input; + ASSERT_EQ(Success, input.Init(&dummy, 0)); + Reader reader(input); + + long value = 1; + ASSERT_EQ(Success, OptionalInteger(reader, -1, value)); + ASSERT_EQ(-1, value); +} + +TEST_F(pkixder_universal_types_tests, OptionalIntegerNonDefaultValue) +{ + static const uint8_t DER[] = { + 0x02, // INTEGER + 0x01, // length + 0x00 + }; + Input input(DER); + Reader reader(input); + + long value = 2; + ASSERT_EQ(Success, OptionalInteger(reader, -1, value)); + ASSERT_EQ(0, value); + ASSERT_TRUE(reader.AtEnd()); +} + +TEST_F(pkixder_universal_types_tests, Null) +{ + const uint8_t DER_NUL[] = { + 0x05, + 0x00 + }; + Input input(DER_NUL); + Reader reader(input); + + ASSERT_EQ(Success, Null(reader)); +} + +TEST_F(pkixder_universal_types_tests, NullWithBadLength) +{ + const uint8_t DER_NULL_BAD_LENGTH[] = { + 0x05, + 0x01, + 0x00 + }; + Input input(DER_NULL_BAD_LENGTH); + Reader reader(input); + + ASSERT_EQ(Result::ERROR_BAD_DER, Null(reader)); +} + +TEST_F(pkixder_universal_types_tests, OID) +{ + const uint8_t DER_VALID_OID[] = { + 0x06, + 0x09, + 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01 + }; + Input input(DER_VALID_OID); + Reader reader(input); + + const uint8_t expectedOID[] = { + 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01 + }; + + ASSERT_EQ(Success, OID(reader, expectedOID)); +} diff --git a/security/pkix/test/gtest/pkixgtest.cpp b/security/pkix/test/gtest/pkixgtest.cpp new file mode 100644 index 000000000..77baef857 --- /dev/null +++ b/security/pkix/test/gtest/pkixgtest.cpp @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2013 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixgtest.h" + +#include <ctime> + +#include "pkix/Time.h" + +namespace mozilla { namespace pkix { namespace test { + +static const std::time_t ONE_DAY_IN_SECONDS_AS_TIME_T = + static_cast<std::time_t>(Time::ONE_DAY_IN_SECONDS); + +// This assumes that time/time_t are POSIX-compliant in that time() returns +// the number of seconds since the Unix epoch. +static const std::time_t now(time(nullptr)); +const std::time_t oneDayBeforeNow(now - ONE_DAY_IN_SECONDS_AS_TIME_T); +const std::time_t oneDayAfterNow(now + ONE_DAY_IN_SECONDS_AS_TIME_T); +const std::time_t twoDaysBeforeNow(now - (2 * ONE_DAY_IN_SECONDS_AS_TIME_T)); +const std::time_t twoDaysAfterNow(now + (2 * ONE_DAY_IN_SECONDS_AS_TIME_T)); +const std::time_t tenDaysBeforeNow(now - (10 * ONE_DAY_IN_SECONDS_AS_TIME_T)); +const std::time_t tenDaysAfterNow(now + (10 * ONE_DAY_IN_SECONDS_AS_TIME_T)); + +} } } // namespace mozilla::pkix::test diff --git a/security/pkix/test/gtest/pkixgtest.h b/security/pkix/test/gtest/pkixgtest.h new file mode 100644 index 000000000..1ec8727e2 --- /dev/null +++ b/security/pkix/test/gtest/pkixgtest.h @@ -0,0 +1,255 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2014 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef mozilla_pkix_pkixgtest_h +#define mozilla_pkix_pkixgtest_h + +#include <ostream> + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" +#pragma clang diagnostic ignored "-Wmissing-noreturn" +#pragma clang diagnostic ignored "-Wshift-sign-overflow" +#pragma clang diagnostic ignored "-Wsign-conversion" +#pragma clang diagnostic ignored "-Wundef" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wextra" +#elif defined(_MSC_VER) +#pragma warning(push, 3) +// C4224: Nonstandard extension used: formal parameter 'X' was previously +// defined as a type. +#pragma warning(disable: 4224) +// C4826: Conversion from 'type1 ' to 'type_2' is sign - extended. This may +// cause unexpected runtime behavior. +#pragma warning(disable: 4826) +#endif + +#include "gtest/gtest.h" + +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#elif defined(_MSC_VER) +#pragma warning(pop) +#endif + +#include "pkix/pkix.h" +#include "pkixtestutil.h" + +// PrintTo must be in the same namespace as the type we're overloading it for. +namespace mozilla { namespace pkix { + +inline void +PrintTo(const Result& result, ::std::ostream* os) +{ + const char* stringified = MapResultToName(result); + if (stringified) { + *os << stringified; + } else { + *os << "mozilla::pkix::Result(" << static_cast<unsigned int>(result) << ")"; + } +} + +} } // namespace mozilla::pkix + +namespace mozilla { namespace pkix { namespace test { + +extern const std::time_t oneDayBeforeNow; +extern const std::time_t oneDayAfterNow; +extern const std::time_t twoDaysBeforeNow; +extern const std::time_t twoDaysAfterNow; +extern const std::time_t tenDaysBeforeNow; +extern const std::time_t tenDaysAfterNow; + + +class EverythingFailsByDefaultTrustDomain : public TrustDomain +{ +public: + Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, + Input, /*out*/ TrustLevel&) override + { + ADD_FAILURE(); + return NotReached("GetCertTrust should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + Result FindIssuer(Input, IssuerChecker&, Time) override + { + ADD_FAILURE(); + return NotReached("FindIssuer should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, + /*optional*/ const Input*, + /*optional*/ const Input*) override + { + ADD_FAILURE(); + return NotReached("CheckRevocation should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + Result IsChainValid(const DERArray&, Time) override + { + ADD_FAILURE(); + return NotReached("IsChainValid should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + Result DigestBuf(Input, DigestAlgorithm, /*out*/ uint8_t*, size_t) override + { + ADD_FAILURE(); + return NotReached("DigestBuf should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + Result CheckSignatureDigestAlgorithm(DigestAlgorithm, + EndEntityOrCA, + Time) override + { + ADD_FAILURE(); + return NotReached("CheckSignatureDigestAlgorithm should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override + { + ADD_FAILURE(); + return NotReached("CheckECDSACurveIsAcceptable should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + Result VerifyECDSASignedDigest(const SignedDigest&, Input) override + { + ADD_FAILURE(); + return NotReached("VerifyECDSASignedDigest should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, unsigned int) + override + { + ADD_FAILURE(); + return NotReached("CheckRSAPublicKeyModulusSizeInBits should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + Result VerifyRSAPKCS1SignedDigest(const SignedDigest&, Input) override + { + ADD_FAILURE(); + return NotReached("VerifyRSAPKCS1SignedDigest should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + Result CheckValidityIsAcceptable(Time, Time, EndEntityOrCA, KeyPurposeId) + override + { + ADD_FAILURE(); + return NotReached("CheckValidityIsAcceptable should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + Result NetscapeStepUpMatchesServerAuth(Time, bool&) override + { + ADD_FAILURE(); + return NotReached("NetscapeStepUpMatchesServerAuth should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + virtual void NoteAuxiliaryExtension(AuxiliaryExtension, Input) override + { + ADD_FAILURE(); + } +}; + +class DefaultCryptoTrustDomain : public EverythingFailsByDefaultTrustDomain +{ + Result DigestBuf(Input item, DigestAlgorithm digestAlg, + /*out*/ uint8_t* digestBuf, size_t digestBufLen) override + { + return TestDigestBuf(item, digestAlg, digestBuf, digestBufLen); + } + + Result CheckSignatureDigestAlgorithm(DigestAlgorithm, EndEntityOrCA, Time) + override + { + return Success; + } + + Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override + { + return Success; + } + + Result VerifyECDSASignedDigest(const SignedDigest& signedDigest, + Input subjectPublicKeyInfo) override + { + return TestVerifyECDSASignedDigest(signedDigest, subjectPublicKeyInfo); + } + + Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, unsigned int) + override + { + return Success; + } + + Result VerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest, + Input subjectPublicKeyInfo) override + { + return TestVerifyRSAPKCS1SignedDigest(signedDigest, subjectPublicKeyInfo); + } + + Result CheckValidityIsAcceptable(Time, Time, EndEntityOrCA, KeyPurposeId) + override + { + return Success; + } + + Result NetscapeStepUpMatchesServerAuth(Time, /*out*/ bool& matches) override + { + matches = true; + return Success; + } + + void NoteAuxiliaryExtension(AuxiliaryExtension, Input) override + { + } +}; + +class DefaultNameMatchingPolicy : public NameMatchingPolicy +{ +public: + virtual Result FallBackToCommonName( + Time, /*out*/ FallBackToSearchWithinSubject& fallBackToCommonName) override + { + fallBackToCommonName = FallBackToSearchWithinSubject::Yes; + return Success; + } +}; + +} } } // namespace mozilla::pkix::test + +#endif // mozilla_pkix_pkixgtest_h diff --git a/security/pkix/test/gtest/pkixnames_tests.cpp b/security/pkix/test/gtest/pkixnames_tests.cpp new file mode 100644 index 000000000..9e2303ad0 --- /dev/null +++ b/security/pkix/test/gtest/pkixnames_tests.cpp @@ -0,0 +1,2811 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2014 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "pkixcheck.h" +#include "pkixder.h" +#include "pkixgtest.h" +#include "pkixutil.h" + +namespace mozilla { namespace pkix { + +Result MatchPresentedDNSIDWithReferenceDNSID(Input presentedDNSID, + Input referenceDNSID, + /*out*/ bool& matches); + +bool IsValidReferenceDNSID(Input hostname); +bool IsValidPresentedDNSID(Input hostname); +bool ParseIPv4Address(Input hostname, /*out*/ uint8_t (&out)[4]); +bool ParseIPv6Address(Input hostname, /*out*/ uint8_t (&out)[16]); + +} } // namespace mozilla::pkix + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +struct PresentedMatchesReference +{ + ByteString presentedDNSID; + ByteString referenceDNSID; + Result expectedResult; + bool expectedMatches; // only valid when expectedResult == Success +}; + +#define DNS_ID_MATCH(a, b) \ + { \ + ByteString(reinterpret_cast<const uint8_t*>(a), sizeof(a) - 1), \ + ByteString(reinterpret_cast<const uint8_t*>(b), sizeof(b) - 1), \ + Success, \ + true \ + } + +#define DNS_ID_MISMATCH(a, b) \ + { \ + ByteString(reinterpret_cast<const uint8_t*>(a), sizeof(a) - 1), \ + ByteString(reinterpret_cast<const uint8_t*>(b), sizeof(b) - 1), \ + Success, \ + false \ + } + +#define DNS_ID_BAD_DER(a, b) \ + { \ + ByteString(reinterpret_cast<const uint8_t*>(a), sizeof(a) - 1), \ + ByteString(reinterpret_cast<const uint8_t*>(b), sizeof(b) - 1), \ + Result::ERROR_BAD_DER, \ + false \ + } + +static const PresentedMatchesReference DNSID_MATCH_PARAMS[] = +{ + DNS_ID_BAD_DER("", "a"), + + DNS_ID_MATCH("a", "a"), + DNS_ID_MISMATCH("b", "a"), + + DNS_ID_MATCH("*.b.a", "c.b.a"), + DNS_ID_MISMATCH("*.b.a", "b.a"), + DNS_ID_MISMATCH("*.b.a", "b.a."), + + // We allow underscores for compatibility with existing practices. + DNS_ID_MATCH("a_b", "a_b"), + DNS_ID_MATCH("*.example.com", "uses_underscore.example.com"), + DNS_ID_MATCH("*.uses_underscore.example.com", "a.uses_underscore.example.com"), + + // See bug 1139039 + DNS_ID_MATCH("_.example.com", "_.example.com"), + DNS_ID_MATCH("*.example.com", "_.example.com"), + DNS_ID_MATCH("_", "_"), + DNS_ID_MATCH("___", "___"), + DNS_ID_MATCH("example_", "example_"), + DNS_ID_MATCH("_example", "_example"), + DNS_ID_MATCH("*._._", "x._._"), + + // See bug 1139039 + // A DNS-ID must not end in an all-numeric label. We don't consider + // underscores to be numeric. + DNS_ID_MATCH("_1", "_1"), + DNS_ID_MATCH("example._1", "example._1"), + DNS_ID_MATCH("example.1_", "example.1_"), + + // Wildcard not in leftmost label + DNS_ID_MATCH("d.c.b.a", "d.c.b.a"), + DNS_ID_BAD_DER("d.*.b.a", "d.c.b.a"), + DNS_ID_BAD_DER("d.c*.b.a", "d.c.b.a"), + DNS_ID_BAD_DER("d.c*.b.a", "d.cc.b.a"), + + // case sensitivity + DNS_ID_MATCH("abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"), + DNS_ID_MATCH("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"), + DNS_ID_MATCH("aBc", "Abc"), + + // digits + DNS_ID_MATCH("a1", "a1"), + + // A trailing dot indicates an absolute name. Absolute presented names are + // not allowed, but absolute reference names are allowed. + DNS_ID_MATCH("example", "example"), + DNS_ID_BAD_DER("example.", "example."), + DNS_ID_MATCH("example", "example."), + DNS_ID_BAD_DER("example.", "example"), + DNS_ID_MATCH("example.com", "example.com"), + DNS_ID_BAD_DER("example.com.", "example.com."), + DNS_ID_MATCH("example.com", "example.com."), + DNS_ID_BAD_DER("example.com.", "example.com"), + DNS_ID_BAD_DER("example.com..", "example.com."), + DNS_ID_BAD_DER("example.com..", "example.com"), + DNS_ID_BAD_DER("example.com...", "example.com."), + + // xn-- IDN prefix + DNS_ID_BAD_DER("x*.b.a", "xa.b.a"), + DNS_ID_BAD_DER("x*.b.a", "xna.b.a"), + DNS_ID_BAD_DER("x*.b.a", "xn-a.b.a"), + DNS_ID_BAD_DER("x*.b.a", "xn--a.b.a"), + DNS_ID_BAD_DER("xn*.b.a", "xn--a.b.a"), + DNS_ID_BAD_DER("xn-*.b.a", "xn--a.b.a"), + DNS_ID_BAD_DER("xn--*.b.a", "xn--a.b.a"), + DNS_ID_BAD_DER("xn*.b.a", "xn--a.b.a"), + DNS_ID_BAD_DER("xn-*.b.a", "xn--a.b.a"), + DNS_ID_BAD_DER("xn--*.b.a", "xn--a.b.a"), + DNS_ID_BAD_DER("xn---*.b.a", "xn--a.b.a"), + + // "*" cannot expand to nothing. + DNS_ID_BAD_DER("c*.b.a", "c.b.a"), + + ///////////////////////////////////////////////////////////////////////////// + // These are test cases adapted from Chromium's x509_certificate_unittest.cc. + // The parameter order is the opposite in Chromium's tests. Also, some tests + // were modified to fit into this framework or due to intentional differences + // between mozilla::pkix and Chromium. + + DNS_ID_MATCH("foo.com", "foo.com"), + DNS_ID_MATCH("f", "f"), + DNS_ID_MISMATCH("i", "h"), + DNS_ID_MATCH("*.foo.com", "bar.foo.com"), + DNS_ID_MATCH("*.test.fr", "www.test.fr"), + DNS_ID_MATCH("*.test.FR", "wwW.tESt.fr"), + DNS_ID_BAD_DER(".uk", "f.uk"), + DNS_ID_BAD_DER("?.bar.foo.com", "w.bar.foo.com"), + DNS_ID_BAD_DER("(www|ftp).foo.com", "www.foo.com"), // regex! + DNS_ID_BAD_DER("www.foo.com\0", "www.foo.com"), + DNS_ID_BAD_DER("www.foo.com\0*.foo.com", "www.foo.com"), + DNS_ID_MISMATCH("ww.house.example", "www.house.example"), + DNS_ID_MISMATCH("www.test.org", "test.org"), + DNS_ID_MISMATCH("*.test.org", "test.org"), + DNS_ID_BAD_DER("*.org", "test.org"), + DNS_ID_BAD_DER("w*.bar.foo.com", "w.bar.foo.com"), + DNS_ID_BAD_DER("ww*ww.bar.foo.com", "www.bar.foo.com"), + DNS_ID_BAD_DER("ww*ww.bar.foo.com", "wwww.bar.foo.com"), + + // Different than Chromium, matches NSS. + DNS_ID_BAD_DER("w*w.bar.foo.com", "wwww.bar.foo.com"), + + DNS_ID_BAD_DER("w*w.bar.foo.c0m", "wwww.bar.foo.com"), + + // '*' must be the only character in the wildcard label + DNS_ID_BAD_DER("wa*.bar.foo.com", "WALLY.bar.foo.com"), + + // We require "*" to be the last character in a wildcard label, but + // Chromium does not. + DNS_ID_BAD_DER("*Ly.bar.foo.com", "wally.bar.foo.com"), + + // Chromium does URL decoding of the reference ID, but we don't, and we also + // require that the reference ID is valid, so we can't test these two. + // DNS_ID_MATCH("www.foo.com", "ww%57.foo.com"), + // DNS_ID_MATCH("www&.foo.com", "www%26.foo.com"), + + DNS_ID_MISMATCH("*.test.de", "www.test.co.jp"), + DNS_ID_BAD_DER("*.jp", "www.test.co.jp"), + DNS_ID_MISMATCH("www.test.co.uk", "www.test.co.jp"), + DNS_ID_BAD_DER("www.*.co.jp", "www.test.co.jp"), + DNS_ID_MATCH("www.bar.foo.com", "www.bar.foo.com"), + DNS_ID_MISMATCH("*.foo.com", "www.bar.foo.com"), + DNS_ID_BAD_DER("*.*.foo.com", "www.bar.foo.com"), + DNS_ID_BAD_DER("*.*.foo.com", "www.bar.foo.com"), + + // Our matcher requires the reference ID to be a valid DNS name, so we cannot + // test this case. + //DNS_ID_BAD_DER("*.*.bar.foo.com", "*..bar.foo.com"), + + DNS_ID_MATCH("www.bath.org", "www.bath.org"), + + // Our matcher requires the reference ID to be a valid DNS name, so we cannot + // test these cases. + // DNS_ID_BAD_DER("www.bath.org", ""), + // DNS_ID_BAD_DER("www.bath.org", "20.30.40.50"), + // DNS_ID_BAD_DER("www.bath.org", "66.77.88.99"), + + // IDN tests + DNS_ID_MATCH("xn--poema-9qae5a.com.br", "xn--poema-9qae5a.com.br"), + DNS_ID_MATCH("*.xn--poema-9qae5a.com.br", "www.xn--poema-9qae5a.com.br"), + DNS_ID_MISMATCH("*.xn--poema-9qae5a.com.br", "xn--poema-9qae5a.com.br"), + DNS_ID_BAD_DER("xn--poema-*.com.br", "xn--poema-9qae5a.com.br"), + DNS_ID_BAD_DER("xn--*-9qae5a.com.br", "xn--poema-9qae5a.com.br"), + DNS_ID_BAD_DER("*--poema-9qae5a.com.br", "xn--poema-9qae5a.com.br"), + + // The following are adapted from the examples quoted from + // http://tools.ietf.org/html/rfc6125#section-6.4.3 + // (e.g., *.example.com would match foo.example.com but + // not bar.foo.example.com or example.com). + DNS_ID_MATCH("*.example.com", "foo.example.com"), + DNS_ID_MISMATCH("*.example.com", "bar.foo.example.com"), + DNS_ID_MISMATCH("*.example.com", "example.com"), + // (e.g., baz*.example.net and *baz.example.net and b*z.example.net would + // be taken to match baz1.example.net and foobaz.example.net and + // buzz.example.net, respectively. However, we don't allow any characters + // other than '*' in the wildcard label. + DNS_ID_BAD_DER("baz*.example.net", "baz1.example.net"), + + // Both of these are different from Chromium, but match NSS, becaues the + // wildcard character "*" is not the last character of the label. + DNS_ID_BAD_DER("*baz.example.net", "foobaz.example.net"), + DNS_ID_BAD_DER("b*z.example.net", "buzz.example.net"), + + // Wildcards should not be valid for public registry controlled domains, + // and unknown/unrecognized domains, at least three domain components must + // be present. For mozilla::pkix and NSS, there must always be at least two + // labels after the wildcard label. + DNS_ID_MATCH("*.test.example", "www.test.example"), + DNS_ID_MATCH("*.example.co.uk", "test.example.co.uk"), + DNS_ID_BAD_DER("*.exmaple", "test.example"), + + // The result is different than Chromium, because Chromium takes into account + // the additional knowledge it has that "co.uk" is a TLD. mozilla::pkix does + // not know that. + DNS_ID_MATCH("*.co.uk", "example.co.uk"), + + DNS_ID_BAD_DER("*.com", "foo.com"), + DNS_ID_BAD_DER("*.us", "foo.us"), + DNS_ID_BAD_DER("*", "foo"), + + // IDN variants of wildcards and registry controlled domains. + DNS_ID_MATCH("*.xn--poema-9qae5a.com.br", "www.xn--poema-9qae5a.com.br"), + DNS_ID_MATCH("*.example.xn--mgbaam7a8h", "test.example.xn--mgbaam7a8h"), + + // RFC6126 allows this, and NSS accepts it, but Chromium disallows it. + // TODO: File bug against Chromium. + DNS_ID_MATCH("*.com.br", "xn--poema-9qae5a.com.br"), + + DNS_ID_BAD_DER("*.xn--mgbaam7a8h", "example.xn--mgbaam7a8h"), + // Wildcards should be permissible for 'private' registry-controlled + // domains. (In mozilla::pkix, we do not know if it is a private registry- + // controlled domain or not.) + DNS_ID_MATCH("*.appspot.com", "www.appspot.com"), + DNS_ID_MATCH("*.s3.amazonaws.com", "foo.s3.amazonaws.com"), + + // Multiple wildcards are not valid. + DNS_ID_BAD_DER("*.*.com", "foo.example.com"), + DNS_ID_BAD_DER("*.bar.*.com", "foo.bar.example.com"), + + // Absolute vs relative DNS name tests. Although not explicitly specified + // in RFC 6125, absolute reference names (those ending in a .) should + // match either absolute or relative presented names. We don't allow + // absolute presented names. + // TODO: File errata against RFC 6125 about this. + DNS_ID_BAD_DER("foo.com.", "foo.com"), + DNS_ID_MATCH("foo.com", "foo.com."), + DNS_ID_BAD_DER("foo.com.", "foo.com."), + DNS_ID_BAD_DER("f.", "f"), + DNS_ID_MATCH("f", "f."), + DNS_ID_BAD_DER("f.", "f."), + DNS_ID_BAD_DER("*.bar.foo.com.", "www-3.bar.foo.com"), + DNS_ID_MATCH("*.bar.foo.com", "www-3.bar.foo.com."), + DNS_ID_BAD_DER("*.bar.foo.com.", "www-3.bar.foo.com."), + + // We require the reference ID to be a valid DNS name, so we cannot test this + // case. + // DNS_ID_MISMATCH(".", "."), + + DNS_ID_BAD_DER("*.com.", "example.com"), + DNS_ID_BAD_DER("*.com", "example.com."), + DNS_ID_BAD_DER("*.com.", "example.com."), + DNS_ID_BAD_DER("*.", "foo."), + DNS_ID_BAD_DER("*.", "foo"), + + // The result is different than Chromium because we don't know that co.uk is + // a TLD. + DNS_ID_MATCH("*.co.uk", "foo.co.uk"), + DNS_ID_MATCH("*.co.uk", "foo.co.uk."), + DNS_ID_BAD_DER("*.co.uk.", "foo.co.uk"), + DNS_ID_BAD_DER("*.co.uk.", "foo.co.uk."), + + DNS_ID_MISMATCH("*.example.com", "localhost"), + DNS_ID_MISMATCH("*.example.com", "localhost."), + // Note that we already have the testcase DNS_ID_BAD_DER("*", "foo") above +}; + +struct InputValidity +{ + ByteString input; + bool isValidReferenceID; + bool isValidPresentedID; +}; + +// str is null-terminated, which is why we subtract 1. str may contain embedded +// nulls (including at the end) preceding the null terminator though. +#define I(str, validReferenceID, validPresentedID) \ + { \ + ByteString(reinterpret_cast<const uint8_t*>(str), sizeof(str) - 1), \ + validReferenceID, \ + validPresentedID, \ + } + +static const InputValidity DNSNAMES_VALIDITY[] = +{ + I("a", true, true), + I("a.b", true, true), + I("a.b.c", true, true), + I("a.b.c.d", true, true), + + // empty labels + I("", false, false), + I(".", false, false), + I("a", true, true), + I(".a", false, false), + I(".a.b", false, false), + I("..a", false, false), + I("a..b", false, false), + I("a...b", false, false), + I("a..b.c", false, false), + I("a.b..c", false, false), + I(".a.b.c.", false, false), + + // absolute names (only allowed for reference names) + I("a.", true, false), + I("a.b.", true, false), + I("a.b.c.", true, false), + + // absolute names with empty label at end + I("a..", false, false), + I("a.b..", false, false), + I("a.b.c..", false, false), + I("a...", false, false), + + // Punycode + I("xn--", false, false), + I("xn--.", false, false), + I("xn--.a", false, false), + I("a.xn--", false, false), + I("a.xn--.", false, false), + I("a.xn--.b", false, false), + I("a.xn--.b", false, false), + I("a.xn--\0.b", false, false), + I("a.xn--a.b", true, true), + I("xn--a", true, true), + I("a.xn--a", true, true), + I("a.xn--a.a", true, true), + I("\xc4\x95.com", false, false), // UTF-8 ĕ + I("xn--jea.com", true, true), // punycode ĕ + I("xn--\xc4\x95.com", false, false), // UTF-8 ĕ, malformed punycode + UTF-8 mashup + + // Surprising punycode + I("xn--google.com", true, true), // 䕮䕵䕶䕱.com + I("xn--citibank.com", true, true), // 岍岊岊岅岉岎.com + I("xn--cnn.com", true, true), // 䁾.com + I("a.xn--cnn", true, true), // a.䁾 + I("a.xn--cnn.com", true, true), // a.䁾.com + + I("1.2.3.4", false, false), // IPv4 address + I("1::2", false, false), // IPV6 address + + // whitespace not allowed anywhere. + I(" ", false, false), + I(" a", false, false), + I("a ", false, false), + I("a b", false, false), + I("a.b 1", false, false), + I("a\t", false, false), + + // Nulls not allowed + I("\0", false, false), + I("a\0", false, false), + I("example.org\0.example.com", false, false), // Hi Moxie! + I("\0a", false, false), + I("xn--\0", false, false), + + // Allowed character set + I("a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z", true, true), + I("A.B.C.D.E.F.G.H.I.J.K.L.M.N.O.P.Q.R.S.T.U.V.W.X.Y.Z", true, true), + I("0.1.2.3.4.5.6.7.8.9.a", true, true), // "a" needed to avoid numeric last label + I("a-b", true, true), // hyphen (a label cannot start or end with a hyphen) + + // Underscores + I("a_b", true, true), + // See bug 1139039 + I("_", true, true), + I("a_", true, true), + I("_a", true, true), + I("_1", true, true), + I("1_", true, true), + I("___", true, true), + + // An invalid character in various positions + I("!", false, false), + I("!a", false, false), + I("a!", false, false), + I("a!b", false, false), + I("a.!", false, false), + I("a.a!", false, false), + I("a.!a", false, false), + I("a.a!a", false, false), + I("a.!a.a", false, false), + I("a.a!.a", false, false), + I("a.a!a.a", false, false), + + // Various other invalid characters + I("a!", false, false), + I("a@", false, false), + I("a#", false, false), + I("a$", false, false), + I("a%", false, false), + I("a^", false, false), + I("a&", false, false), + I("a*", false, false), + I("a(", false, false), + I("a)", false, false), + + // last label can't be fully numeric + I("1", false, false), + I("a.1", false, false), + + // other labels can be fully numeric + I("1.a", true, true), + I("1.2.a", true, true), + I("1.2.3.a", true, true), + + // last label can be *partly* numeric + I("1a", true, true), + I("1.1a", true, true), + I("1-1", true, true), + I("a.1-1", true, true), + I("a.1-a", true, true), + + // labels cannot start with a hyphen + I("-", false, false), + I("-1", false, false), + + // labels cannot end with a hyphen + I("1-", false, false), + I("1-.a", false, false), + I("a-", false, false), + I("a-.a", false, false), + I("a.1-.a", false, false), + I("a.a-.a", false, false), + + // labels can contain a hyphen in the middle + I("a-b", true, true), + I("1-2", true, true), + I("a.a-1", true, true), + + // multiple consecutive hyphens allowed + I("a--1", true, true), + I("1---a", true, true), + I("a-----------------b", true, true), + + // Wildcard specifications are not valid reference names, but are valid + // presented names if there are enough labels and if '*' is the only + // character in the wildcard label. + I("*.a", false, false), + I("a*", false, false), + I("a*.", false, false), + I("a*.a", false, false), + I("a*.a.", false, false), + I("*.a.b", false, true), + I("*.a.b.", false, false), + I("a*.b.c", false, false), + I("*.a.b.c", false, true), + I("a*.b.c.d", false, false), + + // Multiple wildcards are not allowed. + I("a**.b.c", false, false), + I("a*b*.c.d", false, false), + I("a*.b*.c", false, false), + + // Wildcards are only allowed in the first label. + I("a.*", false, false), + I("a.*.b", false, false), + I("a.b.*", false, false), + I("a.b*.c", false, false), + I("*.b*.c", false, false), + I(".*.a.b", false, false), + I(".a*.b.c", false, false), + + // Wildcards must be at the *end* of the first label. + I("*a.b.c", false, false), + I("a*b.c.d", false, false), + + // Wildcards not allowed with IDNA prefix + I("x*.a.b", false, false), + I("xn*.a.b", false, false), + I("xn-*.a.b", false, false), + I("xn--*.a.b", false, false), + I("xn--w*.a.b", false, false), + + // Redacted labels from RFC6962bis draft 4 + // https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-04#section-3.2.2 + I("(PRIVATE).foo", false, false), + + // maximum label length is 63 characters + I("1234567890" "1234567890" "1234567890" + "1234567890" "1234567890" "1234567890" "abc", true, true), + I("1234567890" "1234567890" "1234567890" + "1234567890" "1234567890" "1234567890" "abcd", false, false), + + // maximum total length is 253 characters + I("1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." + "1234567890" "1234567890" "1234567890" "1234567890" "12345678" "a", + true, true), + I("1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." + "1234567890" "1234567890" "1234567890" "1234567890" "123456789" "a", + false, false), +}; + +static const InputValidity DNSNAMES_VALIDITY_TURKISH_I[] = +{ + // http://en.wikipedia.org/wiki/Dotted_and_dotless_I#In_computing + // IDN registration rules disallow "latin capital letter i with dot above," + // but our checks aren't intended to enforce those rules. + I("I", true, true), // ASCII capital I + I("i", true, true), // ASCII lowercase i + I("\xC4\xB0", false, false), // latin capital letter i with dot above + I("\xC4\xB1", false, false), // latin small letter dotless i + I("xn--i-9bb", true, true), // latin capital letter i with dot above, in punycode + I("xn--cfa", true, true), // latin small letter dotless i, in punycode + I("xn--\xC4\xB0", false, false), // latin capital letter i with dot above, mashup + I("xn--\xC4\xB1", false, false), // latin small letter dotless i, mashup +}; + +static const uint8_t LOWERCASE_I_VALUE[1] = { 'i' }; +static const uint8_t UPPERCASE_I_VALUE[1] = { 'I' }; +static const Input LOWERCASE_I(LOWERCASE_I_VALUE); +static const Input UPPERCASE_I(UPPERCASE_I_VALUE); + +template <unsigned int L> +struct IPAddressParams +{ + ByteString input; + bool isValid; + uint8_t expectedValueIfValid[L]; +}; + +#define IPV4_VALID(str, a, b, c, d) \ + { \ + ByteString(reinterpret_cast<const uint8_t*>(str), sizeof(str) - 1), \ + true, \ + { a, b, c, d } \ + } + +// The value of expectedValueIfValid must be ignored for invalid IP addresses. +// The value { 73, 73, 73, 73 } is used because it is unlikely to result in an +// accidental match, unlike { 0, 0, 0, 0 }, which is a value we actually test. +#define IPV4_INVALID(str) \ + { \ + ByteString(reinterpret_cast<const uint8_t*>(str), sizeof(str) - 1), \ + false, \ + { 73, 73, 73, 73 } \ + } + +static const IPAddressParams<4> IPV4_ADDRESSES[] = +{ + IPV4_INVALID(""), + IPV4_INVALID("1"), + IPV4_INVALID("1.2"), + IPV4_INVALID("1.2.3"), + IPV4_VALID("1.2.3.4", 1, 2, 3, 4), + IPV4_INVALID("1.2.3.4.5"), + + IPV4_INVALID("1.2.3.4a"), // a DNSName! + IPV4_INVALID("a.2.3.4"), // not even a DNSName! + IPV4_INVALID("1::2"), // IPv6 address + + // Whitespace not allowed + IPV4_INVALID(" 1.2.3.4"), + IPV4_INVALID("1.2.3.4 "), + IPV4_INVALID("1 .2.3.4"), + IPV4_INVALID("\n1.2.3.4"), + IPV4_INVALID("1.2.3.4\n"), + + // Nulls not allowed + IPV4_INVALID("\0"), + IPV4_INVALID("\0" "1.2.3.4"), + IPV4_INVALID("1.2.3.4\0"), + IPV4_INVALID("1.2.3.4\0.5"), + + // Range + IPV4_VALID("0.0.0.0", 0, 0, 0, 0), + IPV4_VALID("255.255.255.255", 255, 255, 255, 255), + IPV4_INVALID("256.0.0.0"), + IPV4_INVALID("0.256.0.0"), + IPV4_INVALID("0.0.256.0"), + IPV4_INVALID("0.0.0.256"), + IPV4_INVALID("999.0.0.0"), + IPV4_INVALID("9999999999999999999.0.0.0"), + + // All digits allowed + IPV4_VALID("0.1.2.3", 0, 1, 2, 3), + IPV4_VALID("4.5.6.7", 4, 5, 6, 7), + IPV4_VALID("8.9.0.1", 8, 9, 0, 1), + + // Leading zeros not allowed + IPV4_INVALID("01.2.3.4"), + IPV4_INVALID("001.2.3.4"), + IPV4_INVALID("00000000001.2.3.4"), + IPV4_INVALID("010.2.3.4"), + IPV4_INVALID("1.02.3.4"), + IPV4_INVALID("1.2.03.4"), + IPV4_INVALID("1.2.3.04"), + + // Empty components + IPV4_INVALID(".2.3.4"), + IPV4_INVALID("1..3.4"), + IPV4_INVALID("1.2..4"), + IPV4_INVALID("1.2.3."), + + // Too many components + IPV4_INVALID("1.2.3.4.5"), + IPV4_INVALID("1.2.3.4.5.6"), + IPV4_INVALID("0.1.2.3.4"), + IPV4_INVALID("1.2.3.4.0"), + + // Leading/trailing dot + IPV4_INVALID(".1.2.3.4"), + IPV4_INVALID("1.2.3.4."), + + // Other common forms of IPv4 address + // http://en.wikipedia.org/wiki/IPv4#Address_representations + IPV4_VALID("192.0.2.235", 192, 0, 2, 235), // dotted decimal (control value) + IPV4_INVALID("0xC0.0x00.0x02.0xEB"), // dotted hex + IPV4_INVALID("0301.0000.0002.0353"), // dotted octal + IPV4_INVALID("0xC00002EB"), // non-dotted hex + IPV4_INVALID("3221226219"), // non-dotted decimal + IPV4_INVALID("030000001353"), // non-dotted octal + IPV4_INVALID("192.0.0002.0xEB"), // mixed +}; + +#define IPV6_VALID(str, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) \ + { \ + ByteString(reinterpret_cast<const uint8_t*>(str), sizeof(str) - 1), \ + true, \ + { a, b, c, d, \ + e, f, g, h, \ + i, j, k, l, \ + m, n, o, p } \ + } + +#define IPV6_INVALID(str) \ + { \ + ByteString(reinterpret_cast<const uint8_t*>(str), sizeof(str) - 1), \ + false, \ + { 73, 73, 73, 73, \ + 73, 73, 73, 73, \ + 73, 73, 73, 73, \ + 73, 73, 73, 73 } \ + } + +static const IPAddressParams<16> IPV6_ADDRESSES[] = +{ + IPV6_INVALID(""), + IPV6_INVALID("1234"), + IPV6_INVALID("1234:5678"), + IPV6_INVALID("1234:5678:9abc"), + IPV6_INVALID("1234:5678:9abc:def0"), + IPV6_INVALID("1234:5678:9abc:def0:1234:"), + IPV6_INVALID("1234:5678:9abc:def0:1234:5678:"), + IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:"), + IPV6_VALID("1234:5678:9abc:def0:1234:5678:9abc:def0", + 0x12, 0x34, 0x56, 0x78, + 0x9a, 0xbc, 0xde, 0xf0, + 0x12, 0x34, 0x56, 0x78, + 0x9a, 0xbc, 0xde, 0xf0), + IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:def0:"), + IPV6_INVALID(":1234:5678:9abc:def0:1234:5678:9abc:def0"), + IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:def0:0000"), + + // Valid contractions + IPV6_VALID("::1", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01), + IPV6_VALID("::1234", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x12, 0x34), + IPV6_VALID("1234::", + 0x12, 0x34, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00), + IPV6_VALID("1234::5678", + 0x12, 0x34, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x56, 0x78), + IPV6_VALID("1234:5678::abcd", + 0x12, 0x34, 0x56, 0x78, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xab, 0xcd), + IPV6_VALID("1234:5678:9abc:def0:1234:5678:9abc::", + 0x12, 0x34, 0x56, 0x78, + 0x9a, 0xbc, 0xde, 0xf0, + 0x12, 0x34, 0x56, 0x78, + 0x9a, 0xbc, 0x00, 0x00), + + // Contraction in full IPv6 addresses not allowed + IPV6_INVALID("::1234:5678:9abc:def0:1234:5678:9abc:def0"), // start + IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:def0::"), // end + IPV6_INVALID("1234:5678::9abc:def0:1234:5678:9abc:def0"), // interior + + // Multiple contractions not allowed + IPV6_INVALID("::1::"), + IPV6_INVALID("::1::2"), + IPV6_INVALID("1::2::"), + + // Colon madness! + IPV6_INVALID(":"), + IPV6_INVALID("::"), + IPV6_INVALID(":::"), + IPV6_INVALID("::::"), + IPV6_INVALID(":::1"), + IPV6_INVALID("::::1"), + IPV6_INVALID("1:::2"), + IPV6_INVALID("1::::2"), + IPV6_INVALID("1:2:::"), + IPV6_INVALID("1:2::::"), + IPV6_INVALID("::1234:"), + IPV6_INVALID(":1234::"), + + IPV6_INVALID("01234::"), // too many digits, even if zero + IPV6_INVALID("12345678::"), // too many digits or missing colon + + // uppercase + IPV6_VALID("ABCD:EFAB::", + 0xab, 0xcd, 0xef, 0xab, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00), + + // miXeD CAse + IPV6_VALID("aBcd:eFAb::", + 0xab, 0xcd, 0xef, 0xab, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00), + + // IPv4-style + IPV6_VALID("::2.3.4.5", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x02, 0x03, 0x04, 0x05), + IPV6_VALID("1234::2.3.4.5", + 0x12, 0x34, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x02, 0x03, 0x04, 0x05), + IPV6_VALID("::abcd:2.3.4.5", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xab, 0xcd, + 0x02, 0x03, 0x04, 0x05), + IPV6_VALID("1234:5678:9abc:def0:1234:5678:252.253.254.255", + 0x12, 0x34, 0x56, 0x78, + 0x9a, 0xbc, 0xde, 0xf0, + 0x12, 0x34, 0x56, 0x78, + 252, 253, 254, 255), + IPV6_VALID("1234:5678:9abc:def0:1234::252.253.254.255", + 0x12, 0x34, 0x56, 0x78, + 0x9a, 0xbc, 0xde, 0xf0, + 0x12, 0x34, 0x00, 0x00, + 252, 253, 254, 255), + IPV6_INVALID("1234::252.253.254"), + IPV6_INVALID("::252.253.254"), + IPV6_INVALID("::252.253.254.300"), + IPV6_INVALID("1234::252.253.254.255:"), + IPV6_INVALID("1234::252.253.254.255:5678"), + + // Contractions that don't contract + IPV6_INVALID("::1234:5678:9abc:def0:1234:5678:9abc:def0"), + IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:def0::"), + IPV6_INVALID("1234:5678:9abc:def0::1234:5678:9abc:def0"), + IPV6_INVALID("1234:5678:9abc:def0:1234:5678::252.253.254.255"), + + // With and without leading zeros + IPV6_VALID("::123", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x23), + IPV6_VALID("::0123", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x23), + IPV6_VALID("::012", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x12), + IPV6_VALID("::0012", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x12), + IPV6_VALID("::01", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01), + IPV6_VALID("::001", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01), + IPV6_VALID("::0001", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01), + IPV6_VALID("::0", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00), + IPV6_VALID("::00", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00), + IPV6_VALID("::000", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00), + IPV6_VALID("::0000", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00), + IPV6_INVALID("::01234"), + IPV6_INVALID("::00123"), + IPV6_INVALID("::000123"), + + // Trailing zero + IPV6_INVALID("::12340"), + + // Whitespace + IPV6_INVALID(" 1234:5678:9abc:def0:1234:5678:9abc:def0"), + IPV6_INVALID("\t1234:5678:9abc:def0:1234:5678:9abc:def0"), + IPV6_INVALID("\t1234:5678:9abc:def0:1234:5678:9abc:def0\n"), + IPV6_INVALID("1234 :5678:9abc:def0:1234:5678:9abc:def0"), + IPV6_INVALID("1234: 5678:9abc:def0:1234:5678:9abc:def0"), + IPV6_INVALID(":: 2.3.4.5"), + IPV6_INVALID("1234::252.253.254.255 "), + IPV6_INVALID("1234::252.253.254.255\n"), + IPV6_INVALID("1234::252.253. 254.255"), + + // Nulls + IPV6_INVALID("\0"), + IPV6_INVALID("::1\0:2"), + IPV6_INVALID("::1\0"), + IPV6_INVALID("::1.2.3.4\0"), + IPV6_INVALID("::1.2\02.3.4"), +}; + +class pkixnames_MatchPresentedDNSIDWithReferenceDNSID + : public ::testing::Test + , public ::testing::WithParamInterface<PresentedMatchesReference> +{ +public: + DefaultNameMatchingPolicy mNameMatchingPolicy; +}; + +TEST_P(pkixnames_MatchPresentedDNSIDWithReferenceDNSID, + MatchPresentedDNSIDWithReferenceDNSID) +{ + const PresentedMatchesReference& param(GetParam()); + SCOPED_TRACE(param.presentedDNSID.c_str()); + SCOPED_TRACE(param.referenceDNSID.c_str()); + Input presented; + ASSERT_EQ(Success, presented.Init(param.presentedDNSID.data(), + param.presentedDNSID.length())); + Input reference; + ASSERT_EQ(Success, reference.Init(param.referenceDNSID.data(), + param.referenceDNSID.length())); + + // sanity check that test makes sense + ASSERT_TRUE(IsValidReferenceDNSID(reference)); + + bool matches; + ASSERT_EQ(param.expectedResult, + MatchPresentedDNSIDWithReferenceDNSID(presented, reference, + matches)); + if (param.expectedResult == Success) { + ASSERT_EQ(param.expectedMatches, matches); + } +} + +INSTANTIATE_TEST_CASE_P(pkixnames_MatchPresentedDNSIDWithReferenceDNSID, + pkixnames_MatchPresentedDNSIDWithReferenceDNSID, + testing::ValuesIn(DNSID_MATCH_PARAMS)); + +class pkixnames_Turkish_I_Comparison + : public ::testing::Test + , public ::testing::WithParamInterface<InputValidity> +{ +public: + DefaultNameMatchingPolicy mNameMatchingPolicy; +}; + +TEST_P(pkixnames_Turkish_I_Comparison, MatchPresentedDNSIDWithReferenceDNSID) +{ + // Make sure we don't have the similar problems that strcasecmp and others + // have with the other kinds of "i" and "I" commonly used in Turkish locales. + + const InputValidity& inputValidity(GetParam()); + SCOPED_TRACE(inputValidity.input.c_str()); + Input input; + ASSERT_EQ(Success, input.Init(inputValidity.input.data(), + inputValidity.input.length())); + + bool isASCII = InputsAreEqual(LOWERCASE_I, input) || + InputsAreEqual(UPPERCASE_I, input); + { + bool matches; + ASSERT_EQ(inputValidity.isValidPresentedID ? Success + : Result::ERROR_BAD_DER, + MatchPresentedDNSIDWithReferenceDNSID(input, LOWERCASE_I, + matches)); + if (inputValidity.isValidPresentedID) { + ASSERT_EQ(isASCII, matches); + } + } + { + bool matches; + ASSERT_EQ(inputValidity.isValidPresentedID ? Success + : Result::ERROR_BAD_DER, + MatchPresentedDNSIDWithReferenceDNSID(input, UPPERCASE_I, + matches)); + if (inputValidity.isValidPresentedID) { + ASSERT_EQ(isASCII, matches); + } + } +} + +INSTANTIATE_TEST_CASE_P(pkixnames_Turkish_I_Comparison, + pkixnames_Turkish_I_Comparison, + testing::ValuesIn(DNSNAMES_VALIDITY_TURKISH_I)); + +class pkixnames_IsValidReferenceDNSID + : public ::testing::Test + , public ::testing::WithParamInterface<InputValidity> +{ +public: + DefaultNameMatchingPolicy mNameMatchingPolicy; +}; + +TEST_P(pkixnames_IsValidReferenceDNSID, IsValidReferenceDNSID) +{ + const InputValidity& inputValidity(GetParam()); + SCOPED_TRACE(inputValidity.input.c_str()); + Input input; + ASSERT_EQ(Success, input.Init(inputValidity.input.data(), + inputValidity.input.length())); + ASSERT_EQ(inputValidity.isValidReferenceID, IsValidReferenceDNSID(input)); + ASSERT_EQ(inputValidity.isValidPresentedID, IsValidPresentedDNSID(input)); +} + +INSTANTIATE_TEST_CASE_P(pkixnames_IsValidReferenceDNSID, + pkixnames_IsValidReferenceDNSID, + testing::ValuesIn(DNSNAMES_VALIDITY)); +INSTANTIATE_TEST_CASE_P(pkixnames_IsValidReferenceDNSID_Turkish_I, + pkixnames_IsValidReferenceDNSID, + testing::ValuesIn(DNSNAMES_VALIDITY_TURKISH_I)); + +class pkixnames_ParseIPv4Address + : public ::testing::Test + , public ::testing::WithParamInterface<IPAddressParams<4>> +{ +public: + DefaultNameMatchingPolicy mNameMatchingPolicy; +}; + +TEST_P(pkixnames_ParseIPv4Address, ParseIPv4Address) +{ + const IPAddressParams<4>& param(GetParam()); + SCOPED_TRACE(param.input.c_str()); + Input input; + ASSERT_EQ(Success, input.Init(param.input.data(), + param.input.length())); + uint8_t ipAddress[4]; + ASSERT_EQ(param.isValid, ParseIPv4Address(input, ipAddress)); + if (param.isValid) { + for (size_t i = 0; i < sizeof(ipAddress); ++i) { + ASSERT_EQ(param.expectedValueIfValid[i], ipAddress[i]); + } + } +} + +INSTANTIATE_TEST_CASE_P(pkixnames_ParseIPv4Address, + pkixnames_ParseIPv4Address, + testing::ValuesIn(IPV4_ADDRESSES)); + +class pkixnames_ParseIPv6Address + : public ::testing::Test + , public ::testing::WithParamInterface<IPAddressParams<16>> +{ +public: + DefaultNameMatchingPolicy mNameMatchingPolicy; +}; + +TEST_P(pkixnames_ParseIPv6Address, ParseIPv6Address) +{ + const IPAddressParams<16>& param(GetParam()); + SCOPED_TRACE(param.input.c_str()); + Input input; + ASSERT_EQ(Success, input.Init(param.input.data(), + param.input.length())); + uint8_t ipAddress[16]; + ASSERT_EQ(param.isValid, ParseIPv6Address(input, ipAddress)); + if (param.isValid) { + for (size_t i = 0; i < sizeof(ipAddress); ++i) { + ASSERT_EQ(param.expectedValueIfValid[i], ipAddress[i]); + } + } +} + +INSTANTIATE_TEST_CASE_P(pkixnames_ParseIPv6Address, + pkixnames_ParseIPv6Address, + testing::ValuesIn(IPV6_ADDRESSES)); + +// This is an arbitrary string that is used to indicate that no SAN extension +// should be put into the generated certificate. It needs to be different from +// "" or any other subjectAltName value that we actually want to test, but its +// actual value does not matter. Note that this isn't a correctly-encoded SAN +// extension value! +static const ByteString + NO_SAN(reinterpret_cast<const uint8_t*>("I'm a bad, bad, certificate")); + +struct CheckCertHostnameParams +{ + ByteString hostname; + ByteString subject; + ByteString subjectAltName; + Result result; +}; + +class pkixnames_CheckCertHostname + : public ::testing::Test + , public ::testing::WithParamInterface<CheckCertHostnameParams> +{ +public: + DefaultNameMatchingPolicy mNameMatchingPolicy; +}; + +#define WITH_SAN(r, ps, psan, result) \ + { \ + ByteString(reinterpret_cast<const uint8_t*>(r), sizeof(r) - 1), \ + ps, \ + psan, \ + result \ + } + +#define WITHOUT_SAN(r, ps, result) \ + { \ + ByteString(reinterpret_cast<const uint8_t*>(r), sizeof(r) - 1), \ + ps, \ + NO_SAN, \ + result \ + } + +static const uint8_t example_com[] = { + 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm' +}; + +// Note that We avoid zero-valued bytes in these IP addresses so that we don't +// get false negatives from anti-NULL-byte defenses in dNSName decoding. +static const uint8_t ipv4_addr_bytes[] = { + 1, 2, 3, 4 +}; +static const uint8_t ipv4_addr_bytes_as_str[] = "\x01\x02\x03\x04"; +static const uint8_t ipv4_addr_str[] = "1.2.3.4"; +static const uint8_t ipv4_addr_bytes_FFFFFFFF[8] = { + 1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff +}; + +static const uint8_t ipv4_compatible_ipv6_addr_bytes[] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 1, 2, 3, 4 +}; +static const uint8_t ipv4_compatible_ipv6_addr_str[] = "::1.2.3.4"; + +static const uint8_t ipv4_mapped_ipv6_addr_bytes[] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0xFF, 0xFF, + 1, 2, 3, 4 +}; +static const uint8_t ipv4_mapped_ipv6_addr_str[] = "::FFFF:1.2.3.4"; + +static const uint8_t ipv6_addr_bytes[] = { + 0x11, 0x22, 0x33, 0x44, + 0x55, 0x66, 0x77, 0x88, + 0x99, 0xaa, 0xbb, 0xcc, + 0xdd, 0xee, 0xff, 0x11 +}; +static const uint8_t ipv6_addr_bytes_as_str[] = + "\x11\x22\x33\x44" + "\x55\x66\x77\x88" + "\x99\xaa\xbb\xcc" + "\xdd\xee\xff\x11"; + +static const uint8_t ipv6_addr_str[] = + "1122:3344:5566:7788:99aa:bbcc:ddee:ff11"; + +static const uint8_t ipv6_other_addr_bytes[] = { + 0xff, 0xee, 0xdd, 0xcc, + 0xbb, 0xaa, 0x99, 0x88, + 0x77, 0x66, 0x55, 0x44, + 0x33, 0x22, 0x11, 0x00, +}; + +static const uint8_t ipv4_other_addr_bytes[] = { + 5, 6, 7, 8 +}; +static const uint8_t ipv4_other_addr_bytes_FFFFFFFF[] = { + 5, 6, 7, 8, 0xff, 0xff, 0xff, 0xff +}; + +static const uint8_t ipv4_addr_00000000_bytes[] = { + 0, 0, 0, 0 +}; +static const uint8_t ipv4_addr_FFFFFFFF_bytes[] = { + 0, 0, 0, 0 +}; + +static const uint8_t ipv4_constraint_all_zeros_bytes[] = { + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static const uint8_t ipv6_addr_all_zeros_bytes[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static const uint8_t ipv6_constraint_all_zeros_bytes[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static const uint8_t ipv4_constraint_CIDR_16_bytes[] = { + 1, 2, 0, 0, 0xff, 0xff, 0, 0 +}; +static const uint8_t ipv4_constraint_CIDR_17_bytes[] = { + 1, 2, 0, 0, 0xff, 0xff, 0x80, 0 +}; + +// The subnet is 1.2.0.0/16 but it is specified as 1.2.3.0/16 +static const uint8_t ipv4_constraint_CIDR_16_bad_addr_bytes[] = { + 1, 2, 3, 0, 0xff, 0xff, 0, 0 +}; + +// Masks are supposed to be of the form <ones><zeros>, but this one is of the +// form <ones><zeros><ones><zeros>. +static const uint8_t ipv4_constraint_bad_mask_bytes[] = { + 1, 2, 3, 0, 0xff, 0, 0xff, 0 +}; + +static const uint8_t ipv6_constraint_CIDR_16_bytes[] = { + 0x11, 0x22, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0xff, 0xff, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +// The subnet is 1122::/16 but it is specified as 1122:3344::/16 +static const uint8_t ipv6_constraint_CIDR_16_bad_addr_bytes[] = { + 0x11, 0x22, 0x33, 0x44, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0xff, 0xff, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +// Masks are supposed to be of the form <ones><zeros>, but this one is of the +// form <ones><zeros><ones><zeros>. +static const uint8_t ipv6_constraint_bad_mask_bytes[] = { + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0xff, 0xff, 0, 0, 0xff, 0xff, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static const uint8_t ipv4_addr_truncated_bytes[] = { + 1, 2, 3 +}; +static const uint8_t ipv4_addr_overlong_bytes[] = { + 1, 2, 3, 4, 5 +}; +static const uint8_t ipv4_constraint_truncated_bytes[] = { + 0, 0, 0, 0, + 0, 0, 0, +}; +static const uint8_t ipv4_constraint_overlong_bytes[] = { + 0, 0, 0, 0, + 0, 0, 0, 0, 0 +}; + +static const uint8_t ipv6_addr_truncated_bytes[] = { + 0x11, 0x22, 0x33, 0x44, + 0x55, 0x66, 0x77, 0x88, + 0x99, 0xaa, 0xbb, 0xcc, + 0xdd, 0xee, 0xff +}; +static const uint8_t ipv6_addr_overlong_bytes[] = { + 0x11, 0x22, 0x33, 0x44, + 0x55, 0x66, 0x77, 0x88, + 0x99, 0xaa, 0xbb, 0xcc, + 0xdd, 0xee, 0xff, 0x11, 0x00 +}; +static const uint8_t ipv6_constraint_truncated_bytes[] = { + 0x11, 0x22, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0xff, 0xff, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0 +}; +static const uint8_t ipv6_constraint_overlong_bytes[] = { + 0x11, 0x22, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0xff, 0xff, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +// Note that, for DNSNames, these test cases in CHECK_CERT_HOSTNAME_PARAMS are +// mostly about testing different scenerios regarding the structure of entries +// in the subjectAltName and subject of the certificate, than about the how +// specific presented identifier values are matched against the reference +// identifier values. This is because we also use the test cases in +// DNSNAMES_VALIDITY to test CheckCertHostname. Consequently, tests about +// whether specific presented DNSNames (including wildcards, in particular) are +// matched against a reference DNSName only need to be added to +// DNSNAMES_VALIDITY, and not here. +static const CheckCertHostnameParams CHECK_CERT_HOSTNAME_PARAMS[] = +{ + // This is technically illegal. PrintableString is defined in such a way that + // '*' is not an allowed character, but there are many real-world certificates + // that are encoded this way. + WITHOUT_SAN("foo.example.com", RDN(CN("*.example.com", der::PrintableString)), + Success), + WITHOUT_SAN("foo.example.com", RDN(CN("*.example.com", der::UTF8String)), + Success), + + // Many certificates use TeletexString when encoding wildcards in CN-IDs + // because PrintableString is defined as not allowing '*' and UTF8String was, + // at one point in history, considered too new to depend on for compatibility. + // We accept TeletexString-encoded CN-IDs when they don't contain any escape + // sequences. The reference I used for the escape codes was + // https://tools.ietf.org/html/rfc1468. The escaping mechanism is actually + // pretty complex and these tests don't even come close to testing all the + // possibilities. + WITHOUT_SAN("foo.example.com", RDN(CN("*.example.com", der::TeletexString)), + Success), + // "ESC ( B" ({0x1B,0x50,0x42}) is the escape code to switch to ASCII, which + // is redundant because it already the default. + WITHOUT_SAN("foo.example.com", + RDN(CN("\x1B(B*.example.com", der::TeletexString)), + Result::ERROR_BAD_CERT_DOMAIN), + WITHOUT_SAN("foo.example.com", + RDN(CN("*.example\x1B(B.com", der::TeletexString)), + Result::ERROR_BAD_CERT_DOMAIN), + WITHOUT_SAN("foo.example.com", + RDN(CN("*.example.com\x1B(B", der::TeletexString)), + Result::ERROR_BAD_CERT_DOMAIN), + // "ESC $ B" ({0x1B,0x24,0x42}) is the escape code to switch to + // JIS X 0208-1983 (a Japanese character set). + WITHOUT_SAN("foo.example.com", + RDN(CN("\x1B$B*.example.com", der::TeletexString)), + Result::ERROR_BAD_CERT_DOMAIN), + WITHOUT_SAN("foo.example.com", + RDN(CN("*.example.com\x1B$B", der::TeletexString)), + Result::ERROR_BAD_CERT_DOMAIN), + + // Match a DNSName SAN entry with a redundant (ignored) matching CN-ID. + WITH_SAN("a", RDN(CN("a")), DNSName("a"), Success), + // Match a DNSName SAN entry when there is an CN-ID that doesn't match. + WITH_SAN("b", RDN(CN("a")), DNSName("b"), Success), + // Do not match a CN-ID when there is a valid DNSName SAN Entry. + WITH_SAN("a", RDN(CN("a")), DNSName("b"), Result::ERROR_BAD_CERT_DOMAIN), + // Do not match a CN-ID when there is a malformed DNSName SAN Entry. + WITH_SAN("a", RDN(CN("a")), DNSName("!"), Result::ERROR_BAD_DER), + // Do not match a matching CN-ID when there is a valid IPAddress SAN entry. + WITH_SAN("a", RDN(CN("a")), IPAddress(ipv4_addr_bytes), + Result::ERROR_BAD_CERT_DOMAIN), + // Do not match a matching CN-ID when there is a malformed IPAddress SAN entry. + WITH_SAN("a", RDN(CN("a")), IPAddress(example_com), + Result::ERROR_BAD_CERT_DOMAIN), + // Match a DNSName against a matching CN-ID when there is a SAN, but the SAN + // does not contain an DNSName or IPAddress entry. + WITH_SAN("a", RDN(CN("a")), RFC822Name("foo@example.com"), Success), + // Match a matching CN-ID when there is no SAN. + WITHOUT_SAN("a", RDN(CN("a")), Success), + // Do not match a mismatching CN-ID when there is no SAN. + WITHOUT_SAN("a", RDN(CN("b")), Result::ERROR_BAD_CERT_DOMAIN), + + // The first DNSName matches. + WITH_SAN("a", RDN(CN("foo")), DNSName("a") + DNSName("b"), Success), + // The last DNSName matches. + WITH_SAN("b", RDN(CN("foo")), DNSName("a") + DNSName("b"), Success), + // The middle DNSName matches. + WITH_SAN("b", RDN(CN("foo")), + DNSName("a") + DNSName("b") + DNSName("c"), Success), + // After an IP address. + WITH_SAN("b", RDN(CN("foo")), + IPAddress(ipv4_addr_bytes) + DNSName("b"), Success), + // Before an IP address. + WITH_SAN("a", RDN(CN("foo")), + DNSName("a") + IPAddress(ipv4_addr_bytes), Success), + // Between an RFC822Name and an IP address. + WITH_SAN("b", RDN(CN("foo")), + RFC822Name("foo@example.com") + DNSName("b") + + IPAddress(ipv4_addr_bytes), + Success), + // Duplicate DNSName. + WITH_SAN("a", RDN(CN("foo")), DNSName("a") + DNSName("a"), Success), + // After an invalid DNSName. + WITH_SAN("b", RDN(CN("foo")), DNSName("!") + DNSName("b"), + Result::ERROR_BAD_DER), + + // http://tools.ietf.org/html/rfc5280#section-4.2.1.6: "If the subjectAltName + // extension is present, the sequence MUST contain at least one entry." + // However, for compatibility reasons, this is not enforced. See bug 1143085. + // This case is treated as if the extension is not present (i.e. name + // matching falls back to the subject CN). + WITH_SAN("a", RDN(CN("a")), ByteString(), Success), + WITH_SAN("a", RDN(CN("b")), ByteString(), Result::ERROR_BAD_CERT_DOMAIN), + + // http://tools.ietf.org/html/rfc5280#section-4.1.2.6 says "If subject naming + // information is present only in the subjectAltName extension (e.g., a key + // bound only to an email address or URI), then the subject name MUST be an + // empty sequence and the subjectAltName extension MUST be critical." So, we + // have to support an empty subject. We don't enforce that the SAN must be + // critical or even that there is a SAN when the subject is empty, though. + WITH_SAN("a", ByteString(), DNSName("a"), Success), + // Make sure we return ERROR_BAD_CERT_DOMAIN and not ERROR_BAD_DER. + WITHOUT_SAN("a", ByteString(), Result::ERROR_BAD_CERT_DOMAIN), + + // Two CNs in the same RDN, both match. + WITHOUT_SAN("a", RDN(CN("a") + CN("a")), Success), + // Two CNs in the same RDN, both DNSNames, first one matches. + WITHOUT_SAN("a", RDN(CN("a") + CN("b")), + Result::ERROR_BAD_CERT_DOMAIN), + // Two CNs in the same RDN, both DNSNames, last one matches. + WITHOUT_SAN("b", RDN(CN("a") + CN("b")), Success), + // Two CNs in the same RDN, first one matches, second isn't a DNSName. + WITHOUT_SAN("a", RDN(CN("a") + CN("Not a DNSName")), + Result::ERROR_BAD_CERT_DOMAIN), + // Two CNs in the same RDN, first one not a DNSName, second matches. + WITHOUT_SAN("b", RDN(CN("Not a DNSName") + CN("b")), Success), + + // Two CNs in separate RDNs, both match. + WITHOUT_SAN("a", RDN(CN("a")) + RDN(CN("a")), Success), + // Two CNs in separate RDNs, both DNSNames, first one matches. + WITHOUT_SAN("a", RDN(CN("a")) + RDN(CN("b")), + Result::ERROR_BAD_CERT_DOMAIN), + // Two CNs in separate RDNs, both DNSNames, last one matches. + WITHOUT_SAN("b", RDN(CN("a")) + RDN(CN("b")), Success), + // Two CNs in separate RDNs, first one matches, second isn't a DNSName. + WITHOUT_SAN("a", RDN(CN("a")) + RDN(CN("Not a DNSName")), + Result::ERROR_BAD_CERT_DOMAIN), + // Two CNs in separate RDNs, first one not a DNSName, second matches. + WITHOUT_SAN("b", RDN(CN("Not a DNSName")) + RDN(CN("b")), Success), + + // One CN, one RDN, CN is the first AVA in the RDN, CN matches. + WITHOUT_SAN("a", RDN(CN("a") + OU("b")), Success), + // One CN, one RDN, CN is the first AVA in the RDN, CN does not match. + WITHOUT_SAN("b", RDN(CN("a") + OU("b")), + Result::ERROR_BAD_CERT_DOMAIN), + // One CN, one RDN, CN is not the first AVA in the RDN, CN matches. + WITHOUT_SAN("b", RDN(OU("a") + CN("b")), Success), + // One CN, one RDN, CN is not the first AVA in the RDN, CN does not match. + WITHOUT_SAN("a", RDN(OU("a") + CN("b")), + Result::ERROR_BAD_CERT_DOMAIN), + + // One CN, multiple RDNs, CN is in the first RDN, CN matches. + WITHOUT_SAN("a", RDN(CN("a")) + RDN(OU("b")), Success), + // One CN, multiple RDNs, CN is in the first RDN, CN does not match. + WITHOUT_SAN("b", RDN(CN("a")) + RDN(OU("b")), Result::ERROR_BAD_CERT_DOMAIN), + // One CN, multiple RDNs, CN is not in the first RDN, CN matches. + WITHOUT_SAN("b", RDN(OU("a")) + RDN(CN("b")), Success), + // One CN, multiple RDNs, CN is not in the first RDN, CN does not match. + WITHOUT_SAN("a", RDN(OU("a")) + RDN(CN("b")), Result::ERROR_BAD_CERT_DOMAIN), + + // One CN, one RDN, CN is not in the first or last AVA, CN matches. + WITHOUT_SAN("b", RDN(OU("a") + CN("b") + OU("c")), Success), + // One CN, multiple RDNs, CN is not in the first or last RDN, CN matches. + WITHOUT_SAN("b", RDN(OU("a")) + RDN(CN("b")) + RDN(OU("c")), Success), + + // Empty CN does not match. + WITHOUT_SAN("example.com", RDN(CN("")), Result::ERROR_BAD_CERT_DOMAIN), + + WITHOUT_SAN("uses_underscore.example.com", RDN(CN("*.example.com")), Success), + WITHOUT_SAN("a.uses_underscore.example.com", + RDN(CN("*.uses_underscore.example.com")), Success), + WITH_SAN("uses_underscore.example.com", RDN(CN("foo")), + DNSName("*.example.com"), Success), + WITH_SAN("a.uses_underscore.example.com", RDN(CN("foo")), + DNSName("*.uses_underscore.example.com"), Success), + + // Do not match a DNSName that is encoded in a malformed IPAddress. + WITH_SAN("example.com", RDN(CN("foo")), IPAddress(example_com), + Result::ERROR_BAD_CERT_DOMAIN), + + // We skip over the malformed IPAddress and match the DNSName entry because + // we've heard reports of real-world certificates that have malformed + // IPAddress SANs. + WITH_SAN("example.org", RDN(CN("foo")), + IPAddress(example_com) + DNSName("example.org"), Success), + + WITH_SAN("example.com", RDN(CN("foo")), + DNSName("!") + DNSName("example.com"), Result::ERROR_BAD_DER), + + // Match a matching IPv4 address SAN entry. + WITH_SAN(ipv4_addr_str, RDN(CN("foo")), IPAddress(ipv4_addr_bytes), + Success), + // Match a matching IPv4 addresses in the CN when there is no SAN + WITHOUT_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)), Success), + // Do not match a matching IPv4 address in the CN when there is a SAN with + // a DNSName entry. + WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)), + DNSName("example.com"), Result::ERROR_BAD_CERT_DOMAIN), + // Do not match a matching IPv4 address in the CN when there is a SAN with + // a non-matching IPAddress entry. + WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)), + IPAddress(ipv6_addr_bytes), Result::ERROR_BAD_CERT_DOMAIN), + // Match a matching IPv4 address in the CN when there is a SAN with a + // non-IPAddress, non-DNSName entry. + WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)), + RFC822Name("foo@example.com"), Success), + // Do not match a matching IPv4 address in the CN when there is a SAN with a + // malformed IPAddress entry. + WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)), + IPAddress(example_com), Result::ERROR_BAD_CERT_DOMAIN), + // Do not match a matching IPv4 address in the CN when there is a SAN with a + // malformed DNSName entry. + WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)), + DNSName("!"), Result::ERROR_BAD_CERT_DOMAIN), + + // We don't match IPv6 addresses in the CN, regardless of whether there is + // a SAN. + WITHOUT_SAN(ipv6_addr_str, RDN(CN(ipv6_addr_str)), + Result::ERROR_BAD_CERT_DOMAIN), + WITH_SAN(ipv6_addr_str, RDN(CN(ipv6_addr_str)), + DNSName("example.com"), Result::ERROR_BAD_CERT_DOMAIN), + WITH_SAN(ipv6_addr_str, RDN(CN(ipv6_addr_str)), + IPAddress(ipv6_addr_bytes), Success), + WITH_SAN(ipv6_addr_str, RDN(CN("foo")), IPAddress(ipv6_addr_bytes), + Success), + + // We don't match the binary encoding of the bytes of IP addresses in the + // CN. + WITHOUT_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_bytes_as_str)), + Result::ERROR_BAD_CERT_DOMAIN), + WITHOUT_SAN(ipv6_addr_str, RDN(CN(ipv6_addr_bytes_as_str)), + Result::ERROR_BAD_CERT_DOMAIN), + + // We don't match IP addresses with DNSName SANs. + WITH_SAN(ipv4_addr_str, RDN(CN("foo")), + DNSName(ipv4_addr_bytes_as_str), Result::ERROR_BAD_CERT_DOMAIN), + WITH_SAN(ipv4_addr_str, RDN(CN("foo")), DNSName(ipv4_addr_str), + Result::ERROR_BAD_CERT_DOMAIN), + WITH_SAN(ipv6_addr_str, RDN(CN("foo")), + DNSName(ipv6_addr_bytes_as_str), Result::ERROR_BAD_CERT_DOMAIN), + WITH_SAN(ipv6_addr_str, RDN(CN("foo")), DNSName(ipv6_addr_str), + Result::ERROR_BAD_CERT_DOMAIN), + + // Do not match an IPv4 reference ID against the equivalent IPv4-compatible + // IPv6 SAN entry. + WITH_SAN(ipv4_addr_str, RDN(CN("foo")), + IPAddress(ipv4_compatible_ipv6_addr_bytes), + Result::ERROR_BAD_CERT_DOMAIN), + // Do not match an IPv4 reference ID against the equivalent IPv4-mapped IPv6 + // SAN entry. + WITH_SAN(ipv4_addr_str, RDN(CN("foo")), + IPAddress(ipv4_mapped_ipv6_addr_bytes), + Result::ERROR_BAD_CERT_DOMAIN), + // Do not match an IPv4-compatible IPv6 reference ID against the equivalent + // IPv4 SAN entry. + WITH_SAN(ipv4_compatible_ipv6_addr_str, RDN(CN("foo")), + IPAddress(ipv4_addr_bytes), Result::ERROR_BAD_CERT_DOMAIN), + // Do not match an IPv4 reference ID against the equivalent IPv4-mapped IPv6 + // SAN entry. + WITH_SAN(ipv4_mapped_ipv6_addr_str, RDN(CN("foo")), + IPAddress(ipv4_addr_bytes), + Result::ERROR_BAD_CERT_DOMAIN), + + // Test that the presence of an otherName entry is handled appropriately. + // (The actual value of the otherName entry isn't important - that's not what + // we're testing here.) + WITH_SAN("example.com", ByteString(), + // The tag for otherName is CONTEXT_SPECIFIC | CONSTRUCTED | 0 + TLV((2 << 6) | (1 << 5) | 0, ByteString()) + DNSName("example.com"), + Success), + WITH_SAN("example.com", ByteString(), + TLV((2 << 6) | (1 << 5) | 0, ByteString()), + Result::ERROR_BAD_CERT_DOMAIN), +}; + +ByteString +CreateCert(const ByteString& subject, const ByteString& subjectAltName, + EndEntityOrCA endEntityOrCA = EndEntityOrCA::MustBeEndEntity) +{ + ByteString serialNumber(CreateEncodedSerialNumber(1)); + EXPECT_FALSE(ENCODING_FAILED(serialNumber)); + + ByteString issuerDER(Name(RDN(CN("issuer")))); + EXPECT_FALSE(ENCODING_FAILED(issuerDER)); + + ByteString extensions[2]; + if (subjectAltName != NO_SAN) { + extensions[0] = CreateEncodedSubjectAltName(subjectAltName); + EXPECT_FALSE(ENCODING_FAILED(extensions[0])); + } + if (endEntityOrCA == EndEntityOrCA::MustBeCA) { + // Currently, these tests assume that if we're creating a CA certificate, it + // will not have a subjectAlternativeName extension. If that assumption + // changes, this code will have to be updated. Ideally this would be + // ASSERT_EQ, but that inserts a 'return;', which doesn't match this + // function's return type. + EXPECT_EQ(subjectAltName, NO_SAN); + extensions[0] = CreateEncodedBasicConstraints(true, nullptr, + Critical::Yes); + EXPECT_FALSE(ENCODING_FAILED(extensions[0])); + } + + ScopedTestKeyPair keyPair(CloneReusedKeyPair()); + return CreateEncodedCertificate( + v3, sha256WithRSAEncryption(), serialNumber, issuerDER, + oneDayBeforeNow, oneDayAfterNow, Name(subject), *keyPair, + extensions, *keyPair, sha256WithRSAEncryption()); +} + +TEST_P(pkixnames_CheckCertHostname, CheckCertHostname) +{ + const CheckCertHostnameParams& param(GetParam()); + + ByteString cert(CreateCert(param.subject, param.subjectAltName)); + ASSERT_FALSE(ENCODING_FAILED(cert)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); + + Input hostnameInput; + ASSERT_EQ(Success, hostnameInput.Init(param.hostname.data(), + param.hostname.length())); + + ASSERT_EQ(param.result, CheckCertHostname(certInput, hostnameInput, + mNameMatchingPolicy)); +} + +INSTANTIATE_TEST_CASE_P(pkixnames_CheckCertHostname, + pkixnames_CheckCertHostname, + testing::ValuesIn(CHECK_CERT_HOSTNAME_PARAMS)); + +TEST_F(pkixnames_CheckCertHostname, SANWithoutSequence) +{ + // A certificate with a truly empty SAN extension (one that doesn't even + // contain a SEQUENCE at all) is malformed. If we didn't treat this as + // malformed then we'd have to treat it like the CN_EmptySAN cases. + + ByteString serialNumber(CreateEncodedSerialNumber(1)); + EXPECT_FALSE(ENCODING_FAILED(serialNumber)); + + ByteString extensions[2]; + extensions[0] = CreateEncodedEmptySubjectAltName(); + ASSERT_FALSE(ENCODING_FAILED(extensions[0])); + + ScopedTestKeyPair keyPair(CloneReusedKeyPair()); + ByteString certDER(CreateEncodedCertificate( + v3, sha256WithRSAEncryption(), serialNumber, + Name(RDN(CN("issuer"))), oneDayBeforeNow, oneDayAfterNow, + Name(RDN(CN("a"))), *keyPair, extensions, + *keyPair, sha256WithRSAEncryption())); + ASSERT_FALSE(ENCODING_FAILED(certDER)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length())); + + static const uint8_t a[] = { 'a' }; + ASSERT_EQ(Result::ERROR_EXTENSION_VALUE_INVALID, + CheckCertHostname(certInput, Input(a), mNameMatchingPolicy)); +} + +class pkixnames_CheckCertHostname_PresentedMatchesReference + : public ::testing::Test + , public ::testing::WithParamInterface<PresentedMatchesReference> +{ +public: + DefaultNameMatchingPolicy mNameMatchingPolicy; +}; + +TEST_P(pkixnames_CheckCertHostname_PresentedMatchesReference, CN_NoSAN) +{ + // Since there is no SAN, a valid presented DNS ID in the subject CN field + // should result in a match. + + const PresentedMatchesReference& param(GetParam()); + + ByteString cert(CreateCert(RDN(CN(param.presentedDNSID)), NO_SAN)); + ASSERT_FALSE(ENCODING_FAILED(cert)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); + + Input hostnameInput; + ASSERT_EQ(Success, hostnameInput.Init(param.referenceDNSID.data(), + param.referenceDNSID.length())); + + ASSERT_EQ(param.expectedMatches ? Success : Result::ERROR_BAD_CERT_DOMAIN, + CheckCertHostname(certInput, hostnameInput, mNameMatchingPolicy)); +} + +TEST_P(pkixnames_CheckCertHostname_PresentedMatchesReference, + SubjectAltName_CNNotDNSName) +{ + // A DNSName SAN entry should match, regardless of the contents of the + // subject CN. + + const PresentedMatchesReference& param(GetParam()); + + ByteString cert(CreateCert(RDN(CN("Common Name")), + DNSName(param.presentedDNSID))); + ASSERT_FALSE(ENCODING_FAILED(cert)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); + + Input hostnameInput; + ASSERT_EQ(Success, hostnameInput.Init(param.referenceDNSID.data(), + param.referenceDNSID.length())); + Result expectedResult + = param.expectedResult != Success ? param.expectedResult + : param.expectedMatches ? Success + : Result::ERROR_BAD_CERT_DOMAIN; + ASSERT_EQ(expectedResult, CheckCertHostname(certInput, hostnameInput, + mNameMatchingPolicy)); +} + +INSTANTIATE_TEST_CASE_P(pkixnames_CheckCertHostname_DNSID_MATCH_PARAMS, + pkixnames_CheckCertHostname_PresentedMatchesReference, + testing::ValuesIn(DNSID_MATCH_PARAMS)); + +TEST_P(pkixnames_Turkish_I_Comparison, CheckCertHostname_CN_NoSAN) +{ + // Make sure we don't have the similar problems that strcasecmp and others + // have with the other kinds of "i" and "I" commonly used in Turkish locales, + // when we're matching a CN due to lack of subjectAltName. + + const InputValidity& param(GetParam()); + SCOPED_TRACE(param.input.c_str()); + + Input input; + ASSERT_EQ(Success, input.Init(param.input.data(), param.input.length())); + + ByteString cert(CreateCert(RDN(CN(param.input)), NO_SAN)); + ASSERT_FALSE(ENCODING_FAILED(cert)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); + + Result expectedResult = (InputsAreEqual(LOWERCASE_I, input) || + InputsAreEqual(UPPERCASE_I, input)) + ? Success + : Result::ERROR_BAD_CERT_DOMAIN; + + ASSERT_EQ(expectedResult, CheckCertHostname(certInput, UPPERCASE_I, + mNameMatchingPolicy)); + ASSERT_EQ(expectedResult, CheckCertHostname(certInput, LOWERCASE_I, + mNameMatchingPolicy)); +} + +TEST_P(pkixnames_Turkish_I_Comparison, CheckCertHostname_SAN) +{ + // Make sure we don't have the similar problems that strcasecmp and others + // have with the other kinds of "i" and "I" commonly used in Turkish locales, + // when we're matching a dNSName in the SAN. + + const InputValidity& param(GetParam()); + SCOPED_TRACE(param.input.c_str()); + + Input input; + ASSERT_EQ(Success, input.Init(param.input.data(), param.input.length())); + + ByteString cert(CreateCert(RDN(CN("Common Name")), DNSName(param.input))); + ASSERT_FALSE(ENCODING_FAILED(cert)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); + + Result expectedResult + = (!param.isValidPresentedID) ? Result::ERROR_BAD_DER + : (InputsAreEqual(LOWERCASE_I, input) || + InputsAreEqual(UPPERCASE_I, input)) ? Success + : Result::ERROR_BAD_CERT_DOMAIN; + + ASSERT_EQ(expectedResult, CheckCertHostname(certInput, UPPERCASE_I, + mNameMatchingPolicy)); + ASSERT_EQ(expectedResult, CheckCertHostname(certInput, LOWERCASE_I, + mNameMatchingPolicy)); +} + +class pkixnames_CheckCertHostname_IPV4_Addresses + : public ::testing::Test + , public ::testing::WithParamInterface<IPAddressParams<4>> +{ +public: + DefaultNameMatchingPolicy mNameMatchingPolicy; +}; + +TEST_P(pkixnames_CheckCertHostname_IPV4_Addresses, + ValidIPv4AddressInIPAddressSAN) +{ + // When the reference hostname is a valid IPv4 address, a correctly-formed + // IPv4 Address SAN matches it. + + const IPAddressParams<4>& param(GetParam()); + + ByteString cert(CreateCert(RDN(CN("Common Name")), + IPAddress(param.expectedValueIfValid))); + ASSERT_FALSE(ENCODING_FAILED(cert)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); + + Input hostnameInput; + ASSERT_EQ(Success, hostnameInput.Init(param.input.data(), + param.input.length())); + + ASSERT_EQ(param.isValid ? Success : Result::ERROR_BAD_CERT_DOMAIN, + CheckCertHostname(certInput, hostnameInput, mNameMatchingPolicy)); +} + +TEST_P(pkixnames_CheckCertHostname_IPV4_Addresses, + ValidIPv4AddressInCN_NoSAN) +{ + // When the reference hostname is a valid IPv4 address, a correctly-formed + // IPv4 Address in the CN matches it when there is no SAN. + + const IPAddressParams<4>& param(GetParam()); + + SCOPED_TRACE(param.input.c_str()); + + ByteString cert(CreateCert(RDN(CN(param.input)), NO_SAN)); + ASSERT_FALSE(ENCODING_FAILED(cert)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); + + Input hostnameInput; + ASSERT_EQ(Success, hostnameInput.Init(param.input.data(), + param.input.length())); + + // Some of the invalid IPv4 addresses are valid DNS names! + Result expectedResult = (param.isValid || IsValidReferenceDNSID(hostnameInput)) + ? Success + : Result::ERROR_BAD_CERT_DOMAIN; + + ASSERT_EQ(expectedResult, CheckCertHostname(certInput, hostnameInput, + mNameMatchingPolicy)); +} + +INSTANTIATE_TEST_CASE_P(pkixnames_CheckCertHostname_IPV4_ADDRESSES, + pkixnames_CheckCertHostname_IPV4_Addresses, + testing::ValuesIn(IPV4_ADDRESSES)); + +struct NameConstraintParams +{ + ByteString subject; + ByteString subjectAltName; + ByteString subtrees; + Result expectedPermittedSubtreesResult; + Result expectedExcludedSubtreesResult; +}; + +static ByteString +PermittedSubtrees(const ByteString& generalSubtrees) +{ + return TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0, + generalSubtrees); +} + +static ByteString +ExcludedSubtrees(const ByteString& generalSubtrees) +{ + return TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1, + generalSubtrees); +} + +// Does not encode min or max. +static ByteString +GeneralSubtree(const ByteString& base) +{ + return TLV(der::SEQUENCE, base); +} + +static const NameConstraintParams NAME_CONSTRAINT_PARAMS[] = +{ + ///////////////////////////////////////////////////////////////////////////// + // XXX: Malformed name constraints for supported types of names are ignored + // when there are no names of that type to constrain. + { ByteString(), NO_SAN, + GeneralSubtree(DNSName("!")), + Success, Success + }, + { // DirectoryName constraints are an exception, because *every* certificate + // has at least one DirectoryName (tbsCertificate.subject). + ByteString(), NO_SAN, + GeneralSubtree(Name(ByteString(reinterpret_cast<const uint8_t*>("!"), 1))), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { ByteString(), NO_SAN, + GeneralSubtree(IPAddress(ipv4_constraint_truncated_bytes)), + Success, Success + }, + { ByteString(), NO_SAN, + GeneralSubtree(IPAddress(ipv4_constraint_overlong_bytes)), + Success, Success + }, + { ByteString(), NO_SAN, + GeneralSubtree(IPAddress(ipv6_constraint_truncated_bytes)), + Success, Success + }, + { ByteString(), NO_SAN, + GeneralSubtree(IPAddress(ipv6_constraint_overlong_bytes)), + Success, Success + }, + { ByteString(), NO_SAN, + GeneralSubtree(RFC822Name("!")), + Success, Success + }, + + ///////////////////////////////////////////////////////////////////////////// + // Edge cases of name constraint absolute vs. relative and subdomain matching + // that are not clearly explained in RFC 5280. (See the long comment above + // MatchPresentedDNSIDWithReferenceDNSID.) + + // Q: Does a presented identifier equal (case insensitive) to the name + // constraint match the constraint? For example, does the presented + // ID "host.example.com" match a "host.example.com" constraint? + { ByteString(), DNSName("host.example.com"), + GeneralSubtree(DNSName("host.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // This test case is an example from RFC 5280. + ByteString(), DNSName("host1.example.com"), + GeneralSubtree(DNSName("host.example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { ByteString(), RFC822Name("a@host.example.com"), + GeneralSubtree(RFC822Name("host.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // This test case is an example from RFC 5280. + ByteString(), RFC822Name("a@host1.example.com"), + GeneralSubtree(RFC822Name("host.example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + + // Q: When the name constraint does not start with ".", do subdomain + // presented identifiers match it? For example, does the presented + // ID "www.host.example.com" match a "host.example.com" constraint? + { // This test case is an example from RFC 5280. + ByteString(), DNSName("www.host.example.com"), + GeneralSubtree(DNSName( "host.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // The subdomain matching rule for host names that do not start with "." is + // different for RFC822Names than for DNSNames! + ByteString(), RFC822Name("a@www.host.example.com"), + GeneralSubtree(RFC822Name( "host.example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, + Success + }, + + // Q: When the name constraint does not start with ".", does a + // non-subdomain prefix match it? For example, does "bigfoo.bar.com" + // match "foo.bar.com"? + { ByteString(), DNSName("bigfoo.bar.com"), + GeneralSubtree(DNSName( "foo.bar.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { ByteString(), RFC822Name("a@bigfoo.bar.com"), + GeneralSubtree(RFC822Name( "foo.bar.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + + // Q: Is a name constraint that starts with "." valid, and if so, what + // semantics does it have? For example, does a presented ID of + // "www.example.com" match a constraint of ".example.com"? Does a + // presented ID of "example.com" match a constraint of ".example.com"? + { ByteString(), DNSName("www.example.com"), + GeneralSubtree(DNSName( ".example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // When there is no Local-part, an RFC822 name constraint's domain may + // start with '.', and the semantics are the same as for DNSNames. + ByteString(), RFC822Name("a@www.example.com"), + GeneralSubtree(RFC822Name( ".example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // When there is a Local-part, an RFC822 name constraint's domain must not + // start with '.'. + ByteString(), RFC822Name("a@www.example.com"), + GeneralSubtree(RFC822Name( "a@.example.com")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // Check that we only allow subdomains to match. + ByteString(), DNSName( "example.com"), + GeneralSubtree(DNSName(".example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // Check that we only allow subdomains to match. + ByteString(), RFC822Name("a@example.com"), + GeneralSubtree(RFC822Name(".example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // Check that we don't get confused and consider "b" == "." + ByteString(), DNSName("bexample.com"), + GeneralSubtree(DNSName(".example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // Check that we don't get confused and consider "b" == "." + ByteString(), RFC822Name("a@bexample.com"), + GeneralSubtree(RFC822Name( ".example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + + // Q: Is there a way to prevent subdomain matches? + // (This is tested in a different set of tests because it requires a + // combination of permittedSubtrees and excludedSubtrees.) + + // Q: Are name constraints allowed to be specified as absolute names? + // For example, does a presented ID of "example.com" match a name + // constraint of "example.com." and vice versa? + // + { // The DNSName in the constraint is not valid because constraint DNS IDs + // are not allowed to be absolute. + ByteString(), DNSName("example.com"), + GeneralSubtree(DNSName("example.com.")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, + }, + { ByteString(), RFC822Name("a@example.com"), + GeneralSubtree(RFC822Name( "example.com.")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, + }, + { // The DNSName in the SAN is not valid because presented DNS IDs are not + // allowed to be absolute. + ByteString(), DNSName("example.com."), + GeneralSubtree(DNSName("example.com")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, + }, + { ByteString(), RFC822Name("a@example.com."), + GeneralSubtree(RFC822Name( "example.com")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, + }, + { // The presented DNSName is the same length as the constraint, because the + // subdomain is only one character long and because the constraint both + // begins and ends with ".". But, it doesn't matter because absolute names + // are not allowed for DNSName constraints. + ByteString(), DNSName("p.example.com"), + GeneralSubtree(DNSName(".example.com.")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, + }, + { // The presented DNSName is the same length as the constraint, because the + // subdomain is only one character long and because the constraint both + // begins and ends with ".". + ByteString(), RFC822Name("a@p.example.com"), + GeneralSubtree(RFC822Name( ".example.com.")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, + }, + { // Same as previous test case, but using a wildcard presented ID. + ByteString(), DNSName("*.example.com"), + GeneralSubtree(DNSName(".example.com.")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // Same as previous test case, but using a wildcard presented ID, which is + // invalid in an RFC822Name. + ByteString(), RFC822Name("a@*.example.com"), + GeneralSubtree(RFC822Name( ".example.com.")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + + // Q: Are "" and "." valid DNSName constraints? If so, what do they mean? + { ByteString(), DNSName("example.com"), + GeneralSubtree(DNSName("")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), RFC822Name("a@example.com"), + GeneralSubtree(RFC822Name("")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // The malformed (absolute) presented ID does not match. + ByteString(), DNSName("example.com."), + GeneralSubtree(DNSName("")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { ByteString(), RFC822Name("a@example.com."), + GeneralSubtree(RFC822Name("")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // Invalid syntax in name constraint + ByteString(), DNSName("example.com"), + GeneralSubtree(DNSName(".")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, + }, + { // Invalid syntax in name constraint + ByteString(), RFC822Name("a@example.com"), + GeneralSubtree(RFC822Name(".")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, + }, + { ByteString(), DNSName("example.com."), + GeneralSubtree(DNSName(".")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { ByteString(), RFC822Name("a@example.com."), + GeneralSubtree(RFC822Name(".")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + + ///////////////////////////////////////////////////////////////////////////// + // Basic IP Address constraints (non-CN-ID) + + // The Mozilla CA Policy says this means "no IPv4 addresses allowed." + { ByteString(), IPAddress(ipv4_addr_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), IPAddress(ipv4_addr_00000000_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), IPAddress(ipv4_addr_FFFFFFFF_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + + // The Mozilla CA Policy says this means "no IPv6 addresses allowed." + { ByteString(), IPAddress(ipv6_addr_bytes), + GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), IPAddress(ipv6_addr_all_zeros_bytes), + GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + + // RFC 5280 doesn't partition IP address constraints into separate IPv4 and + // IPv6 categories, so a IPv4 permittedSubtrees constraint excludes all IPv6 + // addresses, and vice versa. + { ByteString(), IPAddress(ipv4_addr_bytes), + GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { ByteString(), IPAddress(ipv6_addr_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + + // IPv4 Subnets + { ByteString(), IPAddress(ipv4_addr_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_CIDR_16_bytes)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), IPAddress(ipv4_addr_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_CIDR_17_bytes)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), IPAddress(ipv4_other_addr_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_CIDR_16_bytes)), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // XXX(bug 1089430): We don't reject this even though it is weird. + ByteString(), IPAddress(ipv4_addr_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_CIDR_16_bad_addr_bytes)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // XXX(bug 1089430): We don't reject this even though it is weird. + ByteString(), IPAddress(ipv4_other_addr_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_bad_mask_bytes)), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + + // IPv6 Subnets + { ByteString(), IPAddress(ipv6_addr_bytes), + GeneralSubtree(IPAddress(ipv6_constraint_CIDR_16_bytes)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), IPAddress(ipv6_other_addr_bytes), + GeneralSubtree(IPAddress(ipv6_constraint_CIDR_16_bytes)), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // XXX(bug 1089430): We don't reject this even though it is weird. + ByteString(), IPAddress(ipv6_addr_bytes), + GeneralSubtree(IPAddress(ipv6_constraint_CIDR_16_bad_addr_bytes)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // XXX(bug 1089430): We don't reject this even though it is weird. + ByteString(), IPAddress(ipv6_other_addr_bytes), + GeneralSubtree(IPAddress(ipv6_constraint_bad_mask_bytes)), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + + // Malformed presented IP addresses and constraints + + { // The presented IPv4 address is empty + ByteString(), IPAddress(), + GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // The presented IPv4 address is truncated + ByteString(), IPAddress(ipv4_addr_truncated_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // The presented IPv4 address is too long + ByteString(), IPAddress(ipv4_addr_overlong_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // The presented IPv4 constraint is empty + ByteString(), IPAddress(ipv4_addr_bytes), + GeneralSubtree(IPAddress()), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // The presented IPv4 constraint is truncated + ByteString(), IPAddress(ipv4_addr_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_truncated_bytes)), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // The presented IPv4 constraint is too long + ByteString(), IPAddress(ipv4_addr_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_overlong_bytes)), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // The presented IPv6 address is empty + ByteString(), IPAddress(), + GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // The presented IPv6 address is truncated + ByteString(), IPAddress(ipv6_addr_truncated_bytes), + GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // The presented IPv6 address is too long + ByteString(), IPAddress(ipv6_addr_overlong_bytes), + GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // The presented IPv6 constraint is empty + ByteString(), IPAddress(ipv6_addr_bytes), + GeneralSubtree(IPAddress()), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // The presented IPv6 constraint is truncated + ByteString(), IPAddress(ipv6_addr_bytes), + GeneralSubtree(IPAddress(ipv6_constraint_truncated_bytes)), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // The presented IPv6 constraint is too long + ByteString(), IPAddress(ipv6_addr_bytes), + GeneralSubtree(IPAddress(ipv6_constraint_overlong_bytes)), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + + ///////////////////////////////////////////////////////////////////////////// + // XXX: We don't reject malformed name constraints when there are no names of + // that type. + { ByteString(), NO_SAN, GeneralSubtree(DNSName("!")), + Success, Success + }, + { ByteString(), NO_SAN, GeneralSubtree(IPAddress(ipv4_addr_overlong_bytes)), + Success, Success + }, + { ByteString(), NO_SAN, GeneralSubtree(IPAddress(ipv6_addr_overlong_bytes)), + Success, Success + }, + { ByteString(), NO_SAN, GeneralSubtree(RFC822Name("\0")), + Success, Success + }, + + ///////////////////////////////////////////////////////////////////////////// + // Basic CN-ID DNSName constraint tests. + + { // Empty Name is ignored for DNSName constraints. + ByteString(), NO_SAN, GeneralSubtree(DNSName("a.example.com")), + Success, Success + }, + { // Empty CN is ignored for DNSName constraints because it isn't a + // syntactically-valid DNSName. + // + // NSS gives different results. + RDN(CN("")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), + Success, Success + }, + { // IP Address is ignored for DNSName constraints. + // + // NSS gives different results. + RDN(CN("1.2.3.4")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), + Success, Success + }, + { // OU has something that looks like a dNSName that matches. + RDN(OU("a.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), + Success, Success + }, + { // OU has something that looks like a dNSName that does not match. + RDN(OU("b.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), + Success, Success + }, + { // NSS gives different results. + RDN(CN("Not a DNSName")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), + Success, Success + }, + { RDN(CN("a.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { RDN(CN("b.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // DNSName CN-ID match is detected when there is a SAN w/o any DNSName or + // IPAddress + RDN(CN("a.example.com")), RFC822Name("foo@example.com"), + GeneralSubtree(DNSName("a.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // DNSName CN-ID mismatch is detected when there is a SAN w/o any DNSName + // or IPAddress + RDN(CN("a.example.com")), RFC822Name("foo@example.com"), + GeneralSubtree(DNSName("b.example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // DNSName CN-ID match not reported when there is a DNSName SAN + RDN(CN("a.example.com")), DNSName("b.example.com"), + GeneralSubtree(DNSName("a.example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // DNSName CN-ID mismatch not reported when there is a DNSName SAN + RDN(CN("a.example.com")), DNSName("b.example.com"), + GeneralSubtree(DNSName("b.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE, + }, + { // DNSName CN-ID match not reported when there is an IPAddress SAN + RDN(CN("a.example.com")), IPAddress(ipv4_addr_bytes), + GeneralSubtree(DNSName("a.example.com")), + Success, Success + }, + { // DNSName CN-ID mismatch not reported when there is an IPAddress SAN + RDN(CN("a.example.com")), IPAddress(ipv4_addr_bytes), + GeneralSubtree(DNSName("b.example.com")), + Success, Success + }, + + { // IPAddress CN-ID match is detected when there is a SAN w/o any DNSName or + // IPAddress + RDN(CN(ipv4_addr_str)), RFC822Name("foo@example.com"), + GeneralSubtree(IPAddress(ipv4_addr_bytes_FFFFFFFF)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // IPAddress CN-ID mismatch is detected when there is a SAN w/o any DNSName + // or IPAddress + RDN(CN(ipv4_addr_str)), RFC822Name("foo@example.com"), + GeneralSubtree(IPAddress(ipv4_other_addr_bytes_FFFFFFFF)), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // IPAddress CN-ID match not reported when there is a DNSName SAN + RDN(CN(ipv4_addr_str)), DNSName("b.example.com"), + GeneralSubtree(IPAddress(ipv4_addr_bytes_FFFFFFFF)), + Success, Success + }, + { // IPAddress CN-ID mismatch not reported when there is a DNSName SAN + RDN(CN(ipv4_addr_str)), DNSName("b.example.com"), + GeneralSubtree(IPAddress(ipv4_addr_bytes_FFFFFFFF)), + Success, Success + }, + { // IPAddress CN-ID match not reported when there is an IPAddress SAN + RDN(CN(ipv4_addr_str)), IPAddress(ipv4_other_addr_bytes), + GeneralSubtree(IPAddress(ipv4_addr_bytes_FFFFFFFF)), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // IPAddress CN-ID mismatch not reported when there is an IPAddress SAN + RDN(CN(ipv4_addr_str)), IPAddress(ipv4_other_addr_bytes), + GeneralSubtree(IPAddress(ipv4_other_addr_bytes_FFFFFFFF)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + + ///////////////////////////////////////////////////////////////////////////// + // Test that constraints are applied to the most specific (last) CN, and only + // that CN-ID. + + { // Name constraint only matches a.example.com, but the most specific CN + // (i.e. the CN-ID) is b.example.com. (Two CNs in one RDN.) + RDN(CN("a.example.com") + CN("b.example.com")), NO_SAN, + GeneralSubtree(DNSName("a.example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // Name constraint only matches a.example.com, but the most specific CN + // (i.e. the CN-ID) is b.example.com. (Two CNs in separate RDNs.) + RDN(CN("a.example.com")) + RDN(CN("b.example.com")), NO_SAN, + GeneralSubtree(DNSName("a.example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // Name constraint only permits b.example.com, and the most specific CN + // (i.e. the CN-ID) is b.example.com. (Two CNs in one RDN.) + RDN(CN("a.example.com") + CN("b.example.com")), NO_SAN, + GeneralSubtree(DNSName("b.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // Name constraint only permits b.example.com, and the most specific CN + // (i.e. the CN-ID) is b.example.com. (Two CNs in separate RDNs.) + RDN(CN("a.example.com")) + RDN(CN("b.example.com")), NO_SAN, + GeneralSubtree(DNSName("b.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + + ///////////////////////////////////////////////////////////////////////////// + // Additional RFC822 name constraint tests. There are more tests regarding + // the DNSName part of the constraint mixed into the DNSName constraint + // tests. + + { ByteString(), RFC822Name("a@example.com"), + GeneralSubtree(RFC822Name("a@example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + + // Bug 1056773: name constraints that omit Local-part but include '@' are + // invalid. + { ByteString(), RFC822Name("a@example.com"), + GeneralSubtree(RFC822Name("@example.com")), + Result::ERROR_BAD_DER, + Result::ERROR_BAD_DER + }, + { ByteString(), RFC822Name("@example.com"), + GeneralSubtree(RFC822Name("@example.com")), + Result::ERROR_BAD_DER, + Result::ERROR_BAD_DER + }, + { ByteString(), RFC822Name("example.com"), + GeneralSubtree(RFC822Name("@example.com")), + Result::ERROR_BAD_DER, + Result::ERROR_BAD_DER + }, + { ByteString(), RFC822Name("a@mail.example.com"), + GeneralSubtree(RFC822Name("a@*.example.com")), + Result::ERROR_BAD_DER, + Result::ERROR_BAD_DER + }, + { ByteString(), RFC822Name("a@*.example.com"), + GeneralSubtree(RFC822Name(".example.com")), + Result::ERROR_BAD_DER, + Result::ERROR_BAD_DER + }, + { ByteString(), RFC822Name("@example.com"), + GeneralSubtree(RFC822Name(".example.com")), + Result::ERROR_BAD_DER, + Result::ERROR_BAD_DER + }, + { ByteString(), RFC822Name("@a.example.com"), + GeneralSubtree(RFC822Name(".example.com")), + Result::ERROR_BAD_DER, + Result::ERROR_BAD_DER + }, + + ///////////////////////////////////////////////////////////////////////////// + // Test name constraints with underscores. + // + { ByteString(), DNSName("uses_underscore.example.com"), + GeneralSubtree(DNSName("uses_underscore.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), DNSName("uses_underscore.example.com"), + GeneralSubtree(DNSName("example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), DNSName("a.uses_underscore.example.com"), + GeneralSubtree(DNSName("uses_underscore.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), RFC822Name("a@uses_underscore.example.com"), + GeneralSubtree(RFC822Name("uses_underscore.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), RFC822Name("uses_underscore@example.com"), + GeneralSubtree(RFC822Name("example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), RFC822Name("a@a.uses_underscore.example.com"), + GeneralSubtree(RFC822Name(".uses_underscore.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + + ///////////////////////////////////////////////////////////////////////////// + // Name constraint tests that relate to having an empty SAN. According to RFC + // 5280 this isn't valid, but we allow it for compatibility reasons (see bug + // 1143085). + { // For DNSNames, we fall back to the subject CN. + RDN(CN("a.example.com")), ByteString(), + GeneralSubtree(DNSName("a.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // For RFC822Names, we do not fall back to the subject emailAddress. + // This new implementation seems to conform better to the standards for + // RFC822 name constraints, by only applying the name constraints to + // emailAddress names in the certificate subject if there is no + // subjectAltName extension in the cert. + // In this case, the presence of the (empty) SAN extension means that RFC822 + // name constraints are not enforced on the emailAddress attributes of the + // subject. + RDN(emailAddress("a@example.com")), ByteString(), + GeneralSubtree(RFC822Name("a@example.com")), + Success, Success + }, + { // Compare this to the case where there is no SAN (i.e. the name + // constraints are enforced, because the extension is not present at all). + RDN(emailAddress("a@example.com")), NO_SAN, + GeneralSubtree(RFC822Name("a@example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + + ///////////////////////////////////////////////////////////////////////////// + // DirectoryName name constraint tests + + { // One AVA per RDN + RDN(OU("Example Organization")) + RDN(CN("example.com")), NO_SAN, + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization")) + + RDN(CN("example.com"))))), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // RDNs can have multiple AVAs. + RDN(OU("Example Organization") + CN("example.com")), NO_SAN, + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization") + + CN("example.com"))))), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // The constraint is a prefix of the subject DN. + RDN(OU("Example Organization")) + RDN(CN("example.com")), NO_SAN, + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // The name constraint is not a prefix of the subject DN. + // Note that for excludedSubtrees, we simply prohibit any non-empty + // directoryName constraint to ensure we are not being too lenient. + RDN(OU("Other Example Organization")) + RDN(CN("example.com")), NO_SAN, + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization")) + + RDN(CN("example.com"))))), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // Same as the previous one, but one RDN with multiple AVAs. + RDN(OU("Other Example Organization") + CN("example.com")), NO_SAN, + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization") + + CN("example.com"))))), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // With multiple AVAs per RDN in the subject DN, the constraint is not a + // prefix of the subject DN. + RDN(OU("Example Organization") + CN("example.com")), NO_SAN, + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // The subject DN RDN has multiple AVAs, but the name constraint has only + // one AVA per RDN. + RDN(OU("Example Organization") + CN("example.com")), NO_SAN, + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization")) + + RDN(CN("example.com"))))), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // The name constraint RDN has multiple AVAs, but the subject DN has only + // one AVA per RDN. + RDN(OU("Example Organization")) + RDN(CN("example.com")), NO_SAN, + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization") + + CN("example.com"))))), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // In this case, the constraint uses a different encoding from the subject. + // We consider them to match because we allow UTF8String and + // PrintableString to compare equal when their contents are equal. + RDN(OU("Example Organization", der::UTF8String)) + RDN(CN("example.com")), + NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization", + der::PrintableString)) + + RDN(CN("example.com"))))), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // Same as above, but with UTF8String/PrintableString switched. + RDN(OU("Example Organization", der::PrintableString)) + RDN(CN("example.com")), + NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization", + der::UTF8String)) + + RDN(CN("example.com"))))), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // If the contents aren't the same, then they shouldn't match. + RDN(OU("Other Example Organization", der::UTF8String)) + RDN(CN("example.com")), + NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization", + der::PrintableString)) + + RDN(CN("example.com"))))), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // Only UTF8String and PrintableString are considered equivalent. + RDN(OU("Example Organization", der::PrintableString)) + RDN(CN("example.com")), + NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization", + der::TeletexString)) + + RDN(CN("example.com"))))), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + // Some additional tests for completeness: + // Ensure that wildcards are handled: + { RDN(CN("*.example.com")), NO_SAN, GeneralSubtree(DNSName("example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), DNSName("*.example.com"), + GeneralSubtree(DNSName("example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), DNSName("www.example.com"), + GeneralSubtree(DNSName("*.example.com")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + // Handle multiple name constraint entries: + { RDN(CN("example.com")), NO_SAN, + GeneralSubtree(DNSName("example.org")) + + GeneralSubtree(DNSName("example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), DNSName("example.com"), + GeneralSubtree(DNSName("example.org")) + + GeneralSubtree(DNSName("example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + // Handle multiple names in subject alternative name extension: + { ByteString(), DNSName("example.com") + DNSName("example.org"), + GeneralSubtree(DNSName("example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + // Handle a mix of DNSName and DirectoryName: + { RDN(OU("Example Organization")), DNSName("example.com"), + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))) + + GeneralSubtree(DNSName("example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { RDN(OU("Other Example Organization")), DNSName("example.com"), + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))) + + GeneralSubtree(DNSName("example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { RDN(OU("Example Organization")), DNSName("example.org"), + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))) + + GeneralSubtree(DNSName("example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + // Handle a certificate with no DirectoryName: + { ByteString(), DNSName("example.com"), + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, +}; + +class pkixnames_CheckNameConstraints + : public ::testing::Test + , public ::testing::WithParamInterface<NameConstraintParams> +{ +public: + DefaultNameMatchingPolicy mNameMatchingPolicy; +}; + +TEST_P(pkixnames_CheckNameConstraints, + NameConstraintsEnforcedForDirectlyIssuedEndEntity) +{ + // Test that name constraints are enforced on a certificate directly issued by + // a certificate with the given name constraints. + + const NameConstraintParams& param(GetParam()); + + ByteString certDER(CreateCert(param.subject, param.subjectAltName)); + ASSERT_FALSE(ENCODING_FAILED(certDER)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length())); + BackCert cert(certInput, EndEntityOrCA::MustBeEndEntity, nullptr); + ASSERT_EQ(Success, cert.Init()); + + { + ByteString nameConstraintsDER(TLV(der::SEQUENCE, + PermittedSubtrees(param.subtrees))); + Input nameConstraints; + ASSERT_EQ(Success, + nameConstraints.Init(nameConstraintsDER.data(), + nameConstraintsDER.length())); + ASSERT_EQ(param.expectedPermittedSubtreesResult, + CheckNameConstraints(nameConstraints, cert, + KeyPurposeId::id_kp_serverAuth)); + } + { + ByteString nameConstraintsDER(TLV(der::SEQUENCE, + ExcludedSubtrees(param.subtrees))); + Input nameConstraints; + ASSERT_EQ(Success, + nameConstraints.Init(nameConstraintsDER.data(), + nameConstraintsDER.length())); + ASSERT_EQ(param.expectedExcludedSubtreesResult, + CheckNameConstraints(nameConstraints, cert, + KeyPurposeId::id_kp_serverAuth)); + } + { + ByteString nameConstraintsDER(TLV(der::SEQUENCE, + PermittedSubtrees(param.subtrees) + + ExcludedSubtrees(param.subtrees))); + Input nameConstraints; + ASSERT_EQ(Success, + nameConstraints.Init(nameConstraintsDER.data(), + nameConstraintsDER.length())); + ASSERT_EQ((param.expectedPermittedSubtreesResult == + param.expectedExcludedSubtreesResult) + ? param.expectedExcludedSubtreesResult + : Result::ERROR_CERT_NOT_IN_NAME_SPACE, + CheckNameConstraints(nameConstraints, cert, + KeyPurposeId::id_kp_serverAuth)); + } +} + +INSTANTIATE_TEST_CASE_P(pkixnames_CheckNameConstraints, + pkixnames_CheckNameConstraints, + testing::ValuesIn(NAME_CONSTRAINT_PARAMS)); + +// The |subjectAltName| param is not used for these test cases (hence the use of +// "NO_SAN"). +static const NameConstraintParams NO_FALLBACK_NAME_CONSTRAINT_PARAMS[] = +{ + // The only difference between end-entities being verified for serverAuth and + // intermediates or end-entities being verified for other uses is that for + // the latter cases, there is no fallback matching of DNSName entries to the + // subject common name. + { RDN(CN("Not a DNSName")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), + Success, Success + }, + { RDN(CN("a.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), + Success, Success + }, + { RDN(CN("b.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), + Success, Success + }, + // Sanity-check that name constraints are in fact enforced in these cases. + { RDN(CN("Example Name")), NO_SAN, + GeneralSubtree(DirectoryName(Name(RDN(CN("Example Name"))))), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + // (In this implementation, if a DirectoryName is in excludedSubtrees, nothing + // is considered to be in the name space.) + { RDN(CN("Other Example Name")), NO_SAN, + GeneralSubtree(DirectoryName(Name(RDN(CN("Example Name"))))), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, +}; + +class pkixnames_CheckNameConstraintsOnIntermediate + : public ::testing::Test + , public ::testing::WithParamInterface<NameConstraintParams> +{ +}; + +TEST_P(pkixnames_CheckNameConstraintsOnIntermediate, + NameConstraintsEnforcedOnIntermediate) +{ + // Test that name constraints are enforced on an intermediate certificate + // directly issued by a certificate with the given name constraints. + + const NameConstraintParams& param(GetParam()); + + ByteString certDER(CreateCert(param.subject, NO_SAN, + EndEntityOrCA::MustBeCA)); + ASSERT_FALSE(ENCODING_FAILED(certDER)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length())); + BackCert cert(certInput, EndEntityOrCA::MustBeCA, nullptr); + ASSERT_EQ(Success, cert.Init()); + + { + ByteString nameConstraintsDER(TLV(der::SEQUENCE, + PermittedSubtrees(param.subtrees))); + Input nameConstraints; + ASSERT_EQ(Success, + nameConstraints.Init(nameConstraintsDER.data(), + nameConstraintsDER.length())); + ASSERT_EQ(param.expectedPermittedSubtreesResult, + CheckNameConstraints(nameConstraints, cert, + KeyPurposeId::id_kp_serverAuth)); + } + { + ByteString nameConstraintsDER(TLV(der::SEQUENCE, + ExcludedSubtrees(param.subtrees))); + Input nameConstraints; + ASSERT_EQ(Success, + nameConstraints.Init(nameConstraintsDER.data(), + nameConstraintsDER.length())); + ASSERT_EQ(param.expectedExcludedSubtreesResult, + CheckNameConstraints(nameConstraints, cert, + KeyPurposeId::id_kp_serverAuth)); + } + { + ByteString nameConstraintsDER(TLV(der::SEQUENCE, + PermittedSubtrees(param.subtrees) + + ExcludedSubtrees(param.subtrees))); + Input nameConstraints; + ASSERT_EQ(Success, + nameConstraints.Init(nameConstraintsDER.data(), + nameConstraintsDER.length())); + ASSERT_EQ(param.expectedExcludedSubtreesResult, + CheckNameConstraints(nameConstraints, cert, + KeyPurposeId::id_kp_serverAuth)); + } +} + +INSTANTIATE_TEST_CASE_P(pkixnames_CheckNameConstraintsOnIntermediate, + pkixnames_CheckNameConstraintsOnIntermediate, + testing::ValuesIn(NO_FALLBACK_NAME_CONSTRAINT_PARAMS)); + +class pkixnames_CheckNameConstraintsForNonServerAuthUsage + : public ::testing::Test + , public ::testing::WithParamInterface<NameConstraintParams> +{ +}; + +TEST_P(pkixnames_CheckNameConstraintsForNonServerAuthUsage, + NameConstraintsEnforcedForNonServerAuthUsage) +{ + // Test that for key purposes other than serverAuth, fallback to the subject + // common name does not occur. + + const NameConstraintParams& param(GetParam()); + + ByteString certDER(CreateCert(param.subject, NO_SAN)); + ASSERT_FALSE(ENCODING_FAILED(certDER)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length())); + BackCert cert(certInput, EndEntityOrCA::MustBeEndEntity, nullptr); + ASSERT_EQ(Success, cert.Init()); + + { + ByteString nameConstraintsDER(TLV(der::SEQUENCE, + PermittedSubtrees(param.subtrees))); + Input nameConstraints; + ASSERT_EQ(Success, + nameConstraints.Init(nameConstraintsDER.data(), + nameConstraintsDER.length())); + ASSERT_EQ(param.expectedPermittedSubtreesResult, + CheckNameConstraints(nameConstraints, cert, + KeyPurposeId::id_kp_clientAuth)); + } + { + ByteString nameConstraintsDER(TLV(der::SEQUENCE, + ExcludedSubtrees(param.subtrees))); + Input nameConstraints; + ASSERT_EQ(Success, + nameConstraints.Init(nameConstraintsDER.data(), + nameConstraintsDER.length())); + ASSERT_EQ(param.expectedExcludedSubtreesResult, + CheckNameConstraints(nameConstraints, cert, + KeyPurposeId::id_kp_clientAuth)); + } + { + ByteString nameConstraintsDER(TLV(der::SEQUENCE, + PermittedSubtrees(param.subtrees) + + ExcludedSubtrees(param.subtrees))); + Input nameConstraints; + ASSERT_EQ(Success, + nameConstraints.Init(nameConstraintsDER.data(), + nameConstraintsDER.length())); + ASSERT_EQ(param.expectedExcludedSubtreesResult, + CheckNameConstraints(nameConstraints, cert, + KeyPurposeId::id_kp_clientAuth)); + } +} + +INSTANTIATE_TEST_CASE_P(pkixnames_CheckNameConstraintsForNonServerAuthUsage, + pkixnames_CheckNameConstraintsForNonServerAuthUsage, + testing::ValuesIn(NO_FALLBACK_NAME_CONSTRAINT_PARAMS)); diff --git a/security/pkix/test/gtest/pkixocsp_CreateEncodedOCSPRequest_tests.cpp b/security/pkix/test/gtest/pkixocsp_CreateEncodedOCSPRequest_tests.cpp new file mode 100644 index 000000000..ffc987c86 --- /dev/null +++ b/security/pkix/test/gtest/pkixocsp_CreateEncodedOCSPRequest_tests.cpp @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2013 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixgtest.h" +#include "pkixder.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +class CreateEncodedOCSPRequestTrustDomain final + : public EverythingFailsByDefaultTrustDomain +{ +private: + Result DigestBuf(Input item, DigestAlgorithm digestAlg, + /*out*/ uint8_t *digestBuf, size_t digestBufLen) + override + { + return TestDigestBuf(item, digestAlg, digestBuf, digestBufLen); + } + + Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, unsigned int) + override + { + return Success; + } +}; + +class pkixocsp_CreateEncodedOCSPRequest : public ::testing::Test +{ +protected: + void MakeIssuerCertIDComponents(const char* issuerASCII, + /*out*/ ByteString& issuerDER, + /*out*/ ByteString& issuerSPKI) + { + issuerDER = CNToDERName(issuerASCII); + ASSERT_FALSE(ENCODING_FAILED(issuerDER)); + + ScopedTestKeyPair keyPair(GenerateKeyPair()); + ASSERT_TRUE(keyPair.get()); + issuerSPKI = keyPair->subjectPublicKeyInfo; + } + + CreateEncodedOCSPRequestTrustDomain trustDomain; +}; + +// Test that the large length of the child serial number causes +// CreateEncodedOCSPRequest to fail. +TEST_F(pkixocsp_CreateEncodedOCSPRequest, ChildCertLongSerialNumberTest) +{ + static const uint8_t UNSUPPORTED_LEN = 128; // must be larger than 127 + + ByteString serialNumberString; + // tag + length + value is 1 + 2 + UNSUPPORTED_LEN + // Encoding the length takes two bytes: one byte to indicate that a + // second byte follows, and the second byte to indicate the length. + serialNumberString.push_back(0x80 + 1); + serialNumberString.push_back(UNSUPPORTED_LEN); + // value is 0x010000...00 + serialNumberString.push_back(0x01); + for (size_t i = 1; i < UNSUPPORTED_LEN; ++i) { + serialNumberString.push_back(0x00); + } + + ByteString issuerDER; + ByteString issuerSPKI; + ASSERT_NO_FATAL_FAILURE(MakeIssuerCertIDComponents("CA", issuerDER, + issuerSPKI)); + + Input issuer; + ASSERT_EQ(Success, issuer.Init(issuerDER.data(), issuerDER.length())); + + Input spki; + ASSERT_EQ(Success, spki.Init(issuerSPKI.data(), issuerSPKI.length())); + + Input serialNumber; + ASSERT_EQ(Success, serialNumber.Init(serialNumberString.data(), + serialNumberString.length())); + + uint8_t ocspRequest[OCSP_REQUEST_MAX_LENGTH]; + size_t ocspRequestLength; + ASSERT_EQ(Result::ERROR_BAD_DER, + CreateEncodedOCSPRequest(trustDomain, + CertID(issuer, spki, serialNumber), + ocspRequest, ocspRequestLength)); +} + +// Test that CreateEncodedOCSPRequest handles the longest serial number that +// it's required to support (i.e. 20 octets). +TEST_F(pkixocsp_CreateEncodedOCSPRequest, LongestSupportedSerialNumberTest) +{ + static const uint8_t LONGEST_REQUIRED_LEN = 20; + + ByteString serialNumberString; + // tag + length + value is 1 + 1 + LONGEST_REQUIRED_LEN + serialNumberString.push_back(der::INTEGER); + serialNumberString.push_back(LONGEST_REQUIRED_LEN); + serialNumberString.push_back(0x01); + // value is 0x010000...00 + for (size_t i = 1; i < LONGEST_REQUIRED_LEN; ++i) { + serialNumberString.push_back(0x00); + } + + ByteString issuerDER; + ByteString issuerSPKI; + ASSERT_NO_FATAL_FAILURE(MakeIssuerCertIDComponents("CA", issuerDER, + issuerSPKI)); + + Input issuer; + ASSERT_EQ(Success, issuer.Init(issuerDER.data(), issuerDER.length())); + + Input spki; + ASSERT_EQ(Success, spki.Init(issuerSPKI.data(), issuerSPKI.length())); + + Input serialNumber; + ASSERT_EQ(Success, serialNumber.Init(serialNumberString.data(), + serialNumberString.length())); + + uint8_t ocspRequest[OCSP_REQUEST_MAX_LENGTH]; + size_t ocspRequestLength; + ASSERT_EQ(Success, + CreateEncodedOCSPRequest(trustDomain, + CertID(issuer, spki, serialNumber), + ocspRequest, ocspRequestLength)); +} diff --git a/security/pkix/test/gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp b/security/pkix/test/gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp new file mode 100644 index 000000000..d7dab09d9 --- /dev/null +++ b/security/pkix/test/gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp @@ -0,0 +1,1046 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2014 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixder.h" +#include "pkixgtest.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +const uint16_t END_ENTITY_MAX_LIFETIME_IN_DAYS = 10; + +// Note that CheckRevocation is never called for OCSP signing certificates. +class OCSPTestTrustDomain : public DefaultCryptoTrustDomain +{ +public: + OCSPTestTrustDomain() { } + + Result GetCertTrust(EndEntityOrCA endEntityOrCA, const CertPolicyId&, + Input, /*out*/ TrustLevel& trustLevel) + /*non-final*/ override + { + EXPECT_EQ(endEntityOrCA, EndEntityOrCA::MustBeEndEntity); + trustLevel = TrustLevel::InheritsTrust; + return Success; + } + + virtual void NoteAuxiliaryExtension(AuxiliaryExtension extension, + Input extensionData) override + { + if (extension == AuxiliaryExtension::SCTListFromOCSPResponse) { + signedCertificateTimestamps = InputToByteString(extensionData); + } else { + // We do not currently expect to receive any other extension here. + ADD_FAILURE(); + } + } + + ByteString signedCertificateTimestamps; +}; + +namespace { +char const* const rootName = "Test CA 1"; +void deleteCertID(CertID* certID) { delete certID; } +} // namespace + +class pkixocsp_VerifyEncodedResponse : public ::testing::Test +{ +public: + static void SetUpTestCase() + { + rootKeyPair.reset(GenerateKeyPair()); + if (!rootKeyPair) { + abort(); + } + } + + void SetUp() + { + rootNameDER = CNToDERName(rootName); + if (ENCODING_FAILED(rootNameDER)) { + abort(); + } + Input rootNameDERInput; + if (rootNameDERInput.Init(rootNameDER.data(), rootNameDER.length()) + != Success) { + abort(); + } + + serialNumberDER = + CreateEncodedSerialNumber(static_cast<long>(++rootIssuedCount)); + if (ENCODING_FAILED(serialNumberDER)) { + abort(); + } + Input serialNumberDERInput; + if (serialNumberDERInput.Init(serialNumberDER.data(), + serialNumberDER.length()) != Success) { + abort(); + } + + Input rootSPKIDER; + if (rootSPKIDER.Init(rootKeyPair->subjectPublicKeyInfo.data(), + rootKeyPair->subjectPublicKeyInfo.length()) + != Success) { + abort(); + } + endEntityCertID.reset(new (std::nothrow) CertID(rootNameDERInput, rootSPKIDER, + serialNumberDERInput)); + if (!endEntityCertID) { + abort(); + } + } + + static ScopedTestKeyPair rootKeyPair; + static uint32_t rootIssuedCount; + OCSPTestTrustDomain trustDomain; + + // endEntityCertID references rootKeyPair, rootNameDER, and serialNumberDER. + ByteString rootNameDER; + ByteString serialNumberDER; + // endEntityCertID references rootKeyPair, rootNameDER, and serialNumberDER. + ScopedPtr<CertID, deleteCertID> endEntityCertID; +}; + +/*static*/ ScopedTestKeyPair pkixocsp_VerifyEncodedResponse::rootKeyPair; +/*static*/ uint32_t pkixocsp_VerifyEncodedResponse::rootIssuedCount = 0; + +/////////////////////////////////////////////////////////////////////////////// +// responseStatus + +struct WithoutResponseBytes +{ + uint8_t responseStatus; + Result expectedError; +}; + +static const WithoutResponseBytes WITHOUT_RESPONSEBYTES[] = { + { OCSPResponseContext::successful, Result::ERROR_OCSP_MALFORMED_RESPONSE }, + { OCSPResponseContext::malformedRequest, Result::ERROR_OCSP_MALFORMED_REQUEST }, + { OCSPResponseContext::internalError, Result::ERROR_OCSP_SERVER_ERROR }, + { OCSPResponseContext::tryLater, Result::ERROR_OCSP_TRY_SERVER_LATER }, + { 4/*unused*/, Result::ERROR_OCSP_UNKNOWN_RESPONSE_STATUS }, + { OCSPResponseContext::sigRequired, Result::ERROR_OCSP_REQUEST_NEEDS_SIG }, + { OCSPResponseContext::unauthorized, Result::ERROR_OCSP_UNAUTHORIZED_REQUEST }, + { OCSPResponseContext::unauthorized + 1, + Result::ERROR_OCSP_UNKNOWN_RESPONSE_STATUS + }, +}; + +class pkixocsp_VerifyEncodedResponse_WithoutResponseBytes + : public pkixocsp_VerifyEncodedResponse + , public ::testing::WithParamInterface<WithoutResponseBytes> +{ +protected: + ByteString CreateEncodedOCSPErrorResponse(uint8_t status) + { + static const Input EMPTY; + OCSPResponseContext context(CertID(EMPTY, EMPTY, EMPTY), + oneDayBeforeNow); + context.responseStatus = status; + context.skipResponseBytes = true; + return CreateEncodedOCSPResponse(context); + } +}; + +TEST_P(pkixocsp_VerifyEncodedResponse_WithoutResponseBytes, CorrectErrorCode) +{ + ByteString + responseString(CreateEncodedOCSPErrorResponse(GetParam().responseStatus)); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(GetParam().expectedError, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); +} + +INSTANTIATE_TEST_CASE_P(pkixocsp_VerifyEncodedResponse_WithoutResponseBytes, + pkixocsp_VerifyEncodedResponse_WithoutResponseBytes, + testing::ValuesIn(WITHOUT_RESPONSEBYTES)); + +/////////////////////////////////////////////////////////////////////////////// +// "successful" responses + +namespace { + +// Alias for nullptr to aid readability in the code below. +static const char* byKey = nullptr; + +} // namespace + +class pkixocsp_VerifyEncodedResponse_successful + : public pkixocsp_VerifyEncodedResponse +{ +public: + void SetUp() + { + pkixocsp_VerifyEncodedResponse::SetUp(); + } + + static void SetUpTestCase() + { + pkixocsp_VerifyEncodedResponse::SetUpTestCase(); + } + + ByteString CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::CertStatus certStatus, + const CertID& certID, + /*optional*/ const char* signerName, + const TestKeyPair& signerKeyPair, + time_t producedAt, time_t thisUpdate, + /*optional*/ const time_t* nextUpdate, + const TestSignatureAlgorithm& signatureAlgorithm, + /*optional*/ const ByteString* certs = nullptr, + /*optional*/ OCSPResponseExtension* singleExtensions = nullptr, + /*optional*/ OCSPResponseExtension* responseExtensions = nullptr) + { + OCSPResponseContext context(certID, producedAt); + if (signerName) { + context.signerNameDER = CNToDERName(signerName); + EXPECT_FALSE(ENCODING_FAILED(context.signerNameDER)); + } + context.signerKeyPair.reset(signerKeyPair.Clone()); + EXPECT_TRUE(context.signerKeyPair.get()); + context.responseStatus = OCSPResponseContext::successful; + context.producedAt = producedAt; + context.signatureAlgorithm = signatureAlgorithm; + context.certs = certs; + context.singleExtensions = singleExtensions; + context.responseExtensions = responseExtensions; + + context.certStatus = static_cast<uint8_t>(certStatus); + context.thisUpdate = thisUpdate; + context.nextUpdate = nextUpdate ? *nextUpdate : 0; + context.includeNextUpdate = nextUpdate != nullptr; + + return CreateEncodedOCSPResponse(context); + } +}; + +TEST_F(pkixocsp_VerifyEncodedResponse_successful, good_byKey) +{ + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, byKey, + *rootKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + sha256WithRSAEncryption())); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Success, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, + Now(), END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_successful, good_byName) +{ + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, rootName, + *rootKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + sha256WithRSAEncryption())); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Success, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_successful, good_byKey_without_nextUpdate) +{ + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, byKey, + *rootKeyPair, oneDayBeforeNow, + oneDayBeforeNow, nullptr, + sha256WithRSAEncryption())); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Success, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_successful, revoked) +{ + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::revoked, *endEntityCertID, byKey, + *rootKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + sha256WithRSAEncryption())); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_REVOKED_CERTIFICATE, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_successful, unknown) +{ + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::unknown, *endEntityCertID, byKey, + *rootKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + sha256WithRSAEncryption())); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_UNKNOWN_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_successful, + good_unsupportedSignatureAlgorithm) +{ + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, byKey, + *rootKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + md5WithRSAEncryption())); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, + Now(), END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +// Added for bug 1079436. The output variable validThrough represents the +// latest time for which VerifyEncodedOCSPResponse will succeed, which is +// different from the nextUpdate time in the OCSP response due to the slop we +// add for time comparisons to deal with clock skew. +TEST_F(pkixocsp_VerifyEncodedResponse_successful, check_validThrough) +{ + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, byKey, + *rootKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + sha256WithRSAEncryption())); + Time validThrough(Time::uninitialized); + { + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Success, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, + Now(), END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired, nullptr, + &validThrough)); + ASSERT_FALSE(expired); + // The response was created to be valid until one day after now, so the + // value we got for validThrough should be after that. + Time oneDayAfterNowAsPKIXTime( + TimeFromEpochInSeconds(static_cast<uint64_t>(oneDayAfterNow))); + ASSERT_TRUE(validThrough > oneDayAfterNowAsPKIXTime); + } + { + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + // Given validThrough from a previous verification, this response should be + // valid through that time. + ASSERT_EQ(Success, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, + validThrough, END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); + } + { + Time noLongerValid(validThrough); + ASSERT_EQ(Success, noLongerValid.AddSeconds(1)); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + // The verification time is now after when the response will be considered + // valid. + ASSERT_EQ(Result::ERROR_OCSP_OLD_RESPONSE, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, + noLongerValid, END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_TRUE(expired); + } +} + +TEST_F(pkixocsp_VerifyEncodedResponse_successful, ct_extension) +{ + // python DottedOIDToCode.py --tlv + // id_ocsp_singleExtensionSctList 1.3.6.1.4.1.11129.2.4.5 + static const uint8_t tlv_id_ocsp_singleExtensionSctList[] = { + 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x05 + }; + static const uint8_t dummySctList[] = { + 0x01, 0x02, 0x03, 0x04, 0x05 + }; + + OCSPResponseExtension ctExtension; + ctExtension.id = BytesToByteString(tlv_id_ocsp_singleExtensionSctList); + // SignedCertificateTimestampList structure is encoded as an OCTET STRING + // within the extension value (see RFC 6962 section 3.3). + // pkix decodes it internally and returns the actual structure. + ctExtension.value = TLV(der::OCTET_STRING, BytesToByteString(dummySctList)); + + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, byKey, + *rootKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + sha256WithRSAEncryption(), + /*certs*/ nullptr, + &ctExtension)); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + + bool expired; + ASSERT_EQ(Success, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, + Now(), END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); + ASSERT_EQ(BytesToByteString(dummySctList), + trustDomain.signedCertificateTimestamps); +} + +/////////////////////////////////////////////////////////////////////////////// +// indirect responses (signed by a delegated OCSP responder cert) + +class pkixocsp_VerifyEncodedResponse_DelegatedResponder + : public pkixocsp_VerifyEncodedResponse_successful +{ +protected: + // certSubjectName should be unique for each call. This way, we avoid any + // issues with NSS caching the certificates internally. For the same reason, + // we generate a new keypair on each call. Either one of these should be + // sufficient to avoid issues with the NSS cache, but we do both to be + // cautious. + // + // signerName should be byKey to use the byKey ResponderID construction, or + // another value (usually equal to certSubjectName) to use the byName + // ResponderID construction. + // + // certSignatureAlgorithm specifies the signature algorithm that the + // certificate will be signed with, not the OCSP response. + // + // If signerEKU is omitted, then the certificate will have the + // id-kp-OCSPSigning EKU. If signerEKU is SEC_OID_UNKNOWN then it will not + // have any EKU extension. Otherwise, the certificate will have the given + // EKU. + ByteString CreateEncodedIndirectOCSPSuccessfulResponse( + const char* certSubjectName, + OCSPResponseContext::CertStatus certStatus, + const char* signerName, + const TestSignatureAlgorithm& certSignatureAlgorithm, + /*optional*/ const Input* signerEKUDER = &OCSPSigningEKUDER, + /*optional, out*/ ByteString* signerDEROut = nullptr) + { + assert(certSubjectName); + + const ByteString extensions[] = { + signerEKUDER + ? CreateEncodedEKUExtension(*signerEKUDER, Critical::No) + : ByteString(), + ByteString() + }; + ScopedTestKeyPair signerKeyPair(GenerateKeyPair()); + ByteString signerDER(CreateEncodedCertificate( + ++rootIssuedCount, certSignatureAlgorithm, + rootName, oneDayBeforeNow, oneDayAfterNow, + certSubjectName, *signerKeyPair, + signerEKUDER ? extensions : nullptr, + *rootKeyPair)); + EXPECT_FALSE(ENCODING_FAILED(signerDER)); + if (signerDEROut) { + *signerDEROut = signerDER; + } + + ByteString signerNameDER; + if (signerName) { + signerNameDER = CNToDERName(signerName); + EXPECT_FALSE(ENCODING_FAILED(signerNameDER)); + } + ByteString certs[] = { signerDER, ByteString() }; + return CreateEncodedOCSPSuccessfulResponse(certStatus, *endEntityCertID, + signerName, *signerKeyPair, + oneDayBeforeNow, + oneDayBeforeNow, + &oneDayAfterNow, + sha256WithRSAEncryption(), + certs); + } + + static ByteString CreateEncodedCertificate(uint32_t serialNumber, + const TestSignatureAlgorithm& signatureAlg, + const char* issuer, + time_t notBefore, + time_t notAfter, + const char* subject, + const TestKeyPair& subjectKeyPair, + /*optional*/ const ByteString* extensions, + const TestKeyPair& signerKeyPair) + { + ByteString serialNumberDER(CreateEncodedSerialNumber( + static_cast<long>(serialNumber))); + if (ENCODING_FAILED(serialNumberDER)) { + return ByteString(); + } + ByteString issuerDER(CNToDERName(issuer)); + if (ENCODING_FAILED(issuerDER)) { + return ByteString(); + } + ByteString subjectDER(CNToDERName(subject)); + if (ENCODING_FAILED(subjectDER)) { + return ByteString(); + } + return ::mozilla::pkix::test::CreateEncodedCertificate( + v3, signatureAlg, serialNumberDER, + issuerDER, notBefore, notAfter, + subjectDER, subjectKeyPair, extensions, + signerKeyPair, signatureAlg); + } + + static const Input OCSPSigningEKUDER; +}; + +/*static*/ const Input pkixocsp_VerifyEncodedResponse_DelegatedResponder:: + OCSPSigningEKUDER(tlv_id_kp_OCSPSigning); + +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_byKey) +{ + ByteString responseString( + CreateEncodedIndirectOCSPSuccessfulResponse( + "good_indirect_byKey", OCSPResponseContext::good, + byKey, sha256WithRSAEncryption())); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Success, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_byName) +{ + ByteString responseString( + CreateEncodedIndirectOCSPSuccessfulResponse( + "good_indirect_byName", OCSPResponseContext::good, + "good_indirect_byName", sha256WithRSAEncryption())); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Success, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, + good_byKey_missing_signer) +{ + ScopedTestKeyPair missingSignerKeyPair(GenerateKeyPair()); + ASSERT_TRUE(missingSignerKeyPair.get()); + + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, byKey, + *missingSignerKeyPair, oneDayBeforeNow, + oneDayBeforeNow, nullptr, + sha256WithRSAEncryption())); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, + good_byName_missing_signer) +{ + ScopedTestKeyPair missingSignerKeyPair(GenerateKeyPair()); + ASSERT_TRUE(missingSignerKeyPair.get()); + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, + "missing", *missingSignerKeyPair, + oneDayBeforeNow, oneDayBeforeNow, nullptr, + sha256WithRSAEncryption())); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_expired) +{ + static const char* signerName = "good_indirect_expired"; + + const ByteString extensions[] = { + CreateEncodedEKUExtension(OCSPSigningEKUDER, Critical::No), + ByteString() + }; + + ScopedTestKeyPair signerKeyPair(GenerateKeyPair()); + ByteString signerDER(CreateEncodedCertificate( + ++rootIssuedCount, sha256WithRSAEncryption(), + rootName, + tenDaysBeforeNow, + twoDaysBeforeNow, + signerName, *signerKeyPair, extensions, + *rootKeyPair)); + ASSERT_FALSE(ENCODING_FAILED(signerDER)); + + ByteString certs[] = { signerDER, ByteString() }; + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, + signerName, *signerKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + sha256WithRSAEncryption(), certs)); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_future) +{ + static const char* signerName = "good_indirect_future"; + + const ByteString extensions[] = { + CreateEncodedEKUExtension(OCSPSigningEKUDER, Critical::No), + ByteString() + }; + + ScopedTestKeyPair signerKeyPair(GenerateKeyPair()); + ByteString signerDER(CreateEncodedCertificate( + ++rootIssuedCount, sha256WithRSAEncryption(), + rootName, + twoDaysAfterNow, + tenDaysAfterNow, + signerName, *signerKeyPair, extensions, + *rootKeyPair)); + ASSERT_FALSE(ENCODING_FAILED(signerDER)); + + ByteString certs[] = { signerDER, ByteString() }; + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, + signerName, *signerKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + sha256WithRSAEncryption(), certs)); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_no_eku) +{ + ByteString responseString( + CreateEncodedIndirectOCSPSuccessfulResponse( + "good_indirect_wrong_eku", + OCSPResponseContext::good, byKey, + sha256WithRSAEncryption(), nullptr)); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +static const Input serverAuthEKUDER(tlv_id_kp_serverAuth); + +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, + good_indirect_wrong_eku) +{ + ByteString responseString( + CreateEncodedIndirectOCSPSuccessfulResponse( + "good_indirect_wrong_eku", + OCSPResponseContext::good, byKey, + sha256WithRSAEncryption(), &serverAuthEKUDER)); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +// Test that signature of OCSP response signer cert is verified +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_tampered_eku) +{ + ByteString tamperedResponse( + CreateEncodedIndirectOCSPSuccessfulResponse( + "good_indirect_tampered_eku", + OCSPResponseContext::good, byKey, + sha256WithRSAEncryption(), &serverAuthEKUDER)); + ASSERT_EQ(Success, + TamperOnce(tamperedResponse, + ByteString(tlv_id_kp_serverAuth, + sizeof(tlv_id_kp_serverAuth)), + ByteString(tlv_id_kp_OCSPSigning, + sizeof(tlv_id_kp_OCSPSigning)))); + Input tamperedResponseInput; + ASSERT_EQ(Success, tamperedResponseInput.Init(tamperedResponse.data(), + tamperedResponse.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + tamperedResponseInput, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_unknown_issuer) +{ + static const char* subCAName = "good_indirect_unknown_issuer sub-CA"; + static const char* signerName = "good_indirect_unknown_issuer OCSP signer"; + + // unknown issuer + ScopedTestKeyPair unknownKeyPair(GenerateKeyPair()); + ASSERT_TRUE(unknownKeyPair.get()); + + // Delegated responder cert signed by unknown issuer + const ByteString extensions[] = { + CreateEncodedEKUExtension(OCSPSigningEKUDER, Critical::No), + ByteString() + }; + ScopedTestKeyPair signerKeyPair(GenerateKeyPair()); + ByteString signerDER(CreateEncodedCertificate( + 1, sha256WithRSAEncryption(), subCAName, + oneDayBeforeNow, oneDayAfterNow, signerName, + *signerKeyPair, extensions, *unknownKeyPair)); + ASSERT_FALSE(ENCODING_FAILED(signerDER)); + + // OCSP response signed by that delegated responder + ByteString certs[] = { signerDER, ByteString() }; + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, + signerName, *signerKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + sha256WithRSAEncryption(), certs)); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +// The CA that issued the OCSP responder cert is a sub-CA of the issuer of +// the certificate that the OCSP response is for. That sub-CA cert is included +// in the OCSP response before the OCSP responder cert. +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, + good_indirect_subca_1_first) +{ + static const char* subCAName = "good_indirect_subca_1_first sub-CA"; + static const char* signerName = "good_indirect_subca_1_first OCSP signer"; + + // sub-CA of root (root is the direct issuer of endEntity) + const ByteString subCAExtensions[] = { + CreateEncodedBasicConstraints(true, 0, Critical::No), + ByteString() + }; + ScopedTestKeyPair subCAKeyPair(GenerateKeyPair()); + ByteString subCADER(CreateEncodedCertificate( + ++rootIssuedCount, sha256WithRSAEncryption(), rootName, + oneDayBeforeNow, oneDayAfterNow, subCAName, + *subCAKeyPair, subCAExtensions, *rootKeyPair)); + ASSERT_FALSE(ENCODING_FAILED(subCADER)); + + // Delegated responder cert signed by that sub-CA + const ByteString extensions[] = { + CreateEncodedEKUExtension(OCSPSigningEKUDER, Critical::No), + ByteString(), + }; + ScopedTestKeyPair signerKeyPair(GenerateKeyPair()); + ByteString signerDER(CreateEncodedCertificate( + 1, sha256WithRSAEncryption(), subCAName, + oneDayBeforeNow, oneDayAfterNow, signerName, + *signerKeyPair, extensions, *subCAKeyPair)); + ASSERT_FALSE(ENCODING_FAILED(signerDER)); + + // OCSP response signed by the delegated responder issued by the sub-CA + // that is trying to impersonate the root. + ByteString certs[] = { subCADER, signerDER, ByteString() }; + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, + signerName, *signerKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + sha256WithRSAEncryption(), certs)); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +// The CA that issued the OCSP responder cert is a sub-CA of the issuer of +// the certificate that the OCSP response is for. That sub-CA cert is included +// in the OCSP response after the OCSP responder cert. +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, + good_indirect_subca_1_second) +{ + static const char* subCAName = "good_indirect_subca_1_second sub-CA"; + static const char* signerName = "good_indirect_subca_1_second OCSP signer"; + + // sub-CA of root (root is the direct issuer of endEntity) + const ByteString subCAExtensions[] = { + CreateEncodedBasicConstraints(true, 0, Critical::No), + ByteString() + }; + ScopedTestKeyPair subCAKeyPair(GenerateKeyPair()); + ByteString subCADER(CreateEncodedCertificate(++rootIssuedCount, + sha256WithRSAEncryption(), + rootName, + oneDayBeforeNow, oneDayAfterNow, + subCAName, *subCAKeyPair, + subCAExtensions, *rootKeyPair)); + ASSERT_FALSE(ENCODING_FAILED(subCADER)); + + // Delegated responder cert signed by that sub-CA + const ByteString extensions[] = { + CreateEncodedEKUExtension(OCSPSigningEKUDER, Critical::No), + ByteString() + }; + ScopedTestKeyPair signerKeyPair(GenerateKeyPair()); + ByteString signerDER(CreateEncodedCertificate( + 1, sha256WithRSAEncryption(), subCAName, + oneDayBeforeNow, oneDayAfterNow, signerName, + *signerKeyPair, extensions, *subCAKeyPair)); + ASSERT_FALSE(ENCODING_FAILED(signerDER)); + + // OCSP response signed by the delegated responder issued by the sub-CA + // that is trying to impersonate the root. + ByteString certs[] = { signerDER, subCADER, ByteString() }; + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, + signerName, *signerKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + sha256WithRSAEncryption(), certs)); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, + good_unsupportedSignatureAlgorithmOnResponder) +{ + // Note that the algorithm ID (md5WithRSAEncryption) identifies the signature + // algorithm that will be used to sign the certificate that issues the OCSP + // responses, not the responses themselves. + ByteString responseString( + CreateEncodedIndirectOCSPSuccessfulResponse( + "good_indirect_unsupportedSignatureAlgorithm", + OCSPResponseContext::good, byKey, + md5WithRSAEncryption())); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); +} + +class pkixocsp_VerifyEncodedResponse_GetCertTrust + : public pkixocsp_VerifyEncodedResponse_DelegatedResponder { +public: + void SetUp() + { + pkixocsp_VerifyEncodedResponse_DelegatedResponder::SetUp(); + + responseString = + CreateEncodedIndirectOCSPSuccessfulResponse( + "OCSPGetCertTrustTest Signer", OCSPResponseContext::good, + byKey, sha256WithRSAEncryption(), &OCSPSigningEKUDER, + &signerCertDER); + if (ENCODING_FAILED(responseString)) { + abort(); + } + if (response.Init(responseString.data(), responseString.length()) + != Success) { + abort(); + } + if (signerCertDER.length() == 0) { + abort(); + } + } + + class TrustDomain final : public OCSPTestTrustDomain + { + public: + TrustDomain() + : certTrustLevel(TrustLevel::InheritsTrust) + { + } + + bool SetCertTrust(const ByteString& certDER, TrustLevel certTrustLevel) + { + this->certDER = certDER; + this->certTrustLevel = certTrustLevel; + return true; + } + private: + Result GetCertTrust(EndEntityOrCA endEntityOrCA, const CertPolicyId&, + Input candidateCert, /*out*/ TrustLevel& trustLevel) + override + { + EXPECT_EQ(endEntityOrCA, EndEntityOrCA::MustBeEndEntity); + EXPECT_FALSE(certDER.empty()); + Input certDERInput; + EXPECT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length())); + EXPECT_TRUE(InputsAreEqual(certDERInput, candidateCert)); + trustLevel = certTrustLevel; + return Success; + } + + ByteString certDER; + TrustLevel certTrustLevel; + }; + + TrustDomain trustDomain; + ByteString signerCertDER; + ByteString responseString; + Input response; // references data in responseString +}; + +TEST_F(pkixocsp_VerifyEncodedResponse_GetCertTrust, InheritTrust) +{ + ASSERT_TRUE(trustDomain.SetCertTrust(signerCertDER, + TrustLevel::InheritsTrust)); + bool expired; + ASSERT_EQ(Success, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_GetCertTrust, TrustAnchor) +{ + ASSERT_TRUE(trustDomain.SetCertTrust(signerCertDER, + TrustLevel::TrustAnchor)); + bool expired; + ASSERT_EQ(Success, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_GetCertTrust, ActivelyDistrusted) +{ + ASSERT_TRUE(trustDomain.SetCertTrust(signerCertDER, + TrustLevel::ActivelyDistrusted)); + Input responseInput; + ASSERT_EQ(Success, + responseInput.Init(responseString.data(), + responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + responseInput, expired)); + ASSERT_FALSE(expired); +} diff --git a/security/pkix/test/lib/moz.build b/security/pkix/test/lib/moz.build new file mode 100644 index 000000000..c99aedf3a --- /dev/null +++ b/security/pkix/test/lib/moz.build @@ -0,0 +1,39 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# This code is made available to you under your choice of the following sets +# of licensing terms: +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# Copyright 2013 Mozilla Contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +SOURCES += [ + 'pkixtestalg.cpp', + 'pkixtestnss.cpp', + 'pkixtestutil.cpp', +] + +Library('pkixtestutil') + +LOCAL_INCLUDES += [ + '../../include', + '../../lib', +] + +FINAL_LIBRARY = 'xul-gtest' + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/security/pkix/test/lib/pkixtestalg.cpp b/security/pkix/test/lib/pkixtestalg.cpp new file mode 100644 index 000000000..a19fd26f3 --- /dev/null +++ b/security/pkix/test/lib/pkixtestalg.cpp @@ -0,0 +1,210 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2015 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixtestutil.h" + +#include "pkixder.h" + +// python DottedOIDToCode.py --prefixdefine PREFIX_1_2_840_10040 1.2.840.10040 +#define PREFIX_1_2_840_10040 0x2a, 0x86, 0x48, 0xce, 0x38 + +// python DottedOIDToCode.py --prefixdefine PREFIX_1_2_840_10045 1.2.840.10045 +#define PREFIX_1_2_840_10045 0x2a, 0x86, 0x48, 0xce, 0x3d + +// python DottedOIDToCode.py --prefixdefine PREFIX_1_2_840_113549 1.2.840.113549 +#define PREFIX_1_2_840_113549 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d + +namespace mozilla { namespace pkix { namespace test { + +namespace { + +enum class NULLParam { NO, YES }; + +template <size_t SIZE> +ByteString +OID(const uint8_t (&rawValue)[SIZE]) +{ + return TLV(der::OIDTag, ByteString(rawValue, SIZE)); +} + +template <size_t SIZE> +ByteString +SimpleAlgID(const uint8_t (&rawValue)[SIZE], + NULLParam nullParam = NULLParam::NO) +{ + ByteString sequenceValue(OID(rawValue)); + if (nullParam == NULLParam::YES) { + sequenceValue.append(TLV(der::NULLTag, ByteString())); + } + return TLV(der::SEQUENCE, sequenceValue); +} + +template <size_t SIZE> +ByteString +DERInteger(const uint8_t (&rawValue)[SIZE]) +{ + ByteString value(rawValue, SIZE); + if (value[0] & 0x80u) { + // Prefix with a leading zero to disambiguate this from a negative value. + value.insert(value.begin(), 0x00); + } + return TLV(der::INTEGER, value); +} + +// Generated with "openssl dsaparam -C -noout 2048" and reformatted. +// openssl 1.0 or later must be used so that a 256-bit Q value is +// generated. +static const uint8_t DSS_P_RAW[] = +{ + 0xB3,0xCD,0x29,0x44,0xF0,0x25,0xA7,0x73,0xFC,0x86,0x70,0xA2, + 0x69,0x5A,0x97,0x3F,0xBD,0x1C,0x6F,0xAA,0x4A,0x40,0x42,0x8E, + 0xCF,0xAE,0x62,0x12,0xED,0xB4,0xFD,0x05,0xC2,0xAE,0xB1,0x8C, + 0xFC,0xBE,0x38,0x90,0xBB,0x7C,0xFF,0x16,0xF4,0xED,0xCE,0x72, + 0x12,0x93,0x83,0xF0,0xA4,0xA1,0x71,0xDC,0x4B,0xF0,0x4E,0x3A, + 0x2B,0xFA,0x17,0xB7,0xB3,0x2A,0xCC,0x2C,0xD3,0xC8,0x21,0x49, + 0x7A,0x83,0x71,0x8B,0x3D,0x62,0x96,0xDC,0xAD,0xA8,0x03,0xBE, + 0x1D,0x33,0x11,0xF3,0xEB,0xD8,0x1B,0x8D,0xDB,0x62,0x79,0x83, + 0xF8,0x67,0x4E,0x62,0x21,0x2C,0x81,0x59,0xE8,0x73,0xD7,0xAF, + 0xB9,0x63,0x60,0xEA,0xAE,0xEC,0x68,0x6A,0xB4,0xB0,0x65,0xBA, + 0xA3,0x4C,0x09,0x99,0x29,0x6A,0x2E,0x2B,0xFC,0x6D,0x51,0xCA, + 0x30,0xA2,0x2F,0x7A,0x65,0x76,0xA7,0x55,0x13,0x11,0xA0,0x02, + 0xA2,0x59,0x4B,0xCE,0xA7,0x05,0xF6,0x07,0x35,0x9B,0x41,0xD7, + 0x11,0x5A,0x18,0x57,0xA7,0x78,0x88,0xC3,0xA8,0xE3,0x39,0xF5, + 0x47,0x3D,0x2E,0x18,0x54,0xB0,0xF0,0xBF,0x65,0x3F,0x77,0xC7, + 0x11,0xB8,0x0D,0x52,0xAD,0xC8,0xE8,0x6D,0xF6,0x7E,0x88,0x65, + 0x84,0x2B,0xF7,0xEF,0x8E,0xB5,0x7C,0xBD,0x2E,0x0D,0xF3,0xC6, + 0xDD,0x0B,0xB4,0xF2,0x23,0x1F,0xDA,0x55,0x05,0xF5,0xDC,0x53, + 0xA6,0x83,0xDA,0x5C,0xEF,0x29,0x02,0x78,0x68,0xD0,0xA4,0x39, + 0x09,0x7F,0xFA,0x49,0x18,0xD0,0xB5,0x19,0x35,0x31,0x8E,0xDE, + 0x43,0x35,0xA3,0xB9,0x6D,0xC1,0x70,0xC6,0x0D,0x18,0x24,0xEB, + 0x1E,0x4D,0x52,0xB7, +}; + +static const uint8_t DSS_Q_RAW[] = +{ + 0x8D,0x6B,0x86,0x89,0x9C,0x8D,0x30,0x91,0xCC,0x6E,0x34,0xF1, + 0xE8,0x9C,0x8A,0x5C,0xD6,0xAB,0x01,0x1E,0xC4,0xDB,0xFD,0x07, + 0xEB,0x5F,0x4E,0xE8,0xFA,0xFC,0x98,0x2D, +}; + +static const uint8_t DSS_G_RAW[] = +{ + 0x0E,0x2C,0x34,0xB2,0xE1,0x66,0x49,0xB6,0x9A,0x7D,0x67,0x3E, + 0xEE,0x98,0x35,0x18,0x28,0x35,0xFC,0x05,0x36,0x3B,0x94,0xE6, + 0x1E,0x1C,0x5B,0x05,0x3E,0x86,0x1B,0xE3,0xED,0xD2,0xE1,0xF3, + 0xF7,0xF7,0x60,0x6D,0x7D,0xA1,0xAF,0x9A,0xD1,0xDF,0xA2,0x9C, + 0xFC,0xA2,0xEB,0x90,0x8B,0x1C,0x82,0x92,0x45,0x7B,0x30,0x2A, + 0xFD,0x7A,0xE6,0x68,0x8F,0xEC,0x89,0x3A,0x9A,0xAD,0xFE,0x25, + 0x5E,0x51,0xC5,0x29,0x45,0x7F,0xAC,0xDE,0xFC,0xB4,0x1B,0x3A, + 0xDA,0xC7,0x21,0x68,0x87,0x27,0x8D,0x7B,0xB2,0xBB,0x41,0x60, + 0x46,0x42,0x5B,0x6B,0xE8,0x80,0xD2,0xE4,0xA3,0x30,0x8F,0xD5, + 0x71,0x07,0x8A,0x7B,0x32,0x56,0x84,0x41,0x1C,0xDF,0x69,0xE9, + 0xFD,0xBA,0x48,0xE0,0x43,0xA0,0x38,0x92,0x12,0xF3,0x52,0xA5, + 0x40,0x87,0xCB,0x34,0xBB,0x3E,0x25,0x29,0x3C,0xC6,0xA5,0x17, + 0xFD,0x58,0x47,0x89,0xDB,0x9B,0xB9,0xCF,0xE9,0xA8,0xF2,0xEC, + 0x55,0x76,0xF5,0xF1,0x9C,0x6E,0x0A,0x3F,0x16,0x5F,0x49,0x31, + 0x31,0x1C,0x43,0xA2,0x83,0xDA,0xDD,0x7F,0x1C,0xEA,0x05,0x36, + 0x7B,0xED,0x09,0xFB,0x6F,0x8A,0x2B,0x55,0xB9,0xBC,0x4A,0x8C, + 0x28,0xC1,0x4D,0x13,0x6E,0x47,0xF4,0xAD,0x79,0x00,0xE9,0x5A, + 0xB6,0xC7,0x73,0x28,0xA9,0x89,0xAD,0xE8,0x6E,0xC6,0x54,0xA5, + 0x56,0x2D,0xAA,0x81,0x83,0x9E,0xC1,0x13,0x79,0xA4,0x12,0xE0, + 0x76,0x1F,0x25,0x43,0xB6,0xDE,0x56,0xF7,0x52,0xCC,0x07,0xB8, + 0x37,0xE2,0x8C,0xC5,0x56,0x8C,0xDD,0x63,0xF5,0xB6,0xA3,0x46, + 0x62,0xF6,0x35,0x76, +}; + +} // namespace + +TestSignatureAlgorithm::TestSignatureAlgorithm( + const TestPublicKeyAlgorithm& publicKeyAlg, + TestDigestAlgorithmID digestAlg, + const ByteString& algorithmIdentifier, + bool accepted) + : publicKeyAlg(publicKeyAlg) + , digestAlg(digestAlg) + , algorithmIdentifier(algorithmIdentifier) + , accepted(accepted) +{ +} + +ByteString DSS_P() { return ByteString(DSS_P_RAW, sizeof(DSS_P_RAW)); } +ByteString DSS_Q() { return ByteString(DSS_Q_RAW, sizeof(DSS_Q_RAW)); } +ByteString DSS_G() { return ByteString(DSS_G_RAW, sizeof(DSS_G_RAW)); } + +TestPublicKeyAlgorithm +DSS() +{ + static const uint8_t oidValue[] = { PREFIX_1_2_840_10040, 4, 1 }; + + // RFC 3279 Section-2.3.2 + return TestPublicKeyAlgorithm( + TLV(der::SEQUENCE, + OID(oidValue) + + TLV(der::SEQUENCE, + DERInteger(DSS_P_RAW) + + DERInteger(DSS_Q_RAW) + + DERInteger(DSS_G_RAW)))); +} + +// RFC 3279 Section 2.3.1 +TestPublicKeyAlgorithm +RSA_PKCS1() +{ + static const uint8_t rsaEncryption[] = { PREFIX_1_2_840_113549, 1, 1, 1 }; + return TestPublicKeyAlgorithm(SimpleAlgID(rsaEncryption, NULLParam::YES)); +} + +// RFC 3279 Section 2.2.1 +TestSignatureAlgorithm md2WithRSAEncryption() +{ + static const uint8_t oidValue[] = { PREFIX_1_2_840_113549, 1, 1, 2 }; + return TestSignatureAlgorithm(RSA_PKCS1(), TestDigestAlgorithmID::MD2, + SimpleAlgID(oidValue), false); +} + +// RFC 3279 Section 2.2.1 +TestSignatureAlgorithm md5WithRSAEncryption() +{ + static const uint8_t oidValue[] = { PREFIX_1_2_840_113549, 1, 1, 4 }; + return TestSignatureAlgorithm(RSA_PKCS1(), TestDigestAlgorithmID::MD5, + SimpleAlgID(oidValue), false); +} + +// RFC 3279 Section 2.2.1 +TestSignatureAlgorithm sha1WithRSAEncryption() +{ + static const uint8_t oidValue[] = { PREFIX_1_2_840_113549, 1, 1, 5 }; + return TestSignatureAlgorithm(RSA_PKCS1(), TestDigestAlgorithmID::SHA1, + SimpleAlgID(oidValue), true); +} + +// RFC 4055 Section 5 +TestSignatureAlgorithm sha256WithRSAEncryption() +{ + static const uint8_t oidValue[] = { PREFIX_1_2_840_113549, 1, 1, 11 }; + return TestSignatureAlgorithm(RSA_PKCS1(), TestDigestAlgorithmID::SHA256, + SimpleAlgID(oidValue), true); +} + +} } } // namespace mozilla::pkix diff --git a/security/pkix/test/lib/pkixtestnss.cpp b/security/pkix/test/lib/pkixtestnss.cpp new file mode 100644 index 000000000..4d49b9e31 --- /dev/null +++ b/security/pkix/test/lib/pkixtestnss.cpp @@ -0,0 +1,309 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2013 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixtestutil.h" + +#include <limits> + +#include "cryptohi.h" +#include "keyhi.h" +#include "nss.h" +#include "pk11pqg.h" +#include "pk11pub.h" +#include "pkix/pkixnss.h" +#include "pkixder.h" +#include "pkixutil.h" +#include "prinit.h" +#include "secerr.h" +#include "secitem.h" + +namespace mozilla { namespace pkix { namespace test { + +namespace { + +typedef ScopedPtr<SECKEYPublicKey, SECKEY_DestroyPublicKey> + ScopedSECKEYPublicKey; +typedef ScopedPtr<SECKEYPrivateKey, SECKEY_DestroyPrivateKey> + ScopedSECKEYPrivateKey; + +inline void +SECITEM_FreeItem_true(SECItem* item) +{ + SECITEM_FreeItem(item, true); +} + +typedef mozilla::pkix::ScopedPtr<SECItem, SECITEM_FreeItem_true> ScopedSECItem; + +TestKeyPair* GenerateKeyPairInner(); + +void +InitNSSIfNeeded() +{ + if (NSS_NoDB_Init(nullptr) != SECSuccess) { + abort(); + } +} + +static ScopedTestKeyPair reusedKeyPair; + +PRStatus +InitReusedKeyPair() +{ + InitNSSIfNeeded(); + reusedKeyPair.reset(GenerateKeyPairInner()); + return reusedKeyPair ? PR_SUCCESS : PR_FAILURE; +} + +class NSSTestKeyPair final : public TestKeyPair +{ +public: + // NSSTestKeyPair takes ownership of privateKey. + NSSTestKeyPair(const TestPublicKeyAlgorithm& publicKeyAlg, + const ByteString& spk, + SECKEYPrivateKey* privateKey) + : TestKeyPair(publicKeyAlg, spk) + , privateKey(privateKey) + { + } + + Result SignData(const ByteString& tbs, + const TestSignatureAlgorithm& signatureAlgorithm, + /*out*/ ByteString& signature) const override + { + SECOidTag oidTag; + if (signatureAlgorithm.publicKeyAlg == RSA_PKCS1()) { + switch (signatureAlgorithm.digestAlg) { + case TestDigestAlgorithmID::MD2: + oidTag = SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION; + break; + case TestDigestAlgorithmID::MD5: + oidTag = SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION; + break; + case TestDigestAlgorithmID::SHA1: + oidTag = SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION; + break; + case TestDigestAlgorithmID::SHA224: + oidTag = SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION; + break; + case TestDigestAlgorithmID::SHA256: + oidTag = SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION; + break; + case TestDigestAlgorithmID::SHA384: + oidTag = SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION; + break; + case TestDigestAlgorithmID::SHA512: + oidTag = SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION; + break; + MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM + } + } else { + abort(); + } + + SECItem signatureItem; + if (SEC_SignData(&signatureItem, tbs.data(), + static_cast<int>(tbs.length()), + privateKey.get(), oidTag) != SECSuccess) { + return MapPRErrorCodeToResult(PR_GetError()); + } + signature.assign(signatureItem.data, signatureItem.len); + SECITEM_FreeItem(&signatureItem, false); + return Success; + } + + TestKeyPair* Clone() const override + { + ScopedSECKEYPrivateKey + privateKeyCopy(SECKEY_CopyPrivateKey(privateKey.get())); + if (!privateKeyCopy) { + return nullptr; + } + return new (std::nothrow) NSSTestKeyPair(publicKeyAlg, + subjectPublicKey, + privateKeyCopy.release()); + } + +private: + ScopedSECKEYPrivateKey privateKey; +}; + +} // namespace + +// This private function is also used by Gecko's PSM test framework +// (OCSPCommon.cpp). +// +// Ownership of privateKey is transfered. +TestKeyPair* CreateTestKeyPair(const TestPublicKeyAlgorithm publicKeyAlg, + const SECKEYPublicKey& publicKey, + SECKEYPrivateKey* privateKey) +{ + ScopedPtr<CERTSubjectPublicKeyInfo, SECKEY_DestroySubjectPublicKeyInfo> + spki(SECKEY_CreateSubjectPublicKeyInfo(&publicKey)); + if (!spki) { + return nullptr; + } + SECItem spkDER = spki->subjectPublicKey; + DER_ConvertBitString(&spkDER); // bits to bytes + return new (std::nothrow) NSSTestKeyPair(publicKeyAlg, + ByteString(spkDER.data, spkDER.len), + privateKey); +} + +namespace { + +TestKeyPair* +GenerateKeyPairInner() +{ + ScopedPtr<PK11SlotInfo, PK11_FreeSlot> slot(PK11_GetInternalSlot()); + if (!slot) { + abort(); + } + + // Bug 1012786: PK11_GenerateKeyPair can fail if there is insufficient + // entropy to generate a random key. Attempting to add some entropy and + // retrying appears to solve this issue. + for (uint32_t retries = 0; retries < 10; retries++) { + PK11RSAGenParams params; + params.keySizeInBits = 2048; + params.pe = 3; + SECKEYPublicKey* publicKeyTemp = nullptr; + ScopedSECKEYPrivateKey + privateKey(PK11_GenerateKeyPair(slot.get(), CKM_RSA_PKCS_KEY_PAIR_GEN, + ¶ms, &publicKeyTemp, false, true, + nullptr)); + ScopedSECKEYPublicKey publicKey(publicKeyTemp); + if (privateKey) { + return CreateTestKeyPair(RSA_PKCS1(), *publicKey, privateKey.release()); + } + + assert(!publicKeyTemp); + + if (PR_GetError() != SEC_ERROR_PKCS11_FUNCTION_FAILED) { + break; + } + + // Since these keys are only for testing, we don't need them to be good, + // random keys. + // https://xkcd.com/221/ + static const uint8_t RANDOM_NUMBER[] = { 4, 4, 4, 4, 4, 4, 4, 4 }; + if (PK11_RandomUpdate((void*) &RANDOM_NUMBER, + sizeof(RANDOM_NUMBER)) != SECSuccess) { + break; + } + } + + abort(); +} + +} // namespace + +TestKeyPair* +GenerateKeyPair() +{ + InitNSSIfNeeded(); + return GenerateKeyPairInner(); +} + +TestKeyPair* +CloneReusedKeyPair() +{ + static PRCallOnceType initCallOnce; + if (PR_CallOnce(&initCallOnce, InitReusedKeyPair) != PR_SUCCESS) { + abort(); + } + assert(reusedKeyPair); + return reusedKeyPair->Clone(); +} + +TestKeyPair* +GenerateDSSKeyPair() +{ + InitNSSIfNeeded(); + + ScopedPtr<PK11SlotInfo, PK11_FreeSlot> slot(PK11_GetInternalSlot()); + if (!slot) { + return nullptr; + } + + ByteString p(DSS_P()); + ByteString q(DSS_Q()); + ByteString g(DSS_G()); + + static const PQGParams PARAMS = { + nullptr, + { siBuffer, + const_cast<uint8_t*>(p.data()), + static_cast<unsigned int>(p.length()) + }, + { siBuffer, + const_cast<uint8_t*>(q.data()), + static_cast<unsigned int>(q.length()) + }, + { siBuffer, + const_cast<uint8_t*>(g.data()), + static_cast<unsigned int>(g.length()) + } + }; + + SECKEYPublicKey* publicKeyTemp = nullptr; + ScopedSECKEYPrivateKey + privateKey(PK11_GenerateKeyPair(slot.get(), CKM_DSA_KEY_PAIR_GEN, + const_cast<PQGParams*>(&PARAMS), + &publicKeyTemp, false, true, nullptr)); + if (!privateKey) { + return nullptr; + } + ScopedSECKEYPublicKey publicKey(publicKeyTemp); + return CreateTestKeyPair(DSS(), *publicKey, privateKey.release()); +} + +Result +TestVerifyECDSASignedDigest(const SignedDigest& signedDigest, + Input subjectPublicKeyInfo) +{ + InitNSSIfNeeded(); + return VerifyECDSASignedDigestNSS(signedDigest, subjectPublicKeyInfo, + nullptr); +} + +Result +TestVerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest, + Input subjectPublicKeyInfo) +{ + InitNSSIfNeeded(); + return VerifyRSAPKCS1SignedDigestNSS(signedDigest, subjectPublicKeyInfo, + nullptr); +} + +Result +TestDigestBuf(Input item, + DigestAlgorithm digestAlg, + /*out*/ uint8_t* digestBuf, + size_t digestBufLen) +{ + InitNSSIfNeeded(); + return DigestBufNSS(item, digestAlg, digestBuf, digestBufLen); +} + +} } } // namespace mozilla::pkix::test diff --git a/security/pkix/test/lib/pkixtestutil.cpp b/security/pkix/test/lib/pkixtestutil.cpp new file mode 100644 index 000000000..decdee09a --- /dev/null +++ b/security/pkix/test/lib/pkixtestutil.cpp @@ -0,0 +1,1153 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2013 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixtestutil.h" + +#include <cerrno> +#include <cstdio> +#include <limits> +#include <new> +#include <sstream> +#include <cstdlib> + +#include "pkixder.h" +#include "pkixutil.h" + +using namespace std; + +namespace mozilla { namespace pkix { namespace test { + +namespace { + +inline void +fclose_void(FILE* file) { + (void) fclose(file); +} + +typedef mozilla::pkix::ScopedPtr<FILE, fclose_void> ScopedFILE; + +FILE* +OpenFile(const string& dir, const string& filename, const string& mode) +{ + string path = dir + '/' + filename; + + ScopedFILE file; +#ifdef _MSC_VER + { + FILE* rawFile; + errno_t error = fopen_s(&rawFile, path.c_str(), mode.c_str()); + if (error) { + // TODO: map error to NSPR error code + rawFile = nullptr; + } + file.reset(rawFile); + } +#else + file.reset(fopen(path.c_str(), mode.c_str())); +#endif + return file.release(); +} + +} // namespace + +bool +InputEqualsByteString(Input input, const ByteString& bs) +{ + Input bsInput; + if (bsInput.Init(bs.data(), bs.length()) != Success) { + // Init can only fail if it is given a bad pointer or if the input is too + // long, which won't ever happen. Plus, if it does, it is ok to call abort + // since this is only test code. + abort(); + } + return InputsAreEqual(input, bsInput); +} + +ByteString +InputToByteString(Input input) +{ + ByteString result; + Reader reader(input); + for (;;) { + uint8_t b; + if (reader.Read(b) != Success) { + return result; + } + result.push_back(b); + } +} + +Result +TamperOnce(/*in/out*/ ByteString& item, const ByteString& from, + const ByteString& to) +{ + if (from.length() < 8) { + return Result::FATAL_ERROR_INVALID_ARGS; + } + if (from.length() != to.length()) { + return Result::FATAL_ERROR_INVALID_ARGS; + } + size_t pos = item.find(from); + if (pos == string::npos) { + return Result::FATAL_ERROR_INVALID_ARGS; // No matches. + } + if (item.find(from, pos + from.length()) != string::npos) { + return Result::FATAL_ERROR_INVALID_ARGS; // More than once match. + } + item.replace(pos, from.length(), to); + return Success; +} + +// Given a tag and a value, generates a DER-encoded tag-length-value item. +ByteString +TLV(uint8_t tag, size_t length, const ByteString& value) +{ + ByteString result; + result.push_back(tag); + + if (value.length() < 128) { + result.push_back(static_cast<uint8_t>(length)); + } else if (value.length() < 256) { + result.push_back(0x81u); + result.push_back(static_cast<uint8_t>(length)); + } else if (value.length() < 65536) { + result.push_back(0x82u); + result.push_back(static_cast<uint8_t>(length / 256)); + result.push_back(static_cast<uint8_t>(length % 256)); + } else { + // It is MUCH more convenient for TLV to be infallible than for it to have + // "proper" error handling. + abort(); + } + result.append(value); + return result; +} + +OCSPResponseExtension::OCSPResponseExtension() + : id() + , critical(false) + , value() + , next(nullptr) +{ +} + +OCSPResponseContext::OCSPResponseContext(const CertID& certID, time_t time) + : certID(certID) + , responseStatus(successful) + , skipResponseBytes(false) + , producedAt(time) + , singleExtensions(nullptr) + , responseExtensions(nullptr) + , includeEmptyExtensions(false) + , signatureAlgorithm(sha256WithRSAEncryption()) + , badSignature(false) + , certs(nullptr) + + , certStatus(good) + , revocationTime(0) + , thisUpdate(time) + , nextUpdate(time + static_cast<time_t>(Time::ONE_DAY_IN_SECONDS)) + , includeNextUpdate(true) +{ +} + +static ByteString ResponseBytes(OCSPResponseContext& context); +static ByteString BasicOCSPResponse(OCSPResponseContext& context); +static ByteString ResponseData(OCSPResponseContext& context); +static ByteString ResponderID(OCSPResponseContext& context); +static ByteString KeyHash(const ByteString& subjectPublicKeyInfo); +static ByteString SingleResponse(OCSPResponseContext& context); +static ByteString CertID(OCSPResponseContext& context); +static ByteString CertStatus(OCSPResponseContext& context); + +static ByteString +SHA1(const ByteString& toHash) +{ + uint8_t digestBuf[20]; + Input input; + if (input.Init(toHash.data(), toHash.length()) != Success) { + abort(); + } + Result rv = TestDigestBuf(input, DigestAlgorithm::sha1, digestBuf, + sizeof(digestBuf)); + if (rv != Success) { + abort(); + } + return ByteString(digestBuf, sizeof(digestBuf)); +} + +static ByteString +HashedOctetString(const ByteString& bytes) +{ + ByteString digest(SHA1(bytes)); + if (ENCODING_FAILED(digest)) { + return ByteString(); + } + return TLV(der::OCTET_STRING, digest); +} + +static ByteString +BitString(const ByteString& rawBytes, bool corrupt) +{ + ByteString prefixed; + // We have to add a byte at the beginning indicating no unused bits. + // TODO: add ability to have bit strings of bit length not divisible by 8, + // resulting in unused bits in the bitstring encoding + prefixed.push_back(0); + prefixed.append(rawBytes); + if (corrupt) { + assert(prefixed.length() > 8); + prefixed[8]++; + } + return TLV(der::BIT_STRING, prefixed); +} + +ByteString +Boolean(bool value) +{ + ByteString encodedValue; + encodedValue.push_back(value ? 0xffu : 0x00u); + return TLV(der::BOOLEAN, encodedValue); +} + +ByteString +Integer(long value) +{ + if (value < 0 || value > 127) { + // TODO: add encoding of larger values + // It is MUCH more convenient for Integer to be infallible than for it to + // have "proper" error handling. + abort(); + } + + ByteString encodedValue; + encodedValue.push_back(static_cast<uint8_t>(value)); + return TLV(der::INTEGER, encodedValue); +} + +enum TimeEncoding { UTCTime = 0, GeneralizedTime = 1 }; + +// Windows doesn't provide gmtime_r, but it provides something very similar. +#if defined(WIN32) && !defined(_POSIX_THREAD_SAFE_FUNCTIONS) +static tm* +gmtime_r(const time_t* t, /*out*/ tm* exploded) +{ + if (gmtime_s(exploded, t) != 0) { + return nullptr; + } + return exploded; +} +#endif + +// http://tools.ietf.org/html/rfc5280#section-4.1.2.5 +// UTCTime: YYMMDDHHMMSSZ (years 1950-2049 only) +// GeneralizedTime: YYYYMMDDHHMMSSZ +// +// This assumes that time/time_t are POSIX-compliant in that time() returns +// the number of seconds since the Unix epoch. +static ByteString +TimeToEncodedTime(time_t time, TimeEncoding encoding) +{ + assert(encoding == UTCTime || encoding == GeneralizedTime); + + tm exploded; + if (!gmtime_r(&time, &exploded)) { + return ByteString(); + } + + if (exploded.tm_sec >= 60) { + // round down for leap seconds + exploded.tm_sec = 59; + } + + // exploded.tm_year is the year offset by 1900. + int year = exploded.tm_year + 1900; + + if (encoding == UTCTime && (year < 1950 || year >= 2050)) { + return ByteString(); + } + + ByteString value; + + if (encoding == GeneralizedTime) { + value.push_back(static_cast<uint8_t>('0' + (year / 1000))); + value.push_back(static_cast<uint8_t>('0' + ((year % 1000) / 100))); + } + + value.push_back(static_cast<uint8_t>('0' + ((year % 100) / 10))); + value.push_back(static_cast<uint8_t>('0' + (year % 10))); + value.push_back(static_cast<uint8_t>('0' + ((exploded.tm_mon + 1) / 10))); + value.push_back(static_cast<uint8_t>('0' + ((exploded.tm_mon + 1) % 10))); + value.push_back(static_cast<uint8_t>('0' + (exploded.tm_mday / 10))); + value.push_back(static_cast<uint8_t>('0' + (exploded.tm_mday % 10))); + value.push_back(static_cast<uint8_t>('0' + (exploded.tm_hour / 10))); + value.push_back(static_cast<uint8_t>('0' + (exploded.tm_hour % 10))); + value.push_back(static_cast<uint8_t>('0' + (exploded.tm_min / 10))); + value.push_back(static_cast<uint8_t>('0' + (exploded.tm_min % 10))); + value.push_back(static_cast<uint8_t>('0' + (exploded.tm_sec / 10))); + value.push_back(static_cast<uint8_t>('0' + (exploded.tm_sec % 10))); + value.push_back('Z'); + + return TLV(encoding == GeneralizedTime ? der::GENERALIZED_TIME : der::UTCTime, + value); +} + +static ByteString +TimeToGeneralizedTime(time_t time) +{ + return TimeToEncodedTime(time, GeneralizedTime); +} + +// http://tools.ietf.org/html/rfc5280#section-4.1.2.5: "CAs conforming to this +// profile MUST always encode certificate validity dates through the year 2049 +// as UTCTime; certificate validity dates in 2050 or later MUST be encoded as +// GeneralizedTime." (This is a special case of the rule that we must always +// use the shortest possible encoding.) +static ByteString +TimeToTimeChoice(time_t time) +{ + tm exploded; + if (!gmtime_r(&time, &exploded)) { + return ByteString(); + } + TimeEncoding encoding = (exploded.tm_year + 1900 >= 1950 && + exploded.tm_year + 1900 < 2050) + ? UTCTime + : GeneralizedTime; + + return TimeToEncodedTime(time, encoding); +} + +Time +YMDHMS(uint16_t year, uint16_t month, uint16_t day, + uint16_t hour, uint16_t minutes, uint16_t seconds) +{ + assert(year <= 9999); + assert(month >= 1); + assert(month <= 12); + assert(day >= 1); + assert(hour < 24); + assert(minutes < 60); + assert(seconds < 60); + + uint64_t days = DaysBeforeYear(year); + + { + static const int16_t DAYS_IN_MONTH[] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + }; + + int16_t i = 1; + for (;;) { + int16_t daysInMonth = DAYS_IN_MONTH[i - 1]; + if (i == 2 && + ((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)))) { + // Add leap day + ++daysInMonth; + } + if (i == month) { + assert(day <= daysInMonth); + break; + } + days += daysInMonth; + ++i; + } + } + + days += (day - 1); + + uint64_t totalSeconds = days * Time::ONE_DAY_IN_SECONDS; + totalSeconds += hour * 60 * 60; + totalSeconds += minutes * 60; + totalSeconds += seconds; + return TimeFromElapsedSecondsAD(totalSeconds); +} + +static ByteString +SignedData(const ByteString& tbsData, + const TestKeyPair& keyPair, + const TestSignatureAlgorithm& signatureAlgorithm, + bool corrupt, /*optional*/ const ByteString* certs) +{ + ByteString signature; + if (keyPair.SignData(tbsData, signatureAlgorithm, signature) != Success) { + return ByteString(); + } + + // TODO: add ability to have signatures of bit length not divisible by 8, + // resulting in unused bits in the bitstring encoding + ByteString signatureNested(BitString(signature, corrupt)); + if (ENCODING_FAILED(signatureNested)) { + return ByteString(); + } + + ByteString certsNested; + if (certs) { + ByteString certsSequenceValue; + while (!(*certs).empty()) { + certsSequenceValue.append(*certs); + ++certs; + } + ByteString certsSequence(TLV(der::SEQUENCE, certsSequenceValue)); + certsNested = TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0, + certsSequence); + } + + ByteString value; + value.append(tbsData); + value.append(signatureAlgorithm.algorithmIdentifier); + value.append(signatureNested); + value.append(certsNested); + return TLV(der::SEQUENCE, value); +} + +// Extension ::= SEQUENCE { +// extnID OBJECT IDENTIFIER, +// critical BOOLEAN DEFAULT FALSE, +// extnValue OCTET STRING +// -- contains the DER encoding of an ASN.1 value +// -- corresponding to the extension type identified +// -- by extnID +// } +static ByteString +Extension(Input extnID, Critical critical, const ByteString& extnValueBytes) +{ + ByteString encoded; + + encoded.append(ByteString(extnID.UnsafeGetData(), extnID.GetLength())); + + if (critical == Critical::Yes) { + encoded.append(Boolean(true)); + } + + ByteString extnValueSequence(TLV(der::SEQUENCE, extnValueBytes)); + ByteString extnValue(TLV(der::OCTET_STRING, extnValueSequence)); + encoded.append(extnValue); + return TLV(der::SEQUENCE, encoded); +} + +static ByteString +EmptyExtension(Input extnID, Critical critical) +{ + ByteString encoded(extnID.UnsafeGetData(), extnID.GetLength()); + + if (critical == Critical::Yes) { + encoded.append(Boolean(true)); + } + + ByteString extnValue(TLV(der::OCTET_STRING, ByteString())); + encoded.append(extnValue); + return TLV(der::SEQUENCE, encoded); +} + +std::string +GetEnv(const char* name) +{ + std::string result; + +#ifndef _MSC_VER + // XXX: Not thread safe. + const char* value = getenv(name); + if (value) { + result = value; + } +#else + char* value = nullptr; + size_t valueLength = 0; + if (_dupenv_s(&value, &valueLength, name) != 0) { + abort(); + } + if (value) { + result = value; + free(value); + } +#endif + return result; +} + +void +MaybeLogOutput(const ByteString& result, const char* suffix) +{ + assert(suffix); + + // This allows us to more easily debug the generated output, by creating a + // file in the directory given by MOZILLA_PKIX_TEST_LOG_DIR for each + // NOT THREAD-SAFE!!! + std::string logPath(GetEnv("MOZILLA_PKIX_TEST_LOG_DIR")); + if (!logPath.empty()) { + static int counter = 0; + + std::ostringstream counterStream; + counterStream << counter; + if (!counterStream) { + assert(false); + return; + } + string filename = counterStream.str() + '-' + suffix + ".der"; + + ++counter; + ScopedFILE file(OpenFile(logPath, filename, "wb")); + if (file) { + (void) fwrite(result.data(), result.length(), 1, file.get()); + } + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Certificates + +static ByteString TBSCertificate(long version, const ByteString& serialNumber, + const ByteString& signature, + const ByteString& issuer, + time_t notBefore, time_t notAfter, + const ByteString& subject, + const ByteString& subjectPublicKeyInfo, + /*optional*/ const ByteString* extensions); + +// Certificate ::= SEQUENCE { +// tbsCertificate TBSCertificate, +// signatureAlgorithm AlgorithmIdentifier, +// signatureValue BIT STRING } +ByteString +CreateEncodedCertificate(long version, + const TestSignatureAlgorithm& signature, + const ByteString& serialNumber, + const ByteString& issuerNameDER, + time_t notBefore, time_t notAfter, + const ByteString& subjectNameDER, + const TestKeyPair& subjectKeyPair, + /*optional*/ const ByteString* extensions, + const TestKeyPair& issuerKeyPair, + const TestSignatureAlgorithm& signatureAlgorithm) +{ + ByteString tbsCertificate(TBSCertificate(version, serialNumber, + signature.algorithmIdentifier, + issuerNameDER, notBefore, + notAfter, subjectNameDER, + subjectKeyPair.subjectPublicKeyInfo, + extensions)); + if (ENCODING_FAILED(tbsCertificate)) { + return ByteString(); + } + + ByteString result(SignedData(tbsCertificate, issuerKeyPair, + signatureAlgorithm, false, nullptr)); + if (ENCODING_FAILED(result)) { + return ByteString(); + } + + MaybeLogOutput(result, "cert"); + + return result; +} + +// TBSCertificate ::= SEQUENCE { +// version [0] Version DEFAULT v1, +// serialNumber CertificateSerialNumber, +// signature AlgorithmIdentifier, +// issuer Name, +// validity Validity, +// subject Name, +// subjectPublicKeyInfo SubjectPublicKeyInfo, +// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, +// -- If present, version MUST be v2 or v3 +// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, +// -- If present, version MUST be v2 or v3 +// extensions [3] Extensions OPTIONAL +// -- If present, version MUST be v3 -- } +static ByteString +TBSCertificate(long versionValue, + const ByteString& serialNumber, const ByteString& signature, + const ByteString& issuer, time_t notBeforeTime, + time_t notAfterTime, const ByteString& subject, + const ByteString& subjectPublicKeyInfo, + /*optional*/ const ByteString* extensions) +{ + ByteString value; + + if (versionValue != static_cast<long>(der::Version::v1)) { + ByteString versionInteger(Integer(versionValue)); + ByteString version(TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0, + versionInteger)); + value.append(version); + } + + value.append(serialNumber); + value.append(signature); + value.append(issuer); + + // Validity ::= SEQUENCE { + // notBefore Time, + // notAfter Time } + ByteString validity; + { + ByteString notBefore(TimeToTimeChoice(notBeforeTime)); + if (ENCODING_FAILED(notBefore)) { + return ByteString(); + } + ByteString notAfter(TimeToTimeChoice(notAfterTime)); + if (ENCODING_FAILED(notAfter)) { + return ByteString(); + } + ByteString validityValue; + validityValue.append(notBefore); + validityValue.append(notAfter); + validity = TLV(der::SEQUENCE, validityValue); + if (ENCODING_FAILED(validity)) { + return ByteString(); + } + } + value.append(validity); + + value.append(subject); + + value.append(subjectPublicKeyInfo); + + if (extensions) { + ByteString extensionsValue; + while (!(*extensions).empty()) { + extensionsValue.append(*extensions); + ++extensions; + } + ByteString extensionsSequence(TLV(der::SEQUENCE, extensionsValue)); + if (ENCODING_FAILED(extensionsSequence)) { + return ByteString(); + } + ByteString extensionsWrapped( + TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 3, extensionsSequence)); + if (ENCODING_FAILED(extensionsWrapped)) { + return ByteString(); + } + value.append(extensionsWrapped); + } + + return TLV(der::SEQUENCE, value); +} + +// AttributeTypeAndValue ::= SEQUENCE { +// type AttributeType, +// value AttributeValue } +// +// AttributeType ::= OBJECT IDENTIFIER +// +// AttributeValue ::= ANY -- DEFINED BY AttributeType +// +// DirectoryString ::= CHOICE { +// teletexString TeletexString (SIZE (1..MAX)), +// printableString PrintableString (SIZE (1..MAX)), +// universalString UniversalString (SIZE (1..MAX)), +// utf8String UTF8String (SIZE (1..MAX)), +// bmpString BMPString (SIZE (1..MAX)) } +template <size_t N> +static ByteString +AVA(const uint8_t (&type)[N], uint8_t directoryStringType, + const ByteString& value) +{ + ByteString wrappedValue(TLV(directoryStringType, value)); + ByteString ava; + ava.append(type, N); + ava.append(wrappedValue); + return TLV(der::SEQUENCE, ava); +} + +ByteString +CN(const ByteString& value, uint8_t encodingTag) +{ + // id-at OBJECT IDENTIFIER ::= { joint-iso-ccitt(2) ds(5) 4 } + // id-at-commonName AttributeType ::= { id-at 3 } + // python DottedOIDToCode.py --tlv id-at-commonName 2.5.4.3 + static const uint8_t tlv_id_at_commonName[] = { + 0x06, 0x03, 0x55, 0x04, 0x03 + }; + return AVA(tlv_id_at_commonName, encodingTag, value); +} + +ByteString +OU(const ByteString& value, uint8_t encodingTag) +{ + // id-at OBJECT IDENTIFIER ::= { joint-iso-ccitt(2) ds(5) 4 } + // id-at-organizationalUnitName AttributeType ::= { id-at 11 } + // python DottedOIDToCode.py --tlv id-at-organizationalUnitName 2.5.4.11 + static const uint8_t tlv_id_at_organizationalUnitName[] = { + 0x06, 0x03, 0x55, 0x04, 0x0b + }; + + return AVA(tlv_id_at_organizationalUnitName, encodingTag, value); +} + +ByteString +emailAddress(const ByteString& value) +{ + // id-emailAddress AttributeType ::= { pkcs-9 1 } + // python DottedOIDToCode.py --tlv id-emailAddress 1.2.840.113549.1.9.1 + static const uint8_t tlv_id_emailAddress[] = { + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01 + }; + + return AVA(tlv_id_emailAddress, der::IA5String, value); +} + +// RelativeDistinguishedName ::= +// SET SIZE (1..MAX) OF AttributeTypeAndValue +// +ByteString +RDN(const ByteString& avas) +{ + return TLV(der::SET, avas); +} + +// Name ::= CHOICE { -- only one possibility for now -- +// rdnSequence RDNSequence } +// +// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName +// +ByteString +Name(const ByteString& rdns) +{ + return TLV(der::SEQUENCE, rdns); +} + +ByteString +CreateEncodedSerialNumber(long serialNumberValue) +{ + return Integer(serialNumberValue); +} + +// BasicConstraints ::= SEQUENCE { +// cA BOOLEAN DEFAULT FALSE, +// pathLenConstraint INTEGER (0..MAX) OPTIONAL } +ByteString +CreateEncodedBasicConstraints(bool isCA, + /*optional*/ long* pathLenConstraintValue, + Critical critical) +{ + ByteString value; + + if (isCA) { + ByteString cA(Boolean(true)); + value.append(cA); + } + + if (pathLenConstraintValue) { + ByteString pathLenConstraint(Integer(*pathLenConstraintValue)); + value.append(pathLenConstraint); + } + + // python DottedOIDToCode.py --tlv id-ce-basicConstraints 2.5.29.19 + static const uint8_t tlv_id_ce_basicConstraints[] = { + 0x06, 0x03, 0x55, 0x1d, 0x13 + }; + return Extension(Input(tlv_id_ce_basicConstraints), critical, value); +} + +// ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId +// KeyPurposeId ::= OBJECT IDENTIFIER +ByteString +CreateEncodedEKUExtension(Input ekuOID, Critical critical) +{ + ByteString value(ekuOID.UnsafeGetData(), ekuOID.GetLength()); + + // python DottedOIDToCode.py --tlv id-ce-extKeyUsage 2.5.29.37 + static const uint8_t tlv_id_ce_extKeyUsage[] = { + 0x06, 0x03, 0x55, 0x1d, 0x25 + }; + + return Extension(Input(tlv_id_ce_extKeyUsage), critical, value); +} + +// python DottedOIDToCode.py --tlv id-ce-subjectAltName 2.5.29.17 +static const uint8_t tlv_id_ce_subjectAltName[] = { + 0x06, 0x03, 0x55, 0x1d, 0x11 +}; + +ByteString +CreateEncodedSubjectAltName(const ByteString& names) +{ + return Extension(Input(tlv_id_ce_subjectAltName), Critical::No, names); +} + +ByteString +CreateEncodedEmptySubjectAltName() +{ + return EmptyExtension(Input(tlv_id_ce_subjectAltName), Critical::No); +} + +/////////////////////////////////////////////////////////////////////////////// +// OCSP responses + +ByteString +CreateEncodedOCSPResponse(OCSPResponseContext& context) +{ + if (!context.skipResponseBytes) { + if (!context.signerKeyPair) { + return ByteString(); + } + } + + // OCSPResponse ::= SEQUENCE { + // responseStatus OCSPResponseStatus, + // responseBytes [0] EXPLICIT ResponseBytes OPTIONAL } + + // OCSPResponseStatus ::= ENUMERATED { + // successful (0), -- Response has valid confirmations + // malformedRequest (1), -- Illegal confirmation request + // internalError (2), -- Internal error in issuer + // tryLater (3), -- Try again later + // -- (4) is not used + // sigRequired (5), -- Must sign the request + // unauthorized (6) -- Request unauthorized + // } + ByteString reponseStatusValue; + reponseStatusValue.push_back(context.responseStatus); + ByteString responseStatus(TLV(der::ENUMERATED, reponseStatusValue)); + + ByteString responseBytesNested; + if (!context.skipResponseBytes) { + ByteString responseBytes(ResponseBytes(context)); + if (ENCODING_FAILED(responseBytes)) { + return ByteString(); + } + + responseBytesNested = TLV(der::CONSTRUCTED | der::CONTEXT_SPECIFIC, + responseBytes); + } + + ByteString value; + value.append(responseStatus); + value.append(responseBytesNested); + ByteString result(TLV(der::SEQUENCE, value)); + + MaybeLogOutput(result, "ocsp"); + + return result; +} + +// ResponseBytes ::= SEQUENCE { +// responseType OBJECT IDENTIFIER, +// response OCTET STRING } +ByteString +ResponseBytes(OCSPResponseContext& context) +{ + // Includes tag and length + static const uint8_t id_pkix_ocsp_basic_encoded[] = { + 0x06, 0x09, 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01 + }; + ByteString response(BasicOCSPResponse(context)); + if (ENCODING_FAILED(response)) { + return ByteString(); + } + ByteString responseNested = TLV(der::OCTET_STRING, response); + + ByteString value; + value.append(id_pkix_ocsp_basic_encoded, + sizeof(id_pkix_ocsp_basic_encoded)); + value.append(responseNested); + return TLV(der::SEQUENCE, value); +} + +// BasicOCSPResponse ::= SEQUENCE { +// tbsResponseData ResponseData, +// signatureAlgorithm AlgorithmIdentifier, +// signature BIT STRING, +// certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL } +ByteString +BasicOCSPResponse(OCSPResponseContext& context) +{ + ByteString tbsResponseData(ResponseData(context)); + if (ENCODING_FAILED(tbsResponseData)) { + return ByteString(); + } + + return SignedData(tbsResponseData, *context.signerKeyPair, + context.signatureAlgorithm, context.badSignature, + context.certs); +} + +// Extension ::= SEQUENCE { +// id OBJECT IDENTIFIER, +// critical BOOLEAN DEFAULT FALSE +// value OCTET STRING +// } +static ByteString +OCSPExtension(OCSPResponseExtension& extension) +{ + ByteString encoded; + encoded.append(extension.id); + if (extension.critical) { + encoded.append(Boolean(true)); + } + ByteString value(TLV(der::OCTET_STRING, extension.value)); + encoded.append(value); + return TLV(der::SEQUENCE, encoded); +} + +// Extensions ::= [1] { +// SEQUENCE OF Extension +// } +static ByteString +OCSPExtensions(OCSPResponseExtension* extensions) +{ + ByteString value; + for (OCSPResponseExtension* extension = extensions; + extension; extension = extension->next) { + ByteString extensionEncoded(OCSPExtension(*extension)); + if (ENCODING_FAILED(extensionEncoded)) { + return ByteString(); + } + value.append(extensionEncoded); + } + ByteString sequence(TLV(der::SEQUENCE, value)); + return TLV(der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 1, sequence); +} + +// ResponseData ::= SEQUENCE { +// version [0] EXPLICIT Version DEFAULT v1, +// responderID ResponderID, +// producedAt GeneralizedTime, +// responses SEQUENCE OF SingleResponse, +// responseExtensions [1] EXPLICIT Extensions OPTIONAL } +ByteString +ResponseData(OCSPResponseContext& context) +{ + ByteString responderID(ResponderID(context)); + if (ENCODING_FAILED(responderID)) { + return ByteString(); + } + ByteString producedAtEncoded(TimeToGeneralizedTime(context.producedAt)); + if (ENCODING_FAILED(producedAtEncoded)) { + return ByteString(); + } + ByteString response(SingleResponse(context)); + if (ENCODING_FAILED(response)) { + return ByteString(); + } + ByteString responses(TLV(der::SEQUENCE, response)); + ByteString responseExtensions; + if (context.responseExtensions || context.includeEmptyExtensions) { + responseExtensions = OCSPExtensions(context.responseExtensions); + } + + ByteString value; + value.append(responderID); + value.append(producedAtEncoded); + value.append(responses); + value.append(responseExtensions); + return TLV(der::SEQUENCE, value); +} + +// ResponderID ::= CHOICE { +// byName [1] Name, +// byKey [2] KeyHash } +// } +ByteString +ResponderID(OCSPResponseContext& context) +{ + ByteString contents; + uint8_t responderIDType; + if (!context.signerNameDER.empty()) { + contents = context.signerNameDER; + responderIDType = 1; // byName + } else { + contents = KeyHash(context.signerKeyPair->subjectPublicKey); + if (ENCODING_FAILED(contents)) { + return ByteString(); + } + responderIDType = 2; // byKey + } + + // XXX: MSVC 2015 wrongly warns about signed/unsigned conversion without the + // static_cast. + uint8_t tag = static_cast<uint8_t>(der::CONSTRUCTED | der::CONTEXT_SPECIFIC | + responderIDType); + return TLV(tag, contents); +} + +// KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key +// -- (i.e., the SHA-1 hash of the value of the +// -- BIT STRING subjectPublicKey [excluding +// -- the tag, length, and number of unused +// -- bits] in the responder's certificate) +ByteString +KeyHash(const ByteString& subjectPublicKey) +{ + return HashedOctetString(subjectPublicKey); +} + +// SingleResponse ::= SEQUENCE { +// certID CertID, +// certStatus CertStatus, +// thisUpdate GeneralizedTime, +// nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL, +// singleExtensions [1] EXPLICIT Extensions OPTIONAL } +ByteString +SingleResponse(OCSPResponseContext& context) +{ + ByteString certID(CertID(context)); + if (ENCODING_FAILED(certID)) { + return ByteString(); + } + ByteString certStatus(CertStatus(context)); + if (ENCODING_FAILED(certStatus)) { + return ByteString(); + } + ByteString thisUpdateEncoded(TimeToGeneralizedTime(context.thisUpdate)); + if (ENCODING_FAILED(thisUpdateEncoded)) { + return ByteString(); + } + ByteString nextUpdateEncodedNested; + if (context.includeNextUpdate) { + ByteString nextUpdateEncoded(TimeToGeneralizedTime(context.nextUpdate)); + if (ENCODING_FAILED(nextUpdateEncoded)) { + return ByteString(); + } + nextUpdateEncodedNested = TLV(der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 0, + nextUpdateEncoded); + } + ByteString singleExtensions; + if (context.singleExtensions || context.includeEmptyExtensions) { + singleExtensions = OCSPExtensions(context.singleExtensions); + } + + ByteString value; + value.append(certID); + value.append(certStatus); + value.append(thisUpdateEncoded); + value.append(nextUpdateEncodedNested); + value.append(singleExtensions); + return TLV(der::SEQUENCE, value); +} + +// CertID ::= SEQUENCE { +// hashAlgorithm AlgorithmIdentifier, +// issuerNameHash OCTET STRING, -- Hash of issuer's DN +// issuerKeyHash OCTET STRING, -- Hash of issuer's public key +// serialNumber CertificateSerialNumber } +ByteString +CertID(OCSPResponseContext& context) +{ + ByteString issuerName(context.certID.issuer.UnsafeGetData(), + context.certID.issuer.GetLength()); + ByteString issuerNameHash(HashedOctetString(issuerName)); + if (ENCODING_FAILED(issuerNameHash)) { + return ByteString(); + } + + ByteString issuerKeyHash; + { + // context.certID.issuerSubjectPublicKeyInfo is the entire + // SubjectPublicKeyInfo structure, but we need just the subjectPublicKey + // part. + Reader input(context.certID.issuerSubjectPublicKeyInfo); + Reader contents; + if (der::ExpectTagAndGetValue(input, der::SEQUENCE, contents) != Success) { + return ByteString(); + } + // Skip AlgorithmIdentifier + if (der::ExpectTagAndSkipValue(contents, der::SEQUENCE) != Success) { + return ByteString(); + } + Input subjectPublicKey; + if (der::BitStringWithNoUnusedBits(contents, subjectPublicKey) + != Success) { + return ByteString(); + } + issuerKeyHash = KeyHash(ByteString(subjectPublicKey.UnsafeGetData(), + subjectPublicKey.GetLength())); + if (ENCODING_FAILED(issuerKeyHash)) { + return ByteString(); + } + } + + ByteString serialNumberValue(context.certID.serialNumber.UnsafeGetData(), + context.certID.serialNumber.GetLength()); + ByteString serialNumber(TLV(der::INTEGER, serialNumberValue)); + + // python DottedOIDToCode.py --alg id-sha1 1.3.14.3.2.26 + static const uint8_t alg_id_sha1[] = { + 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a + }; + + ByteString value; + value.append(alg_id_sha1, sizeof(alg_id_sha1)); + value.append(issuerNameHash); + value.append(issuerKeyHash); + value.append(serialNumber); + return TLV(der::SEQUENCE, value); +} + +// CertStatus ::= CHOICE { +// good [0] IMPLICIT NULL, +// revoked [1] IMPLICIT RevokedInfo, +// unknown [2] IMPLICIT UnknownInfo } +// +// RevokedInfo ::= SEQUENCE { +// revocationTime GeneralizedTime, +// revocationReason [0] EXPLICIT CRLReason OPTIONAL } +// +// UnknownInfo ::= NULL +// +ByteString +CertStatus(OCSPResponseContext& context) +{ + switch (context.certStatus) { + // Both good and unknown are ultimately represented as NULL - the only + // difference is in the tag that identifies them. + case 0: + case 2: + { + // XXX: MSVC 2015 wrongly warns about signed/unsigned conversion without + // the static cast. + return TLV(static_cast<uint8_t>(der::CONTEXT_SPECIFIC | + context.certStatus), ByteString()); + } + case 1: + { + ByteString revocationTime(TimeToGeneralizedTime(context.revocationTime)); + if (ENCODING_FAILED(revocationTime)) { + return ByteString(); + } + // TODO(bug 980536): add support for revocationReason + return TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1, revocationTime); + } + default: + assert(false); + // fall through + } + return ByteString(); +} + +static const ByteString NO_UNUSED_BITS(1, 0x00); + +// The SubjectPublicKeyInfo syntax is specified in RFC 5280 Section 4.1. +TestKeyPair::TestKeyPair(const TestPublicKeyAlgorithm& publicKeyAlg, + const ByteString& spk) + : publicKeyAlg(publicKeyAlg) + , subjectPublicKeyInfo(TLV(der::SEQUENCE, + publicKeyAlg.algorithmIdentifier + + TLV(der::BIT_STRING, NO_UNUSED_BITS + spk))) + , subjectPublicKey(spk) +{ +} + +} } } // namespace mozilla::pkix::test diff --git a/security/pkix/test/lib/pkixtestutil.h b/security/pkix/test/lib/pkixtestutil.h new file mode 100644 index 000000000..b36f1f8ad --- /dev/null +++ b/security/pkix/test/lib/pkixtestutil.h @@ -0,0 +1,448 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2013 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef mozilla_pkix_test_pkixtestutils_h +#define mozilla_pkix_test_pkixtestutils_h + +#include <ctime> +#include <stdint.h> // Some Mozilla-supported compilers lack <cstdint> +#include <string> +#include <cstring> + +#include "pkix/pkixtypes.h" +#include "../../lib/ScopedPtr.h" + +namespace mozilla { namespace pkix { namespace test { + +typedef std::basic_string<uint8_t> ByteString; + +inline bool ENCODING_FAILED(const ByteString& bs) { return bs.empty(); } + +template <size_t L> +inline ByteString +BytesToByteString(const uint8_t (&bytes)[L]) +{ + return ByteString(bytes, L); +} + +// XXX: Ideally, we should define this instead: +// +// template <typename T, std::size_t N> +// constexpr inline std::size_t +// ArrayLength(T (&)[N]) +// { +// return N; +// } +// +// However, we don't because not all supported compilers support constexpr, +// and we need to calculate array lengths in static_assert sometimes. +// +// XXX: Evaluates its argument twice +#define MOZILLA_PKIX_ARRAY_LENGTH(x) (sizeof(x) / sizeof((x)[0])) + +bool InputEqualsByteString(Input input, const ByteString& bs); +ByteString InputToByteString(Input input); + +// python DottedOIDToCode.py --tlv id-kp-OCSPSigning 1.3.6.1.5.5.7.3.9 +static const uint8_t tlv_id_kp_OCSPSigning[] = { + 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x09 +}; + +// python DottedOIDToCode.py --tlv id-kp-serverAuth 1.3.6.1.5.5.7.3.1 +static const uint8_t tlv_id_kp_serverAuth[] = { + 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01 +}; + +enum class TestDigestAlgorithmID +{ + MD2, + MD5, + SHA1, + SHA224, + SHA256, + SHA384, + SHA512, +}; + +struct TestPublicKeyAlgorithm +{ + explicit TestPublicKeyAlgorithm(const ByteString& algorithmIdentifier) + : algorithmIdentifier(algorithmIdentifier) { } + bool operator==(const TestPublicKeyAlgorithm& other) const + { + return algorithmIdentifier == other.algorithmIdentifier; + } + ByteString algorithmIdentifier; +}; + +ByteString DSS_P(); +ByteString DSS_Q(); +ByteString DSS_G(); + +TestPublicKeyAlgorithm DSS(); +TestPublicKeyAlgorithm RSA_PKCS1(); + +struct TestSignatureAlgorithm +{ + TestSignatureAlgorithm(const TestPublicKeyAlgorithm& publicKeyAlg, + TestDigestAlgorithmID digestAlg, + const ByteString& algorithmIdentifier, + bool accepted); + + TestPublicKeyAlgorithm publicKeyAlg; + TestDigestAlgorithmID digestAlg; + ByteString algorithmIdentifier; + bool accepted; +}; + +TestSignatureAlgorithm md2WithRSAEncryption(); +TestSignatureAlgorithm md5WithRSAEncryption(); +TestSignatureAlgorithm sha1WithRSAEncryption(); +TestSignatureAlgorithm sha256WithRSAEncryption(); + +// e.g. YMDHMS(2016, 12, 31, 1, 23, 45) => 2016-12-31:01:23:45 (GMT) +mozilla::pkix::Time YMDHMS(uint16_t year, uint16_t month, uint16_t day, + uint16_t hour, uint16_t minutes, uint16_t seconds); + +ByteString TLV(uint8_t tag, size_t length, const ByteString& value); + +inline ByteString +TLV(uint8_t tag, const ByteString& value) +{ + return TLV(tag, value.length(), value); +} + +// Although we can't enforce it without relying on Cuser-defined literals, +// which aren't supported by all of our compilers yet, you should only pass +// string literals as the last parameter to the following two functions. + +template <size_t N> +inline ByteString +TLV(uint8_t tag, const char(&value)[N]) +{ + static_assert(N > 0, "cannot have string literal of size 0"); + assert(value[N - 1] == 0); + return TLV(tag, ByteString(reinterpret_cast<const uint8_t*>(&value), N - 1)); +} + +template <size_t N> +inline ByteString +TLV(uint8_t tag, size_t length, const char(&value)[N]) +{ + static_assert(N > 0, "cannot have string literal of size 0"); + assert(value[N - 1] == 0); + return TLV(tag, length, + ByteString(reinterpret_cast<const uint8_t*>(&value), N - 1)); +} + +ByteString Boolean(bool value); +ByteString Integer(long value); + +ByteString CN(const ByteString&, uint8_t encodingTag = 0x0c /*UTF8String*/); + +inline ByteString +CN(const char* value, uint8_t encodingTag = 0x0c /*UTF8String*/) +{ + return CN(ByteString(reinterpret_cast<const uint8_t*>(value), + std::strlen(value)), encodingTag); +} + +ByteString OU(const ByteString&, uint8_t encodingTag = 0x0c /*UTF8String*/); + +inline ByteString +OU(const char* value, uint8_t encodingTag = 0x0c /*UTF8String*/) +{ + return OU(ByteString(reinterpret_cast<const uint8_t*>(value), + std::strlen(value)), encodingTag); +} + +ByteString emailAddress(const ByteString&); + +inline ByteString +emailAddress(const char* value) +{ + return emailAddress(ByteString(reinterpret_cast<const uint8_t*>(value), + std::strlen(value))); +} + +// RelativeDistinguishedName ::= +// SET SIZE (1..MAX) OF AttributeTypeAndValue +// +ByteString RDN(const ByteString& avas); + +// Name ::= CHOICE { -- only one possibility for now -- +// rdnSequence RDNSequence } +// +// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName +// +ByteString Name(const ByteString& rdns); + +inline ByteString +CNToDERName(const ByteString& cn) +{ + return Name(RDN(CN(cn))); +} + +inline ByteString +CNToDERName(const char* cn) +{ + return Name(RDN(CN(cn))); +} + +// GeneralName ::= CHOICE { +// otherName [0] OtherName, +// rfc822Name [1] IA5String, +// dNSName [2] IA5String, +// x400Address [3] ORAddress, +// directoryName [4] Name, +// ediPartyName [5] EDIPartyName, +// uniformResourceIdentifier [6] IA5String, +// iPAddress [7] OCTET STRING, +// registeredID [8] OBJECT IDENTIFIER } + +inline ByteString +RFC822Name(const ByteString& name) +{ + // (2 << 6) means "context-specific", 1 is the GeneralName tag. + return TLV((2 << 6) | 1, name); +} + +template <size_t L> +inline ByteString +RFC822Name(const char (&bytes)[L]) +{ + return RFC822Name(ByteString(reinterpret_cast<const uint8_t*>(&bytes), + L - 1)); +} + +inline ByteString +DNSName(const ByteString& name) +{ + // (2 << 6) means "context-specific", 2 is the GeneralName tag. + return TLV((2 << 6) | 2, name); +} + +template <size_t L> +inline ByteString +DNSName(const char (&bytes)[L]) +{ + return DNSName(ByteString(reinterpret_cast<const uint8_t*>(&bytes), + L - 1)); +} + +inline ByteString +DirectoryName(const ByteString& name) +{ + // (2 << 6) means "context-specific", (1 << 5) means "constructed", and 4 is + // the DirectoryName tag. + return TLV((2 << 6) | (1 << 5) | 4, name); +} + +inline ByteString +IPAddress() +{ + // (2 << 6) means "context-specific", 7 is the GeneralName tag. + return TLV((2 << 6) | 7, ByteString()); +} + +template <size_t L> +inline ByteString +IPAddress(const uint8_t (&bytes)[L]) +{ + // (2 << 6) means "context-specific", 7 is the GeneralName tag. + return TLV((2 << 6) | 7, ByteString(bytes, L)); +} + +// Names should be zero or more GeneralNames, like DNSName and IPAddress return, +// concatenated together. +// +// CreatedEncodedSubjectAltName(ByteString()) results in a SAN with an empty +// sequence. CreateEmptyEncodedSubjectName() results in a SAN without any +// sequence. +ByteString CreateEncodedSubjectAltName(const ByteString& names); +ByteString CreateEncodedEmptySubjectAltName(); + +class TestKeyPair +{ +public: + virtual ~TestKeyPair() { } + + const TestPublicKeyAlgorithm publicKeyAlg; + + // The DER encoding of the entire SubjectPublicKeyInfo structure. This is + // what is encoded in certificates. + const ByteString subjectPublicKeyInfo; + + // The DER encoding of subjectPublicKeyInfo.subjectPublicKey. This is what is + // hashed to create CertIDs for OCSP. + const ByteString subjectPublicKey; + + virtual Result SignData(const ByteString& tbs, + const TestSignatureAlgorithm& signatureAlgorithm, + /*out*/ ByteString& signature) const = 0; + + virtual TestKeyPair* Clone() const = 0; +protected: + TestKeyPair(const TestPublicKeyAlgorithm& publicKeyAlg, const ByteString& spk); + TestKeyPair(const TestKeyPair&) = delete; + void operator=(const TestKeyPair&) = delete; +}; + +TestKeyPair* CloneReusedKeyPair(); +TestKeyPair* GenerateKeyPair(); +TestKeyPair* GenerateDSSKeyPair(); +inline void DeleteTestKeyPair(TestKeyPair* keyPair) { delete keyPair; } +typedef ScopedPtr<TestKeyPair, DeleteTestKeyPair> ScopedTestKeyPair; + +Result TestVerifyECDSASignedDigest(const SignedDigest& signedDigest, + Input subjectPublicKeyInfo); +Result TestVerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest, + Input subjectPublicKeyInfo); +Result TestDigestBuf(Input item, DigestAlgorithm digestAlg, + /*out*/ uint8_t* digestBuf, size_t digestBufLen); + +// Replace one substring in item with another of the same length, but only if +// the substring was found exactly once. The "same length" restriction is +// useful for avoiding invalidating lengths encoded within the item. The +// "only once" restriction is helpful for avoiding making accidental changes. +// +// The string to search for must be 8 or more bytes long so that it is +// extremely unlikely that there will ever be any false positive matches +// in digital signatures, keys, hashes, etc. +Result TamperOnce(/*in/out*/ ByteString& item, const ByteString& from, + const ByteString& to); + +/////////////////////////////////////////////////////////////////////////////// +// Encode Certificates + +enum Version { v1 = 0, v2 = 1, v3 = 2 }; + +// signature is assumed to be the DER encoding of an AlgorithmIdentifer. It is +// put into the signature field of the TBSCertificate. In most cases, it will +// be the same as signatureAlgorithm, which is the algorithm actually used +// to sign the certificate. +// serialNumber is assumed to be the DER encoding of an INTEGER. +// +// If extensions is null, then no extensions will be encoded. Otherwise, +// extensions must point to an array of ByteStrings, terminated with an empty +// ByteString. (If the first item of the array is empty then an empty +// Extensions sequence will be encoded.) +ByteString CreateEncodedCertificate(long version, + const TestSignatureAlgorithm& signature, + const ByteString& serialNumber, + const ByteString& issuerNameDER, + time_t notBefore, time_t notAfter, + const ByteString& subjectNameDER, + const TestKeyPair& subjectKeyPair, + /*optional*/ const ByteString* extensions, + const TestKeyPair& issuerKeyPair, + const TestSignatureAlgorithm& signatureAlgorithm); + +ByteString CreateEncodedSerialNumber(long value); + +enum class Critical { No = 0, Yes = 1 }; + +ByteString CreateEncodedBasicConstraints(bool isCA, + /*optional*/ long* pathLenConstraint, + Critical critical); + +// Creates a DER-encoded extKeyUsage extension with one EKU OID. +ByteString CreateEncodedEKUExtension(Input eku, Critical critical); + +/////////////////////////////////////////////////////////////////////////////// +// Encode OCSP responses + +class OCSPResponseExtension final +{ +public: + OCSPResponseExtension(); + + ByteString id; + bool critical; + ByteString value; + OCSPResponseExtension* next; +}; + +class OCSPResponseContext final +{ +public: + OCSPResponseContext(const CertID& certID, std::time_t time); + + const CertID& certID; + // TODO(bug 980538): add a way to specify what certificates are included. + + // The fields below are in the order that they appear in an OCSP response. + + enum OCSPResponseStatus + { + successful = 0, + malformedRequest = 1, + internalError = 2, + tryLater = 3, + // 4 is not used + sigRequired = 5, + unauthorized = 6, + }; + uint8_t responseStatus; // an OCSPResponseStatus or an invalid value + bool skipResponseBytes; // If true, don't include responseBytes + + // responderID + ByteString signerNameDER; // If set, responderID will use the byName + // form; otherwise responderID will use the + // byKeyHash form. + + std::time_t producedAt; + + // SingleResponse extensions (for the certID given in the constructor). + OCSPResponseExtension* singleExtensions; + // ResponseData extensions. + OCSPResponseExtension* responseExtensions; + bool includeEmptyExtensions; // If true, include the extension wrapper + // regardless of if there are any actual + // extensions. + ScopedTestKeyPair signerKeyPair; + TestSignatureAlgorithm signatureAlgorithm; + bool badSignature; // If true, alter the signature to fail verification + const ByteString* certs; // optional; array terminated by an empty string + + // The following fields are on a per-SingleResponse basis. In the future we + // may support including multiple SingleResponses per response. + enum CertStatus + { + good = 0, + revoked = 1, + unknown = 2, + }; + uint8_t certStatus; // CertStatus or an invalid value + std::time_t revocationTime; // For certStatus == revoked + std::time_t thisUpdate; + std::time_t nextUpdate; + bool includeNextUpdate; +}; + +ByteString CreateEncodedOCSPResponse(OCSPResponseContext& context); + +} } } // namespace mozilla::pkix::test + +#endif // mozilla_pkix_test_pkixtestutils_h |