diff options
author | wolfbeast <mcwerewolf@gmail.com> | 2018-12-17 14:12:04 +0100 |
---|---|---|
committer | wolfbeast <mcwerewolf@gmail.com> | 2018-12-17 14:12:04 +0100 |
commit | 51b821b3fdc5a7eab2369cb6a6680598a6264b08 (patch) | |
tree | f3608a518bbb9e31b0a42b9a10742fb11ef5b39b /security/nss/gtests/mozpkix_gtest | |
parent | 8e44bbb43789e585fab9fc1ce8becc94b45d566c (diff) | |
parent | 680c3eadb6aaec1f3653636db081a519e0f62ef5 (diff) | |
download | UXP-51b821b3fdc5a7eab2369cb6a6680598a6264b08.tar UXP-51b821b3fdc5a7eab2369cb6a6680598a6264b08.tar.gz UXP-51b821b3fdc5a7eab2369cb6a6680598a6264b08.tar.lz UXP-51b821b3fdc5a7eab2369cb6a6680598a6264b08.tar.xz UXP-51b821b3fdc5a7eab2369cb6a6680598a6264b08.zip |
Merge branch 'master' into Sync-weave
Diffstat (limited to 'security/nss/gtests/mozpkix_gtest')
20 files changed, 10278 insertions, 0 deletions
diff --git a/security/nss/gtests/mozpkix_gtest/README.txt b/security/nss/gtests/mozpkix_gtest/README.txt new file mode 100644 index 000000000..5d3484a21 --- /dev/null +++ b/security/nss/gtests/mozpkix_gtest/README.txt @@ -0,0 +1,61 @@ +------------- +Running Tests +------------- + +Because of the rules below, you can run all the unit tests in this directory, +and only these tests, with: + + mach gtest "pkix*" + +You can run just the tests for functions defined in filename pkixfoo.cpp with: + + mach gtest "pkixfoo*" + +If you run "mach gtest" then you'll end up running every gtest in Gecko. + + + +------------ +Naming Files +------------ + +Name files containing tests according to one of the following patterns: + + * <filename>_tests.cpp + * <filename>_<Function>_tests.cpp + * <filename>_<category>_tests.cpp + + <filename> is the name of the file containing the definitions of the + function(s) being tested by every test. + <Function> is the name of the function that is being tested by every + test. + <category> describes the group of related functions that are being + tested by every test. + + + +------------------------------------------------ +Always Use a Fixture Class: TEST_F(), not TEST() +------------------------------------------------ + +Many tests don't technically need a fixture, and so TEST() could technically +be used to define the test. However, when you use TEST_F() instead of TEST(), +the compiler will not allow you to make any typos in the test case name, but +if you use TEST() then the name of the test case is not checked. + +See https://code.google.com/p/googletest/wiki/Primer#Test_Fixtures:_Using_the_Same_Data_Configuration_for_Multiple_Te +to learn more about test fixtures. + +--------------- +Naming Fixtures +--------------- + +When all tests in a file use the same fixture, use the base name of the file +without the "_tests" suffix as the name of the fixture class; e.g. tests in +"pkixocsp.cpp" should use a fixture "class pkixocsp" by default. + +Sometimes tests in a file need separate fixtures. In this case, name the +fixture class according to the pattern <fixture_base>_<fixture_suffix>, where +<fixture_base> is the base name of the file without the "_tests" suffix, and +<fixture_suffix> is a descriptive name for the fixture class, e.g. +"class pkixocsp_DelegatedResponder". diff --git a/security/nss/gtests/mozpkix_gtest/mozpkix_gtest.gyp b/security/nss/gtests/mozpkix_gtest/mozpkix_gtest.gyp new file mode 100644 index 000000000..899b849fc --- /dev/null +++ b/security/nss/gtests/mozpkix_gtest/mozpkix_gtest.gyp @@ -0,0 +1,71 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +{ + 'includes': [ + '../../coreconf/config.gypi', + '../common/gtest.gypi', + ], + 'targets': [ + { + 'target_name': 'mozpkix_gtest', + 'type': 'executable', + 'sources': [ + '<(DEPTH)/gtests/common/gtests.cc', + 'pkixbuild_tests.cpp', + 'pkixcert_extension_tests.cpp', + 'pkixcert_signature_algorithm_tests.cpp', + 'pkixcheck_CheckExtendedKeyUsage_tests.cpp', + 'pkixcheck_CheckIssuer_tests.cpp', + 'pkixcheck_CheckKeyUsage_tests.cpp', + 'pkixcheck_CheckSignatureAlgorithm_tests.cpp', + 'pkixcheck_CheckValidity_tests.cpp', + 'pkixcheck_ParseValidity_tests.cpp', + 'pkixcheck_TLSFeaturesSatisfiedInternal_tests.cpp', + 'pkixder_input_tests.cpp', + 'pkixder_pki_types_tests.cpp', + 'pkixder_universal_types_tests.cpp', + 'pkixgtest.cpp', + 'pkixnames_tests.cpp', + 'pkixocsp_CreateEncodedOCSPRequest_tests.cpp', + 'pkixocsp_VerifyEncodedOCSPResponse.cpp', + ], + 'dependencies': [ + '<(DEPTH)/exports.gyp:nss_exports', + '<(DEPTH)/gtests/google_test/google_test.gyp:gtest', + '<(DEPTH)/lib/util/util.gyp:nssutil', + '<(DEPTH)/lib/ssl/ssl.gyp:ssl', + '<(DEPTH)/lib/nss/nss.gyp:nss_static', + '<(DEPTH)/lib/pk11wrap/pk11wrap.gyp:pk11wrap_static', + '<(DEPTH)/lib/cryptohi/cryptohi.gyp:cryptohi', + '<(DEPTH)/lib/certhigh/certhigh.gyp:certhi', + '<(DEPTH)/lib/certdb/certdb.gyp:certdb', + '<(DEPTH)/lib/base/base.gyp:nssb', + '<(DEPTH)/lib/dev/dev.gyp:nssdev', + '<(DEPTH)/lib/pki/pki.gyp:nsspki', + '<(DEPTH)/lib/mozpkix/mozpkix.gyp:mozpkix', + '<(DEPTH)/lib/mozpkix/mozpkix.gyp:mozpkix-testlib', + ], + 'include_dirs': [ + '<(DEPTH)/lib/mozpkix/', + '<(DEPTH)/lib/mozpkix/lib', + '<(DEPTH)/lib/mozpkix/include/', + '<(DEPTH)/lib/mozpkix/include/pkix-test/', + ], + 'conditions': [ + [ 'OS=="win"', { + 'libraries': [ + 'advapi32.lib', + ], + }], + ], + 'defines': [ + 'NSS_USE_STATIC_LIBS' + ], + } + ], + 'variables': { + 'module': 'nss', + 'use_static_libs': 1, + } +} diff --git a/security/nss/gtests/mozpkix_gtest/pkixbuild_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixbuild_tests.cpp new file mode 100644 index 000000000..e17321075 --- /dev/null +++ b/security/nss/gtests/mozpkix_gtest/pkixbuild_tests.cpp @@ -0,0 +1,894 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2013 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1900 +// When building with -D_HAS_EXCEPTIONS=0, MSVC's <xtree> header triggers +// warning C4702: unreachable code. +// https://connect.microsoft.com/VisualStudio/feedback/details/809962 +#pragma warning(push) +#pragma warning(disable: 4702) +#endif + +#include <map> +#include <vector> + +#if defined(_MSC_VER) && _MSC_VER < 1900 +#pragma warning(pop) +#endif + +#include "pkixgtest.h" + +#include "mozpkix/pkixder.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +static ByteString +CreateCert(const char* issuerCN, // null means "empty name" + const char* subjectCN, // null means "empty name" + EndEntityOrCA endEntityOrCA, + /*optional modified*/ std::map<ByteString, ByteString>* + subjectDERToCertDER = nullptr, + /*optional*/ const ByteString* extension = nullptr, + /*optional*/ const TestKeyPair* issuerKeyPair = nullptr, + /*optional*/ const TestKeyPair* subjectKeyPair = nullptr) +{ + static long serialNumberValue = 0; + ++serialNumberValue; + ByteString serialNumber(CreateEncodedSerialNumber(serialNumberValue)); + EXPECT_FALSE(ENCODING_FAILED(serialNumber)); + + ByteString issuerDER(issuerCN ? CNToDERName(issuerCN) : Name(ByteString())); + ByteString subjectDER(subjectCN ? CNToDERName(subjectCN) : Name(ByteString())); + + std::vector<ByteString> extensions; + if (endEntityOrCA == EndEntityOrCA::MustBeCA) { + ByteString basicConstraints = + CreateEncodedBasicConstraints(true, nullptr, Critical::Yes); + EXPECT_FALSE(ENCODING_FAILED(basicConstraints)); + extensions.push_back(basicConstraints); + } + if (extension) { + extensions.push_back(*extension); + } + extensions.push_back(ByteString()); // marks the end of the list + + ScopedTestKeyPair reusedKey(CloneReusedKeyPair()); + ByteString certDER(CreateEncodedCertificate( + v3, sha256WithRSAEncryption(), serialNumber, issuerDER, + oneDayBeforeNow, oneDayAfterNow, subjectDER, + subjectKeyPair ? *subjectKeyPair : *reusedKey, + extensions.data(), + issuerKeyPair ? *issuerKeyPair : *reusedKey, + sha256WithRSAEncryption())); + EXPECT_FALSE(ENCODING_FAILED(certDER)); + + if (subjectDERToCertDER) { + (*subjectDERToCertDER)[subjectDER] = certDER; + } + + return certDER; +} + +class TestTrustDomain final : public DefaultCryptoTrustDomain +{ +public: + // The "cert chain tail" is a longish chain of certificates that is used by + // all of the tests here. We share this chain across all the tests in order + // to speed up the tests (generating keypairs for the certs is very slow). + bool SetUpCertChainTail() + { + static char const* const names[] = { + "CA1 (Root)", "CA2", "CA3", "CA4", "CA5", "CA6", "CA7" + }; + + for (size_t i = 0; i < MOZILLA_PKIX_ARRAY_LENGTH(names); ++i) { + const char* issuerName = i == 0 ? names[0] : names[i-1]; + CreateCACert(issuerName, names[i]); + if (i == 0) { + rootCACertDER = leafCACertDER; + } + } + + return true; + } + + void CreateCACert(const char* issuerName, const char* subjectName) + { + leafCACertDER = CreateCert(issuerName, subjectName, + EndEntityOrCA::MustBeCA, &subjectDERToCertDER); + assert(!ENCODING_FAILED(leafCACertDER)); + } + + ByteString GetLeafCACertDER() const { return leafCACertDER; } + +private: + Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert, + /*out*/ TrustLevel& trustLevel) override + { + trustLevel = InputEqualsByteString(candidateCert, rootCACertDER) + ? TrustLevel::TrustAnchor + : TrustLevel::InheritsTrust; + return Success; + } + + Result FindIssuer(Input encodedIssuerName, IssuerChecker& checker, Time) + override + { + ByteString subjectDER(InputToByteString(encodedIssuerName)); + ByteString certDER(subjectDERToCertDER[subjectDER]); + Input derCert; + Result rv = derCert.Init(certDER.data(), certDER.length()); + if (rv != Success) { + return rv; + } + bool keepGoing; + rv = checker.Check(derCert, nullptr/*additionalNameConstraints*/, + keepGoing); + if (rv != Success) { + return rv; + } + return Success; + } + + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, + /*optional*/ const Input*, /*optional*/ const Input*) + override + { + return Success; + } + + Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override + { + return Success; + } + + std::map<ByteString, ByteString> subjectDERToCertDER; + ByteString leafCACertDER; + ByteString rootCACertDER; +}; + +class pkixbuild : public ::testing::Test +{ +public: + static void SetUpTestCase() + { + if (!trustDomain.SetUpCertChainTail()) { + abort(); + } + } + +protected: + + static TestTrustDomain trustDomain; +}; + +/*static*/ TestTrustDomain pkixbuild::trustDomain; + +TEST_F(pkixbuild, MaxAcceptableCertChainLength) +{ + { + ByteString leafCACert(trustDomain.GetLeafCACertDER()); + Input certDER; + ASSERT_EQ(Success, certDER.Init(leafCACert.data(), leafCACert.length())); + ASSERT_EQ(Success, + BuildCertChain(trustDomain, certDER, Now(), + EndEntityOrCA::MustBeCA, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::id_kp_serverAuth, + CertPolicyId::anyPolicy, + nullptr/*stapledOCSPResponse*/)); + } + + { + ByteString certDER(CreateCert("CA7", "Direct End-Entity", + EndEntityOrCA::MustBeEndEntity)); + ASSERT_FALSE(ENCODING_FAILED(certDER)); + Input certDERInput; + ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length())); + ASSERT_EQ(Success, + BuildCertChain(trustDomain, certDERInput, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::id_kp_serverAuth, + CertPolicyId::anyPolicy, + nullptr/*stapledOCSPResponse*/)); + } +} + +TEST_F(pkixbuild, BeyondMaxAcceptableCertChainLength) +{ + static char const* const caCertName = "CA Too Far"; + + trustDomain.CreateCACert("CA7", caCertName); + + { + ByteString certDER(trustDomain.GetLeafCACertDER()); + Input certDERInput; + ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length())); + ASSERT_EQ(Result::ERROR_UNKNOWN_ISSUER, + BuildCertChain(trustDomain, certDERInput, Now(), + EndEntityOrCA::MustBeCA, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::id_kp_serverAuth, + CertPolicyId::anyPolicy, + nullptr/*stapledOCSPResponse*/)); + } + + { + ByteString certDER(CreateCert(caCertName, "End-Entity Too Far", + EndEntityOrCA::MustBeEndEntity)); + ASSERT_FALSE(ENCODING_FAILED(certDER)); + Input certDERInput; + ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length())); + ASSERT_EQ(Result::ERROR_UNKNOWN_ISSUER, + BuildCertChain(trustDomain, certDERInput, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::id_kp_serverAuth, + CertPolicyId::anyPolicy, + nullptr/*stapledOCSPResponse*/)); + } +} + +// A TrustDomain that checks certificates against a given root certificate. +// It is initialized with the DER encoding of a root certificate that +// is treated as a trust anchor and is assumed to have issued all certificates +// (i.e. FindIssuer always attempts to build the next step in the chain with +// it). +class SingleRootTrustDomain : public DefaultCryptoTrustDomain +{ +public: + explicit SingleRootTrustDomain(ByteString aRootDER) + : rootDER(aRootDER) + { + } + + // The CertPolicyId argument is unused because we don't care about EV. + Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert, + /*out*/ TrustLevel& trustLevel) override + { + Input rootCert; + Result rv = rootCert.Init(rootDER.data(), rootDER.length()); + if (rv != Success) { + return rv; + } + if (InputsAreEqual(candidateCert, rootCert)) { + trustLevel = TrustLevel::TrustAnchor; + } else { + trustLevel = TrustLevel::InheritsTrust; + } + return Success; + } + + Result FindIssuer(Input, IssuerChecker& checker, Time) override + { + // keepGoing is an out parameter from IssuerChecker.Check. It would tell us + // whether or not to continue attempting other potential issuers. We only + // know of one potential issuer, however, so we ignore it. + bool keepGoing; + Input rootCert; + Result rv = rootCert.Init(rootDER.data(), rootDER.length()); + if (rv != Success) { + return rv; + } + return checker.Check(rootCert, nullptr, keepGoing); + } + + Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override + { + return Success; + } + + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, + /*optional*/ const Input*, /*optional*/ const Input*) + override + { + return Success; + } + +private: + ByteString rootDER; +}; + +// A TrustDomain that explicitly fails if CheckRevocation is called. +class ExpiredCertTrustDomain final : public SingleRootTrustDomain +{ +public: + explicit ExpiredCertTrustDomain(ByteString aRootDER) + : SingleRootTrustDomain(aRootDER) + { + } + + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, + /*optional*/ const Input*, /*optional*/ const Input*) + override + { + ADD_FAILURE(); + return NotReached("CheckRevocation should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } +}; + +TEST_F(pkixbuild, NoRevocationCheckingForExpiredCert) +{ + const char* rootCN = "Root CA"; + ByteString rootDER(CreateCert(rootCN, rootCN, EndEntityOrCA::MustBeCA, + nullptr)); + EXPECT_FALSE(ENCODING_FAILED(rootDER)); + ExpiredCertTrustDomain expiredCertTrustDomain(rootDER); + + ByteString serialNumber(CreateEncodedSerialNumber(100)); + EXPECT_FALSE(ENCODING_FAILED(serialNumber)); + ByteString issuerDER(CNToDERName(rootCN)); + ByteString subjectDER(CNToDERName("Expired End-Entity Cert")); + ScopedTestKeyPair reusedKey(CloneReusedKeyPair()); + ByteString certDER(CreateEncodedCertificate( + v3, sha256WithRSAEncryption(), + serialNumber, issuerDER, + twoDaysBeforeNow, + oneDayBeforeNow, + subjectDER, *reusedKey, nullptr, *reusedKey, + sha256WithRSAEncryption())); + EXPECT_FALSE(ENCODING_FAILED(certDER)); + + Input cert; + ASSERT_EQ(Success, cert.Init(certDER.data(), certDER.length())); + ASSERT_EQ(Result::ERROR_EXPIRED_CERTIFICATE, + BuildCertChain(expiredCertTrustDomain, cert, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::id_kp_serverAuth, + CertPolicyId::anyPolicy, + nullptr)); +} + +class DSSTrustDomain final : public EverythingFailsByDefaultTrustDomain +{ +public: + Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, + Input, /*out*/ TrustLevel& trustLevel) override + { + trustLevel = TrustLevel::TrustAnchor; + return Success; + } +}; + +class pkixbuild_DSS : public ::testing::Test { }; + +TEST_F(pkixbuild_DSS, DSSEndEntityKeyNotAccepted) +{ + DSSTrustDomain trustDomain; + + ByteString serialNumber(CreateEncodedSerialNumber(1)); + ASSERT_FALSE(ENCODING_FAILED(serialNumber)); + + ByteString subjectDER(CNToDERName("DSS")); + ASSERT_FALSE(ENCODING_FAILED(subjectDER)); + ScopedTestKeyPair subjectKey(GenerateDSSKeyPair()); + ASSERT_TRUE(subjectKey.get()); + + ByteString issuerDER(CNToDERName("RSA")); + ASSERT_FALSE(ENCODING_FAILED(issuerDER)); + ScopedTestKeyPair issuerKey(CloneReusedKeyPair()); + ASSERT_TRUE(issuerKey.get()); + + ByteString cert(CreateEncodedCertificate(v3, sha256WithRSAEncryption(), + serialNumber, issuerDER, + oneDayBeforeNow, oneDayAfterNow, + subjectDER, *subjectKey, nullptr, + *issuerKey, sha256WithRSAEncryption())); + ASSERT_FALSE(ENCODING_FAILED(cert)); + Input certDER; + ASSERT_EQ(Success, certDER.Init(cert.data(), cert.length())); + + ASSERT_EQ(Result::ERROR_UNSUPPORTED_KEYALG, + BuildCertChain(trustDomain, certDER, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::id_kp_serverAuth, + CertPolicyId::anyPolicy, + nullptr/*stapledOCSPResponse*/)); +} + +class IssuerNameCheckTrustDomain final : public DefaultCryptoTrustDomain +{ +public: + IssuerNameCheckTrustDomain(const ByteString& aIssuer, bool aExpectedKeepGoing) + : issuer(aIssuer) + , expectedKeepGoing(aExpectedKeepGoing) + { + } + + Result GetCertTrust(EndEntityOrCA endEntityOrCA, const CertPolicyId&, Input, + /*out*/ TrustLevel& trustLevel) override + { + trustLevel = endEntityOrCA == EndEntityOrCA::MustBeCA + ? TrustLevel::TrustAnchor + : TrustLevel::InheritsTrust; + return Success; + } + + Result FindIssuer(Input, IssuerChecker& checker, Time) override + { + Input issuerInput; + EXPECT_EQ(Success, issuerInput.Init(issuer.data(), issuer.length())); + bool keepGoing; + EXPECT_EQ(Success, + checker.Check(issuerInput, nullptr /*additionalNameConstraints*/, + keepGoing)); + EXPECT_EQ(expectedKeepGoing, keepGoing); + return Success; + } + + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, + /*optional*/ const Input*, /*optional*/ const Input*) + override + { + return Success; + } + + Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override + { + return Success; + } + +private: + const ByteString issuer; + const bool expectedKeepGoing; +}; + +struct IssuerNameCheckParams +{ + const char* subjectIssuerCN; // null means "empty name" + const char* issuerSubjectCN; // null means "empty name" + bool matches; + Result expectedError; +}; + +static const IssuerNameCheckParams ISSUER_NAME_CHECK_PARAMS[] = +{ + { "foo", "foo", true, Success }, + { "foo", "bar", false, Result::ERROR_UNKNOWN_ISSUER }, + { "f", "foo", false, Result::ERROR_UNKNOWN_ISSUER }, // prefix + { "foo", "f", false, Result::ERROR_UNKNOWN_ISSUER }, // prefix + { "foo", "Foo", false, Result::ERROR_UNKNOWN_ISSUER }, // case sensitive + { "", "", true, Success }, + { nullptr, nullptr, false, Result::ERROR_EMPTY_ISSUER_NAME }, // empty issuer + + // check that certificate-related errors are deferred and superseded by + // ERROR_UNKNOWN_ISSUER when a chain can't be built due to name mismatches + { "foo", nullptr, false, Result::ERROR_UNKNOWN_ISSUER }, + { nullptr, "foo", false, Result::ERROR_UNKNOWN_ISSUER } +}; + +class pkixbuild_IssuerNameCheck + : public ::testing::Test + , public ::testing::WithParamInterface<IssuerNameCheckParams> +{ +}; + +TEST_P(pkixbuild_IssuerNameCheck, MatchingName) +{ + const IssuerNameCheckParams& params(GetParam()); + + ByteString issuerCertDER(CreateCert(params.issuerSubjectCN, + params.issuerSubjectCN, + EndEntityOrCA::MustBeCA, nullptr)); + ASSERT_FALSE(ENCODING_FAILED(issuerCertDER)); + + ByteString subjectCertDER(CreateCert(params.subjectIssuerCN, "end-entity", + EndEntityOrCA::MustBeEndEntity, + nullptr)); + ASSERT_FALSE(ENCODING_FAILED(subjectCertDER)); + + Input subjectCertDERInput; + ASSERT_EQ(Success, subjectCertDERInput.Init(subjectCertDER.data(), + subjectCertDER.length())); + + IssuerNameCheckTrustDomain trustDomain(issuerCertDER, !params.matches); + ASSERT_EQ(params.expectedError, + BuildCertChain(trustDomain, subjectCertDERInput, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::id_kp_serverAuth, + CertPolicyId::anyPolicy, + nullptr/*stapledOCSPResponse*/)); +} + +INSTANTIATE_TEST_CASE_P(pkixbuild_IssuerNameCheck, pkixbuild_IssuerNameCheck, + testing::ValuesIn(ISSUER_NAME_CHECK_PARAMS)); + + +// Records the embedded SCT list extension for later examination. +class EmbeddedSCTListTestTrustDomain final : public SingleRootTrustDomain +{ +public: + explicit EmbeddedSCTListTestTrustDomain(ByteString aRootDER) + : SingleRootTrustDomain(aRootDER) + { + } + + virtual void NoteAuxiliaryExtension(AuxiliaryExtension extension, + Input extensionData) override + { + if (extension == AuxiliaryExtension::EmbeddedSCTList) { + signedCertificateTimestamps = InputToByteString(extensionData); + } else { + ADD_FAILURE(); + } + } + + ByteString signedCertificateTimestamps; +}; + +TEST_F(pkixbuild, CertificateTransparencyExtension) +{ + // python security/pkix/tools/DottedOIDToCode.py --tlv + // id-embeddedSctList 1.3.6.1.4.1.11129.2.4.2 + static const uint8_t tlv_id_embeddedSctList[] = { + 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x02 + }; + static const uint8_t dummySctList[] = { + 0x01, 0x02, 0x03, 0x04, 0x05 + }; + + ByteString ctExtension = TLV(der::SEQUENCE, + BytesToByteString(tlv_id_embeddedSctList) + + Boolean(false) + + TLV(der::OCTET_STRING, + // SignedCertificateTimestampList structure is encoded as an OCTET STRING + // within the X.509v3 extension (see RFC 6962 section 3.3). + // pkix decodes it internally and returns the actual structure. + TLV(der::OCTET_STRING, BytesToByteString(dummySctList)))); + + const char* rootCN = "Root CA"; + ByteString rootDER(CreateCert(rootCN, rootCN, EndEntityOrCA::MustBeCA)); + ASSERT_FALSE(ENCODING_FAILED(rootDER)); + + ByteString certDER(CreateCert(rootCN, "Cert with SCT list", + EndEntityOrCA::MustBeEndEntity, + nullptr, /*subjectDERToCertDER*/ + &ctExtension)); + ASSERT_FALSE(ENCODING_FAILED(certDER)); + + Input certInput; + ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length())); + + EmbeddedSCTListTestTrustDomain extTrustDomain(rootDER); + ASSERT_EQ(Success, + BuildCertChain(extTrustDomain, certInput, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::anyExtendedKeyUsage, + CertPolicyId::anyPolicy, + nullptr /*stapledOCSPResponse*/)); + ASSERT_EQ(BytesToByteString(dummySctList), + extTrustDomain.signedCertificateTimestamps); +} + +// This TrustDomain implements a hierarchy like so: +// +// A B +// | | +// C D +// \ / +// E +// +// where A is a trust anchor, B is not a trust anchor and has no known issuer, C +// and D are intermediates with the same subject and subject public key, and E +// is an end-entity (in practice, the end-entity will be generated by the test +// functions using this trust domain). +class MultiplePathTrustDomain: public DefaultCryptoTrustDomain +{ +public: + void SetUpCerts() + { + ASSERT_FALSE(ENCODING_FAILED(CreateCert("UntrustedRoot", "UntrustedRoot", + EndEntityOrCA::MustBeCA, + &subjectDERToCertDER))); + // The subject DER -> cert DER mapping would be overwritten for subject + // "Intermediate" when we create the second "Intermediate" certificate, so + // we keep a copy of this "Intermediate". + intermediateSignedByUntrustedRootCertDER = + CreateCert("UntrustedRoot", "Intermediate", EndEntityOrCA::MustBeCA); + ASSERT_FALSE(ENCODING_FAILED(intermediateSignedByUntrustedRootCertDER)); + rootCACertDER = CreateCert("TrustedRoot", "TrustedRoot", + EndEntityOrCA::MustBeCA, &subjectDERToCertDER); + ASSERT_FALSE(ENCODING_FAILED(rootCACertDER)); + ASSERT_FALSE(ENCODING_FAILED(CreateCert("TrustedRoot", "Intermediate", + EndEntityOrCA::MustBeCA, + &subjectDERToCertDER))); + } + +private: + Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert, + /*out*/ TrustLevel& trustLevel) override + { + trustLevel = InputEqualsByteString(candidateCert, rootCACertDER) + ? TrustLevel::TrustAnchor + : TrustLevel::InheritsTrust; + return Success; + } + + Result CheckCert(ByteString& certDER, IssuerChecker& checker, bool& keepGoing) + { + Input derCert; + Result rv = derCert.Init(certDER.data(), certDER.length()); + if (rv != Success) { + return rv; + } + return checker.Check(derCert, nullptr/*additionalNameConstraints*/, + keepGoing); + } + + Result FindIssuer(Input encodedIssuerName, IssuerChecker& checker, Time) + override + { + ByteString subjectDER(InputToByteString(encodedIssuerName)); + ByteString certDER(subjectDERToCertDER[subjectDER]); + assert(!ENCODING_FAILED(certDER)); + bool keepGoing; + Result rv = CheckCert(certDER, checker, keepGoing); + if (rv != Success) { + return rv; + } + // Also try the other intermediate. + if (keepGoing) { + rv = CheckCert(intermediateSignedByUntrustedRootCertDER, checker, + keepGoing); + if (rv != Success) { + return rv; + } + } + return Success; + } + + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, + /*optional*/ const Input*, + /*optional*/ const Input*) override + { + return Success; + } + + Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override + { + return Success; + } + + std::map<ByteString, ByteString> subjectDERToCertDER; + ByteString rootCACertDER; + ByteString intermediateSignedByUntrustedRootCertDER; +}; + +TEST_F(pkixbuild, BadEmbeddedSCTWithMultiplePaths) +{ + MultiplePathTrustDomain localTrustDomain; + localTrustDomain.SetUpCerts(); + + // python security/pkix/tools/DottedOIDToCode.py --tlv + // id-embeddedSctList 1.3.6.1.4.1.11129.2.4.2 + static const uint8_t tlv_id_embeddedSctList[] = { + 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x02 + }; + static const uint8_t dummySctList[] = { + 0x01, 0x02, 0x03, 0x04, 0x05 + }; + ByteString ctExtension = TLV(der::SEQUENCE, + BytesToByteString(tlv_id_embeddedSctList) + + Boolean(false) + + // The contents of the OCTET STRING are supposed to consist of an OCTET + // STRING of useful data. We're testing what happens if it isn't, so shove + // some bogus (non-OCTET STRING) data in there. + TLV(der::OCTET_STRING, BytesToByteString(dummySctList))); + ByteString certDER(CreateCert("Intermediate", "Cert with bogus SCT list", + EndEntityOrCA::MustBeEndEntity, + nullptr, /*subjectDERToCertDER*/ + &ctExtension)); + ASSERT_FALSE(ENCODING_FAILED(certDER)); + Input certDERInput; + ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length())); + ASSERT_EQ(Result::ERROR_BAD_DER, + BuildCertChain(localTrustDomain, certDERInput, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::id_kp_serverAuth, + CertPolicyId::anyPolicy, + nullptr/*stapledOCSPResponse*/)); +} + +// Same as a MultiplePathTrustDomain, but the end-entity is revoked. +class RevokedEndEntityTrustDomain final : public MultiplePathTrustDomain +{ +public: + Result CheckRevocation(EndEntityOrCA endEntityOrCA, const CertID&, Time, + Duration, /*optional*/ const Input*, + /*optional*/ const Input*) override + { + if (endEntityOrCA == EndEntityOrCA::MustBeEndEntity) { + return Result::ERROR_REVOKED_CERTIFICATE; + } + return Success; + } +}; + +TEST_F(pkixbuild, RevokedEndEntityWithMultiplePaths) +{ + RevokedEndEntityTrustDomain localTrustDomain; + localTrustDomain.SetUpCerts(); + ByteString certDER(CreateCert("Intermediate", "RevokedEndEntity", + EndEntityOrCA::MustBeEndEntity)); + ASSERT_FALSE(ENCODING_FAILED(certDER)); + Input certDERInput; + ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length())); + ASSERT_EQ(Result::ERROR_REVOKED_CERTIFICATE, + BuildCertChain(localTrustDomain, certDERInput, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::id_kp_serverAuth, + CertPolicyId::anyPolicy, + nullptr/*stapledOCSPResponse*/)); +} + +// This represents a collection of different certificates that all have the same +// subject and issuer distinguished name. +class SelfIssuedCertificatesTrustDomain final : public DefaultCryptoTrustDomain +{ +public: + void SetUpCerts(size_t totalCerts) + { + ASSERT_TRUE(totalCerts > 0); + // First we generate a trust anchor. + ScopedTestKeyPair rootKeyPair(GenerateKeyPair()); + rootCACertDER = CreateCert("DN", "DN", EndEntityOrCA::MustBeCA, nullptr, + nullptr, rootKeyPair.get(), rootKeyPair.get()); + ASSERT_FALSE(ENCODING_FAILED(rootCACertDER)); + certs.push_back(rootCACertDER); + ScopedTestKeyPair issuerKeyPair(rootKeyPair.release()); + size_t subCAsGenerated; + // Then we generate 6 sub-CAs (given that we were requested to generate at + // least that many). + for (subCAsGenerated = 0; + subCAsGenerated < totalCerts - 1 && subCAsGenerated < 6; + subCAsGenerated++) { + // Each certificate has to have a unique SPKI (mozilla::pkix does loop + // detection and stops searching if it encounters two certificates in a + // path with the same subject and SPKI). + ScopedTestKeyPair keyPair(GenerateKeyPair()); + ByteString cert(CreateCert("DN", "DN", EndEntityOrCA::MustBeCA, nullptr, + nullptr, issuerKeyPair.get(), keyPair.get())); + ASSERT_FALSE(ENCODING_FAILED(cert)); + certs.push_back(cert); + issuerKeyPair.reset(keyPair.release()); + } + // We set firstIssuerKey here because we can't end up with a path that has + // more than 7 CAs in it (because mozilla::pkix limits the path length). + firstIssuerKey.reset(issuerKeyPair.release()); + // For any more sub CAs we generate, it doesn't matter what their keys are + // as long as they're different. + for (; subCAsGenerated < totalCerts - 1; subCAsGenerated++) { + ScopedTestKeyPair keyPair(GenerateKeyPair()); + ByteString cert(CreateCert("DN", "DN", EndEntityOrCA::MustBeCA, nullptr, + nullptr, keyPair.get(), keyPair.get())); + ASSERT_FALSE(ENCODING_FAILED(cert)); + certs.insert(certs.begin(), cert); + } + } + + const TestKeyPair* GetFirstIssuerKey() + { + return firstIssuerKey.get(); + } + +private: + Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert, + /*out*/ TrustLevel& trustLevel) override + { + trustLevel = InputEqualsByteString(candidateCert, rootCACertDER) + ? TrustLevel::TrustAnchor + : TrustLevel::InheritsTrust; + return Success; + } + + Result FindIssuer(Input, IssuerChecker& checker, Time) override + { + bool keepGoing; + for (auto& cert: certs) { + Input certInput; + Result rv = certInput.Init(cert.data(), cert.length()); + if (rv != Success) { + return rv; + } + rv = checker.Check(certInput, nullptr, keepGoing); + if (rv != Success || !keepGoing) { + return rv; + } + } + return Success; + } + + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, + /*optional*/ const Input*, /*optional*/ const Input*) + override + { + return Success; + } + + Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override + { + return Success; + } + + std::vector<ByteString> certs; + ByteString rootCACertDER; + ScopedTestKeyPair firstIssuerKey; +}; + +TEST_F(pkixbuild, AvoidUnboundedPathSearchingFailure) +{ + SelfIssuedCertificatesTrustDomain localTrustDomain; + // This creates a few hundred million potential paths of length 8 (end entity + // + 6 sub-CAs + root). It would be prohibitively expensive to enumerate all + // of these, so we give mozilla::pkix a budget that is spent when searching + // paths. If the budget is exhausted, it simply returns an unknown issuer + // error. In the future it might be nice to return a specific error that would + // give the front-end a hint that maybe it shouldn't have so many certificates + // that all have the same subject and issuer DN but different SPKIs. + localTrustDomain.SetUpCerts(18); + ByteString certDER(CreateCert("DN", "DN", EndEntityOrCA::MustBeEndEntity, + nullptr, nullptr, + localTrustDomain.GetFirstIssuerKey())); + ASSERT_FALSE(ENCODING_FAILED(certDER)); + Input certDERInput; + ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length())); + ASSERT_EQ(Result::ERROR_UNKNOWN_ISSUER, + BuildCertChain(localTrustDomain, certDERInput, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::id_kp_serverAuth, + CertPolicyId::anyPolicy, + nullptr/*stapledOCSPResponse*/)); +} + +TEST_F(pkixbuild, AvoidUnboundedPathSearchingSuccess) +{ + SelfIssuedCertificatesTrustDomain localTrustDomain; + // This creates a few hundred thousand possible potential paths of length 8 + // (end entity + 6 sub-CAs + root). This will nearly exhaust mozilla::pkix's + // search budget, so this should succeed. + localTrustDomain.SetUpCerts(10); + ByteString certDER(CreateCert("DN", "DN", EndEntityOrCA::MustBeEndEntity, + nullptr, nullptr, + localTrustDomain.GetFirstIssuerKey())); + ASSERT_FALSE(ENCODING_FAILED(certDER)); + Input certDERInput; + ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length())); + ASSERT_EQ(Success, + BuildCertChain(localTrustDomain, certDERInput, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::id_kp_serverAuth, + CertPolicyId::anyPolicy, + nullptr/*stapledOCSPResponse*/)); +} diff --git a/security/nss/gtests/mozpkix_gtest/pkixcert_extension_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixcert_extension_tests.cpp new file mode 100644 index 000000000..762fac146 --- /dev/null +++ b/security/nss/gtests/mozpkix_gtest/pkixcert_extension_tests.cpp @@ -0,0 +1,276 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2013 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixgtest.h" + +#include "mozpkix/pkixder.h" +#include "mozpkix/test/pkixtestutil.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +// Creates a self-signed certificate with the given extension. +static ByteString +CreateCertWithExtensions(const char* subjectCN, + const ByteString* extensions) +{ + static long serialNumberValue = 0; + ++serialNumberValue; + ByteString serialNumber(CreateEncodedSerialNumber(serialNumberValue)); + EXPECT_FALSE(ENCODING_FAILED(serialNumber)); + ByteString issuerDER(CNToDERName(subjectCN)); + EXPECT_FALSE(ENCODING_FAILED(issuerDER)); + ByteString subjectDER(CNToDERName(subjectCN)); + EXPECT_FALSE(ENCODING_FAILED(subjectDER)); + ScopedTestKeyPair subjectKey(CloneReusedKeyPair()); + return CreateEncodedCertificate(v3, sha256WithRSAEncryption(), + serialNumber, issuerDER, + oneDayBeforeNow, oneDayAfterNow, + subjectDER, *subjectKey, extensions, + *subjectKey, + sha256WithRSAEncryption()); +} + +// Creates a self-signed certificate with the given extension. +static ByteString +CreateCertWithOneExtension(const char* subjectStr, const ByteString& extension) +{ + const ByteString extensions[] = { extension, ByteString() }; + return CreateCertWithExtensions(subjectStr, extensions); +} + +class TrustEverythingTrustDomain final : public DefaultCryptoTrustDomain +{ +private: + Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input, + /*out*/ TrustLevel& trustLevel) override + { + trustLevel = TrustLevel::TrustAnchor; + return Success; + } + + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, + /*optional*/ const Input*, /*optional*/ const Input*) + override + { + return Success; + } + + Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override + { + return Success; + } +}; + +// python DottedOIDToCode.py --tlv unknownExtensionOID 1.3.6.1.4.1.13769.666.666.666.1.500.9.3 +static const uint8_t tlv_unknownExtensionOID[] = { + 0x06, 0x12, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xeb, 0x49, 0x85, 0x1a, 0x85, 0x1a, + 0x85, 0x1a, 0x01, 0x83, 0x74, 0x09, 0x03 +}; + +// python DottedOIDToCode.py --tlv id-pe-authorityInformationAccess 1.3.6.1.5.5.7.1.1 +static const uint8_t tlv_id_pe_authorityInformationAccess[] = { + 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01 +}; + +// python DottedOIDToCode.py --tlv wrongExtensionOID 1.3.6.6.1.5.5.7.1.1 +// (there is an extra "6" that shouldn't be in this OID) +static const uint8_t tlv_wrongExtensionOID[] = { + 0x06, 0x09, 0x2b, 0x06, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01 +}; + +// python DottedOIDToCode.py --tlv id-ce-unknown 2.5.29.55 +// (this is a made-up OID for testing "id-ce"-prefixed OIDs that mozilla::pkix +// doesn't handle) +static const uint8_t tlv_id_ce_unknown[] = { + 0x06, 0x03, 0x55, 0x1d, 0x37 +}; + +// python DottedOIDToCode.py --tlv id-ce-inhibitAnyPolicy 2.5.29.54 +static const uint8_t tlv_id_ce_inhibitAnyPolicy[] = { + 0x06, 0x03, 0x55, 0x1d, 0x36 +}; + +// python DottedOIDToCode.py --tlv id-pkix-ocsp-nocheck 1.3.6.1.5.5.7.48.1.5 +static const uint8_t tlv_id_pkix_ocsp_nocheck[] = { + 0x06, 0x09, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x05 +}; + +struct ExtensionTestcase +{ + ByteString extension; + Result expectedResult; +}; + +::std::ostream& operator<<(::std::ostream& os, const ExtensionTestcase&) +{ + return os << "TODO (bug 1318770)"; +} + +static const ExtensionTestcase EXTENSION_TESTCASES[] = +{ + // Tests that a non-critical extension not in the id-ce or id-pe arcs (which + // is thus unknown to us) verifies successfully even if empty (extensions we + // know about aren't normally allowed to be empty). + { TLV(der::SEQUENCE, + BytesToByteString(tlv_unknownExtensionOID) + + TLV(der::OCTET_STRING, ByteString())), + Success + }, + + // Tests that a critical extension not in the id-ce or id-pe arcs (which is + // thus unknown to us) is detected and that verification fails with the + // appropriate error. + { TLV(der::SEQUENCE, + BytesToByteString(tlv_unknownExtensionOID) + + Boolean(true) + + TLV(der::OCTET_STRING, ByteString())), + Result::ERROR_UNKNOWN_CRITICAL_EXTENSION + }, + + // Tests that a id-pe-authorityInformationAccess critical extension + // is detected and that verification succeeds. + // XXX: According to RFC 5280 an AIA that consists of an empty sequence is + // not legal, but we accept it and that is not what we're testing here. + { TLV(der::SEQUENCE, + BytesToByteString(tlv_id_pe_authorityInformationAccess) + + Boolean(true) + + TLV(der::OCTET_STRING, TLV(der::SEQUENCE, ByteString()))), + Success + }, + + // Tests that an incorrect OID for id-pe-authorityInformationAccess + // (when marked critical) is detected and that verification fails. + // (Until bug 1020993 was fixed, this wrong value was used for + // id-pe-authorityInformationAccess.) + { TLV(der::SEQUENCE, + BytesToByteString(tlv_wrongExtensionOID) + + Boolean(true) + + TLV(der::OCTET_STRING, ByteString())), + Result::ERROR_UNKNOWN_CRITICAL_EXTENSION + }, + + // We know about some id-ce extensions (OID arc 2.5.29), but not all of them. + // Tests that an unknown id-ce extension is detected and that verification + // fails. + { TLV(der::SEQUENCE, + BytesToByteString(tlv_id_ce_unknown) + + Boolean(true) + + TLV(der::OCTET_STRING, ByteString())), + Result::ERROR_UNKNOWN_CRITICAL_EXTENSION + }, + + // Tests that a certificate with a known critical id-ce extension (in this + // case, OID 2.5.29.54, which is id-ce-inhibitAnyPolicy), verifies + // successfully. + { TLV(der::SEQUENCE, + BytesToByteString(tlv_id_ce_inhibitAnyPolicy) + + Boolean(true) + + TLV(der::OCTET_STRING, Integer(0))), + Success + }, + + // Tests that a certificate with the id-pkix-ocsp-nocheck extension (marked + // critical) verifies successfully. + // RFC 6960: + // ext-ocsp-nocheck EXTENSION ::= { SYNTAX NULL IDENTIFIED + // BY id-pkix-ocsp-nocheck } + { TLV(der::SEQUENCE, + BytesToByteString(tlv_id_pkix_ocsp_nocheck) + + Boolean(true) + + TLV(der::OCTET_STRING, TLV(der::NULLTag, ByteString()))), + Success + }, + + // Tests that a certificate with another representation of the + // id-pkix-ocsp-nocheck extension (marked critical) verifies successfully. + // According to http://comments.gmane.org/gmane.ietf.x509/30947, + // some code creates certificates where value of the extension is + // an empty OCTET STRING. + { TLV(der::SEQUENCE, + BytesToByteString(tlv_id_pkix_ocsp_nocheck) + + Boolean(true) + + TLV(der::OCTET_STRING, ByteString())), + Success + }, +}; + +class pkixcert_extension + : public ::testing::Test + , public ::testing::WithParamInterface<ExtensionTestcase> +{ +protected: + static TrustEverythingTrustDomain trustDomain; +}; + +/*static*/ TrustEverythingTrustDomain pkixcert_extension::trustDomain; + +TEST_P(pkixcert_extension, ExtensionHandledProperly) +{ + const ExtensionTestcase& testcase(GetParam()); + const char* cn = "Cert Extension Test"; + ByteString cert(CreateCertWithOneExtension(cn, testcase.extension)); + ASSERT_FALSE(ENCODING_FAILED(cert)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); + ASSERT_EQ(testcase.expectedResult, + BuildCertChain(trustDomain, certInput, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::anyExtendedKeyUsage, + CertPolicyId::anyPolicy, + nullptr/*stapledOCSPResponse*/)); +} + +INSTANTIATE_TEST_CASE_P(pkixcert_extension, + pkixcert_extension, + testing::ValuesIn(EXTENSION_TESTCASES)); + +// Two subjectAltNames must result in an error. +TEST_F(pkixcert_extension, DuplicateSubjectAltName) +{ + // python DottedOIDToCode.py --tlv id-ce-subjectAltName 2.5.29.17 + static const uint8_t tlv_id_ce_subjectAltName[] = { + 0x06, 0x03, 0x55, 0x1d, 0x11 + }; + + ByteString subjectAltName( + TLV(der::SEQUENCE, + BytesToByteString(tlv_id_ce_subjectAltName) + + TLV(der::OCTET_STRING, TLV(der::SEQUENCE, DNSName("example.com"))))); + static const ByteString extensions[] = { subjectAltName, subjectAltName, + ByteString() }; + static const char* certCN = "Cert With Duplicate subjectAltName"; + ByteString cert(CreateCertWithExtensions(certCN, extensions)); + ASSERT_FALSE(ENCODING_FAILED(cert)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); + ASSERT_EQ(Result::ERROR_EXTENSION_VALUE_INVALID, + BuildCertChain(trustDomain, certInput, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::anyExtendedKeyUsage, + CertPolicyId::anyPolicy, + nullptr/*stapledOCSPResponse*/)); +} diff --git a/security/nss/gtests/mozpkix_gtest/pkixcert_signature_algorithm_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixcert_signature_algorithm_tests.cpp new file mode 100644 index 000000000..00ccffb04 --- /dev/null +++ b/security/nss/gtests/mozpkix_gtest/pkixcert_signature_algorithm_tests.cpp @@ -0,0 +1,259 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +#include "pkixgtest.h" + +#include "mozpkix/pkixder.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +static ByteString +CreateCert(const char* issuerCN, + const char* subjectCN, + EndEntityOrCA endEntityOrCA, + const TestSignatureAlgorithm& signatureAlgorithm, + /*out*/ ByteString& subjectDER) +{ + static long serialNumberValue = 0; + ++serialNumberValue; + ByteString serialNumber(CreateEncodedSerialNumber(serialNumberValue)); + EXPECT_FALSE(ENCODING_FAILED(serialNumber)); + + ByteString issuerDER(CNToDERName(issuerCN)); + EXPECT_FALSE(ENCODING_FAILED(issuerDER)); + subjectDER = CNToDERName(subjectCN); + EXPECT_FALSE(ENCODING_FAILED(subjectDER)); + + ByteString extensions[2]; + if (endEntityOrCA == EndEntityOrCA::MustBeCA) { + extensions[0] = + CreateEncodedBasicConstraints(true, nullptr, Critical::Yes); + EXPECT_FALSE(ENCODING_FAILED(extensions[0])); + } + + ScopedTestKeyPair reusedKey(CloneReusedKeyPair()); + ByteString certDER(CreateEncodedCertificate(v3, signatureAlgorithm, + serialNumber, issuerDER, + oneDayBeforeNow, oneDayAfterNow, + subjectDER, *reusedKey, + extensions, *reusedKey, + signatureAlgorithm)); + EXPECT_FALSE(ENCODING_FAILED(certDER)); + return certDER; +} + +class AlgorithmTestsTrustDomain final : public DefaultCryptoTrustDomain +{ +public: + AlgorithmTestsTrustDomain(const ByteString& aRootDER, + const ByteString& aRootSubjectDER, + /*optional*/ const ByteString& aIntDER, + /*optional*/ const ByteString& aIntSubjectDER) + : rootDER(aRootDER) + , rootSubjectDER(aRootSubjectDER) + , intDER(aIntDER) + , intSubjectDER(aIntSubjectDER) + { + } + +private: + Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert, + /*out*/ TrustLevel& trustLevel) override + { + if (InputEqualsByteString(candidateCert, rootDER)) { + trustLevel = TrustLevel::TrustAnchor; + } else { + trustLevel = TrustLevel::InheritsTrust; + } + return Success; + } + + Result FindIssuer(Input encodedIssuerName, IssuerChecker& checker, Time) + override + { + ByteString* issuerDER = nullptr; + if (InputEqualsByteString(encodedIssuerName, rootSubjectDER)) { + issuerDER = &rootDER; + } else if (InputEqualsByteString(encodedIssuerName, intSubjectDER)) { + issuerDER = &intDER; + } else { + // FindIssuer just returns success if it can't find a potential issuer. + return Success; + } + Input issuerCert; + Result rv = issuerCert.Init(issuerDER->data(), issuerDER->length()); + if (rv != Success) { + return rv; + } + bool keepGoing; + return checker.Check(issuerCert, nullptr, keepGoing); + } + + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, + const Input*, const Input*) override + { + return Success; + } + + Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override + { + return Success; + } + + ByteString rootDER; + ByteString rootSubjectDER; + ByteString intDER; + ByteString intSubjectDER; +}; + +static const TestSignatureAlgorithm NO_INTERMEDIATE +{ + TestPublicKeyAlgorithm(ByteString()), + TestDigestAlgorithmID::MD2, + ByteString(), + false +}; + +struct ChainValidity final +{ + ChainValidity(const TestSignatureAlgorithm& aEndEntitySignatureAlgorithm, + const TestSignatureAlgorithm& aOptionalIntSignatureAlgorithm, + const TestSignatureAlgorithm& aRootSignatureAlgorithm, + bool aIsValid) + : endEntitySignatureAlgorithm(aEndEntitySignatureAlgorithm) + , optionalIntermediateSignatureAlgorithm(aOptionalIntSignatureAlgorithm) + , rootSignatureAlgorithm(aRootSignatureAlgorithm) + , isValid(aIsValid) + { } + + // In general, a certificate is generated for each of these. However, if + // optionalIntermediateSignatureAlgorithm is NO_INTERMEDIATE, then only 2 + // certificates are generated. + // The certificate generated for the given rootSignatureAlgorithm is the + // trust anchor. + TestSignatureAlgorithm endEntitySignatureAlgorithm; + TestSignatureAlgorithm optionalIntermediateSignatureAlgorithm; + TestSignatureAlgorithm rootSignatureAlgorithm; + bool isValid; +}; + +static const ChainValidity CHAIN_VALIDITY[] = +{ + // The trust anchor may have a signature with an unsupported signature + // algorithm. + ChainValidity(sha256WithRSAEncryption(), + NO_INTERMEDIATE, + md5WithRSAEncryption(), + true), + ChainValidity(sha256WithRSAEncryption(), + NO_INTERMEDIATE, + md2WithRSAEncryption(), + true), + + // Certificates that are not trust anchors must not have a signature with an + // unsupported signature algorithm. + ChainValidity(md5WithRSAEncryption(), + NO_INTERMEDIATE, + sha256WithRSAEncryption(), + false), + ChainValidity(md2WithRSAEncryption(), + NO_INTERMEDIATE, + sha256WithRSAEncryption(), + false), + ChainValidity(md2WithRSAEncryption(), + NO_INTERMEDIATE, + md5WithRSAEncryption(), + false), + ChainValidity(sha256WithRSAEncryption(), + md5WithRSAEncryption(), + sha256WithRSAEncryption(), + false), + ChainValidity(sha256WithRSAEncryption(), + md2WithRSAEncryption(), + sha256WithRSAEncryption(), + false), + ChainValidity(sha256WithRSAEncryption(), + md2WithRSAEncryption(), + md5WithRSAEncryption(), + false), +}; + +class pkixcert_IsValidChainForAlgorithm + : public ::testing::Test + , public ::testing::WithParamInterface<ChainValidity> +{ +}; + +::std::ostream& operator<<(::std::ostream& os, + const pkixcert_IsValidChainForAlgorithm&) +{ + return os << "TODO (bug 1318770)"; +} + +::std::ostream& operator<<(::std::ostream& os, const ChainValidity&) +{ + return os << "TODO (bug 1318770)"; +} + +TEST_P(pkixcert_IsValidChainForAlgorithm, IsValidChainForAlgorithm) +{ + const ChainValidity& chainValidity(GetParam()); + const char* rootCN = "CN=Root"; + ByteString rootSubjectDER; + ByteString rootEncoded( + CreateCert(rootCN, rootCN, EndEntityOrCA::MustBeCA, + chainValidity.rootSignatureAlgorithm, rootSubjectDER)); + EXPECT_FALSE(ENCODING_FAILED(rootEncoded)); + EXPECT_FALSE(ENCODING_FAILED(rootSubjectDER)); + + const char* issuerCN = rootCN; + + const char* intermediateCN = "CN=Intermediate"; + ByteString intermediateSubjectDER; + ByteString intermediateEncoded; + + // If the the algorithmIdentifier is empty, then it's NO_INTERMEDIATE. + if (!chainValidity.optionalIntermediateSignatureAlgorithm + .algorithmIdentifier.empty()) { + intermediateEncoded = + CreateCert(rootCN, intermediateCN, EndEntityOrCA::MustBeCA, + chainValidity.optionalIntermediateSignatureAlgorithm, + intermediateSubjectDER); + EXPECT_FALSE(ENCODING_FAILED(intermediateEncoded)); + EXPECT_FALSE(ENCODING_FAILED(intermediateSubjectDER)); + issuerCN = intermediateCN; + } + + AlgorithmTestsTrustDomain trustDomain(rootEncoded, rootSubjectDER, + intermediateEncoded, + intermediateSubjectDER); + + const char* endEntityCN = "CN=End Entity"; + ByteString endEntitySubjectDER; + ByteString endEntityEncoded( + CreateCert(issuerCN, endEntityCN, EndEntityOrCA::MustBeEndEntity, + chainValidity.endEntitySignatureAlgorithm, + endEntitySubjectDER)); + EXPECT_FALSE(ENCODING_FAILED(endEntityEncoded)); + EXPECT_FALSE(ENCODING_FAILED(endEntitySubjectDER)); + + Input endEntity; + ASSERT_EQ(Success, endEntity.Init(endEntityEncoded.data(), + endEntityEncoded.length())); + Result expectedResult = chainValidity.isValid + ? Success + : Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED; + ASSERT_EQ(expectedResult, + BuildCertChain(trustDomain, endEntity, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::id_kp_serverAuth, + CertPolicyId::anyPolicy, nullptr)); +} + +INSTANTIATE_TEST_CASE_P(pkixcert_IsValidChainForAlgorithm, + pkixcert_IsValidChainForAlgorithm, + testing::ValuesIn(CHAIN_VALIDITY)); diff --git a/security/nss/gtests/mozpkix_gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp new file mode 100644 index 000000000..0aef3d5c1 --- /dev/null +++ b/security/nss/gtests/mozpkix_gtest/pkixcheck_CheckExtendedKeyUsage_tests.cpp @@ -0,0 +1,722 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2016 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixgtest.h" + +#include "mozpkix/pkixder.h" +#include "mozpkix/pkixutil.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +namespace mozilla { namespace pkix { + +extern Result CheckExtendedKeyUsage(EndEntityOrCA endEntityOrCA, + const Input* encodedExtendedKeyUsage, + KeyPurposeId requiredEKU, + TrustDomain& trustDomain, Time notBefore); + +} } // namespace mozilla::pkix + +class pkixcheck_CheckExtendedKeyUsage : public ::testing::Test +{ +protected: + DefaultCryptoTrustDomain mTrustDomain; +}; + +#define ASSERT_BAD(x) ASSERT_EQ(Result::ERROR_INADEQUATE_CERT_TYPE, x) + +// tlv_id_kp_OCSPSigning and tlv_id_kp_serverAuth are defined in pkixtestutil.h + +// python DottedOIDToCode.py --tlv id-kp-clientAuth 1.3.6.1.5.5.7.3.2 +static const uint8_t tlv_id_kp_clientAuth[] = { + 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02 +}; + +// python DottedOIDToCode.py --tlv id-kp-codeSigning 1.3.6.1.5.5.7.3.3 +static const uint8_t tlv_id_kp_codeSigning[] = { + 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x03 +}; + +// python DottedOIDToCode.py --tlv id_kp_emailProtection 1.3.6.1.5.5.7.3.4 +static const uint8_t tlv_id_kp_emailProtection[] = { + 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x04 +}; + +// python DottedOIDToCode.py --tlv id-Netscape-stepUp 2.16.840.1.113730.4.1 +static const uint8_t tlv_id_Netscape_stepUp[] = { + 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x04, 0x01 +}; + +// python DottedOIDToCode.py --tlv unknownOID 1.3.6.1.4.1.13769.666.666.666.1.500.9.3 +static const uint8_t tlv_unknownOID[] = { + 0x06, 0x12, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xeb, 0x49, 0x85, 0x1a, 0x85, 0x1a, + 0x85, 0x1a, 0x01, 0x83, 0x74, 0x09, 0x03 +}; + +// python DottedOIDToCode.py --tlv anyExtendedKeyUsage 2.5.29.37.0 +static const uint8_t tlv_anyExtendedKeyUsage[] = { + 0x06, 0x04, 0x55, 0x1d, 0x25, 0x00 +}; + +TEST_F(pkixcheck_CheckExtendedKeyUsage, none) +{ + // The input Input is nullptr. This means the cert had no extended key usage + // extension. This is always valid except for when the certificate is an + // end-entity and the required usage is id-kp-OCSPSigning. + + ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, + nullptr, + KeyPurposeId::anyExtendedKeyUsage, + mTrustDomain, Now())); + ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr, + KeyPurposeId::anyExtendedKeyUsage, + mTrustDomain, Now())); + ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, + nullptr, + KeyPurposeId::id_kp_serverAuth, + mTrustDomain, Now())); + ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr, + KeyPurposeId::id_kp_serverAuth, + mTrustDomain, Now())); + ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, + nullptr, + KeyPurposeId::id_kp_clientAuth, + mTrustDomain, Now())); + ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr, + KeyPurposeId::id_kp_clientAuth, + mTrustDomain, Now())); + ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, + nullptr, + KeyPurposeId::id_kp_codeSigning, + mTrustDomain, Now())); + ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr, + KeyPurposeId::id_kp_codeSigning, + mTrustDomain, Now())); + ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, + nullptr, + KeyPurposeId::id_kp_emailProtection, + mTrustDomain, Now())); + ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr, + KeyPurposeId::id_kp_emailProtection, + mTrustDomain, Now())); + ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr, + KeyPurposeId::id_kp_OCSPSigning, + mTrustDomain, Now())); + ASSERT_EQ(Success, CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, nullptr, + KeyPurposeId::id_kp_OCSPSigning, + mTrustDomain, Now())); +} + +static const Input empty_null; + +TEST_F(pkixcheck_CheckExtendedKeyUsage, empty) +{ + // The input Input is empty. The cert has an empty extended key usage + // extension, which is syntactically invalid. + ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, &empty_null, + KeyPurposeId::id_kp_serverAuth, + mTrustDomain, Now())); + ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, &empty_null, + KeyPurposeId::id_kp_serverAuth, + mTrustDomain, Now())); + + static const uint8_t dummy = 0x00; + Input empty_nonnull; + ASSERT_EQ(Success, empty_nonnull.Init(&dummy, 0)); + ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, &empty_nonnull, + KeyPurposeId::id_kp_serverAuth, + mTrustDomain, Now())); + ASSERT_BAD(CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, &empty_nonnull, + KeyPurposeId::id_kp_serverAuth, + mTrustDomain, Now())); +} + +struct EKUTestcase +{ + ByteString ekuSEQUENCE; + KeyPurposeId keyPurposeId; + Result expectedResultEndEntity; + Result expectedResultCA; +}; + +::std::ostream& operator<<(::std::ostream& os, const EKUTestcase&) +{ + return os << "TODO (bug 1318770)"; +} + +class CheckExtendedKeyUsageTest + : public ::testing::Test + , public ::testing::WithParamInterface<EKUTestcase> +{ +protected: + DefaultCryptoTrustDomain mTrustDomain; +}; + +TEST_P(CheckExtendedKeyUsageTest, EKUTestcase) +{ + const EKUTestcase& param(GetParam()); + Input encodedEKU; + ASSERT_EQ(Success, encodedEKU.Init(param.ekuSEQUENCE.data(), + param.ekuSEQUENCE.length())); + ASSERT_EQ(param.expectedResultEndEntity, + CheckExtendedKeyUsage(EndEntityOrCA::MustBeEndEntity, &encodedEKU, + param.keyPurposeId, + mTrustDomain, Now())); + ASSERT_EQ(param.expectedResultCA, + CheckExtendedKeyUsage(EndEntityOrCA::MustBeCA, &encodedEKU, + param.keyPurposeId, + mTrustDomain, Now())); +} + +#define SINGLE_EKU_SUCCESS(oidBytes, keyPurposeId) \ + { TLV(der::SEQUENCE, BytesToByteString(oidBytes)), keyPurposeId, \ + Success, Success } +#define SINGLE_EKU_SUCCESS_CA(oidBytes, keyPurposeId) \ + { TLV(der::SEQUENCE, BytesToByteString(oidBytes)), keyPurposeId, \ + Result::ERROR_INADEQUATE_CERT_TYPE, Success } +#define SINGLE_EKU_FAILURE(oidBytes, keyPurposeId) \ + { TLV(der::SEQUENCE, BytesToByteString(oidBytes)), keyPurposeId, \ + Result::ERROR_INADEQUATE_CERT_TYPE, Result::ERROR_INADEQUATE_CERT_TYPE } +#define DOUBLE_EKU_SUCCESS(oidBytes1, oidBytes2, keyPurposeId) \ + { TLV(der::SEQUENCE, \ + BytesToByteString(oidBytes1) + BytesToByteString(oidBytes2)), \ + keyPurposeId, \ + Success, Success } +#define DOUBLE_EKU_SUCCESS_CA(oidBytes1, oidBytes2, keyPurposeId) \ + { TLV(der::SEQUENCE, \ + BytesToByteString(oidBytes1) + BytesToByteString(oidBytes2)), \ + keyPurposeId, \ + Result::ERROR_INADEQUATE_CERT_TYPE, Success } +#define DOUBLE_EKU_FAILURE(oidBytes1, oidBytes2, keyPurposeId) \ + { TLV(der::SEQUENCE, \ + BytesToByteString(oidBytes1) + BytesToByteString(oidBytes2)), \ + keyPurposeId, \ + Result::ERROR_INADEQUATE_CERT_TYPE, Result::ERROR_INADEQUATE_CERT_TYPE } + +static const EKUTestcase EKU_TESTCASES[] = +{ + SINGLE_EKU_SUCCESS(tlv_id_kp_serverAuth, KeyPurposeId::anyExtendedKeyUsage), + SINGLE_EKU_SUCCESS(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_serverAuth), + SINGLE_EKU_FAILURE(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_clientAuth), + SINGLE_EKU_FAILURE(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_codeSigning), + SINGLE_EKU_FAILURE(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_emailProtection), + SINGLE_EKU_FAILURE(tlv_id_kp_serverAuth, KeyPurposeId::id_kp_OCSPSigning), + + SINGLE_EKU_SUCCESS(tlv_id_kp_clientAuth, KeyPurposeId::anyExtendedKeyUsage), + SINGLE_EKU_FAILURE(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_serverAuth), + SINGLE_EKU_SUCCESS(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_clientAuth), + SINGLE_EKU_FAILURE(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_codeSigning), + SINGLE_EKU_FAILURE(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_emailProtection), + SINGLE_EKU_FAILURE(tlv_id_kp_clientAuth, KeyPurposeId::id_kp_OCSPSigning), + + SINGLE_EKU_SUCCESS(tlv_id_kp_codeSigning, KeyPurposeId::anyExtendedKeyUsage), + SINGLE_EKU_FAILURE(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_serverAuth), + SINGLE_EKU_FAILURE(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_clientAuth), + SINGLE_EKU_SUCCESS(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_codeSigning), + SINGLE_EKU_FAILURE(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_emailProtection), + SINGLE_EKU_FAILURE(tlv_id_kp_codeSigning, KeyPurposeId::id_kp_OCSPSigning), + + SINGLE_EKU_SUCCESS(tlv_id_kp_emailProtection, KeyPurposeId::anyExtendedKeyUsage), + SINGLE_EKU_FAILURE(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_serverAuth), + SINGLE_EKU_FAILURE(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_clientAuth), + SINGLE_EKU_FAILURE(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_codeSigning), + SINGLE_EKU_SUCCESS(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_emailProtection), + SINGLE_EKU_FAILURE(tlv_id_kp_emailProtection, KeyPurposeId::id_kp_OCSPSigning), + + // For end-entities, if id-kp-OCSPSigning is present, no usage is allowed + // except OCSPSigning. + SINGLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage), + SINGLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth), + SINGLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth), + SINGLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning), + SINGLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection), + SINGLE_EKU_SUCCESS(tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning), + + SINGLE_EKU_SUCCESS(tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage), + // For compatibility, id-Netscape-stepUp is treated as equivalent to + // id-kp-serverAuth for CAs. + SINGLE_EKU_SUCCESS_CA(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth), + SINGLE_EKU_FAILURE(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth), + SINGLE_EKU_FAILURE(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning), + SINGLE_EKU_FAILURE(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection), + SINGLE_EKU_FAILURE(tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning), + + SINGLE_EKU_SUCCESS(tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage), + SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_serverAuth), + SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_clientAuth), + SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_codeSigning), + SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_emailProtection), + SINGLE_EKU_FAILURE(tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning), + + SINGLE_EKU_SUCCESS(tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage), + SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth), + SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth), + SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning), + SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection), + SINGLE_EKU_FAILURE(tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_clientAuth, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_serverAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_codeSigning, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_clientAuth, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_emailProtection, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_SUCCESS(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_codeSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_id_kp_OCSPSigning, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_SUCCESS(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_kp_emailProtection, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_SUCCESS(tlv_id_kp_OCSPSigning, tlv_id_Netscape_stepUp, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_SUCCESS(tlv_id_kp_OCSPSigning, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS_CA(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_SUCCESS(tlv_id_kp_OCSPSigning, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS_CA(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_unknownOID, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_SUCCESS_CA(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_id_Netscape_stepUp, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning), + + DOUBLE_EKU_SUCCESS(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::anyExtendedKeyUsage), + DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_serverAuth), + DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_clientAuth), + DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_codeSigning), + DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_emailProtection), + DOUBLE_EKU_FAILURE(tlv_unknownOID, tlv_anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning), +}; + +INSTANTIATE_TEST_CASE_P(pkixcheck_CheckExtendedKeyUsage, + CheckExtendedKeyUsageTest, + ::testing::ValuesIn(EKU_TESTCASES)); + +struct EKUChainTestcase +{ + ByteString ekuExtensionEE; + ByteString ekuExtensionCA; + KeyPurposeId keyPurposeId; + Result expectedResult; +}; + +::std::ostream& operator<<(::std::ostream& os, const EKUChainTestcase&) +{ + return os << "TODO (bug 1318770)"; +} + +class CheckExtendedKeyUsageChainTest + : public ::testing::Test + , public ::testing::WithParamInterface<EKUChainTestcase> +{ +}; + +static ByteString +CreateCert(const char* issuerCN, const char* subjectCN, + EndEntityOrCA endEntityOrCA, ByteString encodedEKU) +{ + static long serialNumberValue = 0; + ++serialNumberValue; + ByteString serialNumber(CreateEncodedSerialNumber(serialNumberValue)); + EXPECT_FALSE(ENCODING_FAILED(serialNumber)); + + ByteString issuerDER(CNToDERName(issuerCN)); + ByteString subjectDER(CNToDERName(subjectCN)); + + ByteString extensions[3]; + extensions[0] = + CreateEncodedBasicConstraints(endEntityOrCA == EndEntityOrCA::MustBeCA, + nullptr, Critical::Yes); + EXPECT_FALSE(ENCODING_FAILED(extensions[0])); + if (encodedEKU.length() > 0) { + extensions[1] = encodedEKU; + } + + ScopedTestKeyPair reusedKey(CloneReusedKeyPair()); + ByteString certDER(CreateEncodedCertificate( + v3, sha256WithRSAEncryption(), serialNumber, issuerDER, + oneDayBeforeNow, oneDayAfterNow, subjectDER, + *reusedKey, extensions, *reusedKey, + sha256WithRSAEncryption())); + EXPECT_FALSE(ENCODING_FAILED(certDER)); + + return certDER; +} + +class EKUTrustDomain final : public DefaultCryptoTrustDomain +{ +public: + explicit EKUTrustDomain(ByteString issuerCertDER) + : mIssuerCertDER(issuerCertDER) + { + } + +private: + Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert, + TrustLevel& trustLevel) override + { + trustLevel = InputEqualsByteString(candidateCert, mIssuerCertDER) + ? TrustLevel::TrustAnchor + : TrustLevel::InheritsTrust; + return Success; + } + + Result FindIssuer(Input, IssuerChecker& checker, Time) override + { + Input derCert; + Result rv = derCert.Init(mIssuerCertDER.data(), mIssuerCertDER.length()); + if (rv != Success) { + return rv; + } + bool keepGoing; + return checker.Check(derCert, nullptr, keepGoing); + } + + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, + const Input*, const Input*) override + { + return Success; + } + + Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override + { + return Success; + } + + ByteString mIssuerCertDER; +}; + +TEST_P(CheckExtendedKeyUsageChainTest, EKUChainTestcase) +{ + const EKUChainTestcase& param(GetParam()); + ByteString issuerCertDER(CreateCert("CA", "CA", EndEntityOrCA::MustBeCA, + param.ekuExtensionCA)); + ByteString subjectCertDER(CreateCert("CA", "EE", + EndEntityOrCA::MustBeEndEntity, + param.ekuExtensionEE)); + + EKUTrustDomain trustDomain(issuerCertDER); + + Input subjectCertDERInput; + ASSERT_EQ(Success, subjectCertDERInput.Init(subjectCertDER.data(), + subjectCertDER.length())); + ASSERT_EQ(param.expectedResult, + BuildCertChain(trustDomain, subjectCertDERInput, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + param.keyPurposeId, + CertPolicyId::anyPolicy, + nullptr)); +} + +// python DottedOIDToCode.py --tlv id-ce-extKeyUsage 2.5.29.37 +static const uint8_t tlv_id_ce_extKeyUsage[] = { + 0x06, 0x03, 0x55, 0x1d, 0x25 +}; + +static inline ByteString +CreateEKUExtension(ByteString ekuOIDs) +{ + return TLV(der::SEQUENCE, + BytesToByteString(tlv_id_ce_extKeyUsage) + + TLV(der::OCTET_STRING, TLV(der::SEQUENCE, ekuOIDs))); +} + +static const EKUChainTestcase EKU_CHAIN_TESTCASES[] = +{ + { + // Both end-entity and CA have id-kp-serverAuth => should succeed + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)), + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)), + KeyPurposeId::id_kp_serverAuth, + Success + }, + { + // CA has no EKU extension => should succeed + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)), + ByteString(), + KeyPurposeId::id_kp_serverAuth, + Success + }, + { + // End-entity has no EKU extension => should succeed + ByteString(), + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)), + KeyPurposeId::id_kp_serverAuth, + Success + }, + { + // No EKU extensions at all => should succeed + ByteString(), + ByteString(), + KeyPurposeId::id_kp_serverAuth, + Success + }, + { + // CA has EKU without id-kp-serverAuth => should fail + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)), + CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)), + KeyPurposeId::id_kp_serverAuth, + Result::ERROR_INADEQUATE_CERT_TYPE + }, + { + // End-entity has EKU without id-kp-serverAuth => should fail + CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)), + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)), + KeyPurposeId::id_kp_serverAuth, + Result::ERROR_INADEQUATE_CERT_TYPE + }, + { + // Both end-entity and CA have EKU without id-kp-serverAuth => should fail + CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)), + CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)), + KeyPurposeId::id_kp_serverAuth, + Result::ERROR_INADEQUATE_CERT_TYPE + }, + { + // End-entity has no EKU, CA doesn't have id-kp-serverAuth => should fail + ByteString(), + CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)), + KeyPurposeId::id_kp_serverAuth, + Result::ERROR_INADEQUATE_CERT_TYPE + }, + { + // End-entity doesn't have id-kp-serverAuth, CA has no EKU => should fail + CreateEKUExtension(BytesToByteString(tlv_id_kp_clientAuth)), + ByteString(), + KeyPurposeId::id_kp_serverAuth, + Result::ERROR_INADEQUATE_CERT_TYPE + }, + { + // CA has id-Netscape-stepUp => should succeed + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)), + CreateEKUExtension(BytesToByteString(tlv_id_Netscape_stepUp)), + KeyPurposeId::id_kp_serverAuth, + Success + }, + { + // End-entity has id-Netscape-stepUp => should fail + CreateEKUExtension(BytesToByteString(tlv_id_Netscape_stepUp)), + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth)), + KeyPurposeId::id_kp_serverAuth, + Result::ERROR_INADEQUATE_CERT_TYPE + }, + { + // End-entity and CA have id-kp-serverAuth and id-kp-clientAuth => should + // succeed + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) + + BytesToByteString(tlv_id_kp_clientAuth)), + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) + + BytesToByteString(tlv_id_kp_clientAuth)), + KeyPurposeId::id_kp_serverAuth, + Success + }, + { + // End-entity has id-kp-serverAuth and id-kp-OCSPSigning => should fail + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) + + BytesToByteString(tlv_id_kp_OCSPSigning)), + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) + + BytesToByteString(tlv_id_kp_clientAuth)), + KeyPurposeId::id_kp_serverAuth, + Result::ERROR_INADEQUATE_CERT_TYPE + }, + { + // CA has id-kp-serverAuth and id-kp-OCSPSigning => should succeed + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) + + BytesToByteString(tlv_id_kp_clientAuth)), + CreateEKUExtension(BytesToByteString(tlv_id_kp_serverAuth) + + BytesToByteString(tlv_id_kp_OCSPSigning)), + KeyPurposeId::id_kp_serverAuth, + Success + }, +}; + +INSTANTIATE_TEST_CASE_P(pkixcheck_CheckExtendedKeyUsage, + CheckExtendedKeyUsageChainTest, + ::testing::ValuesIn(EKU_CHAIN_TESTCASES)); diff --git a/security/nss/gtests/mozpkix_gtest/pkixcheck_CheckIssuer_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixcheck_CheckIssuer_tests.cpp new file mode 100644 index 000000000..bcc2c1198 --- /dev/null +++ b/security/nss/gtests/mozpkix_gtest/pkixcheck_CheckIssuer_tests.cpp @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2016 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixgtest.h" + +#include "mozpkix/pkixcheck.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +class pkixcheck_CheckIssuer : public ::testing::Test { }; + +static const uint8_t EMPTY_NAME_DATA[] = { + 0x30, 0x00 /* tag, length */ +}; +static const Input EMPTY_NAME(EMPTY_NAME_DATA); + +static const uint8_t VALID_NAME_DATA[] = { + /* From https://www.example.com/: C=US, O=DigiCert Inc, OU=www.digicert.com, + * CN=DigiCert SHA2 High Assurance Server CA */ + 0x30, 0x70, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, + 0x02, 0x55, 0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0A, + 0x13, 0x0C, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, + 0x6E, 0x63, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0B, 0x13, + 0x10, 0x77, 0x77, 0x77, 0x2E, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, + 0x74, 0x2E, 0x63, 0x6F, 0x6D, 0x31, 0x2F, 0x30, 0x2D, 0x06, 0x03, 0x55, + 0x04, 0x03, 0x13, 0x26, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, + 0x20, 0x53, 0x48, 0x41, 0x32, 0x20, 0x48, 0x69, 0x67, 0x68, 0x20, 0x41, + 0x73, 0x73, 0x75, 0x72, 0x61, 0x6E, 0x63, 0x65, 0x20, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x20, 0x43, 0x41 +}; +static const Input VALID_NAME(VALID_NAME_DATA); + +TEST_F(pkixcheck_CheckIssuer, ValidIssuer) +{ + ASSERT_EQ(Success, CheckIssuer(VALID_NAME)); +} + +TEST_F(pkixcheck_CheckIssuer, EmptyIssuer) +{ + ASSERT_EQ(Result::ERROR_EMPTY_ISSUER_NAME, CheckIssuer(EMPTY_NAME)); +} diff --git a/security/nss/gtests/mozpkix_gtest/pkixcheck_CheckKeyUsage_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixcheck_CheckKeyUsage_tests.cpp new file mode 100644 index 000000000..136f8719a --- /dev/null +++ b/security/nss/gtests/mozpkix_gtest/pkixcheck_CheckKeyUsage_tests.cpp @@ -0,0 +1,284 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2013 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixgtest.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +namespace mozilla { namespace pkix { + +extern Result CheckKeyUsage(EndEntityOrCA endEntityOrCA, + const Input* encodedKeyUsage, + KeyUsage requiredKeyUsageIfPresent); + +} } // namespace mozilla::pkix + +class pkixcheck_CheckKeyUsage : public ::testing::Test { }; + +#define ASSERT_BAD(x) ASSERT_EQ(Result::ERROR_INADEQUATE_KEY_USAGE, x) + +// Make it easy to define test data for the common, simplest cases. +#define NAMED_SIMPLE_KU(name, unusedBits, bits) \ + const uint8_t name##_bytes[4] = { \ + 0x03/*BIT STRING*/, 0x02/*LENGTH=2*/, unusedBits, bits \ + }; \ + const Input name(name##_bytes); + +static const Input empty_null; + +// Note that keyCertSign is really the only interesting case for CA +// certificates since we don't support cRLSign. + +TEST_F(pkixcheck_CheckKeyUsage, EE_none) +{ + // The input Input is nullptr. This means the cert had no keyUsage + // extension. This is always valid because no key usage in an end-entity + // means that there are no key usage restrictions. + + ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr, + KeyUsage::noParticularKeyUsageRequired)); + ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr, + KeyUsage::digitalSignature)); + ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr, + KeyUsage::nonRepudiation)); + ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr, + KeyUsage::keyEncipherment)); + ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr, + KeyUsage::dataEncipherment)); + ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, nullptr, + KeyUsage::keyAgreement)); +} + +TEST_F(pkixcheck_CheckKeyUsage, EE_empty) +{ + // The input Input is empty. The cert had an empty keyUsage extension, + // which is syntactically invalid. + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &empty_null, + KeyUsage::digitalSignature)); + static const uint8_t dummy = 0x00; + Input empty_nonnull; + ASSERT_EQ(Success, empty_nonnull.Init(&dummy, 0)); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &empty_nonnull, + KeyUsage::digitalSignature)); +} + +TEST_F(pkixcheck_CheckKeyUsage, CA_none) +{ + // A CA certificate does not have a KU extension. + ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeCA, nullptr, + KeyUsage::keyCertSign)); +} + +TEST_F(pkixcheck_CheckKeyUsage, CA_empty) +{ + // A CA certificate has an empty KU extension. + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &empty_null, + KeyUsage::keyCertSign)); + static const uint8_t dummy = 0x00; + Input empty_nonnull; + ASSERT_EQ(Success, empty_nonnull.Init(&dummy, 0)); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &empty_nonnull, + KeyUsage::keyCertSign)); +} + +TEST_F(pkixcheck_CheckKeyUsage, maxUnusedBits) +{ + NAMED_SIMPLE_KU(encoded, 7, 0x80); + ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &encoded, + KeyUsage::digitalSignature)); +} + +TEST_F(pkixcheck_CheckKeyUsage, tooManyUnusedBits) +{ + static uint8_t oneValueByteData[] = { + 0x03/*BIT STRING*/, 0x02/*LENGTH=2*/, 8/*unused bits*/, 0x80 + }; + static const Input oneValueByte(oneValueByteData); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &oneValueByte, + KeyUsage::digitalSignature)); + + static uint8_t twoValueBytesData[] = { + 0x03/*BIT STRING*/, 0x03/*LENGTH=3*/, 8/*unused bits*/, 0x01, 0x00 + }; + static const Input twoValueBytes(twoValueBytesData); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &twoValueBytes, + KeyUsage::digitalSignature)); +} + +TEST_F(pkixcheck_CheckKeyUsage, NoValueBytes_NoPaddingBits) +{ + static const uint8_t DER_BYTES[] = { + 0x03/*BIT STRING*/, 0x01/*LENGTH=1*/, 0/*unused bits*/ + }; + static const Input DER(DER_BYTES); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &DER, + KeyUsage::digitalSignature)); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &DER, + KeyUsage::keyCertSign)); +} + +TEST_F(pkixcheck_CheckKeyUsage, NoValueBytes_7PaddingBits) +{ + static const uint8_t DER_BYTES[] = { + 0x03/*BIT STRING*/, 0x01/*LENGTH=1*/, 7/*unused bits*/ + }; + static const Input DER(DER_BYTES); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &DER, + KeyUsage::digitalSignature)); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeCA, &DER, + KeyUsage::keyCertSign)); +} + +void ASSERT_SimpleCase(uint8_t unusedBits, uint8_t bits, KeyUsage usage) +{ + // Test that only the right bit is accepted for the usage for both EE and CA + // certs. + NAMED_SIMPLE_KU(good, unusedBits, bits); + ASSERT_EQ(Success, + CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, &good, usage)); + ASSERT_EQ(Success, CheckKeyUsage(EndEntityOrCA::MustBeCA, &good, usage)); + + // We use (~bits >> unusedBits) << unusedBits) instead of using the same + // calculation that is in CheckKeyUsage to validate that the calculation in + // CheckKeyUsage is correct. + + // Test that none of the other non-padding bits are mistaken for the given + // key usage in the single-byte value case. + NAMED_SIMPLE_KU(notGood, unusedBits, + static_cast<uint8_t>((~bits >> unusedBits) << unusedBits)); + ASSERT_BAD(CheckKeyUsage(EndEntityOrCA::MustBeEndEntity, ¬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/nss/gtests/mozpkix_gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp new file mode 100644 index 000000000..70e6fd410 --- /dev/null +++ b/security/nss/gtests/mozpkix_gtest/pkixcheck_CheckSignatureAlgorithm_tests.cpp @@ -0,0 +1,367 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2015 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixgtest.h" + +#include "mozpkix/pkixder.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +namespace mozilla { namespace pkix { + +extern Result CheckSignatureAlgorithm( + TrustDomain& trustDomain, EndEntityOrCA endEntityOrCA, + Time notBefore, + const der::SignedDataWithSignature& signedData, + Input signatureValue); + +} } // namespace mozilla::pkix + +struct CheckSignatureAlgorithmTestParams +{ + ByteString signatureAlgorithmValue; + ByteString signatureValue; + unsigned int signatureLengthInBytes; + Result expectedResult; +}; + +::std::ostream& operator<<(::std::ostream& os, + const CheckSignatureAlgorithmTestParams&) +{ + return os << "TODO (bug 1318770)"; +} + +#define BS(s) ByteString(s, MOZILLA_PKIX_ARRAY_LENGTH(s)) + +// python DottedOIDToCode.py --tlv sha256WithRSAEncryption 1.2.840.113549.1.1.11 +static const uint8_t tlv_sha256WithRSAEncryption[] = { + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b +}; + +// Same as tlv_sha256WithRSAEncryption, except one without the "0x0b" and with +// the DER length decreased accordingly. +static const uint8_t tlv_sha256WithRSAEncryption_truncated[] = { + 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01 +}; + +// python DottedOIDToCode.py --tlv sha-1WithRSAEncryption 1.2.840.113549.1.1.5 +static const uint8_t tlv_sha_1WithRSAEncryption[] = { + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05 +}; + +// python DottedOIDToCode.py --tlv sha1WithRSASignature 1.3.14.3.2.29 +static const uint8_t tlv_sha1WithRSASignature[] = { + 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1d +}; + +// python DottedOIDToCode.py --tlv md5WithRSAEncryption 1.2.840.113549.1.1.4 +static const uint8_t tlv_md5WithRSAEncryption[] = { + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x04 +}; + +static const CheckSignatureAlgorithmTestParams + CHECKSIGNATUREALGORITHM_TEST_PARAMS[] = +{ + { // Both algorithm IDs are empty + ByteString(), + ByteString(), + 2048 / 8, + Result::ERROR_BAD_DER, + }, + { // signatureAlgorithm is empty, signature is supported. + ByteString(), + BS(tlv_sha256WithRSAEncryption), + 2048 / 8, + Result::ERROR_BAD_DER, + }, + { // signatureAlgorithm is supported, signature is empty. + BS(tlv_sha256WithRSAEncryption), + ByteString(), + 2048 / 8, + Result::ERROR_BAD_DER, + }, + { // Algorithms match, both are supported. + BS(tlv_sha256WithRSAEncryption), + BS(tlv_sha256WithRSAEncryption), + 2048 / 8, + Success + }, + { // Algorithms do not match because signatureAlgorithm is truncated. + BS(tlv_sha256WithRSAEncryption_truncated), + BS(tlv_sha256WithRSAEncryption), + 2048 / 8, + Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED + }, + { // Algorithms do not match because signature is truncated. + BS(tlv_sha256WithRSAEncryption), + BS(tlv_sha256WithRSAEncryption_truncated), + 2048 / 8, + Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED + }, + { // Algorithms do not match, both are supported. + BS(tlv_sha_1WithRSAEncryption), + BS(tlv_sha256WithRSAEncryption), + 2048 / 8, + Result::ERROR_SIGNATURE_ALGORITHM_MISMATCH, + }, + { // Algorithms do not match, both are supported. + BS(tlv_sha256WithRSAEncryption), + BS(tlv_sha_1WithRSAEncryption), + 2048 / 8, + Result::ERROR_SIGNATURE_ALGORITHM_MISMATCH, + }, + { // Algorithms match, both are unsupported. + BS(tlv_md5WithRSAEncryption), + BS(tlv_md5WithRSAEncryption), + 2048 / 8, + Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED + }, + { // signatureAlgorithm is unsupported, signature is supported. + BS(tlv_md5WithRSAEncryption), + BS(tlv_sha256WithRSAEncryption), + 2048 / 8, + Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED + }, + { // signatureAlgorithm is supported, signature is unsupported. + BS(tlv_sha256WithRSAEncryption), + BS(tlv_md5WithRSAEncryption), + 2048 / 8, + Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED + }, + { // Both have the optional NULL parameter. + BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()), + BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()), + 2048 / 8, + Success + }, + { // signatureAlgorithm has the optional NULL parameter, signature doesn't. + BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()), + BS(tlv_sha256WithRSAEncryption), + 2048 / 8, + Success + }, + { // signatureAlgorithm does not have the optional NULL parameter, signature + // does. + BS(tlv_sha256WithRSAEncryption), + BS(tlv_sha256WithRSAEncryption) + TLV(der::NULLTag, ByteString()), + 2048 / 8, + Success + }, + { // The different OIDs for RSA-with-SHA1 we support are semantically + // equivalent. + BS(tlv_sha1WithRSASignature), + BS(tlv_sha_1WithRSAEncryption), + 2048 / 8, + Success, + }, + { // The different OIDs for RSA-with-SHA1 we support are semantically + // equivalent (opposite order). + BS(tlv_sha_1WithRSAEncryption), + BS(tlv_sha1WithRSASignature), + 2048 / 8, + Success, + }, + { // Algorithms match, both are supported, key size is not a multile of 128 + // bits. This test verifies that we're not wrongly rounding up the + // signature size like we did in the original patch for bug 1131767. + BS(tlv_sha256WithRSAEncryption), + BS(tlv_sha256WithRSAEncryption), + (2048 / 8) - 1, + Success + }, +}; + +class pkixcheck_CheckSignatureAlgorithm + : public ::testing::Test + , public ::testing::WithParamInterface<CheckSignatureAlgorithmTestParams> +{ +}; + +class pkixcheck_CheckSignatureAlgorithm_TrustDomain final + : public EverythingFailsByDefaultTrustDomain +{ +public: + explicit pkixcheck_CheckSignatureAlgorithm_TrustDomain( + unsigned int aPublicKeySizeInBits) + : publicKeySizeInBits(aPublicKeySizeInBits) + , checkedDigestAlgorithm(false) + , checkedModulusSizeInBits(false) + { + } + + Result CheckSignatureDigestAlgorithm(DigestAlgorithm, EndEntityOrCA, Time) + override + { + checkedDigestAlgorithm = true; + return Success; + } + + Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA endEntityOrCA, + unsigned int modulusSizeInBits) + override + { + EXPECT_EQ(EndEntityOrCA::MustBeEndEntity, endEntityOrCA); + EXPECT_EQ(publicKeySizeInBits, modulusSizeInBits); + checkedModulusSizeInBits = true; + return Success; + } + + const unsigned int publicKeySizeInBits; + bool checkedDigestAlgorithm; + bool checkedModulusSizeInBits; +}; + +TEST_P(pkixcheck_CheckSignatureAlgorithm, CheckSignatureAlgorithm) +{ + const Time now(Now()); + const CheckSignatureAlgorithmTestParams& params(GetParam()); + + Input signatureValueInput; + ASSERT_EQ(Success, + signatureValueInput.Init(params.signatureValue.data(), + params.signatureValue.length())); + + pkixcheck_CheckSignatureAlgorithm_TrustDomain + trustDomain(params.signatureLengthInBytes * 8); + + der::SignedDataWithSignature signedData; + ASSERT_EQ(Success, + signedData.algorithm.Init(params.signatureAlgorithmValue.data(), + params.signatureAlgorithmValue.length())); + + ByteString dummySignature(params.signatureLengthInBytes, 0xDE); + ASSERT_EQ(Success, + signedData.signature.Init(dummySignature.data(), + dummySignature.length())); + + ASSERT_EQ(params.expectedResult, + CheckSignatureAlgorithm(trustDomain, EndEntityOrCA::MustBeEndEntity, + now, signedData, signatureValueInput)); + ASSERT_EQ(params.expectedResult == Success, + trustDomain.checkedDigestAlgorithm); + ASSERT_EQ(params.expectedResult == Success, + trustDomain.checkedModulusSizeInBits); +} + +INSTANTIATE_TEST_CASE_P( + pkixcheck_CheckSignatureAlgorithm, pkixcheck_CheckSignatureAlgorithm, + testing::ValuesIn(CHECKSIGNATUREALGORITHM_TEST_PARAMS)); + +class pkixcheck_CheckSignatureAlgorithm_BuildCertChain_TrustDomain + : public DefaultCryptoTrustDomain +{ +public: + explicit pkixcheck_CheckSignatureAlgorithm_BuildCertChain_TrustDomain( + const ByteString& aIssuer) + : issuer(aIssuer) + { + } + + Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, + Input cert, /*out*/ TrustLevel& trustLevel) override + { + trustLevel = InputEqualsByteString(cert, issuer) + ? TrustLevel::TrustAnchor + : TrustLevel::InheritsTrust; + return Success; + } + + Result FindIssuer(Input, IssuerChecker& checker, Time) override + { + EXPECT_FALSE(ENCODING_FAILED(issuer)); + + Input issuerInput; + EXPECT_EQ(Success, issuerInput.Init(issuer.data(), issuer.length())); + + bool keepGoing; + EXPECT_EQ(Success, checker.Check(issuerInput, nullptr, keepGoing)); + EXPECT_FALSE(keepGoing); + + return Success; + } + + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, + /*optional*/ const Input*, + /*optional*/ const Input*) override + { + return Success; + } + + Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override + { + return Success; + } + + ByteString issuer; +}; + +// Test that CheckSignatureAlgorithm actually gets called at some point when +// BuildCertChain is called. +TEST_F(pkixcheck_CheckSignatureAlgorithm, BuildCertChain) +{ + ScopedTestKeyPair keyPair(CloneReusedKeyPair()); + ASSERT_TRUE(keyPair.get()); + + ByteString issuerExtensions[2]; + issuerExtensions[0] = CreateEncodedBasicConstraints(true, nullptr, + Critical::No); + ASSERT_FALSE(ENCODING_FAILED(issuerExtensions[0])); + + ByteString issuer(CreateEncodedCertificate(3, + sha256WithRSAEncryption(), + CreateEncodedSerialNumber(1), + CNToDERName("issuer"), + oneDayBeforeNow, oneDayAfterNow, + CNToDERName("issuer"), + *keyPair, + issuerExtensions, + *keyPair, + sha256WithRSAEncryption())); + ASSERT_FALSE(ENCODING_FAILED(issuer)); + + ByteString subject(CreateEncodedCertificate(3, + sha1WithRSAEncryption(), + CreateEncodedSerialNumber(2), + CNToDERName("issuer"), + oneDayBeforeNow, oneDayAfterNow, + CNToDERName("subject"), + *keyPair, + nullptr, + *keyPair, + sha256WithRSAEncryption())); + ASSERT_FALSE(ENCODING_FAILED(subject)); + + Input subjectInput; + ASSERT_EQ(Success, subjectInput.Init(subject.data(), subject.length())); + pkixcheck_CheckSignatureAlgorithm_BuildCertChain_TrustDomain + trustDomain(issuer); + Result rv = BuildCertChain(trustDomain, subjectInput, Now(), + EndEntityOrCA::MustBeEndEntity, + KeyUsage::noParticularKeyUsageRequired, + KeyPurposeId::anyExtendedKeyUsage, + CertPolicyId::anyPolicy, + nullptr); + ASSERT_EQ(Result::ERROR_SIGNATURE_ALGORITHM_MISMATCH, rv); +} diff --git a/security/nss/gtests/mozpkix_gtest/pkixcheck_CheckValidity_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixcheck_CheckValidity_tests.cpp new file mode 100644 index 000000000..a1a6f998b --- /dev/null +++ b/security/nss/gtests/mozpkix_gtest/pkixcheck_CheckValidity_tests.cpp @@ -0,0 +1,128 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2014 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixgtest.h" + +#include "mozpkix/pkixcheck.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +static const Time PAST_TIME(YMDHMS(1998, 12, 31, 12, 23, 56)); + +#define OLDER_GENERALIZEDTIME \ + 0x18, 15, /* tag, length */ \ + '1', '9', '9', '9', '0', '1', '0', '1', /* 1999-01-01 */ \ + '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */ + +#define OLDER_UTCTIME \ + 0x17, 13, /* tag, length */ \ + '9', '9', '0', '1', '0', '1', /* (19)99-01-01 */ \ + '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */ + +static const Time NOW(YMDHMS(2016, 12, 31, 12, 23, 56)); + +#define NEWER_GENERALIZEDTIME \ + 0x18, 15, /* tag, length */ \ + '2', '0', '2', '1', '0', '1', '0', '1', /* 2021-01-01 */ \ + '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */ + +#define NEWER_UTCTIME \ + 0x17, 13, /* tag, length */ \ + '2', '1', '0', '1', '0', '1', /* 2021-01-01 */ \ + '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */ + +static const Time FUTURE_TIME(YMDHMS(2025, 12, 31, 12, 23, 56)); + +class pkixcheck_CheckValidity : public ::testing::Test { }; + +static const uint8_t OLDER_UTCTIME_NEWER_UTCTIME_DATA[] = { + OLDER_UTCTIME, + NEWER_UTCTIME, +}; +static const Input +OLDER_UTCTIME_NEWER_UTCTIME(OLDER_UTCTIME_NEWER_UTCTIME_DATA); + +TEST_F(pkixcheck_CheckValidity, Valid_UTCTIME_UTCTIME) +{ + static Time notBefore(Time::uninitialized); + static Time notAfter(Time::uninitialized); + ASSERT_EQ(Success, ParseValidity(OLDER_UTCTIME_NEWER_UTCTIME, ¬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/nss/gtests/mozpkix_gtest/pkixcheck_ParseValidity_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixcheck_ParseValidity_tests.cpp new file mode 100644 index 000000000..7255bb5df --- /dev/null +++ b/security/nss/gtests/mozpkix_gtest/pkixcheck_ParseValidity_tests.cpp @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2014 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixgtest.h" + +#include "mozpkix/pkixcheck.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +#define OLDER_UTCTIME \ + 0x17, 13, /* tag, length */ \ + '9', '9', '0', '1', '0', '1', /* (19)99-01-01 */ \ + '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */ + +#define NEWER_UTCTIME \ + 0x17, 13, /* tag, length */ \ + '2', '1', '0', '1', '0', '1', /* 2021-01-01 */ \ + '0', '0', '0', '0', '0', '0', 'Z' /* 00:00:00Z */ + +static const Time FUTURE_TIME(YMDHMS(2025, 12, 31, 12, 23, 56)); + +class pkixcheck_ParseValidity : public ::testing::Test { }; + +TEST_F(pkixcheck_ParseValidity, BothEmptyNull) +{ + static const uint8_t DER[] = { + 0x17/*UTCTime*/, 0/*length*/, + 0x17/*UTCTime*/, 0/*length*/, + }; + static const Input validity(DER); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity)); +} + +TEST_F(pkixcheck_ParseValidity, NotBeforeEmptyNull) +{ + static const uint8_t DER[] = { + 0x17/*UTCTime*/, 0x00/*length*/, + NEWER_UTCTIME + }; + static const Input validity(DER); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity)); +} + +TEST_F(pkixcheck_ParseValidity, NotAfterEmptyNull) +{ + static const uint8_t DER[] = { + NEWER_UTCTIME, + 0x17/*UTCTime*/, 0x00/*length*/, + }; + static const Input validity(DER); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity)); +} + +TEST_F(pkixcheck_ParseValidity, InvalidNotAfterBeforeNotBefore) +{ + static const uint8_t DER[] = { + NEWER_UTCTIME, + OLDER_UTCTIME, + }; + static const Input validity(DER); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, ParseValidity(validity)); +} diff --git a/security/nss/gtests/mozpkix_gtest/pkixcheck_TLSFeaturesSatisfiedInternal_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixcheck_TLSFeaturesSatisfiedInternal_tests.cpp new file mode 100644 index 000000000..b7809cc60 --- /dev/null +++ b/security/nss/gtests/mozpkix_gtest/pkixcheck_TLSFeaturesSatisfiedInternal_tests.cpp @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2015 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixgtest.h" + +#include "mozpkix/pkixder.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +namespace mozilla { namespace pkix { + extern Result TLSFeaturesSatisfiedInternal(const Input* requiredTLSFeatures, + const Input* stapledOCSPResponse); +} } // namespace mozilla::pkix + +struct TLSFeaturesTestParams +{ + ByteString requiredTLSFeatures; + Result expectedResultWithResponse; + Result expectedResultWithoutResponse; +}; + +::std::ostream& operator<<(::std::ostream& os, const TLSFeaturesTestParams&) +{ + return os << "TODO (bug 1318770)"; +} + +#define BS(s) ByteString(s, MOZILLA_PKIX_ARRAY_LENGTH(s)) +static const uint8_t statusRequest[] = { + 0x30, 0x03, 0x02, 0x01, 0x05 +}; + +static const uint8_t unknown[] = { + 0x30, 0x03, 0x02, 0x01, 0x06 +}; + +static const uint8_t statusRequestAndUnknown[] = { + 0x30, 0x06, 0x02, 0x01, 0x05, 0x02, 0x01, 0x06 +}; + +static const uint8_t duplicateStatusRequest[] = { + 0x30, 0x06, 0x02, 0x01, 0x05, 0x02, 0x01, 0x05 +}; + +static const uint8_t twoByteUnknown[] = { + 0x30, 0x04, 0x02, 0x02, 0x05, 0x05 +}; + +static const uint8_t zeroByteInteger[] = { + 0x30, 0x02, 0x02, 0x00 +}; + +static const TLSFeaturesTestParams + TLSFEATURESSATISFIED_TEST_PARAMS[] = +{ + // some tests with checks enforced + { ByteString(), Result::ERROR_BAD_DER, Result::ERROR_BAD_DER }, + { BS(statusRequest), Success, Result::ERROR_REQUIRED_TLS_FEATURE_MISSING }, + { BS(unknown), Result::ERROR_REQUIRED_TLS_FEATURE_MISSING, + Result::ERROR_REQUIRED_TLS_FEATURE_MISSING }, + { BS(statusRequestAndUnknown), Result::ERROR_REQUIRED_TLS_FEATURE_MISSING, + Result::ERROR_REQUIRED_TLS_FEATURE_MISSING }, + { BS(duplicateStatusRequest), Success, + Result::ERROR_REQUIRED_TLS_FEATURE_MISSING }, + { BS(twoByteUnknown), Result::ERROR_REQUIRED_TLS_FEATURE_MISSING, + Result::ERROR_REQUIRED_TLS_FEATURE_MISSING }, + { BS(zeroByteInteger), Result::ERROR_REQUIRED_TLS_FEATURE_MISSING, + Result::ERROR_REQUIRED_TLS_FEATURE_MISSING }, +}; + +class pkixcheck_TLSFeaturesSatisfiedInternal + : public ::testing::Test + , public ::testing::WithParamInterface<TLSFeaturesTestParams> +{ +}; + +TEST_P(pkixcheck_TLSFeaturesSatisfiedInternal, TLSFeaturesSatisfiedInternal) { + const TLSFeaturesTestParams& params(GetParam()); + + Input featuresInput; + ASSERT_EQ(Success, featuresInput.Init(params.requiredTLSFeatures.data(), + params.requiredTLSFeatures.length())); + Input responseInput; + // just create an input with any data in it + ByteString stapledOCSPResponse = BS(statusRequest); + ASSERT_EQ(Success, responseInput.Init(stapledOCSPResponse.data(), + stapledOCSPResponse.length())); + // first we omit the response + ASSERT_EQ(params.expectedResultWithoutResponse, + TLSFeaturesSatisfiedInternal(&featuresInput, nullptr)); + // then we try again with the response + ASSERT_EQ(params.expectedResultWithResponse, + TLSFeaturesSatisfiedInternal(&featuresInput, &responseInput)); +} + +INSTANTIATE_TEST_CASE_P( + pkixcheck_TLSFeaturesSatisfiedInternal, + pkixcheck_TLSFeaturesSatisfiedInternal, + testing::ValuesIn(TLSFEATURESSATISFIED_TEST_PARAMS)); diff --git a/security/nss/gtests/mozpkix_gtest/pkixder_input_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixder_input_tests.cpp new file mode 100644 index 000000000..cf91fa2c6 --- /dev/null +++ b/security/nss/gtests/mozpkix_gtest/pkixder_input_tests.cpp @@ -0,0 +1,920 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2013 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <functional> +#include <vector> +#include "pkixgtest.h" + +#include "mozpkix/pkixder.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::der; + +namespace { + +class pkixder_input_tests : public ::testing::Test { }; + +static const uint8_t DER_SEQUENCE_EMPTY[] = { + 0x30, // SEQUENCE + 0x00, // length +}; + +static const uint8_t DER_SEQUENCE_NOT_EMPTY[] = { + 0x30, // SEQUENCE + 0x01, // length + 'X', // value +}; + +static const uint8_t DER_SEQUENCE_NOT_EMPTY_VALUE[] = { + 'X', // value +}; + +static const uint8_t DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED[] = { + 0x30, // SEQUENCE + 0x01, // length +}; + +const uint8_t DER_SEQUENCE_OF_INT8[] = { + 0x30, // SEQUENCE + 0x09, // length + 0x02, 0x01, 0x01, // INTEGER length 1 value 0x01 + 0x02, 0x01, 0x02, // INTEGER length 1 value 0x02 + 0x02, 0x01, 0x03 // INTEGER length 1 value 0x03 +}; + +const uint8_t DER_TRUNCATED_SEQUENCE_OF_INT8[] = { + 0x30, // SEQUENCE + 0x09, // length + 0x02, 0x01, 0x01, // INTEGER length 1 value 0x01 + 0x02, 0x01, 0x02 // INTEGER length 1 value 0x02 + // MISSING DATA HERE ON PURPOSE +}; + +const uint8_t DER_OVERRUN_SEQUENCE_OF_INT8[] = { + 0x30, // SEQUENCE + 0x09, // length + 0x02, 0x01, 0x01, // INTEGER length 1 value 0x01 + 0x02, 0x01, 0x02, // INTEGER length 1 value 0x02 + 0x02, 0x02, 0xFF, 0x03 // INTEGER length 2 value 0xFF03 +}; + +const uint8_t DER_INT16[] = { + 0x02, // INTEGER + 0x02, // length + 0x12, 0x34 // 0x1234 +}; + +static const Input EMPTY_INPUT; + +TEST_F(pkixder_input_tests, InputInit) +{ + Input buf; + ASSERT_EQ(Success, + buf.Init(DER_SEQUENCE_OF_INT8, sizeof DER_SEQUENCE_OF_INT8)); +} + +TEST_F(pkixder_input_tests, InputInitWithNullPointerOrZeroLength) +{ + Input buf; + ASSERT_EQ(Result::ERROR_BAD_DER, buf.Init(nullptr, 0)); + + ASSERT_EQ(Result::ERROR_BAD_DER, buf.Init(nullptr, 100)); + + // Though it seems odd to initialize with zero-length and non-null ptr, this + // is working as intended. The Reader class was intended to protect against + // buffer overflows, and there's no risk with the current behavior. See bug + // 1000354. + ASSERT_EQ(Success, buf.Init((const uint8_t*) "hello", 0)); + ASSERT_TRUE(buf.GetLength() == 0); +} + +TEST_F(pkixder_input_tests, InputInitWithLargeData) +{ + Input buf; + // Data argument length does not matter, it is not touched, just + // needs to be non-null + ASSERT_EQ(Result::ERROR_BAD_DER, buf.Init((const uint8_t*) "", 0xffff+1)); + + ASSERT_EQ(Success, buf.Init((const uint8_t*) "", 0xffff)); +} + +TEST_F(pkixder_input_tests, InputInitMultipleTimes) +{ + Input buf; + + ASSERT_EQ(Success, + buf.Init(DER_SEQUENCE_OF_INT8, sizeof DER_SEQUENCE_OF_INT8)); + + ASSERT_EQ(Result::FATAL_ERROR_INVALID_ARGS, + buf.Init(DER_SEQUENCE_OF_INT8, sizeof DER_SEQUENCE_OF_INT8)); +} + +TEST_F(pkixder_input_tests, PeekWithinBounds) +{ + const uint8_t der[] = { 0x11, 0x11 }; + Input buf(der); + Reader input(buf); + ASSERT_TRUE(input.Peek(0x11)); + ASSERT_FALSE(input.Peek(0x22)); +} + +TEST_F(pkixder_input_tests, PeekPastBounds) +{ + const uint8_t der[] = { 0x11, 0x22 }; + Input buf; + ASSERT_EQ(Success, buf.Init(der, 1)); + Reader input(buf); + + uint8_t readByte; + ASSERT_EQ(Success, input.Read(readByte)); + ASSERT_EQ(0x11, readByte); + ASSERT_FALSE(input.Peek(0x22)); +} + +TEST_F(pkixder_input_tests, ReadByte) +{ + const uint8_t der[] = { 0x11, 0x22 }; + Input buf(der); + Reader input(buf); + + uint8_t readByte1; + ASSERT_EQ(Success, input.Read(readByte1)); + ASSERT_EQ(0x11, readByte1); + + uint8_t readByte2; + ASSERT_EQ(Success, input.Read(readByte2)); + ASSERT_EQ(0x22, readByte2); +} + +TEST_F(pkixder_input_tests, ReadBytePastEnd) +{ + const uint8_t der[] = { 0x11, 0x22 }; + Input buf; + ASSERT_EQ(Success, buf.Init(der, 1)); + Reader input(buf); + + uint8_t readByte1 = 0; + ASSERT_EQ(Success, input.Read(readByte1)); + ASSERT_EQ(0x11, readByte1); + + uint8_t readByte2 = 0; + ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(readByte2)); + ASSERT_NE(0x22, readByte2); +} + +TEST_F(pkixder_input_tests, ReadByteWrapAroundPointer) +{ + // The original implementation of our buffer read overflow checks was + // susceptible to integer overflows which could make the checks ineffective. + // This attempts to verify that we've fixed that. Unfortunately, decrementing + // a null pointer is undefined behavior according to the C++ language spec., + // but this should catch the problem on at least some compilers, if not all of + // them. + const uint8_t* der = nullptr; + --der; + Input buf; + ASSERT_EQ(Success, buf.Init(der, 0)); + Reader input(buf); + + uint8_t b; + ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(b)); +} + +TEST_F(pkixder_input_tests, ReadWord) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + + uint16_t readWord1 = 0; + ASSERT_EQ(Success, input.Read(readWord1)); + ASSERT_EQ(0x1122, readWord1); + + uint16_t readWord2 = 0; + ASSERT_EQ(Success, input.Read(readWord2)); + ASSERT_EQ(0x3344, readWord2); +} + +TEST_F(pkixder_input_tests, ReadWordPastEnd) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf; + ASSERT_EQ(Success, buf.Init(der, 2)); // Initialize with too-short length + Reader input(buf); + + uint16_t readWord1 = 0; + ASSERT_EQ(Success, input.Read(readWord1)); + ASSERT_EQ(0x1122, readWord1); + + uint16_t readWord2 = 0; + ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(readWord2)); + ASSERT_NE(0x3344, readWord2); +} + +TEST_F(pkixder_input_tests, ReadWordWithInsufficentData) +{ + const uint8_t der[] = { 0x11, 0x22 }; + Input buf; + ASSERT_EQ(Success, buf.Init(der, 1)); + Reader input(buf); + + uint16_t readWord1 = 0; + ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(readWord1)); + ASSERT_NE(0x1122, readWord1); +} + +TEST_F(pkixder_input_tests, ReadWordWrapAroundPointer) +{ + // The original implementation of our buffer read overflow checks was + // susceptible to integer overflows which could make the checks ineffective. + // This attempts to verify that we've fixed that. Unfortunately, decrementing + // a null pointer is undefined behavior according to the C++ language spec., + // but this should catch the problem on at least some compilers, if not all of + // them. + const uint8_t* der = nullptr; + --der; + Input buf; + ASSERT_EQ(Success, buf.Init(der, 0)); + Reader input(buf); + uint16_t b; + ASSERT_EQ(Result::ERROR_BAD_DER, input.Read(b)); +} + +TEST_F(pkixder_input_tests, Skip) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + + ASSERT_EQ(Success, input.Skip(1)); + + uint8_t readByte1 = 0; + ASSERT_EQ(Success, input.Read(readByte1)); + ASSERT_EQ(0x22, readByte1); + + ASSERT_EQ(Success, input.Skip(1)); + + uint8_t readByte2 = 0; + ASSERT_EQ(Success, input.Read(readByte2)); + ASSERT_EQ(0x44, readByte2); +} + +TEST_F(pkixder_input_tests, Skip_ToEnd) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + ASSERT_EQ(Success, input.Skip(sizeof der)); + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, Skip_PastEnd) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + + ASSERT_EQ(Result::ERROR_BAD_DER, input.Skip(sizeof der + 1)); +} + +TEST_F(pkixder_input_tests, Skip_ToNewInput) +{ + const uint8_t der[] = { 0x01, 0x02, 0x03, 0x04 }; + Input buf(der); + Reader input(buf); + + Reader skippedInput; + ASSERT_EQ(Success, input.Skip(3, skippedInput)); + + uint8_t readByte1 = 0; + ASSERT_EQ(Success, input.Read(readByte1)); + ASSERT_EQ(0x04, readByte1); + + ASSERT_TRUE(input.AtEnd()); + + // Reader has no Remaining() or Length() so we simply read the bytes + // and then expect to be at the end. + + for (uint8_t i = 1; i <= 3; ++i) { + uint8_t readByte = 0; + ASSERT_EQ(Success, skippedInput.Read(readByte)); + ASSERT_EQ(i, readByte); + } + + ASSERT_TRUE(skippedInput.AtEnd()); +} + +TEST_F(pkixder_input_tests, Skip_ToNewInputPastEnd) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + + Reader skippedInput; + ASSERT_EQ(Result::ERROR_BAD_DER, input.Skip(sizeof der * 2, skippedInput)); +} + +TEST_F(pkixder_input_tests, Skip_ToInput) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + + const uint8_t expectedItemData[] = { 0x11, 0x22, 0x33 }; + + Input item; + ASSERT_EQ(Success, input.Skip(sizeof expectedItemData, item)); + + Input expected(expectedItemData); + ASSERT_TRUE(InputsAreEqual(expected, item)); +} + +TEST_F(pkixder_input_tests, Skip_WrapAroundPointer) +{ + // The original implementation of our buffer read overflow checks was + // susceptible to integer overflows which could make the checks ineffective. + // This attempts to verify that we've fixed that. Unfortunately, decrementing + // a null pointer is undefined behavior according to the C++ language spec., + // but this should catch the problem on at least some compilers, if not all of + // them. + const uint8_t* der = nullptr; + --der; + Input buf; + ASSERT_EQ(Success, buf.Init(der, 0)); + Reader input(buf); + ASSERT_EQ(Result::ERROR_BAD_DER, input.Skip(1)); +} + +TEST_F(pkixder_input_tests, Skip_ToInputPastEnd) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + + Input skipped; + ASSERT_EQ(Result::ERROR_BAD_DER, input.Skip(sizeof der + 1, skipped)); +} + +TEST_F(pkixder_input_tests, SkipToEnd_ToInput) +{ + static const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + + Input skipped; + ASSERT_EQ(Success, input.SkipToEnd(skipped)); +} + +TEST_F(pkixder_input_tests, SkipToEnd_ToInput_InputAlreadyInited) +{ + static const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + + static const uint8_t initialValue[] = { 0x01, 0x02, 0x03 }; + Input x(initialValue); + // Fails because skipped was already initialized once, and Inputs are not + // allowed to be Init()d multiple times. + ASSERT_EQ(Result::FATAL_ERROR_INVALID_ARGS, input.SkipToEnd(x)); + ASSERT_TRUE(InputsAreEqual(x, Input(initialValue))); +} + +TEST_F(pkixder_input_tests, ExpectTagAndSkipValue) +{ + Input buf(DER_SEQUENCE_OF_INT8); + Reader input(buf); + + ASSERT_EQ(Success, ExpectTagAndSkipValue(input, SEQUENCE)); + ASSERT_EQ(Success, End(input)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndSkipValueWithTruncatedData) +{ + Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8); + Reader input(buf); + + ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndSkipValue(input, SEQUENCE)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndSkipValueWithOverrunData) +{ + Input buf(DER_OVERRUN_SEQUENCE_OF_INT8); + Reader input(buf); + ASSERT_EQ(Success, ExpectTagAndSkipValue(input, SEQUENCE)); + ASSERT_EQ(Result::ERROR_BAD_DER, End(input)); +} + +TEST_F(pkixder_input_tests, AtEndOnUnInitializedInput) +{ + Reader input; + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, AtEndAtBeginning) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + ASSERT_FALSE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, AtEndAtEnd) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + ASSERT_EQ(Success, input.Skip(sizeof der)); + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, MarkAndGetInput) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + + Reader::Mark mark = input.GetMark(); + + const uint8_t expectedItemData[] = { 0x11, 0x22, 0x33 }; + + ASSERT_EQ(Success, input.Skip(sizeof expectedItemData)); + + Input item; + ASSERT_EQ(Success, input.GetInput(mark, item)); + Input expected(expectedItemData); + ASSERT_TRUE(InputsAreEqual(expected, item)); +} + +// Cannot run this test on debug builds because of the NotReached +#ifdef NDEBUG +TEST_F(pkixder_input_tests, MarkAndGetInputDifferentInput) +{ + const uint8_t der[] = { 0x11, 0x22, 0x33, 0x44 }; + Input buf(der); + Reader input(buf); + + Reader another; + Reader::Mark mark = another.GetMark(); + + ASSERT_EQ(Success, input.Skip(3)); + + Input item; + ASSERT_EQ(Result::FATAL_ERROR_INVALID_ARGS, input.GetInput(mark, item)); +} +#endif + +TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_AtEnd) +{ + Reader input(EMPTY_INPUT); + uint8_t tag; + Input value; + ASSERT_EQ(Result::ERROR_BAD_DER, ReadTagAndGetValue(input, tag, value)); +} + +TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_TruncatedAfterTag) +{ + static const uint8_t DER[] = { SEQUENCE }; + Input buf(DER); + Reader input(buf); + uint8_t tag; + Input value; + ASSERT_EQ(Result::ERROR_BAD_DER, ReadTagAndGetValue(input, tag, value)); +} + +TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_ValidEmpty) +{ + Input buf(DER_SEQUENCE_EMPTY); + Reader input(buf); + uint8_t tag = 0; + Input value; + ASSERT_EQ(Success, ReadTagAndGetValue(input, tag, value)); + ASSERT_EQ(SEQUENCE, tag); + ASSERT_EQ(0u, value.GetLength()); + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_ValidNotEmpty) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY); + Reader input(buf); + uint8_t tag = 0; + Input value; + ASSERT_EQ(Success, ReadTagAndGetValue(input, tag, value)); + ASSERT_EQ(SEQUENCE, tag); + Input expected(DER_SEQUENCE_NOT_EMPTY_VALUE); + ASSERT_TRUE(InputsAreEqual(expected, value)); + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, + ReadTagAndGetValue_Input_InvalidNotEmptyValueTruncated) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED); + Reader input(buf); + uint8_t tag; + Input value; + ASSERT_EQ(Result::ERROR_BAD_DER, ReadTagAndGetValue(input, tag, value)); +} + +TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_InvalidWrongLength) +{ + Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8); + Reader input(buf); + uint8_t tag; + Input value; + ASSERT_EQ(Result::ERROR_BAD_DER, + ReadTagAndGetValue(input, tag, value)); +} + +TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_InvalidHighTagNumberForm1) +{ + // High tag number form is not allowed (illegal 1 byte tag) + // + // If the decoder treats 0x1F as a valid low tag number tag, then it will + // treat the actual tag (1) as a length, and then it will return Success + // with value == { 0x00 } and tag == 0x1f. + // + // It is illegal to encode tag 1 in the high tag number form because it isn't + // the shortest encoding (the low tag number form is). + static const uint8_t DER[] = { + 0x1F, // high tag number form indicator + 1, // tag 1 (not legal!) + 0 // length zero + }; + Input buf(DER); + Reader input(buf); + uint8_t tag; + Input value; + ASSERT_EQ(Result::ERROR_BAD_DER, + ReadTagAndGetValue(input, tag, value)); +} + +TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_InvalidHighTagNumberForm2) +{ + // High tag number form is not allowed (legal 1 byte tag). + // + // ReadTagAndGetValue's check to prohibit the high tag number form has no + // effect on whether this test passes or fails, because ReadTagAndGetValue + // will interpret the second byte (31) as a length, and the input doesn't + // have 31 bytes following it. This test is here to guard against the case + // where somebody actually implements high tag number form parsing, to remind + // that person that they need to add tests here, including in particular + // tests for overly-long encodings. + static const uint8_t DER[] = { + 0x1F, // high tag number form indicator + 31, // tag 31 + 0 // length zero + }; + Input buf(DER); + Reader input(buf); + uint8_t tag; + Input value; + ASSERT_EQ(Result::ERROR_BAD_DER, + ReadTagAndGetValue(input, tag, value)); +} + +TEST_F(pkixder_input_tests, ReadTagAndGetValue_Input_InvalidHighTagNumberForm3) +{ + // High tag number form is not allowed (2 byte legal tag) + // + // ReadTagAndGetValue's check to prohibit the high tag number form has no + // effect on whether this test passes or fails, because ReadTagAndGetValue + // will interpret the second byte as a length, and the input doesn't have + // that many bytes following it. This test is here to guard against the case + // where somebody actually implements high tag number form parsing, to remind + // that person that they need to add tests here, including in particular + // tests for overly-long encodings. + static const uint8_t DER[] = { + 0x1F, // high tag number form indicator + 0x80 | 0x01, 0x00, // tag 0x100 (256) + 0 // length zero + }; + Input buf(DER); + Reader input(buf); + uint8_t tag; + Input value; + ASSERT_EQ(Result::ERROR_BAD_DER, + ReadTagAndGetValue(input, tag, value)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Reader_ValidEmpty) +{ + Input buf(DER_SEQUENCE_EMPTY); + Reader input(buf); + Reader value; + ASSERT_EQ(Success, ExpectTagAndGetValue(input, SEQUENCE, value)); + ASSERT_TRUE(value.AtEnd()); + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Reader_ValidNotEmpty) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY); + Reader input(buf); + Reader value; + ASSERT_EQ(Success, ExpectTagAndGetValue(input, SEQUENCE, value)); + ASSERT_TRUE(value.MatchRest(DER_SEQUENCE_NOT_EMPTY_VALUE)); + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, + ExpectTagAndGetValue_Reader_InvalidNotEmptyValueTruncated) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED); + Reader input(buf); + Reader value; + ASSERT_EQ(Result::ERROR_BAD_DER, + ExpectTagAndGetValue(input, SEQUENCE, value)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Reader_InvalidWrongLength) +{ + Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8); + Reader input(buf); + Reader value; + ASSERT_EQ(Result::ERROR_BAD_DER, + ExpectTagAndGetValue(input, SEQUENCE, value)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Reader_InvalidWrongTag) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY); + Reader input(buf); + Reader value; + ASSERT_EQ(Result::ERROR_BAD_DER, + ExpectTagAndGetValue(input, INTEGER, value)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Input_ValidEmpty) +{ + Input buf(DER_SEQUENCE_EMPTY); + Reader input(buf); + Input value; + ASSERT_EQ(Success, ExpectTagAndGetValue(input, SEQUENCE, value)); + ASSERT_EQ(0u, value.GetLength()); + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Input_ValidNotEmpty) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY); + Reader input(buf); + Input value; + ASSERT_EQ(Success, ExpectTagAndGetValue(input, SEQUENCE, value)); + Input expected(DER_SEQUENCE_NOT_EMPTY_VALUE); + ASSERT_TRUE(InputsAreEqual(expected, value)); + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, + ExpectTagAndGetValue_Input_InvalidNotEmptyValueTruncated) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED); + Reader input(buf); + Input value; + ASSERT_EQ(Result::ERROR_BAD_DER, + ExpectTagAndGetValue(input, SEQUENCE, value)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Input_InvalidWrongLength) +{ + Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8); + Reader input(buf); + Input value; + ASSERT_EQ(Result::ERROR_BAD_DER, + ExpectTagAndGetValue(input, SEQUENCE, value)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetValue_Input_InvalidWrongTag) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY); + Reader input(buf); + Input value; + ASSERT_EQ(Result::ERROR_BAD_DER, + ExpectTagAndGetValue(input, INTEGER, value)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndEmptyValue_ValidEmpty) +{ + Input buf(DER_SEQUENCE_EMPTY); + Reader input(buf); + ASSERT_EQ(Success, ExpectTagAndEmptyValue(input, SEQUENCE)); + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, ExpectTagAndEmptyValue_InValidNotEmpty) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY); + Reader input(buf); + ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndEmptyValue(input, SEQUENCE)); +} + +TEST_F(pkixder_input_tests, + ExpectTagAndEmptyValue_Input_InvalidNotEmptyValueTruncated) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED); + Reader input(buf); + ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndEmptyValue(input, SEQUENCE)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndEmptyValue_InvalidWrongLength) +{ + Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8); + Reader input(buf); + ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndEmptyValue(input, SEQUENCE)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndEmptyValue_InvalidWrongTag) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY); + Reader input(buf); + ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndEmptyValue(input, INTEGER)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetTLV_Input_ValidEmpty) +{ + Input buf(DER_SEQUENCE_EMPTY); + Reader input(buf); + Input tlv; + ASSERT_EQ(Success, ExpectTagAndGetTLV(input, SEQUENCE, tlv)); + Input expected(DER_SEQUENCE_EMPTY); + ASSERT_TRUE(InputsAreEqual(expected, tlv)); + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetTLV_Input_ValidNotEmpty) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY); + Reader input(buf); + Input tlv; + ASSERT_EQ(Success, ExpectTagAndGetTLV(input, SEQUENCE, tlv)); + Input expected(DER_SEQUENCE_NOT_EMPTY); + ASSERT_TRUE(InputsAreEqual(expected, tlv)); + ASSERT_TRUE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, + ExpectTagAndGetTLV_Input_InvalidNotEmptyValueTruncated) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY_VALUE_TRUNCATED); + Reader input(buf); + Input tlv; + ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndGetTLV(input, SEQUENCE, tlv)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetTLV_Input_InvalidWrongLength) +{ + Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8); + Reader input(buf); + Input tlv; + ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndGetTLV(input, SEQUENCE, tlv)); +} + +TEST_F(pkixder_input_tests, ExpectTagAndGetTLV_Input_InvalidWrongTag) +{ + Input buf(DER_SEQUENCE_NOT_EMPTY); + Reader input(buf); + Input tlv; + ASSERT_EQ(Result::ERROR_BAD_DER, ExpectTagAndGetTLV(input, INTEGER, tlv)); +} + +TEST_F(pkixder_input_tests, EndAtEnd) +{ + Input buf(DER_INT16); + Reader input(buf); + ASSERT_EQ(Success, input.Skip(4)); + ASSERT_EQ(Success, End(input)); +} + +TEST_F(pkixder_input_tests, EndBeforeEnd) +{ + Input buf(DER_INT16); + Reader input(buf); + ASSERT_EQ(Success, input.Skip(2)); + ASSERT_EQ(Result::ERROR_BAD_DER, End(input)); +} + +TEST_F(pkixder_input_tests, EndAtBeginning) +{ + Input buf(DER_INT16); + Reader input(buf); + ASSERT_EQ(Result::ERROR_BAD_DER, End(input)); +} + +// TODO: Need tests for Nested too? + +Result NestedOfHelper(Reader& input, std::vector<uint8_t>& readValues) +{ + uint8_t value = 0; + Result rv = input.Read(value); + EXPECT_EQ(Success, rv); + if (rv != Success) { + return rv; + } + readValues.push_back(value); + return Success; +} + +TEST_F(pkixder_input_tests, NestedOf) +{ + Input buf(DER_SEQUENCE_OF_INT8); + Reader input(buf); + + std::vector<uint8_t> readValues; + ASSERT_EQ(Success, + NestedOf(input, SEQUENCE, INTEGER, EmptyAllowed::No, + [&readValues](Reader& r) { + return NestedOfHelper(r, readValues); + })); + ASSERT_EQ(3u, readValues.size()); + ASSERT_EQ(0x01, readValues[0]); + ASSERT_EQ(0x02, readValues[1]); + ASSERT_EQ(0x03, readValues[2]); + ASSERT_EQ(Success, End(input)); +} + +TEST_F(pkixder_input_tests, NestedOfWithTruncatedData) +{ + Input buf(DER_TRUNCATED_SEQUENCE_OF_INT8); + Reader input(buf); + + std::vector<uint8_t> readValues; + ASSERT_EQ(Result::ERROR_BAD_DER, + NestedOf(input, SEQUENCE, INTEGER, EmptyAllowed::No, + [&readValues](Reader& r) { + return NestedOfHelper(r, readValues); + })); + ASSERT_EQ(0u, readValues.size()); +} + +TEST_F(pkixder_input_tests, MatchRestAtEnd) +{ + static const uint8_t der[1] = { }; + Input buf; + ASSERT_EQ(Success, buf.Init(der, 0)); + Reader input(buf); + ASSERT_TRUE(input.AtEnd()); + static const uint8_t toMatch[] = { 1 }; + ASSERT_FALSE(input.MatchRest(toMatch)); +} + +TEST_F(pkixder_input_tests, MatchRest1Match) +{ + static const uint8_t der[] = { 1 }; + Input buf(der); + Reader input(buf); + ASSERT_FALSE(input.AtEnd()); + ASSERT_TRUE(input.MatchRest(der)); +} + +TEST_F(pkixder_input_tests, MatchRest1Mismatch) +{ + static const uint8_t der[] = { 1 }; + Input buf(der); + Reader input(buf); + static const uint8_t toMatch[] = { 2 }; + ASSERT_FALSE(input.MatchRest(toMatch)); + ASSERT_FALSE(input.AtEnd()); +} + +TEST_F(pkixder_input_tests, MatchRest2WithTrailingByte) +{ + static const uint8_t der[] = { 1, 2, 3 }; + Input buf(der); + Reader input(buf); + static const uint8_t toMatch[] = { 1, 2 }; + ASSERT_FALSE(input.MatchRest(toMatch)); +} + +TEST_F(pkixder_input_tests, MatchRest2Mismatch) +{ + static const uint8_t der[] = { 1, 2, 3 }; + Input buf(der); + Reader input(buf); + static const uint8_t toMatchMismatch[] = { 1, 3 }; + ASSERT_FALSE(input.MatchRest(toMatchMismatch)); + ASSERT_TRUE(input.MatchRest(der)); +} + +} // namespace diff --git a/security/nss/gtests/mozpkix_gtest/pkixder_pki_types_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixder_pki_types_tests.cpp new file mode 100644 index 000000000..989f3d296 --- /dev/null +++ b/security/nss/gtests/mozpkix_gtest/pkixder_pki_types_tests.cpp @@ -0,0 +1,480 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2013 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <functional> +#include <vector> + +#include "pkixgtest.h" + +#include "mozpkix/pkixtypes.h" +#include "mozpkix/pkixder.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::der; + +class pkixder_pki_types_tests : public ::testing::Test { }; + +TEST_F(pkixder_pki_types_tests, CertificateSerialNumber) +{ + const uint8_t DER_CERT_SERIAL[] = { + 0x02, // INTEGER + 8, // length + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef + }; + Input input(DER_CERT_SERIAL); + Reader reader(input); + + Input item; + ASSERT_EQ(Success, CertificateSerialNumber(reader, item)); + + Input expected; + ASSERT_EQ(Success, + expected.Init(DER_CERT_SERIAL + 2, sizeof DER_CERT_SERIAL - 2)); + ASSERT_TRUE(InputsAreEqual(expected, item)); +} + +TEST_F(pkixder_pki_types_tests, CertificateSerialNumberLongest) +{ + const uint8_t DER_CERT_SERIAL_LONGEST[] = { + 0x02, // INTEGER + 20, // length + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 + }; + Input input(DER_CERT_SERIAL_LONGEST); + Reader reader(input); + + Input item; + ASSERT_EQ(Success, CertificateSerialNumber(reader, item)); + + Input expected; + ASSERT_EQ(Success, + expected.Init(DER_CERT_SERIAL_LONGEST + 2, + sizeof DER_CERT_SERIAL_LONGEST - 2)); + ASSERT_TRUE(InputsAreEqual(expected, item)); +} + +TEST_F(pkixder_pki_types_tests, CertificateSerialNumberCrazyLong) +{ + const uint8_t DER_CERT_SERIAL_CRAZY_LONG[] = { + 0x02, // INTEGER + 32, // length + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 + }; + Input input(DER_CERT_SERIAL_CRAZY_LONG); + Reader reader(input); + + Input item; + ASSERT_EQ(Success, CertificateSerialNumber(reader, item)); +} + +TEST_F(pkixder_pki_types_tests, CertificateSerialNumberZeroLength) +{ + const uint8_t DER_CERT_SERIAL_ZERO_LENGTH[] = { + 0x02, // INTEGER + 0x00 // length + }; + Input input(DER_CERT_SERIAL_ZERO_LENGTH); + Reader reader(input); + + Input item; + ASSERT_EQ(Result::ERROR_INVALID_INTEGER_ENCODING, + CertificateSerialNumber(reader, item)); +} + +TEST_F(pkixder_pki_types_tests, OptionalVersionV1ExplicitEncodingAllowed) +{ + const uint8_t DER_OPTIONAL_VERSION_V1[] = { + 0xa0, 0x03, // context specific 0 + 0x02, 0x01, 0x00 // INTEGER(0) + }; + Input input(DER_OPTIONAL_VERSION_V1); + Reader reader(input); + + // XXX(bug 1031093): We shouldn't accept an explicit encoding of v1, but we + // do here for compatibility reasons. + // Version version; + // ASSERT_EQ(Result::ERROR_BAD_DER, OptionalVersion(reader, version)); + der::Version version = der::Version::v3; + ASSERT_EQ(Success, OptionalVersion(reader, version)); + ASSERT_EQ(der::Version::v1, version); +} + +TEST_F(pkixder_pki_types_tests, OptionalVersionV2) +{ + const uint8_t DER_OPTIONAL_VERSION_V2[] = { + 0xa0, 0x03, // context specific 0 + 0x02, 0x01, 0x01 // INTEGER(1) + }; + Input input(DER_OPTIONAL_VERSION_V2); + Reader reader(input); + + der::Version version = der::Version::v1; + ASSERT_EQ(Success, OptionalVersion(reader, version)); + ASSERT_EQ(der::Version::v2, version); +} + +TEST_F(pkixder_pki_types_tests, OptionalVersionV3) +{ + const uint8_t DER_OPTIONAL_VERSION_V3[] = { + 0xa0, 0x03, // context specific 0 + 0x02, 0x01, 0x02 // INTEGER(2) + }; + Input input(DER_OPTIONAL_VERSION_V3); + Reader reader(input); + + der::Version version = der::Version::v1; + ASSERT_EQ(Success, OptionalVersion(reader, version)); + ASSERT_EQ(der::Version::v3, version); +} + +TEST_F(pkixder_pki_types_tests, OptionalVersionUnknown) +{ + const uint8_t DER_OPTIONAL_VERSION_INVALID[] = { + 0xa0, 0x03, // context specific 0 + 0x02, 0x01, 0x42 // INTEGER(0x42) + }; + Input input(DER_OPTIONAL_VERSION_INVALID); + Reader reader(input); + + der::Version version = der::Version::v1; + ASSERT_EQ(Result::ERROR_BAD_DER, OptionalVersion(reader, version)); +} + +TEST_F(pkixder_pki_types_tests, OptionalVersionInvalidTooLong) +{ + const uint8_t DER_OPTIONAL_VERSION_INVALID_TOO_LONG[] = { + 0xa0, 0x03, // context specific 0 + 0x02, 0x02, 0x12, 0x34 // INTEGER(0x1234) + }; + Input input(DER_OPTIONAL_VERSION_INVALID_TOO_LONG); + Reader reader(input); + + der::Version version; + ASSERT_EQ(Result::ERROR_BAD_DER, OptionalVersion(reader, version)); +} + +TEST_F(pkixder_pki_types_tests, OptionalVersionMissing) +{ + const uint8_t DER_OPTIONAL_VERSION_MISSING[] = { + 0x02, 0x11, 0x22 // INTEGER + }; + Input input(DER_OPTIONAL_VERSION_MISSING); + Reader reader(input); + + der::Version version = der::Version::v3; + ASSERT_EQ(Success, OptionalVersion(reader, version)); + ASSERT_EQ(der::Version::v1, version); +} + +static const size_t MAX_ALGORITHM_OID_DER_LENGTH = 13; + +struct InvalidAlgorithmIdentifierTestInfo +{ + uint8_t der[MAX_ALGORITHM_OID_DER_LENGTH]; + size_t derLength; +}; + +struct ValidDigestAlgorithmIdentifierTestInfo +{ + DigestAlgorithm algorithm; + uint8_t der[MAX_ALGORITHM_OID_DER_LENGTH]; + size_t derLength; +}; + +class pkixder_DigestAlgorithmIdentifier_Valid + : public ::testing::Test + , public ::testing::WithParamInterface<ValidDigestAlgorithmIdentifierTestInfo> +{ +}; + +static const ValidDigestAlgorithmIdentifierTestInfo + VALID_DIGEST_ALGORITHM_TEST_INFO[] = +{ + { DigestAlgorithm::sha512, + { 0x30, 0x0b, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03 }, + 13 + }, + { DigestAlgorithm::sha384, + { 0x30, 0x0b, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02 }, + 13 + }, + { DigestAlgorithm::sha256, + { 0x30, 0x0b, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 }, + 13 + }, + { DigestAlgorithm::sha1, + { 0x30, 0x07, 0x06, 0x05, + 0x2b, 0x0e, 0x03, 0x02, 0x1a }, + 9 + }, +}; + +TEST_P(pkixder_DigestAlgorithmIdentifier_Valid, Valid) +{ + const ValidDigestAlgorithmIdentifierTestInfo& param(GetParam()); + + { + Input input; + ASSERT_EQ(Success, input.Init(param.der, param.derLength)); + Reader reader(input); + DigestAlgorithm alg; + ASSERT_EQ(Success, DigestAlgorithmIdentifier(reader, alg)); + ASSERT_EQ(param.algorithm, alg); + ASSERT_EQ(Success, End(reader)); + } + + { + uint8_t derWithNullParam[MAX_ALGORITHM_OID_DER_LENGTH + 2]; + memcpy(derWithNullParam, param.der, param.derLength); + derWithNullParam[1] += 2; // we're going to expand the value by 2 bytes + derWithNullParam[param.derLength] = 0x05; // NULL tag + derWithNullParam[param.derLength + 1] = 0x00; // length zero + + Input input; + ASSERT_EQ(Success, input.Init(derWithNullParam, param.derLength + 2)); + Reader reader(input); + DigestAlgorithm alg; + ASSERT_EQ(Success, DigestAlgorithmIdentifier(reader, alg)); + ASSERT_EQ(param.algorithm, alg); + ASSERT_EQ(Success, End(reader)); + } +} + +INSTANTIATE_TEST_CASE_P(pkixder_DigestAlgorithmIdentifier_Valid, + pkixder_DigestAlgorithmIdentifier_Valid, + testing::ValuesIn(VALID_DIGEST_ALGORITHM_TEST_INFO)); + +class pkixder_DigestAlgorithmIdentifier_Invalid + : public ::testing::Test + , public ::testing::WithParamInterface<InvalidAlgorithmIdentifierTestInfo> +{ +}; + +static const InvalidAlgorithmIdentifierTestInfo + INVALID_DIGEST_ALGORITHM_TEST_INFO[] = +{ + { // MD5 + { 0x30, 0x0a, 0x06, 0x08, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05 }, + 12, + }, + { // ecdsa-with-SHA256 (1.2.840.10045.4.3.2) (not a hash algorithm) + { 0x30, 0x0a, 0x06, 0x08, + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02 }, + 12, + }, +}; + +TEST_P(pkixder_DigestAlgorithmIdentifier_Invalid, Invalid) +{ + const InvalidAlgorithmIdentifierTestInfo& param(GetParam()); + Input input; + ASSERT_EQ(Success, input.Init(param.der, param.derLength)); + Reader reader(input); + DigestAlgorithm alg; + ASSERT_EQ(Result::ERROR_INVALID_ALGORITHM, + DigestAlgorithmIdentifier(reader, alg)); +} + +INSTANTIATE_TEST_CASE_P(pkixder_DigestAlgorithmIdentifier_Invalid, + pkixder_DigestAlgorithmIdentifier_Invalid, + testing::ValuesIn(INVALID_DIGEST_ALGORITHM_TEST_INFO)); + +struct ValidSignatureAlgorithmIdentifierValueTestInfo +{ + PublicKeyAlgorithm publicKeyAlg; + DigestAlgorithm digestAlg; + uint8_t der[MAX_ALGORITHM_OID_DER_LENGTH]; + size_t derLength; +}; + +static const ValidSignatureAlgorithmIdentifierValueTestInfo + VALID_SIGNATURE_ALGORITHM_VALUE_TEST_INFO[] = +{ + // ECDSA + { PublicKeyAlgorithm::ECDSA, + DigestAlgorithm::sha512, + { 0x06, 0x08, + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04 }, + 10, + }, + { PublicKeyAlgorithm::ECDSA, + DigestAlgorithm::sha384, + { 0x06, 0x08, + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03 }, + 10, + }, + { PublicKeyAlgorithm::ECDSA, + DigestAlgorithm::sha256, + { 0x06, 0x08, + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02 }, + 10, + }, + { PublicKeyAlgorithm::ECDSA, + DigestAlgorithm::sha1, + { 0x06, 0x07, + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x01 }, + 9, + }, + + // RSA + { PublicKeyAlgorithm::RSA_PKCS1, + DigestAlgorithm::sha512, + { 0x06, 0x09, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0d }, + 11, + }, + { PublicKeyAlgorithm::RSA_PKCS1, + DigestAlgorithm::sha384, + { 0x06, 0x09, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0c }, + 11, + }, + { PublicKeyAlgorithm::RSA_PKCS1, + DigestAlgorithm::sha256, + { 0x06, 0x09, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b }, + 11, + }, + { PublicKeyAlgorithm::RSA_PKCS1, + DigestAlgorithm::sha1, + // IETF Standard OID + { 0x06, 0x09, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05 }, + 11, + }, + { PublicKeyAlgorithm::RSA_PKCS1, + DigestAlgorithm::sha1, + // Legacy OIW OID (bug 1042479) + { 0x06, 0x05, + 0x2b, 0x0e, 0x03, 0x02, 0x1d }, + 7, + }, +}; + +class pkixder_SignatureAlgorithmIdentifierValue_Valid + : public ::testing::Test + , public ::testing::WithParamInterface< + ValidSignatureAlgorithmIdentifierValueTestInfo> +{ +}; + +TEST_P(pkixder_SignatureAlgorithmIdentifierValue_Valid, Valid) +{ + const ValidSignatureAlgorithmIdentifierValueTestInfo& param(GetParam()); + + { + Input input; + ASSERT_EQ(Success, input.Init(param.der, param.derLength)); + Reader reader(input); + PublicKeyAlgorithm publicKeyAlg; + DigestAlgorithm digestAlg; + ASSERT_EQ(Success, + SignatureAlgorithmIdentifierValue(reader, publicKeyAlg, + digestAlg)); + ASSERT_EQ(param.publicKeyAlg, publicKeyAlg); + ASSERT_EQ(param.digestAlg, digestAlg); + ASSERT_EQ(Success, End(reader)); + } + + { + uint8_t derWithNullParam[MAX_ALGORITHM_OID_DER_LENGTH + 2]; + memcpy(derWithNullParam, param.der, param.derLength); + derWithNullParam[param.derLength] = 0x05; // NULL tag + derWithNullParam[param.derLength + 1] = 0x00; // length zero + + Input input; + ASSERT_EQ(Success, input.Init(derWithNullParam, param.derLength + 2)); + Reader reader(input); + PublicKeyAlgorithm publicKeyAlg; + DigestAlgorithm digestAlg; + ASSERT_EQ(Success, + SignatureAlgorithmIdentifierValue(reader, publicKeyAlg, + digestAlg)); + ASSERT_EQ(param.publicKeyAlg, publicKeyAlg); + ASSERT_EQ(param.digestAlg, digestAlg); + ASSERT_EQ(Success, End(reader)); + } +} + +INSTANTIATE_TEST_CASE_P( + pkixder_SignatureAlgorithmIdentifierValue_Valid, + pkixder_SignatureAlgorithmIdentifierValue_Valid, + testing::ValuesIn(VALID_SIGNATURE_ALGORITHM_VALUE_TEST_INFO)); + +static const InvalidAlgorithmIdentifierTestInfo + INVALID_SIGNATURE_ALGORITHM_VALUE_TEST_INFO[] = +{ + // id-dsa-with-sha256 (2.16.840.1.101.3.4.3.2) + { { 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x02 }, + 11, + }, + + // id-dsa-with-sha1 (1.2.840.10040.4.3) + { { 0x06, 0x07, + 0x2a, 0x86, 0x48, 0xce, 0x38, 0x04, 0x03 }, + 9, + }, + + // RSA-with-MD5 (1.2.840.113549.1.1.4) + { { 0x06, 0x09, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x04 }, + 11, + }, + + // id-sha256 (2.16.840.1.101.3.4.2.1). It is invalid because SHA-256 is not + // a signature algorithm. + { { 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 }, + 11, + }, +}; + +class pkixder_SignatureAlgorithmIdentifier_Invalid + : public ::testing::Test + , public ::testing::WithParamInterface<InvalidAlgorithmIdentifierTestInfo> +{ +}; + +TEST_P(pkixder_SignatureAlgorithmIdentifier_Invalid, Invalid) +{ + const InvalidAlgorithmIdentifierTestInfo& param(GetParam()); + Input input; + ASSERT_EQ(Success, input.Init(param.der, param.derLength)); + Reader reader(input); + der::PublicKeyAlgorithm publicKeyAlg; + DigestAlgorithm digestAlg; + ASSERT_EQ(Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED, + SignatureAlgorithmIdentifierValue(reader, publicKeyAlg, digestAlg)); +} + +INSTANTIATE_TEST_CASE_P( + pkixder_SignatureAlgorithmIdentifier_Invalid, + pkixder_SignatureAlgorithmIdentifier_Invalid, + testing::ValuesIn(INVALID_SIGNATURE_ALGORITHM_VALUE_TEST_INFO)); diff --git a/security/nss/gtests/mozpkix_gtest/pkixder_universal_types_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixder_universal_types_tests.cpp new file mode 100644 index 000000000..260c735ec --- /dev/null +++ b/security/nss/gtests/mozpkix_gtest/pkixder_universal_types_tests.cpp @@ -0,0 +1,1226 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2013 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <limits> +#include <stdint.h> +#include <vector> + +#include "pkixgtest.h" + +#include "mozpkix/pkixder.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::der; +using namespace mozilla::pkix::test; +using namespace std; + +class pkixder_universal_types_tests : public ::testing::Test { }; + +TEST_F(pkixder_universal_types_tests, BooleanTrue01) +{ + const uint8_t DER_BOOLEAN_TRUE_01[] = { + 0x01, // BOOLEAN + 0x01, // length + 0x01 // invalid + }; + Input input(DER_BOOLEAN_TRUE_01); + Reader reader(input); + bool value = false; + ASSERT_EQ(Result::ERROR_BAD_DER, Boolean(reader, value)); +} + +TEST_F(pkixder_universal_types_tests, BooleanTrue42) +{ + const uint8_t DER_BOOLEAN_TRUE_42[] = { + 0x01, // BOOLEAN + 0x01, // length + 0x42 // invalid + }; + Input input(DER_BOOLEAN_TRUE_42); + Reader reader(input); + bool value = false; + ASSERT_EQ(Result::ERROR_BAD_DER, Boolean(reader, value)); +} + +static const uint8_t DER_BOOLEAN_TRUE[] = { + 0x01, // BOOLEAN + 0x01, // length + 0xff // true +}; + +TEST_F(pkixder_universal_types_tests, BooleanTrueFF) +{ + Input input(DER_BOOLEAN_TRUE); + Reader reader(input); + bool value = false; + ASSERT_EQ(Success, Boolean(reader, value)); + ASSERT_TRUE(value); +} + +TEST_F(pkixder_universal_types_tests, BooleanFalse) +{ + const uint8_t DER_BOOLEAN_FALSE[] = { + 0x01, // BOOLEAN + 0x01, // length + 0x00 // false + }; + Input input(DER_BOOLEAN_FALSE); + Reader reader(input); + + bool value = true; + ASSERT_EQ(Success, Boolean(reader, value)); + ASSERT_FALSE(value); +} + +TEST_F(pkixder_universal_types_tests, BooleanInvalidLength) +{ + const uint8_t DER_BOOLEAN_INVALID_LENGTH[] = { + 0x01, // BOOLEAN + 0x02, // length + 0x42, 0x42 // invalid + }; + Input input(DER_BOOLEAN_INVALID_LENGTH); + Reader reader(input); + + bool value = true; + ASSERT_EQ(Result::ERROR_BAD_DER, Boolean(reader, value)); +} + +TEST_F(pkixder_universal_types_tests, BooleanInvalidZeroLength) +{ + const uint8_t DER_BOOLEAN_INVALID_ZERO_LENGTH[] = { + 0x01, // BOOLEAN + 0x00 // length + }; + Input input(DER_BOOLEAN_INVALID_ZERO_LENGTH); + Reader reader(input); + + bool value = true; + ASSERT_EQ(Result::ERROR_BAD_DER, Boolean(reader, value)); +} + +// OptionalBoolean implements decoding of OPTIONAL BOOLEAN DEFAULT FALSE. +// If the field is present, it must be a valid encoding of a BOOLEAN with +// value TRUE. If the field is not present, it defaults to FALSE. For +// compatibility reasons, OptionalBoolean also accepts encodings where the field +// is present with value FALSE (this is technically not a valid DER encoding). +TEST_F(pkixder_universal_types_tests, OptionalBooleanValidEncodings) +{ + { + const uint8_t DER_OPTIONAL_BOOLEAN_PRESENT_TRUE[] = { + 0x01, // BOOLEAN + 0x01, // length + 0xff // true + }; + Input input(DER_OPTIONAL_BOOLEAN_PRESENT_TRUE); + Reader reader(input); + bool value = false; + ASSERT_EQ(Success, OptionalBoolean(reader, value)) << + "Should accept the only valid encoding of a present OPTIONAL BOOLEAN"; + ASSERT_TRUE(value); + ASSERT_TRUE(reader.AtEnd()); + } + + { + // The OPTIONAL BOOLEAN is omitted in this data. + const uint8_t DER_INTEGER_05[] = { + 0x02, // INTEGER + 0x01, // length + 0x05 + }; + Input input(DER_INTEGER_05); + Reader reader(input); + bool value = true; + ASSERT_EQ(Success, OptionalBoolean(reader, value)) << + "Should accept a valid encoding of an omitted OPTIONAL BOOLEAN"; + ASSERT_FALSE(value); + ASSERT_FALSE(reader.AtEnd()); + } + + { + Input input; + ASSERT_EQ(Success, input.Init(reinterpret_cast<const uint8_t*>(""), 0)); + Reader reader(input); + bool value = true; + ASSERT_EQ(Success, OptionalBoolean(reader, value)) << + "Should accept another valid encoding of an omitted OPTIONAL BOOLEAN"; + ASSERT_FALSE(value); + ASSERT_TRUE(reader.AtEnd()); + } +} + +TEST_F(pkixder_universal_types_tests, OptionalBooleanInvalidEncodings) +{ + const uint8_t DER_OPTIONAL_BOOLEAN_PRESENT_FALSE[] = { + 0x01, // BOOLEAN + 0x01, // length + 0x00 // false + }; + + { + Input input(DER_OPTIONAL_BOOLEAN_PRESENT_FALSE); + Reader reader(input); + bool value = true; + ASSERT_EQ(Success, OptionalBoolean(reader, value)) << + "Should accept an invalid, default-value encoding of OPTIONAL BOOLEAN"; + ASSERT_FALSE(value); + ASSERT_TRUE(reader.AtEnd()); + } + + const uint8_t DER_OPTIONAL_BOOLEAN_PRESENT_42[] = { + 0x01, // BOOLEAN + 0x01, // length + 0x42 // (invalid value for a BOOLEAN) + }; + + { + Input input(DER_OPTIONAL_BOOLEAN_PRESENT_42); + Reader reader(input); + bool value; + ASSERT_EQ(Result::ERROR_BAD_DER, OptionalBoolean(reader, value)) << + "Should reject an invalid-valued encoding of OPTIONAL BOOLEAN"; + } +} + +TEST_F(pkixder_universal_types_tests, Enumerated) +{ + const uint8_t DER_ENUMERATED[] = { + 0x0a, // ENUMERATED + 0x01, // length + 0x42 // value + }; + Input input(DER_ENUMERATED); + Reader reader(input); + + uint8_t value = 0; + ASSERT_EQ(Success, Enumerated(reader, value)); + ASSERT_EQ(0x42, value); +} + +TEST_F(pkixder_universal_types_tests, EnumeratedNotShortestPossibleDER) +{ + const uint8_t DER_ENUMERATED[] = { + 0x0a, // ENUMERATED + 0x02, // length + 0x00, 0x01 // value + }; + Input input(DER_ENUMERATED); + Reader reader(input); + + uint8_t value = 0; + ASSERT_EQ(Result::ERROR_INVALID_INTEGER_ENCODING, Enumerated(reader, value)); +} + +TEST_F(pkixder_universal_types_tests, EnumeratedOutOfAcceptedRange) +{ + // Although this is a valid ENUMERATED value according to ASN.1, we + // intentionally don't support these large values because there are no + // ENUMERATED values in X.509 certs or OCSP this large, and we're trying to + // keep the parser simple and fast. + const uint8_t DER_ENUMERATED_INVALID_LENGTH[] = { + 0x0a, // ENUMERATED + 0x02, // length + 0x12, 0x34 // value + }; + Input input(DER_ENUMERATED_INVALID_LENGTH); + Reader reader(input); + + uint8_t value = 0; + ASSERT_EQ(Result::ERROR_INVALID_INTEGER_ENCODING, Enumerated(reader, value)); +} + +TEST_F(pkixder_universal_types_tests, EnumeratedInvalidZeroLength) +{ + const uint8_t DER_ENUMERATED_INVALID_ZERO_LENGTH[] = { + 0x0a, // ENUMERATED + 0x00 // length + }; + Input input(DER_ENUMERATED_INVALID_ZERO_LENGTH); + Reader reader(input); + + uint8_t value = 0; + ASSERT_EQ(Result::ERROR_INVALID_INTEGER_ENCODING, Enumerated(reader, value)); +} + +//////////////////////////////////////// +// GeneralizedTime and TimeChoice +// +// From RFC 5280 section 4.1.2.5.2 +// +// For the purposes of this profile, GeneralizedTime values MUST be +// expressed in Greenwich Mean Time (Zulu) and MUST include seconds +// (i.e., times are YYYYMMDDHHMMSSZ), even where the number of seconds +// is zero. GeneralizedTime values MUST NOT include fractional seconds. +// +// And from from RFC 6960 (OCSP) section 4.2.2.1: +// +// Responses can contain four times -- thisUpdate, nextUpdate, +// producedAt, and revocationTime. The semantics of these fields are +// defined in Section 2.4. The format for GeneralizedTime is as +// specified in Section 4.1.2.5.2 of [RFC5280]. +// +// So while we can could accept other ASN1 (ITU-T X.680) encodings for +// GeneralizedTime we should not accept them, and breaking reading of these +// other encodings is actually encouraged. + +// e.g. TWO_CHARS(53) => '5', '3' +#define TWO_CHARS(t) \ + static_cast<uint8_t>('0' + (static_cast<uint8_t>(t) / 10u)), \ + static_cast<uint8_t>('0' + (static_cast<uint8_t>(t) % 10u)) + +// Calls TimeChoice on the UTCTime variant of the given generalized time. +template <uint16_t LENGTH> +Result +TimeChoiceForEquivalentUTCTime(const uint8_t (&generalizedTimeDER)[LENGTH], + /*out*/ Time& value) +{ + static_assert(LENGTH >= 4, + "TimeChoiceForEquivalentUTCTime input too small"); + uint8_t utcTimeDER[LENGTH - 2]; + utcTimeDER[0] = 0x17; // tag UTCTime + utcTimeDER[1] = LENGTH - 1/*tag*/ - 1/*value*/ - 2/*century*/; + // Copy the value except for the first two digits of the year + for (size_t i = 2; i < LENGTH - 2; ++i) { + utcTimeDER[i] = generalizedTimeDER[i + 2]; + } + + Input input(utcTimeDER); + Reader reader(input); + return TimeChoice(reader, value); +} + +template <uint16_t LENGTH> +void +ExpectGoodTime(Time expectedValue, + const uint8_t (&generalizedTimeDER)[LENGTH]) +{ + // GeneralizedTime + { + Input input(generalizedTimeDER); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Success, GeneralizedTime(reader, value)); + EXPECT_EQ(expectedValue, value); + } + + // TimeChoice: GeneralizedTime + { + Input input(generalizedTimeDER); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Success, TimeChoice(reader, value)); + EXPECT_EQ(expectedValue, value); + } + + // TimeChoice: UTCTime + { + Time value(Time::uninitialized); + ASSERT_EQ(Success, + TimeChoiceForEquivalentUTCTime(generalizedTimeDER, value)); + EXPECT_EQ(expectedValue, value); + } +} + +template <uint16_t LENGTH> +void +ExpectBadTime(const uint8_t (&generalizedTimeDER)[LENGTH]) +{ + // GeneralizedTime + { + Input input(generalizedTimeDER); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, GeneralizedTime(reader, value)); + } + + // TimeChoice: GeneralizedTime + { + Input input(generalizedTimeDER); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, TimeChoice(reader, value)); + } + + // TimeChoice: UTCTime + { + Time value(Time::uninitialized); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, + TimeChoiceForEquivalentUTCTime(generalizedTimeDER, value)); + } +} + +// Control value: a valid time +TEST_F(pkixder_universal_types_tests, ValidControl) +{ + const uint8_t GT_DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '1', '9', '9', '1', '0', '5', '0', '6', '1', '6', '4', '5', '4', '0', 'Z' + }; + ExpectGoodTime(YMDHMS(1991, 5, 6, 16, 45, 40), GT_DER); +} + +TEST_F(pkixder_universal_types_tests, TimeTimeZoneOffset) +{ + const uint8_t DER_GENERALIZED_TIME_OFFSET[] = { + 0x18, // Generalized Time + 19, // Length = 19 + '1', '9', '9', '1', '0', '5', '0', '6', '1', '6', '4', '5', '4', '0', '-', + '0', '7', '0', '0' + }; + ExpectBadTime(DER_GENERALIZED_TIME_OFFSET); +} + +TEST_F(pkixder_universal_types_tests, TimeInvalidZeroLength) +{ + const uint8_t DER_GENERALIZED_TIME_INVALID_ZERO_LENGTH[] = { + 0x18, // GeneralizedTime + 0x00 // Length = 0 + }; + + Time value(Time::uninitialized); + + // GeneralizedTime + Input gtBuf(DER_GENERALIZED_TIME_INVALID_ZERO_LENGTH); + Reader gt(gtBuf); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, GeneralizedTime(gt, value)); + + // TimeChoice: GeneralizedTime + Input tc_gt_buf(DER_GENERALIZED_TIME_INVALID_ZERO_LENGTH); + Reader tc_gt(tc_gt_buf); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, TimeChoice(tc_gt, value)); + + // TimeChoice: UTCTime + const uint8_t DER_UTCTIME_INVALID_ZERO_LENGTH[] = { + 0x17, // UTCTime + 0x00 // Length = 0 + }; + Input tc_utc_buf(DER_UTCTIME_INVALID_ZERO_LENGTH); + Reader tc_utc(tc_utc_buf); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, TimeChoice(tc_utc, value)); +} + +// A non zulu time should fail +TEST_F(pkixder_universal_types_tests, TimeInvalidLocal) +{ + const uint8_t DER_GENERALIZED_TIME_INVALID_LOCAL[] = { + 0x18, // Generalized Time + 14, // Length = 14 + '1', '9', '9', '1', '0', '5', '0', '6', '1', '6', '4', '5', '4', '0' + }; + ExpectBadTime(DER_GENERALIZED_TIME_INVALID_LOCAL); +} + +// A time missing seconds and zulu should fail +TEST_F(pkixder_universal_types_tests, TimeInvalidTruncated) +{ + const uint8_t DER_GENERALIZED_TIME_INVALID_TRUNCATED[] = { + 0x18, // Generalized Time + 12, // Length = 12 + '1', '9', '9', '1', '0', '5', '0', '6', '1', '6', '4', '5' + }; + ExpectBadTime(DER_GENERALIZED_TIME_INVALID_TRUNCATED); +} + +TEST_F(pkixder_universal_types_tests, TimeNoSeconds) +{ + const uint8_t DER_GENERALIZED_TIME_NO_SECONDS[] = { + 0x18, // Generalized Time + 13, // Length = 13 + '1', '9', '9', '1', '0', '5', '0', '6', '1', '6', '4', '5', 'Z' + }; + ExpectBadTime(DER_GENERALIZED_TIME_NO_SECONDS); +} + +TEST_F(pkixder_universal_types_tests, TimeInvalidPrefixedYear) +{ + const uint8_t DER_GENERALIZED_TIME_INVALID_PREFIXED_YEAR[] = { + 0x18, // Generalized Time + 16, // Length = 16 + ' ', '1', '9', '9', '1', '0', '1', '0', '1', '0', '1', '0', '1', '0', '1', 'Z' + }; + ExpectBadTime(DER_GENERALIZED_TIME_INVALID_PREFIXED_YEAR); +} + +TEST_F(pkixder_universal_types_tests, TimeTooManyDigits) +{ + const uint8_t DER_GENERALIZED_TIME_TOO_MANY_DIGITS[] = { + 0x18, // Generalized Time + 16, // Length = 16 + '1', '1', '1', '1', '1', '0', '1', '0', '1', '0', '1', '0', '1', '0', '1', 'Z' + }; + ExpectBadTime(DER_GENERALIZED_TIME_TOO_MANY_DIGITS); +} + +// In order to ensure we we don't run into any trouble with conversions to and +// from time_t we only accept times from 1970 onwards. +TEST_F(pkixder_universal_types_tests, GeneralizedTimeYearValidRange) +{ + // Note that by using the last second of the last day of the year, we're also + // effectively testing all the accumulated conversions from Gregorian to to + // Julian time, including in particular the effects of leap years. + + for (uint16_t i = 1970; i <= 9999; ++i) { + const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + TWO_CHARS(i / 100), TWO_CHARS(i % 100), // YYYY + '1', '2', '3', '1', // 12-31 + '2', '3', '5', '9', '5', '9', 'Z' // 23:59:59Z + }; + + Time expectedValue = YMDHMS(i, 12, 31, 23, 59, 59); + + // We have to test GeneralizedTime separately from UTCTime instead of using + // ExpectGooDtime because the range of UTCTime is less than the range of + // GeneralizedTime. + + // GeneralizedTime + { + Input input(DER); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Success, GeneralizedTime(reader, value)); + EXPECT_EQ(expectedValue, value); + } + + // TimeChoice: GeneralizedTime + { + Input input(DER); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Success, TimeChoice(reader, value)); + EXPECT_EQ(expectedValue, value); + } + + // TimeChoice: UTCTime, which is limited to years less than 2049. + if (i <= 2049) { + Time value(Time::uninitialized); + ASSERT_EQ(Success, TimeChoiceForEquivalentUTCTime(DER, value)); + EXPECT_EQ(expectedValue, value); + } + } +} + +// In order to ensure we we don't run into any trouble with conversions to and +// from time_t we only accept times from 1970 onwards. +TEST_F(pkixder_universal_types_tests, TimeYearInvalid1969) +{ + static const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '1', '9', '6', '9', '1', '2', '3', '1', // !!!1969!!!-12-31 + '2', '3', '5', '9', '5', '9', 'Z' // 23:59:59Z + }; + ExpectBadTime(DER); +} + +static const uint8_t DAYS_IN_MONTH[] = { + 0, // unused + 31, // January + 28, // February (leap years tested separately) + 31, // March + 30, // April + 31, // May + 30, // Jun + 31, // July + 31, // August + 30, // September + 31, // October + 30, // November + 31, // December +}; + +TEST_F(pkixder_universal_types_tests, TimeMonthDaysValidRange) +{ + for (uint16_t month = 1; month <= 12; ++month) { + for (uint8_t day = 1; day <= DAYS_IN_MONTH[month]; ++day) { + const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '5', TWO_CHARS(month), TWO_CHARS(day), // (2015-mm-dd) + '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40 + }; + ExpectGoodTime(YMDHMS(2015, month, day, 16, 45, 40), DER); + } + } +} + +TEST_F(pkixder_universal_types_tests, TimeMonthInvalid0) +{ + static const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '5', '0', '0', '1', '5', // 2015-!!!00!!!-15 + '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40 + }; + ExpectBadTime(DER); +} + +TEST_F(pkixder_universal_types_tests, TimeMonthInvalid13) +{ + const uint8_t DER_GENERALIZED_TIME_13TH_MONTH[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '1', '9', '9', '1', //YYYY (1991) + '1', '3', //MM 13th month of the year + '0', '6', '1', '6', '4', '5', '4', '0', 'Z' + }; + ExpectBadTime(DER_GENERALIZED_TIME_13TH_MONTH); +} + +TEST_F(pkixder_universal_types_tests, TimeDayInvalid0) +{ + static const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '5', '0', '1', '0', '0', // 2015-01-!!!00!!! + '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40 + }; + ExpectBadTime(DER); +} + +TEST_F(pkixder_universal_types_tests, TimeMonthDayInvalidPastEndOfMonth) +{ + for (int16_t month = 1; month <= 12; ++month) { + const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '1', '9', '9', '1', // YYYY 1991 + TWO_CHARS(month), // MM + TWO_CHARS(1 + (month == 2 ? 29 : DAYS_IN_MONTH[month])), // !!!DD!!! + '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40 + }; + ExpectBadTime(DER); + } +} + +TEST_F(pkixder_universal_types_tests, TimeMonthFebLeapYear2016) +{ + static const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '6', '0', '2', '2', '9', // 2016-02-29 + '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40 + }; + ExpectGoodTime(YMDHMS(2016, 2, 29, 16, 45, 40), DER); +} + +TEST_F(pkixder_universal_types_tests, TimeMonthFebLeapYear2000) +{ + static const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '0', '0', '0', '2', '2', '9', // 2000-02-29 + '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40 + }; + ExpectGoodTime(YMDHMS(2000, 2, 29, 16, 45, 40), DER); +} + +TEST_F(pkixder_universal_types_tests, TimeMonthFebLeapYear2400) +{ + static const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '4', '0', '0', '0', '2', '2', '9', // 2400-02-29 + '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40 + }; + + // We don't use ExpectGoodTime here because UTCTime can't represent 2400. + + Time expectedValue = YMDHMS(2400, 2, 29, 16, 45, 40); + + // GeneralizedTime + { + Input input(DER); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Success, GeneralizedTime(reader, value)); + EXPECT_EQ(expectedValue, value); + } + + // TimeChoice: GeneralizedTime + { + Input input(DER); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Success, TimeChoice(reader, value)); + EXPECT_EQ(expectedValue, value); + } +} + +TEST_F(pkixder_universal_types_tests, TimeMonthFebNotLeapYear2014) +{ + static const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '4', '0', '2', '2', '9', // 2014-02-29 + '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40 + }; + ExpectBadTime(DER); +} + +TEST_F(pkixder_universal_types_tests, TimeMonthFebNotLeapYear2100) +{ + static const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '1', '0', '0', '0', '2', '2', '9', // 2100-02-29 + '1', '6', '4', '5', '4', '0', 'Z' // 16:45:40 + }; + + // We don't use ExpectBadTime here because UTCTime can't represent 2100. + + // GeneralizedTime + { + Input input(DER); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, GeneralizedTime(reader, value)); + } + + // TimeChoice: GeneralizedTime + { + Input input(DER); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, TimeChoice(reader, value)); + } +} + +TEST_F(pkixder_universal_types_tests, TimeHoursValidRange) +{ + for (uint8_t i = 0; i <= 23; ++i) { + const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30) + TWO_CHARS(i), '5', '9', '0', '1', 'Z' // HHMMSSZ (!!!!ii!!!!:59:01 Zulu) + }; + ExpectGoodTime(YMDHMS(2012, 6, 30, i, 59, 1), DER); + } +} + +TEST_F(pkixder_universal_types_tests, TimeHoursInvalid_24_00_00) +{ + static const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30) + '2', '4', '0', '0', '0', '0', 'Z' // HHMMSSZ (!!24!!:00:00 Zulu) + }; + ExpectBadTime(DER); +} + +TEST_F(pkixder_universal_types_tests, TimeMinutesValidRange) +{ + for (uint8_t i = 0; i <= 59; ++i) { + const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30) + '2', '3', TWO_CHARS(i), '0', '1', 'Z' // HHMMSSZ (23:!!!!ii!!!!:01 Zulu) + }; + ExpectGoodTime(YMDHMS(2012, 6, 30, 23, i, 1), DER); + } +} + +TEST_F(pkixder_universal_types_tests, TimeMinutesInvalid60) +{ + const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30) + '2', '3', '6', '0', '5', '9', 'Z' // HHMMSSZ (23:!!!60!!!:01 Zulu) + }; + ExpectBadTime(DER); +} + +TEST_F(pkixder_universal_types_tests, TimeSecondsValidRange) +{ + for (uint8_t i = 0; i <= 59; ++i) { + const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30) + '2', '3', '5', '9', TWO_CHARS(i), 'Z' // HHMMSSZ (23:59:!!!!ii!!!! Zulu) + }; + ExpectGoodTime(YMDHMS(2012, 6, 30, 23, 59, i), DER); + } +} + +// No Leap Seconds (60) +TEST_F(pkixder_universal_types_tests, TimeSecondsInvalid60) +{ + static const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30) + '2', '3', '5', '9', '6', '0', 'Z' // HHMMSSZ (23:59:!!!!60!!!! Zulu) + }; + ExpectBadTime(DER); +} + +// No Leap Seconds (61) +TEST_F(pkixder_universal_types_tests, TimeSecondsInvalid61) +{ + static const uint8_t DER[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30) + '2', '3', '5', '9', '6', '1', 'Z' // HHMMSSZ (23:59:!!!!61!!!! Zulu) + }; + ExpectBadTime(DER); +} + +TEST_F(pkixder_universal_types_tests, TimeInvalidZulu) +{ + const uint8_t DER_GENERALIZED_TIME_INVALID_ZULU[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30) + '2', '3', '5', '9', '5', '9', 'z' // HHMMSSZ (23:59:59 !!!z!!!) should be Z + }; + ExpectBadTime(DER_GENERALIZED_TIME_INVALID_ZULU); +} + +TEST_F(pkixder_universal_types_tests, TimeInvalidExtraData) +{ + const uint8_t DER_GENERALIZED_TIME_INVALID_EXTRA_DATA[] = { + 0x18, // Generalized Time + 16, // Length = 16 + '2', '0', '1', '2', '0', '6', '3', '0', // YYYYMMDD (2012-06-30) + '2', '3', '5', '9', '5', '9', 'Z', // HHMMSSZ (23:59:59Z) + 0 // Extra null character + }; + ExpectBadTime(DER_GENERALIZED_TIME_INVALID_EXTRA_DATA); +} + +TEST_F(pkixder_universal_types_tests, TimeInvalidCenturyChar) +{ + const uint8_t DER_GENERALIZED_TIME_INVALID_CENTURY_CHAR[] = { + 0x18, // Generalized Time + 15, // Length = 15 + 'X', '9', '9', '1', '1', '2', '0', '6', // YYYYMMDD (X991-12-06) + '1', '6', '4', '5', '4', '0', 'Z' // HHMMSSZ (16:45:40Z) + }; + + // We can't use ExpectBadTime here, because ExpectBadTime requires + // consistent results for GeneralizedTime and UTCTime, but the results + // for this input are different. + + // GeneralizedTime + { + Input input(DER_GENERALIZED_TIME_INVALID_CENTURY_CHAR); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, GeneralizedTime(reader, value)); + } + + // TimeChoice: GeneralizedTime + { + Input input(DER_GENERALIZED_TIME_INVALID_CENTURY_CHAR); + Reader reader(input); + Time value(Time::uninitialized); + ASSERT_EQ(Result::ERROR_INVALID_DER_TIME, TimeChoice(reader, value)); + } + + // This test is not applicable to TimeChoice: UTCTime +} + +TEST_F(pkixder_universal_types_tests, TimeInvalidYearChar) +{ + const uint8_t DER_GENERALIZED_TIME_INVALID_YEAR_CHAR[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '1', '9', '9', 'I', '0', '1', '0', '6', // YYYYMMDD (199I-12-06) + '1', '6', '4', '5', '4', '0', 'Z' // HHMMSSZ (16:45:40Z) + }; + ExpectBadTime(DER_GENERALIZED_TIME_INVALID_YEAR_CHAR); +} + +TEST_F(pkixder_universal_types_tests, GeneralizedTimeInvalidMonthChar) +{ + const uint8_t DER_GENERALIZED_TIME_INVALID_MONTH_CHAR[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '1', '9', '9', '1', '0', 'I', '0', '6', // YYYYMMDD (1991-0I-06) + '1', '6', '4', '5', '4', '0', 'Z' // HHMMSSZ (16:45:40Z) + }; + ExpectBadTime(DER_GENERALIZED_TIME_INVALID_MONTH_CHAR); +} + +TEST_F(pkixder_universal_types_tests, TimeInvalidDayChar) +{ + const uint8_t DER_GENERALIZED_TIME_INVALID_DAY_CHAR[] = { + 0x18, // Generalized Time + 15, // Length = 15 + '1', '9', '9', '1', '0', '1', '0', 'S', // YYYYMMDD (1991-01-0S) + '1', '6', '4', '5', '4', '0', 'Z' // HHMMSSZ (16:45:40Z) + }; + ExpectBadTime(DER_GENERALIZED_TIME_INVALID_DAY_CHAR); +} + +TEST_F(pkixder_universal_types_tests, TimeInvalidFractionalSeconds) +{ + const uint8_t DER_GENERALIZED_TIME_INVALID_FRACTIONAL_SECONDS[] = { + 0x18, // Generalized Time + 17, // Length = 17 + '1', '9', '9', '1', '0', '1', '0', '1', // YYYYMMDD (1991-01-01) + '1', '6', '4', '5', '4', '0', '.', '3', 'Z' // HHMMSS.FFF (16:45:40.3Z) + }; + ExpectBadTime(DER_GENERALIZED_TIME_INVALID_FRACTIONAL_SECONDS); +} + +struct IntegerTestParams +{ + ByteString encoded; + struct PositiveIntegerParams + { + Result expectedResult; + Input::size_type significantBytesIfValid; + } positiveInteger; + struct SmallNonnegativeIntegerParams + { + Result expectedResult; + uint8_t valueIfValid; + } smallNonnegativeInteger; +}; + +class pkixder_universal_types_tests_Integer + : public ::testing::Test + , public ::testing::WithParamInterface<IntegerTestParams> +{ +}; + +::std::ostream& operator<<(::std::ostream& os, const IntegerTestParams&) +{ + return os << "TODO (bug 1318770)"; +} + +#define INVALID 0xFF + +static const IntegerTestParams INTEGER_TEST_PARAMS[] = +{ + // Zero is encoded with one value byte of 0x00. + { TLV(2, ByteString()), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\x00"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Success, 0 } }, + + // Positive single-byte values + { TLV(2, "\x01"), { Success, 1 }, { Success, 1} }, + { TLV(2, "\x02"), { Success, 1 }, { Success, 2} }, + { TLV(2, "\x7e"), { Success, 1 }, { Success, 0x7e} }, + { TLV(2, "\x7f"), { Success, 1 }, { Success, 0x7f} }, + + // Negative single-byte values + { TLV(2, "\x80"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\x81"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\xFE"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\xFF"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + + // Positive two-byte values not starting with 0x00 + { TLV(2, "\x7F\x00"), + { Success, 2 }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\x01\x00"), + { Success, 2 }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\x01\x02"), + { Success, 2 }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + + // Negative two-byte values not starting with 0xFF + { TLV(2, "\x80\x00"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\x80\x7F"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\x80\x80"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\x80\xFF"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + + // The leading zero is necessary. + { TLV(2, "\x00\x80"), + { Success, 1}, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\x00\x81"), + { Success, 1}, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\x00\xFF"), + { Success, 1}, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + + // The leading zero is unnecessary. + { TLV(2, "\x00\x01"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\x00\x7F"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + + // The leading 0xFF is necessary. + { TLV(2, "\xFF\x00"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\xFF\x7F"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + + // The leading 0xFF is unnecessary. + { TLV(2, "\xFF\x80"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, "\xFF\xFF"), + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + + // Truncated values + { TLV(2, 1, ByteString(/*missing value*/)), + { Result::ERROR_BAD_DER, INVALID }, + { Result::ERROR_BAD_DER, INVALID } }, + { TLV(2, 3, "\x11\x22" /*truncated*/), + { Result::ERROR_BAD_DER, INVALID }, + { Result::ERROR_BAD_DER, INVALID } }, + { TLV(2, 4, "\x11\x22" /*truncated*/), + { Result::ERROR_BAD_DER, INVALID }, + { Result::ERROR_BAD_DER, INVALID } }, + { TLV(2, 2, "\x00" /*truncated*/), + { Result::ERROR_BAD_DER, INVALID }, + { Result::ERROR_BAD_DER, INVALID } }, + { TLV(2, 2, "\xFF" /*truncated*/), + { Result::ERROR_BAD_DER, INVALID }, + { Result::ERROR_BAD_DER, INVALID } }, + { TLV(2, 3, "\x00\x80" /*truncated*/), + { Result::ERROR_BAD_DER, INVALID }, + { Result::ERROR_BAD_DER, INVALID } }, + { TLV(2, 3, "\xFF\x00" /*truncated*/), + { Result::ERROR_BAD_DER, INVALID }, + { Result::ERROR_BAD_DER, INVALID } }, + + // Misc. larger values + { TLV(2, 4, "\x11\x22\x33\x44"), + { Success, 4 }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, + { TLV(2, + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"), + { Success, 256 }, + { Result::ERROR_INVALID_INTEGER_ENCODING, INVALID } }, +}; + +TEST_P(pkixder_universal_types_tests_Integer, Integer) +{ + const IntegerTestParams& params(GetParam()); + Input input; + ASSERT_EQ(Success, input.Init(params.encoded.data(), + params.encoded.length())); + Reader reader(input); + Result expectedResult = params.smallNonnegativeInteger.expectedResult; + uint8_t value; + ASSERT_EQ(expectedResult, der::Integer(reader, value)); + if (expectedResult == Success) { + ASSERT_EQ(params.smallNonnegativeInteger.valueIfValid, value); + ASSERT_TRUE(reader.AtEnd()); + } +} + +TEST_P(pkixder_universal_types_tests_Integer, + PositiveInteger_without_significantBytes) +{ + const IntegerTestParams& params(GetParam()); + Input input; + ASSERT_EQ(Success, input.Init(params.encoded.data(), + params.encoded.length())); + Reader reader(input); + Result expectedResult = params.positiveInteger.expectedResult; + Input value; + ASSERT_EQ(expectedResult, der::PositiveInteger(reader, value)); + if (expectedResult == Success) { + Reader anotherReader(input); + Input expectedValue; + ASSERT_EQ(Success, ExpectTagAndGetValue(anotherReader, + der::INTEGER, expectedValue)); + ASSERT_TRUE(InputsAreEqual(expectedValue, value)); + ASSERT_TRUE(reader.AtEnd()); + } +} + +TEST_P(pkixder_universal_types_tests_Integer, + PositiveInteger_with_significantBytes) +{ + const IntegerTestParams& params(GetParam()); + Input input; + ASSERT_EQ(Success, input.Init(params.encoded.data(), + params.encoded.length())); + Reader reader(input); + Result expectedResult = params.positiveInteger.expectedResult; + Input value; + Input::size_type significantBytes = INVALID; + ASSERT_EQ(expectedResult, der::PositiveInteger(reader, value, + &significantBytes)); + if (expectedResult == Success) { + ASSERT_NE(INVALID, params.positiveInteger.significantBytesIfValid); + ASSERT_EQ(params.positiveInteger.significantBytesIfValid, + significantBytes); + + Reader anotherReader(input); + Input expectedValue; + ASSERT_EQ(Success, ExpectTagAndGetValue(anotherReader, + der::INTEGER, expectedValue)); + ASSERT_TRUE(InputsAreEqual(expectedValue, value)); + ASSERT_TRUE(reader.AtEnd()); + } +} + +#undef INVALID + +INSTANTIATE_TEST_CASE_P(pkixder_universal_types_tests_Integer, + pkixder_universal_types_tests_Integer, + testing::ValuesIn(INTEGER_TEST_PARAMS)); + +TEST_F(pkixder_universal_types_tests, OptionalIntegerSupportedDefault) +{ + // The input is a BOOLEAN and not INTEGER for the input so we'll not parse + // anything and instead use the default value. + Input input(DER_BOOLEAN_TRUE); + Reader reader(input); + + long value = 1; + ASSERT_EQ(Success, OptionalInteger(reader, -1, value)); + ASSERT_EQ(-1, value); + bool boolValue; + ASSERT_EQ(Success, Boolean(reader, boolValue)); +} + +TEST_F(pkixder_universal_types_tests, OptionalIntegerUnsupportedDefault) +{ + // The same as the previous test, except with an unsupported default value + // passed in. + Input input(DER_BOOLEAN_TRUE); + Reader reader(input); + + long value; + ASSERT_EQ(Result::FATAL_ERROR_INVALID_ARGS, OptionalInteger(reader, 0, value)); +} + +TEST_F(pkixder_universal_types_tests, OptionalIntegerSupportedDefaultAtEnd) +{ + static const uint8_t dummy = 1; + Input input; + ASSERT_EQ(Success, input.Init(&dummy, 0)); + Reader reader(input); + + long value = 1; + ASSERT_EQ(Success, OptionalInteger(reader, -1, value)); + ASSERT_EQ(-1, value); +} + +TEST_F(pkixder_universal_types_tests, OptionalIntegerNonDefaultValue) +{ + static const uint8_t DER[] = { + 0x02, // INTEGER + 0x01, // length + 0x00 + }; + Input input(DER); + Reader reader(input); + + long value = 2; + ASSERT_EQ(Success, OptionalInteger(reader, -1, value)); + ASSERT_EQ(0, value); + ASSERT_TRUE(reader.AtEnd()); +} + +TEST_F(pkixder_universal_types_tests, Null) +{ + const uint8_t DER_NUL[] = { + 0x05, + 0x00 + }; + Input input(DER_NUL); + Reader reader(input); + + ASSERT_EQ(Success, Null(reader)); +} + +TEST_F(pkixder_universal_types_tests, NullWithBadLength) +{ + const uint8_t DER_NULL_BAD_LENGTH[] = { + 0x05, + 0x01, + 0x00 + }; + Input input(DER_NULL_BAD_LENGTH); + Reader reader(input); + + ASSERT_EQ(Result::ERROR_BAD_DER, Null(reader)); +} + +TEST_F(pkixder_universal_types_tests, OID) +{ + const uint8_t DER_VALID_OID[] = { + 0x06, + 0x09, + 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01 + }; + Input input(DER_VALID_OID); + Reader reader(input); + + const uint8_t expectedOID[] = { + 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01 + }; + + ASSERT_EQ(Success, OID(reader, expectedOID)); +} diff --git a/security/nss/gtests/mozpkix_gtest/pkixgtest.cpp b/security/nss/gtests/mozpkix_gtest/pkixgtest.cpp new file mode 100644 index 000000000..45932731b --- /dev/null +++ b/security/nss/gtests/mozpkix_gtest/pkixgtest.cpp @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2013 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixgtest.h" + +#include <ctime> + +#include "mozpkix/Time.h" + +namespace mozilla { namespace pkix { namespace test { + +static const std::time_t ONE_DAY_IN_SECONDS_AS_TIME_T = + static_cast<std::time_t>(Time::ONE_DAY_IN_SECONDS); + +// This assumes that time/time_t are POSIX-compliant in that time() returns +// the number of seconds since the Unix epoch. +static const std::time_t now(time(nullptr)); +const std::time_t oneDayBeforeNow(now - ONE_DAY_IN_SECONDS_AS_TIME_T); +const std::time_t oneDayAfterNow(now + ONE_DAY_IN_SECONDS_AS_TIME_T); +const std::time_t twoDaysBeforeNow(now - (2 * ONE_DAY_IN_SECONDS_AS_TIME_T)); +const std::time_t twoDaysAfterNow(now + (2 * ONE_DAY_IN_SECONDS_AS_TIME_T)); +const std::time_t tenDaysBeforeNow(now - (10 * ONE_DAY_IN_SECONDS_AS_TIME_T)); +const std::time_t tenDaysAfterNow(now + (10 * ONE_DAY_IN_SECONDS_AS_TIME_T)); + +} } } // namespace mozilla::pkix::test diff --git a/security/nss/gtests/mozpkix_gtest/pkixgtest.h b/security/nss/gtests/mozpkix_gtest/pkixgtest.h new file mode 100644 index 000000000..bb3491d44 --- /dev/null +++ b/security/nss/gtests/mozpkix_gtest/pkixgtest.h @@ -0,0 +1,229 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2014 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef mozilla_pkix_pkixgtest_h +#define mozilla_pkix_pkixgtest_h + +#include <ostream> + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" +#pragma clang diagnostic ignored "-Wmissing-noreturn" +#pragma clang diagnostic ignored "-Wshift-sign-overflow" +#pragma clang diagnostic ignored "-Wsign-conversion" +#pragma clang diagnostic ignored "-Wundef" +#elif defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wextra" +#elif defined(_MSC_VER) +#pragma warning(push, 3) +// C4224: Nonstandard extension used: formal parameter 'X' was previously +// defined as a type. +#pragma warning(disable : 4224) +// C4826: Conversion from 'type1 ' to 'type_2' is sign - extended. This may +// cause unexpected runtime behavior. +#pragma warning(disable : 4826) +#endif + +#include "gtest/gtest.h" + +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC diagnostic pop +#elif defined(_MSC_VER) +#pragma warning(pop) +#endif + +#include "mozpkix/pkix.h" +#include "mozpkix/test/pkixtestutil.h" + +// PrintTo must be in the same namespace as the type we're overloading it for. +namespace mozilla { +namespace pkix { + +inline void PrintTo(const Result& result, ::std::ostream* os) { + const char* stringified = MapResultToName(result); + if (stringified) { + *os << stringified; + } else { + *os << "mozilla::pkix::Result(" << static_cast<unsigned int>(result) << ")"; + } +} +} +} // namespace mozilla::pkix + +namespace mozilla { +namespace pkix { +namespace test { + +extern const std::time_t oneDayBeforeNow; +extern const std::time_t oneDayAfterNow; +extern const std::time_t twoDaysBeforeNow; +extern const std::time_t twoDaysAfterNow; +extern const std::time_t tenDaysBeforeNow; +extern const std::time_t tenDaysAfterNow; + +class EverythingFailsByDefaultTrustDomain : public TrustDomain { + public: + Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input, + /*out*/ TrustLevel&) override { + ADD_FAILURE(); + return NotReached("GetCertTrust should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + Result FindIssuer(Input, IssuerChecker&, Time) override { + ADD_FAILURE(); + return NotReached("FindIssuer should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, + /*optional*/ const Input*, + /*optional*/ const Input*) override { + ADD_FAILURE(); + return NotReached("CheckRevocation should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override { + ADD_FAILURE(); + return NotReached("IsChainValid should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + Result DigestBuf(Input, DigestAlgorithm, /*out*/ uint8_t*, size_t) override { + ADD_FAILURE(); + return NotReached("DigestBuf should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + Result CheckSignatureDigestAlgorithm(DigestAlgorithm, EndEntityOrCA, + Time) override { + ADD_FAILURE(); + return NotReached("CheckSignatureDigestAlgorithm should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override { + ADD_FAILURE(); + return NotReached("CheckECDSACurveIsAcceptable should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + Result VerifyECDSASignedDigest(const SignedDigest&, Input) override { + ADD_FAILURE(); + return NotReached("VerifyECDSASignedDigest should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, + unsigned int) override { + ADD_FAILURE(); + return NotReached("CheckRSAPublicKeyModulusSizeInBits should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + Result VerifyRSAPKCS1SignedDigest(const SignedDigest&, Input) override { + ADD_FAILURE(); + return NotReached("VerifyRSAPKCS1SignedDigest should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + Result CheckValidityIsAcceptable(Time, Time, EndEntityOrCA, + KeyPurposeId) override { + ADD_FAILURE(); + return NotReached("CheckValidityIsAcceptable should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + Result NetscapeStepUpMatchesServerAuth(Time, bool&) override { + ADD_FAILURE(); + return NotReached("NetscapeStepUpMatchesServerAuth should not be called", + Result::FATAL_ERROR_LIBRARY_FAILURE); + } + + virtual void NoteAuxiliaryExtension(AuxiliaryExtension, Input) override { + ADD_FAILURE(); + } +}; + +class DefaultCryptoTrustDomain : public EverythingFailsByDefaultTrustDomain { + Result DigestBuf(Input item, DigestAlgorithm digestAlg, + /*out*/ uint8_t* digestBuf, size_t digestBufLen) override { + return TestDigestBuf(item, digestAlg, digestBuf, digestBufLen); + } + + Result CheckSignatureDigestAlgorithm(DigestAlgorithm, EndEntityOrCA, + Time) override { + return Success; + } + + Result CheckECDSACurveIsAcceptable(EndEntityOrCA, NamedCurve) override { + return Success; + } + + Result VerifyECDSASignedDigest(const SignedDigest& signedDigest, + Input subjectPublicKeyInfo) override { + return TestVerifyECDSASignedDigest(signedDigest, subjectPublicKeyInfo); + } + + Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, + unsigned int) override { + return Success; + } + + Result VerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest, + Input subjectPublicKeyInfo) override { + return TestVerifyRSAPKCS1SignedDigest(signedDigest, subjectPublicKeyInfo); + } + + Result CheckValidityIsAcceptable(Time, Time, EndEntityOrCA, + KeyPurposeId) override { + return Success; + } + + Result NetscapeStepUpMatchesServerAuth(Time, /*out*/ bool& matches) override { + matches = true; + return Success; + } + + void NoteAuxiliaryExtension(AuxiliaryExtension, Input) override {} +}; + +class DefaultNameMatchingPolicy : public NameMatchingPolicy { + public: + virtual Result FallBackToCommonName( + Time, + /*out*/ FallBackToSearchWithinSubject& fallBackToCommonName) override { + fallBackToCommonName = FallBackToSearchWithinSubject::Yes; + return Success; + } +}; +} +} +} // namespace mozilla::pkix::test + +#endif // mozilla_pkix_pkixgtest_h diff --git a/security/nss/gtests/mozpkix_gtest/pkixnames_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixnames_tests.cpp new file mode 100644 index 000000000..2169db9db --- /dev/null +++ b/security/nss/gtests/mozpkix_gtest/pkixnames_tests.cpp @@ -0,0 +1,2838 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2014 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "pkixgtest.h" + +#include "mozpkix/pkixcheck.h" +#include "mozpkix/pkixder.h" +#include "mozpkix/pkixutil.h" + +namespace mozilla { namespace pkix { + +Result MatchPresentedDNSIDWithReferenceDNSID(Input presentedDNSID, + Input referenceDNSID, + /*out*/ bool& matches); + +bool IsValidReferenceDNSID(Input hostname); +bool IsValidPresentedDNSID(Input hostname); +bool ParseIPv4Address(Input hostname, /*out*/ uint8_t (&out)[4]); +bool ParseIPv6Address(Input hostname, /*out*/ uint8_t (&out)[16]); + +} } // namespace mozilla::pkix + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +struct PresentedMatchesReference +{ + ByteString presentedDNSID; + ByteString referenceDNSID; + Result expectedResult; + bool expectedMatches; // only valid when expectedResult == Success +}; + +::std::ostream& operator<<(::std::ostream& os, const PresentedMatchesReference&) +{ + return os << "TODO (bug 1318770)"; +} + +#define DNS_ID_MATCH(a, b) \ + { \ + ByteString(reinterpret_cast<const uint8_t*>(a), sizeof(a) - 1), \ + ByteString(reinterpret_cast<const uint8_t*>(b), sizeof(b) - 1), \ + Success, \ + true \ + } + +#define DNS_ID_MISMATCH(a, b) \ + { \ + ByteString(reinterpret_cast<const uint8_t*>(a), sizeof(a) - 1), \ + ByteString(reinterpret_cast<const uint8_t*>(b), sizeof(b) - 1), \ + Success, \ + false \ + } + +#define DNS_ID_BAD_DER(a, b) \ + { \ + ByteString(reinterpret_cast<const uint8_t*>(a), sizeof(a) - 1), \ + ByteString(reinterpret_cast<const uint8_t*>(b), sizeof(b) - 1), \ + Result::ERROR_BAD_DER, \ + false \ + } + +static const PresentedMatchesReference DNSID_MATCH_PARAMS[] = +{ + DNS_ID_BAD_DER("", "a"), + + DNS_ID_MATCH("a", "a"), + DNS_ID_MISMATCH("b", "a"), + + DNS_ID_MATCH("*.b.a", "c.b.a"), + DNS_ID_MISMATCH("*.b.a", "b.a"), + DNS_ID_MISMATCH("*.b.a", "b.a."), + + // We allow underscores for compatibility with existing practices. + DNS_ID_MATCH("a_b", "a_b"), + DNS_ID_MATCH("*.example.com", "uses_underscore.example.com"), + DNS_ID_MATCH("*.uses_underscore.example.com", "a.uses_underscore.example.com"), + + // See bug 1139039 + DNS_ID_MATCH("_.example.com", "_.example.com"), + DNS_ID_MATCH("*.example.com", "_.example.com"), + DNS_ID_MATCH("_", "_"), + DNS_ID_MATCH("___", "___"), + DNS_ID_MATCH("example_", "example_"), + DNS_ID_MATCH("_example", "_example"), + DNS_ID_MATCH("*._._", "x._._"), + + // See bug 1139039 + // A DNS-ID must not end in an all-numeric label. We don't consider + // underscores to be numeric. + DNS_ID_MATCH("_1", "_1"), + DNS_ID_MATCH("example._1", "example._1"), + DNS_ID_MATCH("example.1_", "example.1_"), + + // Wildcard not in leftmost label + DNS_ID_MATCH("d.c.b.a", "d.c.b.a"), + DNS_ID_BAD_DER("d.*.b.a", "d.c.b.a"), + DNS_ID_BAD_DER("d.c*.b.a", "d.c.b.a"), + DNS_ID_BAD_DER("d.c*.b.a", "d.cc.b.a"), + + // case sensitivity + DNS_ID_MATCH("abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"), + DNS_ID_MATCH("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"), + DNS_ID_MATCH("aBc", "Abc"), + + // digits + DNS_ID_MATCH("a1", "a1"), + + // A trailing dot indicates an absolute name. Absolute presented names are + // not allowed, but absolute reference names are allowed. + DNS_ID_MATCH("example", "example"), + DNS_ID_BAD_DER("example.", "example."), + DNS_ID_MATCH("example", "example."), + DNS_ID_BAD_DER("example.", "example"), + DNS_ID_MATCH("example.com", "example.com"), + DNS_ID_BAD_DER("example.com.", "example.com."), + DNS_ID_MATCH("example.com", "example.com."), + DNS_ID_BAD_DER("example.com.", "example.com"), + DNS_ID_BAD_DER("example.com..", "example.com."), + DNS_ID_BAD_DER("example.com..", "example.com"), + DNS_ID_BAD_DER("example.com...", "example.com."), + + // xn-- IDN prefix + DNS_ID_BAD_DER("x*.b.a", "xa.b.a"), + DNS_ID_BAD_DER("x*.b.a", "xna.b.a"), + DNS_ID_BAD_DER("x*.b.a", "xn-a.b.a"), + DNS_ID_BAD_DER("x*.b.a", "xn--a.b.a"), + DNS_ID_BAD_DER("xn*.b.a", "xn--a.b.a"), + DNS_ID_BAD_DER("xn-*.b.a", "xn--a.b.a"), + DNS_ID_BAD_DER("xn--*.b.a", "xn--a.b.a"), + DNS_ID_BAD_DER("xn*.b.a", "xn--a.b.a"), + DNS_ID_BAD_DER("xn-*.b.a", "xn--a.b.a"), + DNS_ID_BAD_DER("xn--*.b.a", "xn--a.b.a"), + DNS_ID_BAD_DER("xn---*.b.a", "xn--a.b.a"), + + // "*" cannot expand to nothing. + DNS_ID_BAD_DER("c*.b.a", "c.b.a"), + + ///////////////////////////////////////////////////////////////////////////// + // These are test cases adapted from Chromium's x509_certificate_unittest.cc. + // The parameter order is the opposite in Chromium's tests. Also, some tests + // were modified to fit into this framework or due to intentional differences + // between mozilla::pkix and Chromium. + + DNS_ID_MATCH("foo.com", "foo.com"), + DNS_ID_MATCH("f", "f"), + DNS_ID_MISMATCH("i", "h"), + DNS_ID_MATCH("*.foo.com", "bar.foo.com"), + DNS_ID_MATCH("*.test.fr", "www.test.fr"), + DNS_ID_MATCH("*.test.FR", "wwW.tESt.fr"), + DNS_ID_BAD_DER(".uk", "f.uk"), + DNS_ID_BAD_DER("?.bar.foo.com", "w.bar.foo.com"), + DNS_ID_BAD_DER("(www|ftp).foo.com", "www.foo.com"), // regex! + DNS_ID_BAD_DER("www.foo.com\0", "www.foo.com"), + DNS_ID_BAD_DER("www.foo.com\0*.foo.com", "www.foo.com"), + DNS_ID_MISMATCH("ww.house.example", "www.house.example"), + DNS_ID_MISMATCH("www.test.org", "test.org"), + DNS_ID_MISMATCH("*.test.org", "test.org"), + DNS_ID_BAD_DER("*.org", "test.org"), + DNS_ID_BAD_DER("w*.bar.foo.com", "w.bar.foo.com"), + DNS_ID_BAD_DER("ww*ww.bar.foo.com", "www.bar.foo.com"), + DNS_ID_BAD_DER("ww*ww.bar.foo.com", "wwww.bar.foo.com"), + + // Different than Chromium, matches NSS. + DNS_ID_BAD_DER("w*w.bar.foo.com", "wwww.bar.foo.com"), + + DNS_ID_BAD_DER("w*w.bar.foo.c0m", "wwww.bar.foo.com"), + + // '*' must be the only character in the wildcard label + DNS_ID_BAD_DER("wa*.bar.foo.com", "WALLY.bar.foo.com"), + + // We require "*" to be the last character in a wildcard label, but + // Chromium does not. + DNS_ID_BAD_DER("*Ly.bar.foo.com", "wally.bar.foo.com"), + + // Chromium does URL decoding of the reference ID, but we don't, and we also + // require that the reference ID is valid, so we can't test these two. + // DNS_ID_MATCH("www.foo.com", "ww%57.foo.com"), + // DNS_ID_MATCH("www&.foo.com", "www%26.foo.com"), + + DNS_ID_MISMATCH("*.test.de", "www.test.co.jp"), + DNS_ID_BAD_DER("*.jp", "www.test.co.jp"), + DNS_ID_MISMATCH("www.test.co.uk", "www.test.co.jp"), + DNS_ID_BAD_DER("www.*.co.jp", "www.test.co.jp"), + DNS_ID_MATCH("www.bar.foo.com", "www.bar.foo.com"), + DNS_ID_MISMATCH("*.foo.com", "www.bar.foo.com"), + DNS_ID_BAD_DER("*.*.foo.com", "www.bar.foo.com"), + DNS_ID_BAD_DER("*.*.foo.com", "www.bar.foo.com"), + + // Our matcher requires the reference ID to be a valid DNS name, so we cannot + // test this case. + //DNS_ID_BAD_DER("*.*.bar.foo.com", "*..bar.foo.com"), + + DNS_ID_MATCH("www.bath.org", "www.bath.org"), + + // Our matcher requires the reference ID to be a valid DNS name, so we cannot + // test these cases. + // DNS_ID_BAD_DER("www.bath.org", ""), + // DNS_ID_BAD_DER("www.bath.org", "20.30.40.50"), + // DNS_ID_BAD_DER("www.bath.org", "66.77.88.99"), + + // IDN tests + DNS_ID_MATCH("xn--poema-9qae5a.com.br", "xn--poema-9qae5a.com.br"), + DNS_ID_MATCH("*.xn--poema-9qae5a.com.br", "www.xn--poema-9qae5a.com.br"), + DNS_ID_MISMATCH("*.xn--poema-9qae5a.com.br", "xn--poema-9qae5a.com.br"), + DNS_ID_BAD_DER("xn--poema-*.com.br", "xn--poema-9qae5a.com.br"), + DNS_ID_BAD_DER("xn--*-9qae5a.com.br", "xn--poema-9qae5a.com.br"), + DNS_ID_BAD_DER("*--poema-9qae5a.com.br", "xn--poema-9qae5a.com.br"), + + // The following are adapted from the examples quoted from + // http://tools.ietf.org/html/rfc6125#section-6.4.3 + // (e.g., *.example.com would match foo.example.com but + // not bar.foo.example.com or example.com). + DNS_ID_MATCH("*.example.com", "foo.example.com"), + DNS_ID_MISMATCH("*.example.com", "bar.foo.example.com"), + DNS_ID_MISMATCH("*.example.com", "example.com"), + // (e.g., baz*.example.net and *baz.example.net and b*z.example.net would + // be taken to match baz1.example.net and foobaz.example.net and + // buzz.example.net, respectively. However, we don't allow any characters + // other than '*' in the wildcard label. + DNS_ID_BAD_DER("baz*.example.net", "baz1.example.net"), + + // Both of these are different from Chromium, but match NSS, becaues the + // wildcard character "*" is not the last character of the label. + DNS_ID_BAD_DER("*baz.example.net", "foobaz.example.net"), + DNS_ID_BAD_DER("b*z.example.net", "buzz.example.net"), + + // Wildcards should not be valid for public registry controlled domains, + // and unknown/unrecognized domains, at least three domain components must + // be present. For mozilla::pkix and NSS, there must always be at least two + // labels after the wildcard label. + DNS_ID_MATCH("*.test.example", "www.test.example"), + DNS_ID_MATCH("*.example.co.uk", "test.example.co.uk"), + DNS_ID_BAD_DER("*.exmaple", "test.example"), + + // The result is different than Chromium, because Chromium takes into account + // the additional knowledge it has that "co.uk" is a TLD. mozilla::pkix does + // not know that. + DNS_ID_MATCH("*.co.uk", "example.co.uk"), + + DNS_ID_BAD_DER("*.com", "foo.com"), + DNS_ID_BAD_DER("*.us", "foo.us"), + DNS_ID_BAD_DER("*", "foo"), + + // IDN variants of wildcards and registry controlled domains. + DNS_ID_MATCH("*.xn--poema-9qae5a.com.br", "www.xn--poema-9qae5a.com.br"), + DNS_ID_MATCH("*.example.xn--mgbaam7a8h", "test.example.xn--mgbaam7a8h"), + + // RFC6126 allows this, and NSS accepts it, but Chromium disallows it. + // TODO: File bug against Chromium. + DNS_ID_MATCH("*.com.br", "xn--poema-9qae5a.com.br"), + + DNS_ID_BAD_DER("*.xn--mgbaam7a8h", "example.xn--mgbaam7a8h"), + // Wildcards should be permissible for 'private' registry-controlled + // domains. (In mozilla::pkix, we do not know if it is a private registry- + // controlled domain or not.) + DNS_ID_MATCH("*.appspot.com", "www.appspot.com"), + DNS_ID_MATCH("*.s3.amazonaws.com", "foo.s3.amazonaws.com"), + + // Multiple wildcards are not valid. + DNS_ID_BAD_DER("*.*.com", "foo.example.com"), + DNS_ID_BAD_DER("*.bar.*.com", "foo.bar.example.com"), + + // Absolute vs relative DNS name tests. Although not explicitly specified + // in RFC 6125, absolute reference names (those ending in a .) should + // match either absolute or relative presented names. We don't allow + // absolute presented names. + // TODO: File errata against RFC 6125 about this. + DNS_ID_BAD_DER("foo.com.", "foo.com"), + DNS_ID_MATCH("foo.com", "foo.com."), + DNS_ID_BAD_DER("foo.com.", "foo.com."), + DNS_ID_BAD_DER("f.", "f"), + DNS_ID_MATCH("f", "f."), + DNS_ID_BAD_DER("f.", "f."), + DNS_ID_BAD_DER("*.bar.foo.com.", "www-3.bar.foo.com"), + DNS_ID_MATCH("*.bar.foo.com", "www-3.bar.foo.com."), + DNS_ID_BAD_DER("*.bar.foo.com.", "www-3.bar.foo.com."), + + // We require the reference ID to be a valid DNS name, so we cannot test this + // case. + // DNS_ID_MISMATCH(".", "."), + + DNS_ID_BAD_DER("*.com.", "example.com"), + DNS_ID_BAD_DER("*.com", "example.com."), + DNS_ID_BAD_DER("*.com.", "example.com."), + DNS_ID_BAD_DER("*.", "foo."), + DNS_ID_BAD_DER("*.", "foo"), + + // The result is different than Chromium because we don't know that co.uk is + // a TLD. + DNS_ID_MATCH("*.co.uk", "foo.co.uk"), + DNS_ID_MATCH("*.co.uk", "foo.co.uk."), + DNS_ID_BAD_DER("*.co.uk.", "foo.co.uk"), + DNS_ID_BAD_DER("*.co.uk.", "foo.co.uk."), + + DNS_ID_MISMATCH("*.example.com", "localhost"), + DNS_ID_MISMATCH("*.example.com", "localhost."), + // Note that we already have the testcase DNS_ID_BAD_DER("*", "foo") above +}; + +struct InputValidity +{ + ByteString input; + bool isValidReferenceID; + bool isValidPresentedID; +}; + +::std::ostream& operator<<(::std::ostream& os, const InputValidity&) +{ + return os << "TODO (bug 1318770)"; +} + +// str is null-terminated, which is why we subtract 1. str may contain embedded +// nulls (including at the end) preceding the null terminator though. +#define I(str, validReferenceID, validPresentedID) \ + { \ + ByteString(reinterpret_cast<const uint8_t*>(str), sizeof(str) - 1), \ + validReferenceID, \ + validPresentedID, \ + } + +static const InputValidity DNSNAMES_VALIDITY[] = +{ + I("a", true, true), + I("a.b", true, true), + I("a.b.c", true, true), + I("a.b.c.d", true, true), + + // empty labels + I("", false, false), + I(".", false, false), + I("a", true, true), + I(".a", false, false), + I(".a.b", false, false), + I("..a", false, false), + I("a..b", false, false), + I("a...b", false, false), + I("a..b.c", false, false), + I("a.b..c", false, false), + I(".a.b.c.", false, false), + + // absolute names (only allowed for reference names) + I("a.", true, false), + I("a.b.", true, false), + I("a.b.c.", true, false), + + // absolute names with empty label at end + I("a..", false, false), + I("a.b..", false, false), + I("a.b.c..", false, false), + I("a...", false, false), + + // Punycode + I("xn--", false, false), + I("xn--.", false, false), + I("xn--.a", false, false), + I("a.xn--", false, false), + I("a.xn--.", false, false), + I("a.xn--.b", false, false), + I("a.xn--.b", false, false), + I("a.xn--\0.b", false, false), + I("a.xn--a.b", true, true), + I("xn--a", true, true), + I("a.xn--a", true, true), + I("a.xn--a.a", true, true), + I("\xc4\x95.com", false, false), // UTF-8 ĕ + I("xn--jea.com", true, true), // punycode ĕ + I("xn--\xc4\x95.com", false, false), // UTF-8 ĕ, malformed punycode + UTF-8 mashup + + // Surprising punycode + I("xn--google.com", true, true), // 䕮䕵䕶䕱.com + I("xn--citibank.com", true, true), // 岍岊岊岅岉岎.com + I("xn--cnn.com", true, true), // 䁾.com + I("a.xn--cnn", true, true), // a.䁾 + I("a.xn--cnn.com", true, true), // a.䁾.com + + I("1.2.3.4", false, false), // IPv4 address + I("1::2", false, false), // IPV6 address + + // whitespace not allowed anywhere. + I(" ", false, false), + I(" a", false, false), + I("a ", false, false), + I("a b", false, false), + I("a.b 1", false, false), + I("a\t", false, false), + + // Nulls not allowed + I("\0", false, false), + I("a\0", false, false), + I("example.org\0.example.com", false, false), // Hi Moxie! + I("\0a", false, false), + I("xn--\0", false, false), + + // Allowed character set + I("a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z", true, true), + I("A.B.C.D.E.F.G.H.I.J.K.L.M.N.O.P.Q.R.S.T.U.V.W.X.Y.Z", true, true), + I("0.1.2.3.4.5.6.7.8.9.a", true, true), // "a" needed to avoid numeric last label + I("a-b", true, true), // hyphen (a label cannot start or end with a hyphen) + + // Underscores + I("a_b", true, true), + // See bug 1139039 + I("_", true, true), + I("a_", true, true), + I("_a", true, true), + I("_1", true, true), + I("1_", true, true), + I("___", true, true), + + // An invalid character in various positions + I("!", false, false), + I("!a", false, false), + I("a!", false, false), + I("a!b", false, false), + I("a.!", false, false), + I("a.a!", false, false), + I("a.!a", false, false), + I("a.a!a", false, false), + I("a.!a.a", false, false), + I("a.a!.a", false, false), + I("a.a!a.a", false, false), + + // Various other invalid characters + I("a!", false, false), + I("a@", false, false), + I("a#", false, false), + I("a$", false, false), + I("a%", false, false), + I("a^", false, false), + I("a&", false, false), + I("a*", false, false), + I("a(", false, false), + I("a)", false, false), + + // last label can't be fully numeric + I("1", false, false), + I("a.1", false, false), + + // other labels can be fully numeric + I("1.a", true, true), + I("1.2.a", true, true), + I("1.2.3.a", true, true), + + // last label can be *partly* numeric + I("1a", true, true), + I("1.1a", true, true), + I("1-1", true, true), + I("a.1-1", true, true), + I("a.1-a", true, true), + + // labels cannot start with a hyphen + I("-", false, false), + I("-1", false, false), + + // labels cannot end with a hyphen + I("1-", false, false), + I("1-.a", false, false), + I("a-", false, false), + I("a-.a", false, false), + I("a.1-.a", false, false), + I("a.a-.a", false, false), + + // labels can contain a hyphen in the middle + I("a-b", true, true), + I("1-2", true, true), + I("a.a-1", true, true), + + // multiple consecutive hyphens allowed + I("a--1", true, true), + I("1---a", true, true), + I("a-----------------b", true, true), + + // Wildcard specifications are not valid reference names, but are valid + // presented names if there are enough labels and if '*' is the only + // character in the wildcard label. + I("*.a", false, false), + I("a*", false, false), + I("a*.", false, false), + I("a*.a", false, false), + I("a*.a.", false, false), + I("*.a.b", false, true), + I("*.a.b.", false, false), + I("a*.b.c", false, false), + I("*.a.b.c", false, true), + I("a*.b.c.d", false, false), + + // Multiple wildcards are not allowed. + I("a**.b.c", false, false), + I("a*b*.c.d", false, false), + I("a*.b*.c", false, false), + + // Wildcards are only allowed in the first label. + I("a.*", false, false), + I("a.*.b", false, false), + I("a.b.*", false, false), + I("a.b*.c", false, false), + I("*.b*.c", false, false), + I(".*.a.b", false, false), + I(".a*.b.c", false, false), + + // Wildcards must be at the *end* of the first label. + I("*a.b.c", false, false), + I("a*b.c.d", false, false), + + // Wildcards not allowed with IDNA prefix + I("x*.a.b", false, false), + I("xn*.a.b", false, false), + I("xn-*.a.b", false, false), + I("xn--*.a.b", false, false), + I("xn--w*.a.b", false, false), + + // Redacted labels from RFC6962bis draft 4 + // https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-04#section-3.2.2 + I("(PRIVATE).foo", false, false), + + // maximum label length is 63 characters + I("1234567890" "1234567890" "1234567890" + "1234567890" "1234567890" "1234567890" "abc", true, true), + I("1234567890" "1234567890" "1234567890" + "1234567890" "1234567890" "1234567890" "abcd", false, false), + + // maximum total length is 253 characters + I("1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." + "1234567890" "1234567890" "1234567890" "1234567890" "12345678" "a", + true, true), + I("1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" "." + "1234567890" "1234567890" "1234567890" "1234567890" "123456789" "a", + false, false), +}; + +static const InputValidity DNSNAMES_VALIDITY_TURKISH_I[] = +{ + // http://en.wikipedia.org/wiki/Dotted_and_dotless_I#In_computing + // IDN registration rules disallow "latin capital letter i with dot above," + // but our checks aren't intended to enforce those rules. + I("I", true, true), // ASCII capital I + I("i", true, true), // ASCII lowercase i + I("\xC4\xB0", false, false), // latin capital letter i with dot above + I("\xC4\xB1", false, false), // latin small letter dotless i + I("xn--i-9bb", true, true), // latin capital letter i with dot above, in punycode + I("xn--cfa", true, true), // latin small letter dotless i, in punycode + I("xn--\xC4\xB0", false, false), // latin capital letter i with dot above, mashup + I("xn--\xC4\xB1", false, false), // latin small letter dotless i, mashup +}; + +static const uint8_t LOWERCASE_I_VALUE[1] = { 'i' }; +static const uint8_t UPPERCASE_I_VALUE[1] = { 'I' }; +static const Input LOWERCASE_I(LOWERCASE_I_VALUE); +static const Input UPPERCASE_I(UPPERCASE_I_VALUE); + +template <unsigned int L> +struct IPAddressParams +{ + ByteString input; + bool isValid; + uint8_t expectedValueIfValid[L]; +}; + +template <unsigned int L> +::std::ostream& operator<<(::std::ostream& os, const IPAddressParams<L>&) +{ + return os << "TODO (bug 1318770)"; +} + +#define IPV4_VALID(str, a, b, c, d) \ + { \ + ByteString(reinterpret_cast<const uint8_t*>(str), sizeof(str) - 1), \ + true, \ + { a, b, c, d } \ + } + +// The value of expectedValueIfValid must be ignored for invalid IP addresses. +// The value { 73, 73, 73, 73 } is used because it is unlikely to result in an +// accidental match, unlike { 0, 0, 0, 0 }, which is a value we actually test. +#define IPV4_INVALID(str) \ + { \ + ByteString(reinterpret_cast<const uint8_t*>(str), sizeof(str) - 1), \ + false, \ + { 73, 73, 73, 73 } \ + } + +static const IPAddressParams<4> IPV4_ADDRESSES[] = +{ + IPV4_INVALID(""), + IPV4_INVALID("1"), + IPV4_INVALID("1.2"), + IPV4_INVALID("1.2.3"), + IPV4_VALID("1.2.3.4", 1, 2, 3, 4), + IPV4_INVALID("1.2.3.4.5"), + + IPV4_INVALID("1.2.3.4a"), // a DNSName! + IPV4_INVALID("a.2.3.4"), // not even a DNSName! + IPV4_INVALID("1::2"), // IPv6 address + + // Whitespace not allowed + IPV4_INVALID(" 1.2.3.4"), + IPV4_INVALID("1.2.3.4 "), + IPV4_INVALID("1 .2.3.4"), + IPV4_INVALID("\n1.2.3.4"), + IPV4_INVALID("1.2.3.4\n"), + + // Nulls not allowed + IPV4_INVALID("\0"), + IPV4_INVALID("\0" "1.2.3.4"), + IPV4_INVALID("1.2.3.4\0"), + IPV4_INVALID("1.2.3.4\0.5"), + + // Range + IPV4_VALID("0.0.0.0", 0, 0, 0, 0), + IPV4_VALID("255.255.255.255", 255, 255, 255, 255), + IPV4_INVALID("256.0.0.0"), + IPV4_INVALID("0.256.0.0"), + IPV4_INVALID("0.0.256.0"), + IPV4_INVALID("0.0.0.256"), + IPV4_INVALID("999.0.0.0"), + IPV4_INVALID("9999999999999999999.0.0.0"), + + // All digits allowed + IPV4_VALID("0.1.2.3", 0, 1, 2, 3), + IPV4_VALID("4.5.6.7", 4, 5, 6, 7), + IPV4_VALID("8.9.0.1", 8, 9, 0, 1), + + // Leading zeros not allowed + IPV4_INVALID("01.2.3.4"), + IPV4_INVALID("001.2.3.4"), + IPV4_INVALID("00000000001.2.3.4"), + IPV4_INVALID("010.2.3.4"), + IPV4_INVALID("1.02.3.4"), + IPV4_INVALID("1.2.03.4"), + IPV4_INVALID("1.2.3.04"), + + // Empty components + IPV4_INVALID(".2.3.4"), + IPV4_INVALID("1..3.4"), + IPV4_INVALID("1.2..4"), + IPV4_INVALID("1.2.3."), + + // Too many components + IPV4_INVALID("1.2.3.4.5"), + IPV4_INVALID("1.2.3.4.5.6"), + IPV4_INVALID("0.1.2.3.4"), + IPV4_INVALID("1.2.3.4.0"), + + // Leading/trailing dot + IPV4_INVALID(".1.2.3.4"), + IPV4_INVALID("1.2.3.4."), + + // Other common forms of IPv4 address + // http://en.wikipedia.org/wiki/IPv4#Address_representations + IPV4_VALID("192.0.2.235", 192, 0, 2, 235), // dotted decimal (control value) + IPV4_INVALID("0xC0.0x00.0x02.0xEB"), // dotted hex + IPV4_INVALID("0301.0000.0002.0353"), // dotted octal + IPV4_INVALID("0xC00002EB"), // non-dotted hex + IPV4_INVALID("3221226219"), // non-dotted decimal + IPV4_INVALID("030000001353"), // non-dotted octal + IPV4_INVALID("192.0.0002.0xEB"), // mixed +}; + +#define IPV6_VALID(str, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) \ + { \ + ByteString(reinterpret_cast<const uint8_t*>(str), sizeof(str) - 1), \ + true, \ + { a, b, c, d, \ + e, f, g, h, \ + i, j, k, l, \ + m, n, o, p } \ + } + +#define IPV6_INVALID(str) \ + { \ + ByteString(reinterpret_cast<const uint8_t*>(str), sizeof(str) - 1), \ + false, \ + { 73, 73, 73, 73, \ + 73, 73, 73, 73, \ + 73, 73, 73, 73, \ + 73, 73, 73, 73 } \ + } + +static const IPAddressParams<16> IPV6_ADDRESSES[] = +{ + IPV6_INVALID(""), + IPV6_INVALID("1234"), + IPV6_INVALID("1234:5678"), + IPV6_INVALID("1234:5678:9abc"), + IPV6_INVALID("1234:5678:9abc:def0"), + IPV6_INVALID("1234:5678:9abc:def0:1234:"), + IPV6_INVALID("1234:5678:9abc:def0:1234:5678:"), + IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:"), + IPV6_VALID("1234:5678:9abc:def0:1234:5678:9abc:def0", + 0x12, 0x34, 0x56, 0x78, + 0x9a, 0xbc, 0xde, 0xf0, + 0x12, 0x34, 0x56, 0x78, + 0x9a, 0xbc, 0xde, 0xf0), + IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:def0:"), + IPV6_INVALID(":1234:5678:9abc:def0:1234:5678:9abc:def0"), + IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:def0:0000"), + + // Valid contractions + IPV6_VALID("::1", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01), + IPV6_VALID("::1234", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x12, 0x34), + IPV6_VALID("1234::", + 0x12, 0x34, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00), + IPV6_VALID("1234::5678", + 0x12, 0x34, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x56, 0x78), + IPV6_VALID("1234:5678::abcd", + 0x12, 0x34, 0x56, 0x78, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xab, 0xcd), + IPV6_VALID("1234:5678:9abc:def0:1234:5678:9abc::", + 0x12, 0x34, 0x56, 0x78, + 0x9a, 0xbc, 0xde, 0xf0, + 0x12, 0x34, 0x56, 0x78, + 0x9a, 0xbc, 0x00, 0x00), + + // Contraction in full IPv6 addresses not allowed + IPV6_INVALID("::1234:5678:9abc:def0:1234:5678:9abc:def0"), // start + IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:def0::"), // end + IPV6_INVALID("1234:5678::9abc:def0:1234:5678:9abc:def0"), // interior + + // Multiple contractions not allowed + IPV6_INVALID("::1::"), + IPV6_INVALID("::1::2"), + IPV6_INVALID("1::2::"), + + // Colon madness! + IPV6_INVALID(":"), + IPV6_INVALID("::"), + IPV6_INVALID(":::"), + IPV6_INVALID("::::"), + IPV6_INVALID(":::1"), + IPV6_INVALID("::::1"), + IPV6_INVALID("1:::2"), + IPV6_INVALID("1::::2"), + IPV6_INVALID("1:2:::"), + IPV6_INVALID("1:2::::"), + IPV6_INVALID("::1234:"), + IPV6_INVALID(":1234::"), + + IPV6_INVALID("01234::"), // too many digits, even if zero + IPV6_INVALID("12345678::"), // too many digits or missing colon + + // uppercase + IPV6_VALID("ABCD:EFAB::", + 0xab, 0xcd, 0xef, 0xab, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00), + + // miXeD CAse + IPV6_VALID("aBcd:eFAb::", + 0xab, 0xcd, 0xef, 0xab, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00), + + // IPv4-style + IPV6_VALID("::2.3.4.5", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x02, 0x03, 0x04, 0x05), + IPV6_VALID("1234::2.3.4.5", + 0x12, 0x34, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x02, 0x03, 0x04, 0x05), + IPV6_VALID("::abcd:2.3.4.5", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xab, 0xcd, + 0x02, 0x03, 0x04, 0x05), + IPV6_VALID("1234:5678:9abc:def0:1234:5678:252.253.254.255", + 0x12, 0x34, 0x56, 0x78, + 0x9a, 0xbc, 0xde, 0xf0, + 0x12, 0x34, 0x56, 0x78, + 252, 253, 254, 255), + IPV6_VALID("1234:5678:9abc:def0:1234::252.253.254.255", + 0x12, 0x34, 0x56, 0x78, + 0x9a, 0xbc, 0xde, 0xf0, + 0x12, 0x34, 0x00, 0x00, + 252, 253, 254, 255), + IPV6_INVALID("1234::252.253.254"), + IPV6_INVALID("::252.253.254"), + IPV6_INVALID("::252.253.254.300"), + IPV6_INVALID("1234::252.253.254.255:"), + IPV6_INVALID("1234::252.253.254.255:5678"), + + // Contractions that don't contract + IPV6_INVALID("::1234:5678:9abc:def0:1234:5678:9abc:def0"), + IPV6_INVALID("1234:5678:9abc:def0:1234:5678:9abc:def0::"), + IPV6_INVALID("1234:5678:9abc:def0::1234:5678:9abc:def0"), + IPV6_INVALID("1234:5678:9abc:def0:1234:5678::252.253.254.255"), + + // With and without leading zeros + IPV6_VALID("::123", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x23), + IPV6_VALID("::0123", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x23), + IPV6_VALID("::012", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x12), + IPV6_VALID("::0012", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x12), + IPV6_VALID("::01", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01), + IPV6_VALID("::001", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01), + IPV6_VALID("::0001", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01), + IPV6_VALID("::0", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00), + IPV6_VALID("::00", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00), + IPV6_VALID("::000", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00), + IPV6_VALID("::0000", + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00), + IPV6_INVALID("::01234"), + IPV6_INVALID("::00123"), + IPV6_INVALID("::000123"), + + // Trailing zero + IPV6_INVALID("::12340"), + + // Whitespace + IPV6_INVALID(" 1234:5678:9abc:def0:1234:5678:9abc:def0"), + IPV6_INVALID("\t1234:5678:9abc:def0:1234:5678:9abc:def0"), + IPV6_INVALID("\t1234:5678:9abc:def0:1234:5678:9abc:def0\n"), + IPV6_INVALID("1234 :5678:9abc:def0:1234:5678:9abc:def0"), + IPV6_INVALID("1234: 5678:9abc:def0:1234:5678:9abc:def0"), + IPV6_INVALID(":: 2.3.4.5"), + IPV6_INVALID("1234::252.253.254.255 "), + IPV6_INVALID("1234::252.253.254.255\n"), + IPV6_INVALID("1234::252.253. 254.255"), + + // Nulls + IPV6_INVALID("\0"), + IPV6_INVALID("::1\0:2"), + IPV6_INVALID("::1\0"), + IPV6_INVALID("::1.2.3.4\0"), + IPV6_INVALID("::1.2\02.3.4"), +}; + +class pkixnames_MatchPresentedDNSIDWithReferenceDNSID + : public ::testing::Test + , public ::testing::WithParamInterface<PresentedMatchesReference> +{ +public: + DefaultNameMatchingPolicy mNameMatchingPolicy; +}; + +TEST_P(pkixnames_MatchPresentedDNSIDWithReferenceDNSID, + MatchPresentedDNSIDWithReferenceDNSID) +{ + const PresentedMatchesReference& param(GetParam()); + SCOPED_TRACE(param.presentedDNSID.c_str()); + SCOPED_TRACE(param.referenceDNSID.c_str()); + Input presented; + ASSERT_EQ(Success, presented.Init(param.presentedDNSID.data(), + param.presentedDNSID.length())); + Input reference; + ASSERT_EQ(Success, reference.Init(param.referenceDNSID.data(), + param.referenceDNSID.length())); + + // sanity check that test makes sense + ASSERT_TRUE(IsValidReferenceDNSID(reference)); + + bool matches; + ASSERT_EQ(param.expectedResult, + MatchPresentedDNSIDWithReferenceDNSID(presented, reference, + matches)); + if (param.expectedResult == Success) { + ASSERT_EQ(param.expectedMatches, matches); + } +} + +INSTANTIATE_TEST_CASE_P(pkixnames_MatchPresentedDNSIDWithReferenceDNSID, + pkixnames_MatchPresentedDNSIDWithReferenceDNSID, + testing::ValuesIn(DNSID_MATCH_PARAMS)); + +class pkixnames_Turkish_I_Comparison + : public ::testing::Test + , public ::testing::WithParamInterface<InputValidity> +{ +public: + DefaultNameMatchingPolicy mNameMatchingPolicy; +}; + +TEST_P(pkixnames_Turkish_I_Comparison, MatchPresentedDNSIDWithReferenceDNSID) +{ + // Make sure we don't have the similar problems that strcasecmp and others + // have with the other kinds of "i" and "I" commonly used in Turkish locales. + + const InputValidity& inputValidity(GetParam()); + SCOPED_TRACE(inputValidity.input.c_str()); + Input input; + ASSERT_EQ(Success, input.Init(inputValidity.input.data(), + inputValidity.input.length())); + + bool isASCII = InputsAreEqual(LOWERCASE_I, input) || + InputsAreEqual(UPPERCASE_I, input); + { + bool matches; + ASSERT_EQ(inputValidity.isValidPresentedID ? Success + : Result::ERROR_BAD_DER, + MatchPresentedDNSIDWithReferenceDNSID(input, LOWERCASE_I, + matches)); + if (inputValidity.isValidPresentedID) { + ASSERT_EQ(isASCII, matches); + } + } + { + bool matches; + ASSERT_EQ(inputValidity.isValidPresentedID ? Success + : Result::ERROR_BAD_DER, + MatchPresentedDNSIDWithReferenceDNSID(input, UPPERCASE_I, + matches)); + if (inputValidity.isValidPresentedID) { + ASSERT_EQ(isASCII, matches); + } + } +} + +INSTANTIATE_TEST_CASE_P(pkixnames_Turkish_I_Comparison, + pkixnames_Turkish_I_Comparison, + testing::ValuesIn(DNSNAMES_VALIDITY_TURKISH_I)); + +class pkixnames_IsValidReferenceDNSID + : public ::testing::Test + , public ::testing::WithParamInterface<InputValidity> +{ +public: + DefaultNameMatchingPolicy mNameMatchingPolicy; +}; + +TEST_P(pkixnames_IsValidReferenceDNSID, IsValidReferenceDNSID) +{ + const InputValidity& inputValidity(GetParam()); + SCOPED_TRACE(inputValidity.input.c_str()); + Input input; + ASSERT_EQ(Success, input.Init(inputValidity.input.data(), + inputValidity.input.length())); + ASSERT_EQ(inputValidity.isValidReferenceID, IsValidReferenceDNSID(input)); + ASSERT_EQ(inputValidity.isValidPresentedID, IsValidPresentedDNSID(input)); +} + +INSTANTIATE_TEST_CASE_P(pkixnames_IsValidReferenceDNSID, + pkixnames_IsValidReferenceDNSID, + testing::ValuesIn(DNSNAMES_VALIDITY)); +INSTANTIATE_TEST_CASE_P(pkixnames_IsValidReferenceDNSID_Turkish_I, + pkixnames_IsValidReferenceDNSID, + testing::ValuesIn(DNSNAMES_VALIDITY_TURKISH_I)); + +class pkixnames_ParseIPv4Address + : public ::testing::Test + , public ::testing::WithParamInterface<IPAddressParams<4>> +{ +public: + DefaultNameMatchingPolicy mNameMatchingPolicy; +}; + +TEST_P(pkixnames_ParseIPv4Address, ParseIPv4Address) +{ + const IPAddressParams<4>& param(GetParam()); + SCOPED_TRACE(param.input.c_str()); + Input input; + ASSERT_EQ(Success, input.Init(param.input.data(), + param.input.length())); + uint8_t ipAddress[4]; + ASSERT_EQ(param.isValid, ParseIPv4Address(input, ipAddress)); + if (param.isValid) { + for (size_t i = 0; i < sizeof(ipAddress); ++i) { + ASSERT_EQ(param.expectedValueIfValid[i], ipAddress[i]); + } + } +} + +INSTANTIATE_TEST_CASE_P(pkixnames_ParseIPv4Address, + pkixnames_ParseIPv4Address, + testing::ValuesIn(IPV4_ADDRESSES)); + +class pkixnames_ParseIPv6Address + : public ::testing::Test + , public ::testing::WithParamInterface<IPAddressParams<16>> +{ +public: + DefaultNameMatchingPolicy mNameMatchingPolicy; +}; + +TEST_P(pkixnames_ParseIPv6Address, ParseIPv6Address) +{ + const IPAddressParams<16>& param(GetParam()); + SCOPED_TRACE(param.input.c_str()); + Input input; + ASSERT_EQ(Success, input.Init(param.input.data(), + param.input.length())); + uint8_t ipAddress[16]; + ASSERT_EQ(param.isValid, ParseIPv6Address(input, ipAddress)); + if (param.isValid) { + for (size_t i = 0; i < sizeof(ipAddress); ++i) { + ASSERT_EQ(param.expectedValueIfValid[i], ipAddress[i]); + } + } +} + +INSTANTIATE_TEST_CASE_P(pkixnames_ParseIPv6Address, + pkixnames_ParseIPv6Address, + testing::ValuesIn(IPV6_ADDRESSES)); + +// This is an arbitrary string that is used to indicate that no SAN extension +// should be put into the generated certificate. It needs to be different from +// "" or any other subjectAltName value that we actually want to test, but its +// actual value does not matter. Note that this isn't a correctly-encoded SAN +// extension value! +static const ByteString + NO_SAN(reinterpret_cast<const uint8_t*>("I'm a bad, bad, certificate")); + +struct CheckCertHostnameParams +{ + ByteString hostname; + ByteString subject; + ByteString subjectAltName; + Result result; +}; + +::std::ostream& operator<<(::std::ostream& os, const CheckCertHostnameParams&) +{ + return os << "TODO (bug 1318770)"; +} + +class pkixnames_CheckCertHostname + : public ::testing::Test + , public ::testing::WithParamInterface<CheckCertHostnameParams> +{ +public: + DefaultNameMatchingPolicy mNameMatchingPolicy; +}; + +#define WITH_SAN(r, ps, psan, result) \ + { \ + ByteString(reinterpret_cast<const uint8_t*>(r), sizeof(r) - 1), \ + ps, \ + psan, \ + result \ + } + +#define WITHOUT_SAN(r, ps, result) \ + { \ + ByteString(reinterpret_cast<const uint8_t*>(r), sizeof(r) - 1), \ + ps, \ + NO_SAN, \ + result \ + } + +static const uint8_t example_com[] = { + 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm' +}; + +// Note that We avoid zero-valued bytes in these IP addresses so that we don't +// get false negatives from anti-NULL-byte defenses in dNSName decoding. +static const uint8_t ipv4_addr_bytes[] = { + 1, 2, 3, 4 +}; +static const uint8_t ipv4_addr_bytes_as_str[] = "\x01\x02\x03\x04"; +static const uint8_t ipv4_addr_str[] = "1.2.3.4"; +static const uint8_t ipv4_addr_bytes_FFFFFFFF[8] = { + 1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff +}; + +static const uint8_t ipv4_compatible_ipv6_addr_bytes[] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 1, 2, 3, 4 +}; +static const uint8_t ipv4_compatible_ipv6_addr_str[] = "::1.2.3.4"; + +static const uint8_t ipv4_mapped_ipv6_addr_bytes[] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0xFF, 0xFF, + 1, 2, 3, 4 +}; +static const uint8_t ipv4_mapped_ipv6_addr_str[] = "::FFFF:1.2.3.4"; + +static const uint8_t ipv6_addr_bytes[] = { + 0x11, 0x22, 0x33, 0x44, + 0x55, 0x66, 0x77, 0x88, + 0x99, 0xaa, 0xbb, 0xcc, + 0xdd, 0xee, 0xff, 0x11 +}; +static const uint8_t ipv6_addr_bytes_as_str[] = + "\x11\x22\x33\x44" + "\x55\x66\x77\x88" + "\x99\xaa\xbb\xcc" + "\xdd\xee\xff\x11"; + +static const uint8_t ipv6_addr_str[] = + "1122:3344:5566:7788:99aa:bbcc:ddee:ff11"; + +static const uint8_t ipv6_other_addr_bytes[] = { + 0xff, 0xee, 0xdd, 0xcc, + 0xbb, 0xaa, 0x99, 0x88, + 0x77, 0x66, 0x55, 0x44, + 0x33, 0x22, 0x11, 0x00, +}; + +static const uint8_t ipv4_other_addr_bytes[] = { + 5, 6, 7, 8 +}; +static const uint8_t ipv4_other_addr_bytes_FFFFFFFF[] = { + 5, 6, 7, 8, 0xff, 0xff, 0xff, 0xff +}; + +static const uint8_t ipv4_addr_00000000_bytes[] = { + 0, 0, 0, 0 +}; +static const uint8_t ipv4_addr_FFFFFFFF_bytes[] = { + 0, 0, 0, 0 +}; + +static const uint8_t ipv4_constraint_all_zeros_bytes[] = { + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static const uint8_t ipv6_addr_all_zeros_bytes[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static const uint8_t ipv6_constraint_all_zeros_bytes[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static const uint8_t ipv4_constraint_CIDR_16_bytes[] = { + 1, 2, 0, 0, 0xff, 0xff, 0, 0 +}; +static const uint8_t ipv4_constraint_CIDR_17_bytes[] = { + 1, 2, 0, 0, 0xff, 0xff, 0x80, 0 +}; + +// The subnet is 1.2.0.0/16 but it is specified as 1.2.3.0/16 +static const uint8_t ipv4_constraint_CIDR_16_bad_addr_bytes[] = { + 1, 2, 3, 0, 0xff, 0xff, 0, 0 +}; + +// Masks are supposed to be of the form <ones><zeros>, but this one is of the +// form <ones><zeros><ones><zeros>. +static const uint8_t ipv4_constraint_bad_mask_bytes[] = { + 1, 2, 3, 0, 0xff, 0, 0xff, 0 +}; + +static const uint8_t ipv6_constraint_CIDR_16_bytes[] = { + 0x11, 0x22, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0xff, 0xff, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +// The subnet is 1122::/16 but it is specified as 1122:3344::/16 +static const uint8_t ipv6_constraint_CIDR_16_bad_addr_bytes[] = { + 0x11, 0x22, 0x33, 0x44, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0xff, 0xff, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +// Masks are supposed to be of the form <ones><zeros>, but this one is of the +// form <ones><zeros><ones><zeros>. +static const uint8_t ipv6_constraint_bad_mask_bytes[] = { + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0xff, 0xff, 0, 0, 0xff, 0xff, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static const uint8_t ipv4_addr_truncated_bytes[] = { + 1, 2, 3 +}; +static const uint8_t ipv4_addr_overlong_bytes[] = { + 1, 2, 3, 4, 5 +}; +static const uint8_t ipv4_constraint_truncated_bytes[] = { + 0, 0, 0, 0, + 0, 0, 0, +}; +static const uint8_t ipv4_constraint_overlong_bytes[] = { + 0, 0, 0, 0, + 0, 0, 0, 0, 0 +}; + +static const uint8_t ipv6_addr_truncated_bytes[] = { + 0x11, 0x22, 0x33, 0x44, + 0x55, 0x66, 0x77, 0x88, + 0x99, 0xaa, 0xbb, 0xcc, + 0xdd, 0xee, 0xff +}; +static const uint8_t ipv6_addr_overlong_bytes[] = { + 0x11, 0x22, 0x33, 0x44, + 0x55, 0x66, 0x77, 0x88, + 0x99, 0xaa, 0xbb, 0xcc, + 0xdd, 0xee, 0xff, 0x11, 0x00 +}; +static const uint8_t ipv6_constraint_truncated_bytes[] = { + 0x11, 0x22, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0xff, 0xff, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0 +}; +static const uint8_t ipv6_constraint_overlong_bytes[] = { + 0x11, 0x22, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0xff, 0xff, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +// Note that, for DNSNames, these test cases in CHECK_CERT_HOSTNAME_PARAMS are +// mostly about testing different scenerios regarding the structure of entries +// in the subjectAltName and subject of the certificate, than about the how +// specific presented identifier values are matched against the reference +// identifier values. This is because we also use the test cases in +// DNSNAMES_VALIDITY to test CheckCertHostname. Consequently, tests about +// whether specific presented DNSNames (including wildcards, in particular) are +// matched against a reference DNSName only need to be added to +// DNSNAMES_VALIDITY, and not here. +static const CheckCertHostnameParams CHECK_CERT_HOSTNAME_PARAMS[] = +{ + // This is technically illegal. PrintableString is defined in such a way that + // '*' is not an allowed character, but there are many real-world certificates + // that are encoded this way. + WITHOUT_SAN("foo.example.com", RDN(CN("*.example.com", der::PrintableString)), + Success), + WITHOUT_SAN("foo.example.com", RDN(CN("*.example.com", der::UTF8String)), + Success), + + // Many certificates use TeletexString when encoding wildcards in CN-IDs + // because PrintableString is defined as not allowing '*' and UTF8String was, + // at one point in history, considered too new to depend on for compatibility. + // We accept TeletexString-encoded CN-IDs when they don't contain any escape + // sequences. The reference I used for the escape codes was + // https://tools.ietf.org/html/rfc1468. The escaping mechanism is actually + // pretty complex and these tests don't even come close to testing all the + // possibilities. + WITHOUT_SAN("foo.example.com", RDN(CN("*.example.com", der::TeletexString)), + Success), + // "ESC ( B" ({0x1B,0x50,0x42}) is the escape code to switch to ASCII, which + // is redundant because it already the default. + WITHOUT_SAN("foo.example.com", + RDN(CN("\x1B(B*.example.com", der::TeletexString)), + Result::ERROR_BAD_CERT_DOMAIN), + WITHOUT_SAN("foo.example.com", + RDN(CN("*.example\x1B(B.com", der::TeletexString)), + Result::ERROR_BAD_CERT_DOMAIN), + WITHOUT_SAN("foo.example.com", + RDN(CN("*.example.com\x1B(B", der::TeletexString)), + Result::ERROR_BAD_CERT_DOMAIN), + // "ESC $ B" ({0x1B,0x24,0x42}) is the escape code to switch to + // JIS X 0208-1983 (a Japanese character set). + WITHOUT_SAN("foo.example.com", + RDN(CN("\x1B$B*.example.com", der::TeletexString)), + Result::ERROR_BAD_CERT_DOMAIN), + WITHOUT_SAN("foo.example.com", + RDN(CN("*.example.com\x1B$B", der::TeletexString)), + Result::ERROR_BAD_CERT_DOMAIN), + + // Match a DNSName SAN entry with a redundant (ignored) matching CN-ID. + WITH_SAN("a", RDN(CN("a")), DNSName("a"), Success), + // Match a DNSName SAN entry when there is an CN-ID that doesn't match. + WITH_SAN("b", RDN(CN("a")), DNSName("b"), Success), + // Do not match a CN-ID when there is a valid DNSName SAN Entry. + WITH_SAN("a", RDN(CN("a")), DNSName("b"), Result::ERROR_BAD_CERT_DOMAIN), + // Do not match a CN-ID when there is a malformed DNSName SAN Entry. + WITH_SAN("a", RDN(CN("a")), DNSName("!"), Result::ERROR_BAD_DER), + // Do not match a matching CN-ID when there is a valid IPAddress SAN entry. + WITH_SAN("a", RDN(CN("a")), IPAddress(ipv4_addr_bytes), + Result::ERROR_BAD_CERT_DOMAIN), + // Do not match a matching CN-ID when there is a malformed IPAddress SAN entry. + WITH_SAN("a", RDN(CN("a")), IPAddress(example_com), + Result::ERROR_BAD_CERT_DOMAIN), + // Match a DNSName against a matching CN-ID when there is a SAN, but the SAN + // does not contain an DNSName or IPAddress entry. + WITH_SAN("a", RDN(CN("a")), RFC822Name("foo@example.com"), Success), + // Match a matching CN-ID when there is no SAN. + WITHOUT_SAN("a", RDN(CN("a")), Success), + // Do not match a mismatching CN-ID when there is no SAN. + WITHOUT_SAN("a", RDN(CN("b")), Result::ERROR_BAD_CERT_DOMAIN), + + // The first DNSName matches. + WITH_SAN("a", RDN(CN("foo")), DNSName("a") + DNSName("b"), Success), + // The last DNSName matches. + WITH_SAN("b", RDN(CN("foo")), DNSName("a") + DNSName("b"), Success), + // The middle DNSName matches. + WITH_SAN("b", RDN(CN("foo")), + DNSName("a") + DNSName("b") + DNSName("c"), Success), + // After an IP address. + WITH_SAN("b", RDN(CN("foo")), + IPAddress(ipv4_addr_bytes) + DNSName("b"), Success), + // Before an IP address. + WITH_SAN("a", RDN(CN("foo")), + DNSName("a") + IPAddress(ipv4_addr_bytes), Success), + // Between an RFC822Name and an IP address. + WITH_SAN("b", RDN(CN("foo")), + RFC822Name("foo@example.com") + DNSName("b") + + IPAddress(ipv4_addr_bytes), + Success), + // Duplicate DNSName. + WITH_SAN("a", RDN(CN("foo")), DNSName("a") + DNSName("a"), Success), + // After an invalid DNSName. + WITH_SAN("b", RDN(CN("foo")), DNSName("!") + DNSName("b"), + Result::ERROR_BAD_DER), + + // http://tools.ietf.org/html/rfc5280#section-4.2.1.6: "If the subjectAltName + // extension is present, the sequence MUST contain at least one entry." + // However, for compatibility reasons, this is not enforced. See bug 1143085. + // This case is treated as if the extension is not present (i.e. name + // matching falls back to the subject CN). + WITH_SAN("a", RDN(CN("a")), ByteString(), Success), + WITH_SAN("a", RDN(CN("b")), ByteString(), Result::ERROR_BAD_CERT_DOMAIN), + + // http://tools.ietf.org/html/rfc5280#section-4.1.2.6 says "If subject naming + // information is present only in the subjectAltName extension (e.g., a key + // bound only to an email address or URI), then the subject name MUST be an + // empty sequence and the subjectAltName extension MUST be critical." So, we + // have to support an empty subject. We don't enforce that the SAN must be + // critical or even that there is a SAN when the subject is empty, though. + WITH_SAN("a", ByteString(), DNSName("a"), Success), + // Make sure we return ERROR_BAD_CERT_DOMAIN and not ERROR_BAD_DER. + WITHOUT_SAN("a", ByteString(), Result::ERROR_BAD_CERT_DOMAIN), + + // Two CNs in the same RDN, both match. + WITHOUT_SAN("a", RDN(CN("a") + CN("a")), Success), + // Two CNs in the same RDN, both DNSNames, first one matches. + WITHOUT_SAN("a", RDN(CN("a") + CN("b")), + Result::ERROR_BAD_CERT_DOMAIN), + // Two CNs in the same RDN, both DNSNames, last one matches. + WITHOUT_SAN("b", RDN(CN("a") + CN("b")), Success), + // Two CNs in the same RDN, first one matches, second isn't a DNSName. + WITHOUT_SAN("a", RDN(CN("a") + CN("Not a DNSName")), + Result::ERROR_BAD_CERT_DOMAIN), + // Two CNs in the same RDN, first one not a DNSName, second matches. + WITHOUT_SAN("b", RDN(CN("Not a DNSName") + CN("b")), Success), + + // Two CNs in separate RDNs, both match. + WITHOUT_SAN("a", RDN(CN("a")) + RDN(CN("a")), Success), + // Two CNs in separate RDNs, both DNSNames, first one matches. + WITHOUT_SAN("a", RDN(CN("a")) + RDN(CN("b")), + Result::ERROR_BAD_CERT_DOMAIN), + // Two CNs in separate RDNs, both DNSNames, last one matches. + WITHOUT_SAN("b", RDN(CN("a")) + RDN(CN("b")), Success), + // Two CNs in separate RDNs, first one matches, second isn't a DNSName. + WITHOUT_SAN("a", RDN(CN("a")) + RDN(CN("Not a DNSName")), + Result::ERROR_BAD_CERT_DOMAIN), + // Two CNs in separate RDNs, first one not a DNSName, second matches. + WITHOUT_SAN("b", RDN(CN("Not a DNSName")) + RDN(CN("b")), Success), + + // One CN, one RDN, CN is the first AVA in the RDN, CN matches. + WITHOUT_SAN("a", RDN(CN("a") + OU("b")), Success), + // One CN, one RDN, CN is the first AVA in the RDN, CN does not match. + WITHOUT_SAN("b", RDN(CN("a") + OU("b")), + Result::ERROR_BAD_CERT_DOMAIN), + // One CN, one RDN, CN is not the first AVA in the RDN, CN matches. + WITHOUT_SAN("b", RDN(OU("a") + CN("b")), Success), + // One CN, one RDN, CN is not the first AVA in the RDN, CN does not match. + WITHOUT_SAN("a", RDN(OU("a") + CN("b")), + Result::ERROR_BAD_CERT_DOMAIN), + + // One CN, multiple RDNs, CN is in the first RDN, CN matches. + WITHOUT_SAN("a", RDN(CN("a")) + RDN(OU("b")), Success), + // One CN, multiple RDNs, CN is in the first RDN, CN does not match. + WITHOUT_SAN("b", RDN(CN("a")) + RDN(OU("b")), Result::ERROR_BAD_CERT_DOMAIN), + // One CN, multiple RDNs, CN is not in the first RDN, CN matches. + WITHOUT_SAN("b", RDN(OU("a")) + RDN(CN("b")), Success), + // One CN, multiple RDNs, CN is not in the first RDN, CN does not match. + WITHOUT_SAN("a", RDN(OU("a")) + RDN(CN("b")), Result::ERROR_BAD_CERT_DOMAIN), + + // One CN, one RDN, CN is not in the first or last AVA, CN matches. + WITHOUT_SAN("b", RDN(OU("a") + CN("b") + OU("c")), Success), + // One CN, multiple RDNs, CN is not in the first or last RDN, CN matches. + WITHOUT_SAN("b", RDN(OU("a")) + RDN(CN("b")) + RDN(OU("c")), Success), + + // Empty CN does not match. + WITHOUT_SAN("example.com", RDN(CN("")), Result::ERROR_BAD_CERT_DOMAIN), + + WITHOUT_SAN("uses_underscore.example.com", RDN(CN("*.example.com")), Success), + WITHOUT_SAN("a.uses_underscore.example.com", + RDN(CN("*.uses_underscore.example.com")), Success), + WITH_SAN("uses_underscore.example.com", RDN(CN("foo")), + DNSName("*.example.com"), Success), + WITH_SAN("a.uses_underscore.example.com", RDN(CN("foo")), + DNSName("*.uses_underscore.example.com"), Success), + + // Do not match a DNSName that is encoded in a malformed IPAddress. + WITH_SAN("example.com", RDN(CN("foo")), IPAddress(example_com), + Result::ERROR_BAD_CERT_DOMAIN), + + // We skip over the malformed IPAddress and match the DNSName entry because + // we've heard reports of real-world certificates that have malformed + // IPAddress SANs. + WITH_SAN("example.org", RDN(CN("foo")), + IPAddress(example_com) + DNSName("example.org"), Success), + + WITH_SAN("example.com", RDN(CN("foo")), + DNSName("!") + DNSName("example.com"), Result::ERROR_BAD_DER), + + // Match a matching IPv4 address SAN entry. + WITH_SAN(ipv4_addr_str, RDN(CN("foo")), IPAddress(ipv4_addr_bytes), + Success), + // Match a matching IPv4 addresses in the CN when there is no SAN + WITHOUT_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)), Success), + // Do not match a matching IPv4 address in the CN when there is a SAN with + // a DNSName entry. + WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)), + DNSName("example.com"), Result::ERROR_BAD_CERT_DOMAIN), + // Do not match a matching IPv4 address in the CN when there is a SAN with + // a non-matching IPAddress entry. + WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)), + IPAddress(ipv6_addr_bytes), Result::ERROR_BAD_CERT_DOMAIN), + // Match a matching IPv4 address in the CN when there is a SAN with a + // non-IPAddress, non-DNSName entry. + WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)), + RFC822Name("foo@example.com"), Success), + // Do not match a matching IPv4 address in the CN when there is a SAN with a + // malformed IPAddress entry. + WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)), + IPAddress(example_com), Result::ERROR_BAD_CERT_DOMAIN), + // Do not match a matching IPv4 address in the CN when there is a SAN with a + // malformed DNSName entry. + WITH_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_str)), + DNSName("!"), Result::ERROR_BAD_CERT_DOMAIN), + + // We don't match IPv6 addresses in the CN, regardless of whether there is + // a SAN. + WITHOUT_SAN(ipv6_addr_str, RDN(CN(ipv6_addr_str)), + Result::ERROR_BAD_CERT_DOMAIN), + WITH_SAN(ipv6_addr_str, RDN(CN(ipv6_addr_str)), + DNSName("example.com"), Result::ERROR_BAD_CERT_DOMAIN), + WITH_SAN(ipv6_addr_str, RDN(CN(ipv6_addr_str)), + IPAddress(ipv6_addr_bytes), Success), + WITH_SAN(ipv6_addr_str, RDN(CN("foo")), IPAddress(ipv6_addr_bytes), + Success), + + // We don't match the binary encoding of the bytes of IP addresses in the + // CN. + WITHOUT_SAN(ipv4_addr_str, RDN(CN(ipv4_addr_bytes_as_str)), + Result::ERROR_BAD_CERT_DOMAIN), + WITHOUT_SAN(ipv6_addr_str, RDN(CN(ipv6_addr_bytes_as_str)), + Result::ERROR_BAD_CERT_DOMAIN), + + // We don't match IP addresses with DNSName SANs. + WITH_SAN(ipv4_addr_str, RDN(CN("foo")), + DNSName(ipv4_addr_bytes_as_str), Result::ERROR_BAD_CERT_DOMAIN), + WITH_SAN(ipv4_addr_str, RDN(CN("foo")), DNSName(ipv4_addr_str), + Result::ERROR_BAD_CERT_DOMAIN), + WITH_SAN(ipv6_addr_str, RDN(CN("foo")), + DNSName(ipv6_addr_bytes_as_str), Result::ERROR_BAD_CERT_DOMAIN), + WITH_SAN(ipv6_addr_str, RDN(CN("foo")), DNSName(ipv6_addr_str), + Result::ERROR_BAD_CERT_DOMAIN), + + // Do not match an IPv4 reference ID against the equivalent IPv4-compatible + // IPv6 SAN entry. + WITH_SAN(ipv4_addr_str, RDN(CN("foo")), + IPAddress(ipv4_compatible_ipv6_addr_bytes), + Result::ERROR_BAD_CERT_DOMAIN), + // Do not match an IPv4 reference ID against the equivalent IPv4-mapped IPv6 + // SAN entry. + WITH_SAN(ipv4_addr_str, RDN(CN("foo")), + IPAddress(ipv4_mapped_ipv6_addr_bytes), + Result::ERROR_BAD_CERT_DOMAIN), + // Do not match an IPv4-compatible IPv6 reference ID against the equivalent + // IPv4 SAN entry. + WITH_SAN(ipv4_compatible_ipv6_addr_str, RDN(CN("foo")), + IPAddress(ipv4_addr_bytes), Result::ERROR_BAD_CERT_DOMAIN), + // Do not match an IPv4 reference ID against the equivalent IPv4-mapped IPv6 + // SAN entry. + WITH_SAN(ipv4_mapped_ipv6_addr_str, RDN(CN("foo")), + IPAddress(ipv4_addr_bytes), + Result::ERROR_BAD_CERT_DOMAIN), + + // Test that the presence of an otherName entry is handled appropriately. + // (The actual value of the otherName entry isn't important - that's not what + // we're testing here.) + WITH_SAN("example.com", ByteString(), + // The tag for otherName is CONTEXT_SPECIFIC | CONSTRUCTED | 0 + TLV((2 << 6) | (1 << 5) | 0, ByteString()) + DNSName("example.com"), + Success), + WITH_SAN("example.com", ByteString(), + TLV((2 << 6) | (1 << 5) | 0, ByteString()), + Result::ERROR_BAD_CERT_DOMAIN), +}; + +ByteString +CreateCert(const ByteString& subject, const ByteString& subjectAltName, + EndEntityOrCA endEntityOrCA = EndEntityOrCA::MustBeEndEntity) +{ + ByteString serialNumber(CreateEncodedSerialNumber(1)); + EXPECT_FALSE(ENCODING_FAILED(serialNumber)); + + ByteString issuerDER(Name(RDN(CN("issuer")))); + EXPECT_FALSE(ENCODING_FAILED(issuerDER)); + + ByteString extensions[2]; + if (subjectAltName != NO_SAN) { + extensions[0] = CreateEncodedSubjectAltName(subjectAltName); + EXPECT_FALSE(ENCODING_FAILED(extensions[0])); + } + if (endEntityOrCA == EndEntityOrCA::MustBeCA) { + // Currently, these tests assume that if we're creating a CA certificate, it + // will not have a subjectAlternativeName extension. If that assumption + // changes, this code will have to be updated. Ideally this would be + // ASSERT_EQ, but that inserts a 'return;', which doesn't match this + // function's return type. + EXPECT_EQ(subjectAltName, NO_SAN); + extensions[0] = CreateEncodedBasicConstraints(true, nullptr, + Critical::Yes); + EXPECT_FALSE(ENCODING_FAILED(extensions[0])); + } + + ScopedTestKeyPair keyPair(CloneReusedKeyPair()); + return CreateEncodedCertificate( + v3, sha256WithRSAEncryption(), serialNumber, issuerDER, + oneDayBeforeNow, oneDayAfterNow, Name(subject), *keyPair, + extensions, *keyPair, sha256WithRSAEncryption()); +} + +TEST_P(pkixnames_CheckCertHostname, CheckCertHostname) +{ + const CheckCertHostnameParams& param(GetParam()); + + ByteString cert(CreateCert(param.subject, param.subjectAltName)); + ASSERT_FALSE(ENCODING_FAILED(cert)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); + + Input hostnameInput; + ASSERT_EQ(Success, hostnameInput.Init(param.hostname.data(), + param.hostname.length())); + + ASSERT_EQ(param.result, CheckCertHostname(certInput, hostnameInput, + mNameMatchingPolicy)); +} + +INSTANTIATE_TEST_CASE_P(pkixnames_CheckCertHostname, + pkixnames_CheckCertHostname, + testing::ValuesIn(CHECK_CERT_HOSTNAME_PARAMS)); + +TEST_F(pkixnames_CheckCertHostname, SANWithoutSequence) +{ + // A certificate with a truly empty SAN extension (one that doesn't even + // contain a SEQUENCE at all) is malformed. If we didn't treat this as + // malformed then we'd have to treat it like the CN_EmptySAN cases. + + ByteString serialNumber(CreateEncodedSerialNumber(1)); + EXPECT_FALSE(ENCODING_FAILED(serialNumber)); + + ByteString extensions[2]; + extensions[0] = CreateEncodedEmptySubjectAltName(); + ASSERT_FALSE(ENCODING_FAILED(extensions[0])); + + ScopedTestKeyPair keyPair(CloneReusedKeyPair()); + ByteString certDER(CreateEncodedCertificate( + v3, sha256WithRSAEncryption(), serialNumber, + Name(RDN(CN("issuer"))), oneDayBeforeNow, oneDayAfterNow, + Name(RDN(CN("a"))), *keyPair, extensions, + *keyPair, sha256WithRSAEncryption())); + ASSERT_FALSE(ENCODING_FAILED(certDER)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length())); + + static const uint8_t a[] = { 'a' }; + ASSERT_EQ(Result::ERROR_EXTENSION_VALUE_INVALID, + CheckCertHostname(certInput, Input(a), mNameMatchingPolicy)); +} + +class pkixnames_CheckCertHostname_PresentedMatchesReference + : public ::testing::Test + , public ::testing::WithParamInterface<PresentedMatchesReference> +{ +public: + DefaultNameMatchingPolicy mNameMatchingPolicy; +}; + +TEST_P(pkixnames_CheckCertHostname_PresentedMatchesReference, CN_NoSAN) +{ + // Since there is no SAN, a valid presented DNS ID in the subject CN field + // should result in a match. + + const PresentedMatchesReference& param(GetParam()); + + ByteString cert(CreateCert(RDN(CN(param.presentedDNSID)), NO_SAN)); + ASSERT_FALSE(ENCODING_FAILED(cert)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); + + Input hostnameInput; + ASSERT_EQ(Success, hostnameInput.Init(param.referenceDNSID.data(), + param.referenceDNSID.length())); + + ASSERT_EQ(param.expectedMatches ? Success : Result::ERROR_BAD_CERT_DOMAIN, + CheckCertHostname(certInput, hostnameInput, mNameMatchingPolicy)); +} + +TEST_P(pkixnames_CheckCertHostname_PresentedMatchesReference, + SubjectAltName_CNNotDNSName) +{ + // A DNSName SAN entry should match, regardless of the contents of the + // subject CN. + + const PresentedMatchesReference& param(GetParam()); + + ByteString cert(CreateCert(RDN(CN("Common Name")), + DNSName(param.presentedDNSID))); + ASSERT_FALSE(ENCODING_FAILED(cert)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); + + Input hostnameInput; + ASSERT_EQ(Success, hostnameInput.Init(param.referenceDNSID.data(), + param.referenceDNSID.length())); + Result expectedResult + = param.expectedResult != Success ? param.expectedResult + : param.expectedMatches ? Success + : Result::ERROR_BAD_CERT_DOMAIN; + ASSERT_EQ(expectedResult, CheckCertHostname(certInput, hostnameInput, + mNameMatchingPolicy)); +} + +INSTANTIATE_TEST_CASE_P(pkixnames_CheckCertHostname_DNSID_MATCH_PARAMS, + pkixnames_CheckCertHostname_PresentedMatchesReference, + testing::ValuesIn(DNSID_MATCH_PARAMS)); + +TEST_P(pkixnames_Turkish_I_Comparison, CheckCertHostname_CN_NoSAN) +{ + // Make sure we don't have the similar problems that strcasecmp and others + // have with the other kinds of "i" and "I" commonly used in Turkish locales, + // when we're matching a CN due to lack of subjectAltName. + + const InputValidity& param(GetParam()); + SCOPED_TRACE(param.input.c_str()); + + Input input; + ASSERT_EQ(Success, input.Init(param.input.data(), param.input.length())); + + ByteString cert(CreateCert(RDN(CN(param.input)), NO_SAN)); + ASSERT_FALSE(ENCODING_FAILED(cert)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); + + Result expectedResult = (InputsAreEqual(LOWERCASE_I, input) || + InputsAreEqual(UPPERCASE_I, input)) + ? Success + : Result::ERROR_BAD_CERT_DOMAIN; + + ASSERT_EQ(expectedResult, CheckCertHostname(certInput, UPPERCASE_I, + mNameMatchingPolicy)); + ASSERT_EQ(expectedResult, CheckCertHostname(certInput, LOWERCASE_I, + mNameMatchingPolicy)); +} + +TEST_P(pkixnames_Turkish_I_Comparison, CheckCertHostname_SAN) +{ + // Make sure we don't have the similar problems that strcasecmp and others + // have with the other kinds of "i" and "I" commonly used in Turkish locales, + // when we're matching a dNSName in the SAN. + + const InputValidity& param(GetParam()); + SCOPED_TRACE(param.input.c_str()); + + Input input; + ASSERT_EQ(Success, input.Init(param.input.data(), param.input.length())); + + ByteString cert(CreateCert(RDN(CN("Common Name")), DNSName(param.input))); + ASSERT_FALSE(ENCODING_FAILED(cert)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); + + Result expectedResult + = (!param.isValidPresentedID) ? Result::ERROR_BAD_DER + : (InputsAreEqual(LOWERCASE_I, input) || + InputsAreEqual(UPPERCASE_I, input)) ? Success + : Result::ERROR_BAD_CERT_DOMAIN; + + ASSERT_EQ(expectedResult, CheckCertHostname(certInput, UPPERCASE_I, + mNameMatchingPolicy)); + ASSERT_EQ(expectedResult, CheckCertHostname(certInput, LOWERCASE_I, + mNameMatchingPolicy)); +} + +class pkixnames_CheckCertHostname_IPV4_Addresses + : public ::testing::Test + , public ::testing::WithParamInterface<IPAddressParams<4>> +{ +public: + DefaultNameMatchingPolicy mNameMatchingPolicy; +}; + +TEST_P(pkixnames_CheckCertHostname_IPV4_Addresses, + ValidIPv4AddressInIPAddressSAN) +{ + // When the reference hostname is a valid IPv4 address, a correctly-formed + // IPv4 Address SAN matches it. + + const IPAddressParams<4>& param(GetParam()); + + ByteString cert(CreateCert(RDN(CN("Common Name")), + IPAddress(param.expectedValueIfValid))); + ASSERT_FALSE(ENCODING_FAILED(cert)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); + + Input hostnameInput; + ASSERT_EQ(Success, hostnameInput.Init(param.input.data(), + param.input.length())); + + ASSERT_EQ(param.isValid ? Success : Result::ERROR_BAD_CERT_DOMAIN, + CheckCertHostname(certInput, hostnameInput, mNameMatchingPolicy)); +} + +TEST_P(pkixnames_CheckCertHostname_IPV4_Addresses, + ValidIPv4AddressInCN_NoSAN) +{ + // When the reference hostname is a valid IPv4 address, a correctly-formed + // IPv4 Address in the CN matches it when there is no SAN. + + const IPAddressParams<4>& param(GetParam()); + + SCOPED_TRACE(param.input.c_str()); + + ByteString cert(CreateCert(RDN(CN(param.input)), NO_SAN)); + ASSERT_FALSE(ENCODING_FAILED(cert)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(cert.data(), cert.length())); + + Input hostnameInput; + ASSERT_EQ(Success, hostnameInput.Init(param.input.data(), + param.input.length())); + + // Some of the invalid IPv4 addresses are valid DNS names! + Result expectedResult = (param.isValid || IsValidReferenceDNSID(hostnameInput)) + ? Success + : Result::ERROR_BAD_CERT_DOMAIN; + + ASSERT_EQ(expectedResult, CheckCertHostname(certInput, hostnameInput, + mNameMatchingPolicy)); +} + +INSTANTIATE_TEST_CASE_P(pkixnames_CheckCertHostname_IPV4_ADDRESSES, + pkixnames_CheckCertHostname_IPV4_Addresses, + testing::ValuesIn(IPV4_ADDRESSES)); + +struct NameConstraintParams +{ + ByteString subject; + ByteString subjectAltName; + ByteString subtrees; + Result expectedPermittedSubtreesResult; + Result expectedExcludedSubtreesResult; +}; + +::std::ostream& operator<<(::std::ostream& os, const NameConstraintParams&) +{ + return os << "TODO (bug 1318770)"; +} + +static ByteString +PermittedSubtrees(const ByteString& generalSubtrees) +{ + return TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0, + generalSubtrees); +} + +static ByteString +ExcludedSubtrees(const ByteString& generalSubtrees) +{ + return TLV(der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1, + generalSubtrees); +} + +// Does not encode min or max. +static ByteString +GeneralSubtree(const ByteString& base) +{ + return TLV(der::SEQUENCE, base); +} + +static const NameConstraintParams NAME_CONSTRAINT_PARAMS[] = +{ + ///////////////////////////////////////////////////////////////////////////// + // XXX: Malformed name constraints for supported types of names are ignored + // when there are no names of that type to constrain. + { ByteString(), NO_SAN, + GeneralSubtree(DNSName("!")), + Success, Success + }, + { // DirectoryName constraints are an exception, because *every* certificate + // has at least one DirectoryName (tbsCertificate.subject). + ByteString(), NO_SAN, + GeneralSubtree(Name(ByteString(reinterpret_cast<const uint8_t*>("!"), 1))), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { ByteString(), NO_SAN, + GeneralSubtree(IPAddress(ipv4_constraint_truncated_bytes)), + Success, Success + }, + { ByteString(), NO_SAN, + GeneralSubtree(IPAddress(ipv4_constraint_overlong_bytes)), + Success, Success + }, + { ByteString(), NO_SAN, + GeneralSubtree(IPAddress(ipv6_constraint_truncated_bytes)), + Success, Success + }, + { ByteString(), NO_SAN, + GeneralSubtree(IPAddress(ipv6_constraint_overlong_bytes)), + Success, Success + }, + { ByteString(), NO_SAN, + GeneralSubtree(RFC822Name("!")), + Success, Success + }, + + ///////////////////////////////////////////////////////////////////////////// + // Edge cases of name constraint absolute vs. relative and subdomain matching + // that are not clearly explained in RFC 5280. (See the long comment above + // MatchPresentedDNSIDWithReferenceDNSID.) + + // Q: Does a presented identifier equal (case insensitive) to the name + // constraint match the constraint? For example, does the presented + // ID "host.example.com" match a "host.example.com" constraint? + { ByteString(), DNSName("host.example.com"), + GeneralSubtree(DNSName("host.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // This test case is an example from RFC 5280. + ByteString(), DNSName("host1.example.com"), + GeneralSubtree(DNSName("host.example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { ByteString(), RFC822Name("a@host.example.com"), + GeneralSubtree(RFC822Name("host.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // This test case is an example from RFC 5280. + ByteString(), RFC822Name("a@host1.example.com"), + GeneralSubtree(RFC822Name("host.example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + + // Q: When the name constraint does not start with ".", do subdomain + // presented identifiers match it? For example, does the presented + // ID "www.host.example.com" match a "host.example.com" constraint? + { // This test case is an example from RFC 5280. + ByteString(), DNSName("www.host.example.com"), + GeneralSubtree(DNSName( "host.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // The subdomain matching rule for host names that do not start with "." is + // different for RFC822Names than for DNSNames! + ByteString(), RFC822Name("a@www.host.example.com"), + GeneralSubtree(RFC822Name( "host.example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, + Success + }, + + // Q: When the name constraint does not start with ".", does a + // non-subdomain prefix match it? For example, does "bigfoo.bar.com" + // match "foo.bar.com"? + { ByteString(), DNSName("bigfoo.bar.com"), + GeneralSubtree(DNSName( "foo.bar.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { ByteString(), RFC822Name("a@bigfoo.bar.com"), + GeneralSubtree(RFC822Name( "foo.bar.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + + // Q: Is a name constraint that starts with "." valid, and if so, what + // semantics does it have? For example, does a presented ID of + // "www.example.com" match a constraint of ".example.com"? Does a + // presented ID of "example.com" match a constraint of ".example.com"? + { ByteString(), DNSName("www.example.com"), + GeneralSubtree(DNSName( ".example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // When there is no Local-part, an RFC822 name constraint's domain may + // start with '.', and the semantics are the same as for DNSNames. + ByteString(), RFC822Name("a@www.example.com"), + GeneralSubtree(RFC822Name( ".example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // When there is a Local-part, an RFC822 name constraint's domain must not + // start with '.'. + ByteString(), RFC822Name("a@www.example.com"), + GeneralSubtree(RFC822Name( "a@.example.com")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // Check that we only allow subdomains to match. + ByteString(), DNSName( "example.com"), + GeneralSubtree(DNSName(".example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // Check that we only allow subdomains to match. + ByteString(), RFC822Name("a@example.com"), + GeneralSubtree(RFC822Name(".example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // Check that we don't get confused and consider "b" == "." + ByteString(), DNSName("bexample.com"), + GeneralSubtree(DNSName(".example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // Check that we don't get confused and consider "b" == "." + ByteString(), RFC822Name("a@bexample.com"), + GeneralSubtree(RFC822Name( ".example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + + // Q: Is there a way to prevent subdomain matches? + // (This is tested in a different set of tests because it requires a + // combination of permittedSubtrees and excludedSubtrees.) + + // Q: Are name constraints allowed to be specified as absolute names? + // For example, does a presented ID of "example.com" match a name + // constraint of "example.com." and vice versa? + // + { // The DNSName in the constraint is not valid because constraint DNS IDs + // are not allowed to be absolute. + ByteString(), DNSName("example.com"), + GeneralSubtree(DNSName("example.com.")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, + }, + { ByteString(), RFC822Name("a@example.com"), + GeneralSubtree(RFC822Name( "example.com.")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, + }, + { // The DNSName in the SAN is not valid because presented DNS IDs are not + // allowed to be absolute. + ByteString(), DNSName("example.com."), + GeneralSubtree(DNSName("example.com")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, + }, + { ByteString(), RFC822Name("a@example.com."), + GeneralSubtree(RFC822Name( "example.com")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, + }, + { // The presented DNSName is the same length as the constraint, because the + // subdomain is only one character long and because the constraint both + // begins and ends with ".". But, it doesn't matter because absolute names + // are not allowed for DNSName constraints. + ByteString(), DNSName("p.example.com"), + GeneralSubtree(DNSName(".example.com.")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, + }, + { // The presented DNSName is the same length as the constraint, because the + // subdomain is only one character long and because the constraint both + // begins and ends with ".". + ByteString(), RFC822Name("a@p.example.com"), + GeneralSubtree(RFC822Name( ".example.com.")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, + }, + { // Same as previous test case, but using a wildcard presented ID. + ByteString(), DNSName("*.example.com"), + GeneralSubtree(DNSName(".example.com.")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // Same as previous test case, but using a wildcard presented ID, which is + // invalid in an RFC822Name. + ByteString(), RFC822Name("a@*.example.com"), + GeneralSubtree(RFC822Name( ".example.com.")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + + // Q: Are "" and "." valid DNSName constraints? If so, what do they mean? + { ByteString(), DNSName("example.com"), + GeneralSubtree(DNSName("")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), RFC822Name("a@example.com"), + GeneralSubtree(RFC822Name("")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // The malformed (absolute) presented ID does not match. + ByteString(), DNSName("example.com."), + GeneralSubtree(DNSName("")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { ByteString(), RFC822Name("a@example.com."), + GeneralSubtree(RFC822Name("")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // Invalid syntax in name constraint + ByteString(), DNSName("example.com"), + GeneralSubtree(DNSName(".")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, + }, + { // Invalid syntax in name constraint + ByteString(), RFC822Name("a@example.com"), + GeneralSubtree(RFC822Name(".")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER, + }, + { ByteString(), DNSName("example.com."), + GeneralSubtree(DNSName(".")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { ByteString(), RFC822Name("a@example.com."), + GeneralSubtree(RFC822Name(".")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + + ///////////////////////////////////////////////////////////////////////////// + // Basic IP Address constraints (non-CN-ID) + + // The Mozilla CA Policy says this means "no IPv4 addresses allowed." + { ByteString(), IPAddress(ipv4_addr_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), IPAddress(ipv4_addr_00000000_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), IPAddress(ipv4_addr_FFFFFFFF_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + + // The Mozilla CA Policy says this means "no IPv6 addresses allowed." + { ByteString(), IPAddress(ipv6_addr_bytes), + GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), IPAddress(ipv6_addr_all_zeros_bytes), + GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + + // RFC 5280 doesn't partition IP address constraints into separate IPv4 and + // IPv6 categories, so a IPv4 permittedSubtrees constraint excludes all IPv6 + // addresses, and vice versa. + { ByteString(), IPAddress(ipv4_addr_bytes), + GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { ByteString(), IPAddress(ipv6_addr_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + + // IPv4 Subnets + { ByteString(), IPAddress(ipv4_addr_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_CIDR_16_bytes)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), IPAddress(ipv4_addr_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_CIDR_17_bytes)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), IPAddress(ipv4_other_addr_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_CIDR_16_bytes)), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // XXX(bug 1089430): We don't reject this even though it is weird. + ByteString(), IPAddress(ipv4_addr_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_CIDR_16_bad_addr_bytes)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // XXX(bug 1089430): We don't reject this even though it is weird. + ByteString(), IPAddress(ipv4_other_addr_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_bad_mask_bytes)), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + + // IPv6 Subnets + { ByteString(), IPAddress(ipv6_addr_bytes), + GeneralSubtree(IPAddress(ipv6_constraint_CIDR_16_bytes)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), IPAddress(ipv6_other_addr_bytes), + GeneralSubtree(IPAddress(ipv6_constraint_CIDR_16_bytes)), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // XXX(bug 1089430): We don't reject this even though it is weird. + ByteString(), IPAddress(ipv6_addr_bytes), + GeneralSubtree(IPAddress(ipv6_constraint_CIDR_16_bad_addr_bytes)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // XXX(bug 1089430): We don't reject this even though it is weird. + ByteString(), IPAddress(ipv6_other_addr_bytes), + GeneralSubtree(IPAddress(ipv6_constraint_bad_mask_bytes)), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + + // Malformed presented IP addresses and constraints + + { // The presented IPv4 address is empty + ByteString(), IPAddress(), + GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // The presented IPv4 address is truncated + ByteString(), IPAddress(ipv4_addr_truncated_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // The presented IPv4 address is too long + ByteString(), IPAddress(ipv4_addr_overlong_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_all_zeros_bytes)), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // The presented IPv4 constraint is empty + ByteString(), IPAddress(ipv4_addr_bytes), + GeneralSubtree(IPAddress()), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // The presented IPv4 constraint is truncated + ByteString(), IPAddress(ipv4_addr_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_truncated_bytes)), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // The presented IPv4 constraint is too long + ByteString(), IPAddress(ipv4_addr_bytes), + GeneralSubtree(IPAddress(ipv4_constraint_overlong_bytes)), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // The presented IPv6 address is empty + ByteString(), IPAddress(), + GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // The presented IPv6 address is truncated + ByteString(), IPAddress(ipv6_addr_truncated_bytes), + GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // The presented IPv6 address is too long + ByteString(), IPAddress(ipv6_addr_overlong_bytes), + GeneralSubtree(IPAddress(ipv6_constraint_all_zeros_bytes)), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // The presented IPv6 constraint is empty + ByteString(), IPAddress(ipv6_addr_bytes), + GeneralSubtree(IPAddress()), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // The presented IPv6 constraint is truncated + ByteString(), IPAddress(ipv6_addr_bytes), + GeneralSubtree(IPAddress(ipv6_constraint_truncated_bytes)), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + { // The presented IPv6 constraint is too long + ByteString(), IPAddress(ipv6_addr_bytes), + GeneralSubtree(IPAddress(ipv6_constraint_overlong_bytes)), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + + ///////////////////////////////////////////////////////////////////////////// + // XXX: We don't reject malformed name constraints when there are no names of + // that type. + { ByteString(), NO_SAN, GeneralSubtree(DNSName("!")), + Success, Success + }, + { ByteString(), NO_SAN, GeneralSubtree(IPAddress(ipv4_addr_overlong_bytes)), + Success, Success + }, + { ByteString(), NO_SAN, GeneralSubtree(IPAddress(ipv6_addr_overlong_bytes)), + Success, Success + }, + { ByteString(), NO_SAN, GeneralSubtree(RFC822Name("\0")), + Success, Success + }, + + ///////////////////////////////////////////////////////////////////////////// + // Basic CN-ID DNSName constraint tests. + + { // Empty Name is ignored for DNSName constraints. + ByteString(), NO_SAN, GeneralSubtree(DNSName("a.example.com")), + Success, Success + }, + { // Empty CN is ignored for DNSName constraints because it isn't a + // syntactically-valid DNSName. + // + // NSS gives different results. + RDN(CN("")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), + Success, Success + }, + { // IP Address is ignored for DNSName constraints. + // + // NSS gives different results. + RDN(CN("1.2.3.4")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), + Success, Success + }, + { // OU has something that looks like a dNSName that matches. + RDN(OU("a.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), + Success, Success + }, + { // OU has something that looks like a dNSName that does not match. + RDN(OU("b.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), + Success, Success + }, + { // NSS gives different results. + RDN(CN("Not a DNSName")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), + Success, Success + }, + { RDN(CN("a.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { RDN(CN("b.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // DNSName CN-ID match is detected when there is a SAN w/o any DNSName or + // IPAddress + RDN(CN("a.example.com")), RFC822Name("foo@example.com"), + GeneralSubtree(DNSName("a.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // DNSName CN-ID mismatch is detected when there is a SAN w/o any DNSName + // or IPAddress + RDN(CN("a.example.com")), RFC822Name("foo@example.com"), + GeneralSubtree(DNSName("b.example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // DNSName CN-ID match not reported when there is a DNSName SAN + RDN(CN("a.example.com")), DNSName("b.example.com"), + GeneralSubtree(DNSName("a.example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // DNSName CN-ID mismatch not reported when there is a DNSName SAN + RDN(CN("a.example.com")), DNSName("b.example.com"), + GeneralSubtree(DNSName("b.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE, + }, + { // DNSName CN-ID match not reported when there is an IPAddress SAN + RDN(CN("a.example.com")), IPAddress(ipv4_addr_bytes), + GeneralSubtree(DNSName("a.example.com")), + Success, Success + }, + { // DNSName CN-ID mismatch not reported when there is an IPAddress SAN + RDN(CN("a.example.com")), IPAddress(ipv4_addr_bytes), + GeneralSubtree(DNSName("b.example.com")), + Success, Success + }, + + { // IPAddress CN-ID match is detected when there is a SAN w/o any DNSName or + // IPAddress + RDN(CN(ipv4_addr_str)), RFC822Name("foo@example.com"), + GeneralSubtree(IPAddress(ipv4_addr_bytes_FFFFFFFF)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // IPAddress CN-ID mismatch is detected when there is a SAN w/o any DNSName + // or IPAddress + RDN(CN(ipv4_addr_str)), RFC822Name("foo@example.com"), + GeneralSubtree(IPAddress(ipv4_other_addr_bytes_FFFFFFFF)), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // IPAddress CN-ID match not reported when there is a DNSName SAN + RDN(CN(ipv4_addr_str)), DNSName("b.example.com"), + GeneralSubtree(IPAddress(ipv4_addr_bytes_FFFFFFFF)), + Success, Success + }, + { // IPAddress CN-ID mismatch not reported when there is a DNSName SAN + RDN(CN(ipv4_addr_str)), DNSName("b.example.com"), + GeneralSubtree(IPAddress(ipv4_addr_bytes_FFFFFFFF)), + Success, Success + }, + { // IPAddress CN-ID match not reported when there is an IPAddress SAN + RDN(CN(ipv4_addr_str)), IPAddress(ipv4_other_addr_bytes), + GeneralSubtree(IPAddress(ipv4_addr_bytes_FFFFFFFF)), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // IPAddress CN-ID mismatch not reported when there is an IPAddress SAN + RDN(CN(ipv4_addr_str)), IPAddress(ipv4_other_addr_bytes), + GeneralSubtree(IPAddress(ipv4_other_addr_bytes_FFFFFFFF)), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + + ///////////////////////////////////////////////////////////////////////////// + // Test that constraints are applied to the most specific (last) CN, and only + // that CN-ID. + + { // Name constraint only matches a.example.com, but the most specific CN + // (i.e. the CN-ID) is b.example.com. (Two CNs in one RDN.) + RDN(CN("a.example.com") + CN("b.example.com")), NO_SAN, + GeneralSubtree(DNSName("a.example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // Name constraint only matches a.example.com, but the most specific CN + // (i.e. the CN-ID) is b.example.com. (Two CNs in separate RDNs.) + RDN(CN("a.example.com")) + RDN(CN("b.example.com")), NO_SAN, + GeneralSubtree(DNSName("a.example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Success + }, + { // Name constraint only permits b.example.com, and the most specific CN + // (i.e. the CN-ID) is b.example.com. (Two CNs in one RDN.) + RDN(CN("a.example.com") + CN("b.example.com")), NO_SAN, + GeneralSubtree(DNSName("b.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // Name constraint only permits b.example.com, and the most specific CN + // (i.e. the CN-ID) is b.example.com. (Two CNs in separate RDNs.) + RDN(CN("a.example.com")) + RDN(CN("b.example.com")), NO_SAN, + GeneralSubtree(DNSName("b.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + + ///////////////////////////////////////////////////////////////////////////// + // Additional RFC822 name constraint tests. There are more tests regarding + // the DNSName part of the constraint mixed into the DNSName constraint + // tests. + + { ByteString(), RFC822Name("a@example.com"), + GeneralSubtree(RFC822Name("a@example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + + // Bug 1056773: name constraints that omit Local-part but include '@' are + // invalid. + { ByteString(), RFC822Name("a@example.com"), + GeneralSubtree(RFC822Name("@example.com")), + Result::ERROR_BAD_DER, + Result::ERROR_BAD_DER + }, + { ByteString(), RFC822Name("@example.com"), + GeneralSubtree(RFC822Name("@example.com")), + Result::ERROR_BAD_DER, + Result::ERROR_BAD_DER + }, + { ByteString(), RFC822Name("example.com"), + GeneralSubtree(RFC822Name("@example.com")), + Result::ERROR_BAD_DER, + Result::ERROR_BAD_DER + }, + { ByteString(), RFC822Name("a@mail.example.com"), + GeneralSubtree(RFC822Name("a@*.example.com")), + Result::ERROR_BAD_DER, + Result::ERROR_BAD_DER + }, + { ByteString(), RFC822Name("a@*.example.com"), + GeneralSubtree(RFC822Name(".example.com")), + Result::ERROR_BAD_DER, + Result::ERROR_BAD_DER + }, + { ByteString(), RFC822Name("@example.com"), + GeneralSubtree(RFC822Name(".example.com")), + Result::ERROR_BAD_DER, + Result::ERROR_BAD_DER + }, + { ByteString(), RFC822Name("@a.example.com"), + GeneralSubtree(RFC822Name(".example.com")), + Result::ERROR_BAD_DER, + Result::ERROR_BAD_DER + }, + + ///////////////////////////////////////////////////////////////////////////// + // Test name constraints with underscores. + // + { ByteString(), DNSName("uses_underscore.example.com"), + GeneralSubtree(DNSName("uses_underscore.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), DNSName("uses_underscore.example.com"), + GeneralSubtree(DNSName("example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), DNSName("a.uses_underscore.example.com"), + GeneralSubtree(DNSName("uses_underscore.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), RFC822Name("a@uses_underscore.example.com"), + GeneralSubtree(RFC822Name("uses_underscore.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), RFC822Name("uses_underscore@example.com"), + GeneralSubtree(RFC822Name("example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), RFC822Name("a@a.uses_underscore.example.com"), + GeneralSubtree(RFC822Name(".uses_underscore.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + + ///////////////////////////////////////////////////////////////////////////// + // Name constraint tests that relate to having an empty SAN. According to RFC + // 5280 this isn't valid, but we allow it for compatibility reasons (see bug + // 1143085). + { // For DNSNames, we fall back to the subject CN. + RDN(CN("a.example.com")), ByteString(), + GeneralSubtree(DNSName("a.example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // For RFC822Names, we do not fall back to the subject emailAddress. + // This new implementation seems to conform better to the standards for + // RFC822 name constraints, by only applying the name constraints to + // emailAddress names in the certificate subject if there is no + // subjectAltName extension in the cert. + // In this case, the presence of the (empty) SAN extension means that RFC822 + // name constraints are not enforced on the emailAddress attributes of the + // subject. + RDN(emailAddress("a@example.com")), ByteString(), + GeneralSubtree(RFC822Name("a@example.com")), + Success, Success + }, + { // Compare this to the case where there is no SAN (i.e. the name + // constraints are enforced, because the extension is not present at all). + RDN(emailAddress("a@example.com")), NO_SAN, + GeneralSubtree(RFC822Name("a@example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + + ///////////////////////////////////////////////////////////////////////////// + // DirectoryName name constraint tests + + { // One AVA per RDN + RDN(OU("Example Organization")) + RDN(CN("example.com")), NO_SAN, + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization")) + + RDN(CN("example.com"))))), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // RDNs can have multiple AVAs. + RDN(OU("Example Organization") + CN("example.com")), NO_SAN, + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization") + + CN("example.com"))))), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // The constraint is a prefix of the subject DN. + RDN(OU("Example Organization")) + RDN(CN("example.com")), NO_SAN, + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // The name constraint is not a prefix of the subject DN. + // Note that for excludedSubtrees, we simply prohibit any non-empty + // directoryName constraint to ensure we are not being too lenient. + RDN(OU("Other Example Organization")) + RDN(CN("example.com")), NO_SAN, + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization")) + + RDN(CN("example.com"))))), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // Same as the previous one, but one RDN with multiple AVAs. + RDN(OU("Other Example Organization") + CN("example.com")), NO_SAN, + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization") + + CN("example.com"))))), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // With multiple AVAs per RDN in the subject DN, the constraint is not a + // prefix of the subject DN. + RDN(OU("Example Organization") + CN("example.com")), NO_SAN, + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // The subject DN RDN has multiple AVAs, but the name constraint has only + // one AVA per RDN. + RDN(OU("Example Organization") + CN("example.com")), NO_SAN, + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization")) + + RDN(CN("example.com"))))), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // The name constraint RDN has multiple AVAs, but the subject DN has only + // one AVA per RDN. + RDN(OU("Example Organization")) + RDN(CN("example.com")), NO_SAN, + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization") + + CN("example.com"))))), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // In this case, the constraint uses a different encoding from the subject. + // We consider them to match because we allow UTF8String and + // PrintableString to compare equal when their contents are equal. + RDN(OU("Example Organization", der::UTF8String)) + RDN(CN("example.com")), + NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization", + der::PrintableString)) + + RDN(CN("example.com"))))), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // Same as above, but with UTF8String/PrintableString switched. + RDN(OU("Example Organization", der::PrintableString)) + RDN(CN("example.com")), + NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization", + der::UTF8String)) + + RDN(CN("example.com"))))), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // If the contents aren't the same, then they shouldn't match. + RDN(OU("Other Example Organization", der::UTF8String)) + RDN(CN("example.com")), + NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization", + der::PrintableString)) + + RDN(CN("example.com"))))), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { // Only UTF8String and PrintableString are considered equivalent. + RDN(OU("Example Organization", der::PrintableString)) + RDN(CN("example.com")), + NO_SAN, GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization", + der::TeletexString)) + + RDN(CN("example.com"))))), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + // Some additional tests for completeness: + // Ensure that wildcards are handled: + { RDN(CN("*.example.com")), NO_SAN, GeneralSubtree(DNSName("example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), DNSName("*.example.com"), + GeneralSubtree(DNSName("example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), DNSName("www.example.com"), + GeneralSubtree(DNSName("*.example.com")), + Result::ERROR_BAD_DER, Result::ERROR_BAD_DER + }, + // Handle multiple name constraint entries: + { RDN(CN("example.com")), NO_SAN, + GeneralSubtree(DNSName("example.org")) + + GeneralSubtree(DNSName("example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { ByteString(), DNSName("example.com"), + GeneralSubtree(DNSName("example.org")) + + GeneralSubtree(DNSName("example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + // Handle multiple names in subject alternative name extension: + { ByteString(), DNSName("example.com") + DNSName("example.org"), + GeneralSubtree(DNSName("example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + // Handle a mix of DNSName and DirectoryName: + { RDN(OU("Example Organization")), DNSName("example.com"), + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))) + + GeneralSubtree(DNSName("example.com")), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { RDN(OU("Other Example Organization")), DNSName("example.com"), + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))) + + GeneralSubtree(DNSName("example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + { RDN(OU("Example Organization")), DNSName("example.org"), + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))) + + GeneralSubtree(DNSName("example.com")), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + // Handle a certificate with no DirectoryName: + { ByteString(), DNSName("example.com"), + GeneralSubtree(DirectoryName(Name(RDN(OU("Example Organization"))))), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, +}; + +class pkixnames_CheckNameConstraints + : public ::testing::Test + , public ::testing::WithParamInterface<NameConstraintParams> +{ +public: + DefaultNameMatchingPolicy mNameMatchingPolicy; +}; + +TEST_P(pkixnames_CheckNameConstraints, + NameConstraintsEnforcedForDirectlyIssuedEndEntity) +{ + // Test that name constraints are enforced on a certificate directly issued by + // a certificate with the given name constraints. + + const NameConstraintParams& param(GetParam()); + + ByteString certDER(CreateCert(param.subject, param.subjectAltName)); + ASSERT_FALSE(ENCODING_FAILED(certDER)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length())); + BackCert cert(certInput, EndEntityOrCA::MustBeEndEntity, nullptr); + ASSERT_EQ(Success, cert.Init()); + + { + ByteString nameConstraintsDER(TLV(der::SEQUENCE, + PermittedSubtrees(param.subtrees))); + Input nameConstraints; + ASSERT_EQ(Success, + nameConstraints.Init(nameConstraintsDER.data(), + nameConstraintsDER.length())); + ASSERT_EQ(param.expectedPermittedSubtreesResult, + CheckNameConstraints(nameConstraints, cert, + KeyPurposeId::id_kp_serverAuth)); + } + { + ByteString nameConstraintsDER(TLV(der::SEQUENCE, + ExcludedSubtrees(param.subtrees))); + Input nameConstraints; + ASSERT_EQ(Success, + nameConstraints.Init(nameConstraintsDER.data(), + nameConstraintsDER.length())); + ASSERT_EQ(param.expectedExcludedSubtreesResult, + CheckNameConstraints(nameConstraints, cert, + KeyPurposeId::id_kp_serverAuth)); + } + { + ByteString nameConstraintsDER(TLV(der::SEQUENCE, + PermittedSubtrees(param.subtrees) + + ExcludedSubtrees(param.subtrees))); + Input nameConstraints; + ASSERT_EQ(Success, + nameConstraints.Init(nameConstraintsDER.data(), + nameConstraintsDER.length())); + ASSERT_EQ((param.expectedPermittedSubtreesResult == + param.expectedExcludedSubtreesResult) + ? param.expectedExcludedSubtreesResult + : Result::ERROR_CERT_NOT_IN_NAME_SPACE, + CheckNameConstraints(nameConstraints, cert, + KeyPurposeId::id_kp_serverAuth)); + } +} + +INSTANTIATE_TEST_CASE_P(pkixnames_CheckNameConstraints, + pkixnames_CheckNameConstraints, + testing::ValuesIn(NAME_CONSTRAINT_PARAMS)); + +// The |subjectAltName| param is not used for these test cases (hence the use of +// "NO_SAN"). +static const NameConstraintParams NO_FALLBACK_NAME_CONSTRAINT_PARAMS[] = +{ + // The only difference between end-entities being verified for serverAuth and + // intermediates or end-entities being verified for other uses is that for + // the latter cases, there is no fallback matching of DNSName entries to the + // subject common name. + { RDN(CN("Not a DNSName")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), + Success, Success + }, + { RDN(CN("a.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), + Success, Success + }, + { RDN(CN("b.example.com")), NO_SAN, GeneralSubtree(DNSName("a.example.com")), + Success, Success + }, + // Sanity-check that name constraints are in fact enforced in these cases. + { RDN(CN("Example Name")), NO_SAN, + GeneralSubtree(DirectoryName(Name(RDN(CN("Example Name"))))), + Success, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, + // (In this implementation, if a DirectoryName is in excludedSubtrees, nothing + // is considered to be in the name space.) + { RDN(CN("Other Example Name")), NO_SAN, + GeneralSubtree(DirectoryName(Name(RDN(CN("Example Name"))))), + Result::ERROR_CERT_NOT_IN_NAME_SPACE, Result::ERROR_CERT_NOT_IN_NAME_SPACE + }, +}; + +class pkixnames_CheckNameConstraintsOnIntermediate + : public ::testing::Test + , public ::testing::WithParamInterface<NameConstraintParams> +{ +}; + +TEST_P(pkixnames_CheckNameConstraintsOnIntermediate, + NameConstraintsEnforcedOnIntermediate) +{ + // Test that name constraints are enforced on an intermediate certificate + // directly issued by a certificate with the given name constraints. + + const NameConstraintParams& param(GetParam()); + + ByteString certDER(CreateCert(param.subject, NO_SAN, + EndEntityOrCA::MustBeCA)); + ASSERT_FALSE(ENCODING_FAILED(certDER)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length())); + BackCert cert(certInput, EndEntityOrCA::MustBeCA, nullptr); + ASSERT_EQ(Success, cert.Init()); + + { + ByteString nameConstraintsDER(TLV(der::SEQUENCE, + PermittedSubtrees(param.subtrees))); + Input nameConstraints; + ASSERT_EQ(Success, + nameConstraints.Init(nameConstraintsDER.data(), + nameConstraintsDER.length())); + ASSERT_EQ(param.expectedPermittedSubtreesResult, + CheckNameConstraints(nameConstraints, cert, + KeyPurposeId::id_kp_serverAuth)); + } + { + ByteString nameConstraintsDER(TLV(der::SEQUENCE, + ExcludedSubtrees(param.subtrees))); + Input nameConstraints; + ASSERT_EQ(Success, + nameConstraints.Init(nameConstraintsDER.data(), + nameConstraintsDER.length())); + ASSERT_EQ(param.expectedExcludedSubtreesResult, + CheckNameConstraints(nameConstraints, cert, + KeyPurposeId::id_kp_serverAuth)); + } + { + ByteString nameConstraintsDER(TLV(der::SEQUENCE, + PermittedSubtrees(param.subtrees) + + ExcludedSubtrees(param.subtrees))); + Input nameConstraints; + ASSERT_EQ(Success, + nameConstraints.Init(nameConstraintsDER.data(), + nameConstraintsDER.length())); + ASSERT_EQ(param.expectedExcludedSubtreesResult, + CheckNameConstraints(nameConstraints, cert, + KeyPurposeId::id_kp_serverAuth)); + } +} + +INSTANTIATE_TEST_CASE_P(pkixnames_CheckNameConstraintsOnIntermediate, + pkixnames_CheckNameConstraintsOnIntermediate, + testing::ValuesIn(NO_FALLBACK_NAME_CONSTRAINT_PARAMS)); + +class pkixnames_CheckNameConstraintsForNonServerAuthUsage + : public ::testing::Test + , public ::testing::WithParamInterface<NameConstraintParams> +{ +}; + +TEST_P(pkixnames_CheckNameConstraintsForNonServerAuthUsage, + NameConstraintsEnforcedForNonServerAuthUsage) +{ + // Test that for key purposes other than serverAuth, fallback to the subject + // common name does not occur. + + const NameConstraintParams& param(GetParam()); + + ByteString certDER(CreateCert(param.subject, NO_SAN)); + ASSERT_FALSE(ENCODING_FAILED(certDER)); + Input certInput; + ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length())); + BackCert cert(certInput, EndEntityOrCA::MustBeEndEntity, nullptr); + ASSERT_EQ(Success, cert.Init()); + + { + ByteString nameConstraintsDER(TLV(der::SEQUENCE, + PermittedSubtrees(param.subtrees))); + Input nameConstraints; + ASSERT_EQ(Success, + nameConstraints.Init(nameConstraintsDER.data(), + nameConstraintsDER.length())); + ASSERT_EQ(param.expectedPermittedSubtreesResult, + CheckNameConstraints(nameConstraints, cert, + KeyPurposeId::id_kp_clientAuth)); + } + { + ByteString nameConstraintsDER(TLV(der::SEQUENCE, + ExcludedSubtrees(param.subtrees))); + Input nameConstraints; + ASSERT_EQ(Success, + nameConstraints.Init(nameConstraintsDER.data(), + nameConstraintsDER.length())); + ASSERT_EQ(param.expectedExcludedSubtreesResult, + CheckNameConstraints(nameConstraints, cert, + KeyPurposeId::id_kp_clientAuth)); + } + { + ByteString nameConstraintsDER(TLV(der::SEQUENCE, + PermittedSubtrees(param.subtrees) + + ExcludedSubtrees(param.subtrees))); + Input nameConstraints; + ASSERT_EQ(Success, + nameConstraints.Init(nameConstraintsDER.data(), + nameConstraintsDER.length())); + ASSERT_EQ(param.expectedExcludedSubtreesResult, + CheckNameConstraints(nameConstraints, cert, + KeyPurposeId::id_kp_clientAuth)); + } +} + +INSTANTIATE_TEST_CASE_P(pkixnames_CheckNameConstraintsForNonServerAuthUsage, + pkixnames_CheckNameConstraintsForNonServerAuthUsage, + testing::ValuesIn(NO_FALLBACK_NAME_CONSTRAINT_PARAMS)); diff --git a/security/nss/gtests/mozpkix_gtest/pkixocsp_CreateEncodedOCSPRequest_tests.cpp b/security/nss/gtests/mozpkix_gtest/pkixocsp_CreateEncodedOCSPRequest_tests.cpp new file mode 100644 index 000000000..ff154e7ec --- /dev/null +++ b/security/nss/gtests/mozpkix_gtest/pkixocsp_CreateEncodedOCSPRequest_tests.cpp @@ -0,0 +1,146 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2013 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixgtest.h" + +#include "mozpkix/pkixder.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +class CreateEncodedOCSPRequestTrustDomain final + : public EverythingFailsByDefaultTrustDomain +{ +private: + Result DigestBuf(Input item, DigestAlgorithm digestAlg, + /*out*/ uint8_t *digestBuf, size_t digestBufLen) + override + { + return TestDigestBuf(item, digestAlg, digestBuf, digestBufLen); + } + + Result CheckRSAPublicKeyModulusSizeInBits(EndEntityOrCA, unsigned int) + override + { + return Success; + } +}; + +class pkixocsp_CreateEncodedOCSPRequest : public ::testing::Test +{ +protected: + void MakeIssuerCertIDComponents(const char* issuerASCII, + /*out*/ ByteString& issuerDER, + /*out*/ ByteString& issuerSPKI) + { + issuerDER = CNToDERName(issuerASCII); + ASSERT_FALSE(ENCODING_FAILED(issuerDER)); + + ScopedTestKeyPair keyPair(GenerateKeyPair()); + ASSERT_TRUE(keyPair.get()); + issuerSPKI = keyPair->subjectPublicKeyInfo; + } + + CreateEncodedOCSPRequestTrustDomain trustDomain; +}; + +// Test that the large length of the child serial number causes +// CreateEncodedOCSPRequest to fail. +TEST_F(pkixocsp_CreateEncodedOCSPRequest, ChildCertLongSerialNumberTest) +{ + static const uint8_t UNSUPPORTED_LEN = 128; // must be larger than 127 + + ByteString serialNumberString; + // tag + length + value is 1 + 2 + UNSUPPORTED_LEN + // Encoding the length takes two bytes: one byte to indicate that a + // second byte follows, and the second byte to indicate the length. + serialNumberString.push_back(0x80 + 1); + serialNumberString.push_back(UNSUPPORTED_LEN); + // value is 0x010000...00 + serialNumberString.push_back(0x01); + for (size_t i = 1; i < UNSUPPORTED_LEN; ++i) { + serialNumberString.push_back(0x00); + } + + ByteString issuerDER; + ByteString issuerSPKI; + ASSERT_NO_FATAL_FAILURE(MakeIssuerCertIDComponents("CA", issuerDER, + issuerSPKI)); + + Input issuer; + ASSERT_EQ(Success, issuer.Init(issuerDER.data(), issuerDER.length())); + + Input spki; + ASSERT_EQ(Success, spki.Init(issuerSPKI.data(), issuerSPKI.length())); + + Input serialNumber; + ASSERT_EQ(Success, serialNumber.Init(serialNumberString.data(), + serialNumberString.length())); + + uint8_t ocspRequest[OCSP_REQUEST_MAX_LENGTH]; + size_t ocspRequestLength; + ASSERT_EQ(Result::ERROR_BAD_DER, + CreateEncodedOCSPRequest(trustDomain, + CertID(issuer, spki, serialNumber), + ocspRequest, ocspRequestLength)); +} + +// Test that CreateEncodedOCSPRequest handles the longest serial number that +// it's required to support (i.e. 20 octets). +TEST_F(pkixocsp_CreateEncodedOCSPRequest, LongestSupportedSerialNumberTest) +{ + static const uint8_t LONGEST_REQUIRED_LEN = 20; + + ByteString serialNumberString; + // tag + length + value is 1 + 1 + LONGEST_REQUIRED_LEN + serialNumberString.push_back(der::INTEGER); + serialNumberString.push_back(LONGEST_REQUIRED_LEN); + serialNumberString.push_back(0x01); + // value is 0x010000...00 + for (size_t i = 1; i < LONGEST_REQUIRED_LEN; ++i) { + serialNumberString.push_back(0x00); + } + + ByteString issuerDER; + ByteString issuerSPKI; + ASSERT_NO_FATAL_FAILURE(MakeIssuerCertIDComponents("CA", issuerDER, + issuerSPKI)); + + Input issuer; + ASSERT_EQ(Success, issuer.Init(issuerDER.data(), issuerDER.length())); + + Input spki; + ASSERT_EQ(Success, spki.Init(issuerSPKI.data(), issuerSPKI.length())); + + Input serialNumber; + ASSERT_EQ(Success, serialNumber.Init(serialNumberString.data(), + serialNumberString.length())); + + uint8_t ocspRequest[OCSP_REQUEST_MAX_LENGTH]; + size_t ocspRequestLength; + ASSERT_EQ(Success, + CreateEncodedOCSPRequest(trustDomain, + CertID(issuer, spki, serialNumber), + ocspRequest, ocspRequestLength)); +} diff --git a/security/nss/gtests/mozpkix_gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp b/security/nss/gtests/mozpkix_gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp new file mode 100644 index 000000000..3fe4e7b5a --- /dev/null +++ b/security/nss/gtests/mozpkix_gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp @@ -0,0 +1,1064 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This code is made available to you under your choice of the following sets + * of licensing terms: + */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/* Copyright 2014 Mozilla Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pkixgtest.h" + +#include "mozpkix/pkixder.h" + +using namespace mozilla::pkix; +using namespace mozilla::pkix::test; + +const uint16_t END_ENTITY_MAX_LIFETIME_IN_DAYS = 10; + +// Note that CheckRevocation is never called for OCSP signing certificates. +class OCSPTestTrustDomain : public DefaultCryptoTrustDomain +{ +public: + OCSPTestTrustDomain() { } + + Result GetCertTrust(EndEntityOrCA endEntityOrCA, const CertPolicyId&, + Input, /*out*/ TrustLevel& trustLevel) + /*non-final*/ override + { + EXPECT_EQ(endEntityOrCA, EndEntityOrCA::MustBeEndEntity); + trustLevel = TrustLevel::InheritsTrust; + return Success; + } + + virtual void NoteAuxiliaryExtension(AuxiliaryExtension extension, + Input extensionData) override + { + if (extension == AuxiliaryExtension::SCTListFromOCSPResponse) { + signedCertificateTimestamps = InputToByteString(extensionData); + } else { + // We do not currently expect to receive any other extension here. + ADD_FAILURE(); + } + } + + ByteString signedCertificateTimestamps; +}; + +namespace { +char const* const rootName = "Test CA 1"; +} // namespace + +class pkixocsp_VerifyEncodedResponse : public ::testing::Test +{ +public: + static void SetUpTestCase() + { + rootKeyPair.reset(GenerateKeyPair()); + if (!rootKeyPair) { + abort(); + } + } + + void SetUp() + { + rootNameDER = CNToDERName(rootName); + if (ENCODING_FAILED(rootNameDER)) { + abort(); + } + Input rootNameDERInput; + if (rootNameDERInput.Init(rootNameDER.data(), rootNameDER.length()) + != Success) { + abort(); + } + + serialNumberDER = + CreateEncodedSerialNumber(static_cast<long>(++rootIssuedCount)); + if (ENCODING_FAILED(serialNumberDER)) { + abort(); + } + Input serialNumberDERInput; + if (serialNumberDERInput.Init(serialNumberDER.data(), + serialNumberDER.length()) != Success) { + abort(); + } + + Input rootSPKIDER; + if (rootSPKIDER.Init(rootKeyPair->subjectPublicKeyInfo.data(), + rootKeyPair->subjectPublicKeyInfo.length()) + != Success) { + abort(); + } + endEntityCertID.reset(new (std::nothrow) CertID(rootNameDERInput, rootSPKIDER, + serialNumberDERInput)); + if (!endEntityCertID) { + abort(); + } + } + + static ScopedTestKeyPair rootKeyPair; + static uint32_t rootIssuedCount; + OCSPTestTrustDomain trustDomain; + + // endEntityCertID references rootKeyPair, rootNameDER, and serialNumberDER. + ByteString rootNameDER; + ByteString serialNumberDER; + // endEntityCertID references rootKeyPair, rootNameDER, and serialNumberDER. + ScopedCertID endEntityCertID; +}; + +/*static*/ ScopedTestKeyPair pkixocsp_VerifyEncodedResponse::rootKeyPair; +/*static*/ uint32_t pkixocsp_VerifyEncodedResponse::rootIssuedCount = 0; + +/////////////////////////////////////////////////////////////////////////////// +// responseStatus + +struct WithoutResponseBytes +{ + uint8_t responseStatus; + Result expectedError; +}; + +static const WithoutResponseBytes WITHOUT_RESPONSEBYTES[] = { + { OCSPResponseContext::successful, Result::ERROR_OCSP_MALFORMED_RESPONSE }, + { OCSPResponseContext::malformedRequest, Result::ERROR_OCSP_MALFORMED_REQUEST }, + { OCSPResponseContext::internalError, Result::ERROR_OCSP_SERVER_ERROR }, + { OCSPResponseContext::tryLater, Result::ERROR_OCSP_TRY_SERVER_LATER }, + { 4/*unused*/, Result::ERROR_OCSP_UNKNOWN_RESPONSE_STATUS }, + { OCSPResponseContext::sigRequired, Result::ERROR_OCSP_REQUEST_NEEDS_SIG }, + { OCSPResponseContext::unauthorized, Result::ERROR_OCSP_UNAUTHORIZED_REQUEST }, + { OCSPResponseContext::unauthorized + 1, + Result::ERROR_OCSP_UNKNOWN_RESPONSE_STATUS + }, +}; + +class pkixocsp_VerifyEncodedResponse_WithoutResponseBytes + : public pkixocsp_VerifyEncodedResponse + , public ::testing::WithParamInterface<WithoutResponseBytes> +{ +protected: + ByteString CreateEncodedOCSPErrorResponse(uint8_t status) + { + static const Input EMPTY; + OCSPResponseContext context(CertID(EMPTY, EMPTY, EMPTY), + oneDayBeforeNow); + context.responseStatus = status; + context.skipResponseBytes = true; + return CreateEncodedOCSPResponse(context); + } +}; + +TEST_P(pkixocsp_VerifyEncodedResponse_WithoutResponseBytes, CorrectErrorCode) +{ + ByteString + responseString(CreateEncodedOCSPErrorResponse(GetParam().responseStatus)); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(GetParam().expectedError, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); +} + +INSTANTIATE_TEST_CASE_P(pkixocsp_VerifyEncodedResponse_WithoutResponseBytes, + pkixocsp_VerifyEncodedResponse_WithoutResponseBytes, + testing::ValuesIn(WITHOUT_RESPONSEBYTES)); + +/////////////////////////////////////////////////////////////////////////////// +// "successful" responses + +namespace { + +// Alias for nullptr to aid readability in the code below. +static const char* byKey = nullptr; + +} // namespace + +class pkixocsp_VerifyEncodedResponse_successful + : public pkixocsp_VerifyEncodedResponse +{ +public: + void SetUp() + { + pkixocsp_VerifyEncodedResponse::SetUp(); + } + + static void SetUpTestCase() + { + pkixocsp_VerifyEncodedResponse::SetUpTestCase(); + } + + ByteString CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::CertStatus certStatus, + const CertID& certID, + /*optional*/ const char* signerName, + const TestKeyPair& signerKeyPair, + time_t producedAt, time_t thisUpdate, + /*optional*/ const time_t* nextUpdate, + const TestSignatureAlgorithm& signatureAlgorithm, + /*optional*/ const ByteString* certs = nullptr, + /*optional*/ OCSPResponseExtension* singleExtensions = nullptr, + /*optional*/ OCSPResponseExtension* responseExtensions = nullptr) + { + OCSPResponseContext context(certID, producedAt); + if (signerName) { + context.signerNameDER = CNToDERName(signerName); + EXPECT_FALSE(ENCODING_FAILED(context.signerNameDER)); + } + context.signerKeyPair.reset(signerKeyPair.Clone()); + EXPECT_TRUE(context.signerKeyPair.get()); + context.responseStatus = OCSPResponseContext::successful; + context.producedAt = producedAt; + context.signatureAlgorithm = signatureAlgorithm; + context.certs = certs; + context.singleExtensions = singleExtensions; + context.responseExtensions = responseExtensions; + + context.certStatus = static_cast<uint8_t>(certStatus); + context.thisUpdate = thisUpdate; + context.nextUpdate = nextUpdate ? *nextUpdate : 0; + context.includeNextUpdate = nextUpdate != nullptr; + + return CreateEncodedOCSPResponse(context); + } +}; + +TEST_F(pkixocsp_VerifyEncodedResponse_successful, good_byKey) +{ + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, byKey, + *rootKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + sha256WithRSAEncryption())); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Success, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, + Now(), END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_successful, good_byName) +{ + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, rootName, + *rootKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + sha256WithRSAEncryption())); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Success, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_successful, good_byKey_without_nextUpdate) +{ + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, byKey, + *rootKeyPair, oneDayBeforeNow, + oneDayBeforeNow, nullptr, + sha256WithRSAEncryption())); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Success, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_successful, revoked) +{ + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::revoked, *endEntityCertID, byKey, + *rootKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + sha256WithRSAEncryption())); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_REVOKED_CERTIFICATE, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_successful, unknown) +{ + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::unknown, *endEntityCertID, byKey, + *rootKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + sha256WithRSAEncryption())); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_UNKNOWN_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_successful, + good_unsupportedSignatureAlgorithm) +{ + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, byKey, + *rootKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + md5WithRSAEncryption())); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, + Now(), END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +// Added for bug 1079436. The output variable validThrough represents the +// latest time for which VerifyEncodedOCSPResponse will succeed, which is +// different from the nextUpdate time in the OCSP response due to the slop we +// add for time comparisons to deal with clock skew. +TEST_F(pkixocsp_VerifyEncodedResponse_successful, check_validThrough) +{ + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, byKey, + *rootKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + sha256WithRSAEncryption())); + Time validThrough(Time::uninitialized); + { + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Success, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, + Now(), END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired, nullptr, + &validThrough)); + ASSERT_FALSE(expired); + // The response was created to be valid until one day after now, so the + // value we got for validThrough should be after that. + Time oneDayAfterNowAsPKIXTime( + TimeFromEpochInSeconds(static_cast<uint64_t>(oneDayAfterNow))); + ASSERT_TRUE(validThrough > oneDayAfterNowAsPKIXTime); + } + { + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + // Given validThrough from a previous verification, this response should be + // valid through that time. + ASSERT_EQ(Success, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, + validThrough, END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); + } + { + Time noLongerValid(validThrough); + ASSERT_EQ(Success, noLongerValid.AddSeconds(1)); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + // The verification time is now after when the response will be considered + // valid. + ASSERT_EQ(Result::ERROR_OCSP_OLD_RESPONSE, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, + noLongerValid, END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_TRUE(expired); + } +} + +TEST_F(pkixocsp_VerifyEncodedResponse_successful, ct_extension) +{ + // python DottedOIDToCode.py --tlv + // id_ocsp_singleExtensionSctList 1.3.6.1.4.1.11129.2.4.5 + static const uint8_t tlv_id_ocsp_singleExtensionSctList[] = { + 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x05 + }; + static const uint8_t dummySctList[] = { + 0x01, 0x02, 0x03, 0x04, 0x05 + }; + + OCSPResponseExtension ctExtension; + ctExtension.id = BytesToByteString(tlv_id_ocsp_singleExtensionSctList); + // SignedCertificateTimestampList structure is encoded as an OCTET STRING + // within the extension value (see RFC 6962 section 3.3). + // pkix decodes it internally and returns the actual structure. + ctExtension.value = TLV(der::OCTET_STRING, BytesToByteString(dummySctList)); + + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, byKey, + *rootKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + sha256WithRSAEncryption(), + /*certs*/ nullptr, + &ctExtension)); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + + bool expired; + ASSERT_EQ(Success, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, + Now(), END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); + ASSERT_EQ(BytesToByteString(dummySctList), + trustDomain.signedCertificateTimestamps); +} + +/////////////////////////////////////////////////////////////////////////////// +// indirect responses (signed by a delegated OCSP responder cert) + +class pkixocsp_VerifyEncodedResponse_DelegatedResponder + : public pkixocsp_VerifyEncodedResponse_successful +{ +protected: + // certSubjectName should be unique for each call. This way, we avoid any + // issues with NSS caching the certificates internally. For the same reason, + // we generate a new keypair on each call. Either one of these should be + // sufficient to avoid issues with the NSS cache, but we do both to be + // cautious. + // + // signerName should be byKey to use the byKey ResponderID construction, or + // another value (usually equal to certSubjectName) to use the byName + // ResponderID construction. + // + // certSignatureAlgorithm specifies the signature algorithm that the + // certificate will be signed with, not the OCSP response. + // + // If signerEKU is omitted, then the certificate will have the + // id-kp-OCSPSigning EKU. If signerEKU is SEC_OID_UNKNOWN then it will not + // have any EKU extension. Otherwise, the certificate will have the given + // EKU. + ByteString CreateEncodedIndirectOCSPSuccessfulResponse( + const char* certSubjectName, + OCSPResponseContext::CertStatus certStatus, + const char* signerName, + const TestSignatureAlgorithm& certSignatureAlgorithm, + /*optional*/ const Input* signerEKUDER = &OCSPSigningEKUDER, + /*optional, out*/ ByteString* signerDEROut = nullptr) + { + assert(certSubjectName); + + const ByteString extensions[] = { + signerEKUDER + ? CreateEncodedEKUExtension(*signerEKUDER, Critical::No) + : ByteString(), + ByteString() + }; + ScopedTestKeyPair signerKeyPair(GenerateKeyPair()); + ByteString signerDER(CreateEncodedCertificate( + ++rootIssuedCount, certSignatureAlgorithm, + rootName, oneDayBeforeNow, oneDayAfterNow, + certSubjectName, *signerKeyPair, + signerEKUDER ? extensions : nullptr, + *rootKeyPair)); + EXPECT_FALSE(ENCODING_FAILED(signerDER)); + if (signerDEROut) { + *signerDEROut = signerDER; + } + + ByteString signerNameDER; + if (signerName) { + signerNameDER = CNToDERName(signerName); + EXPECT_FALSE(ENCODING_FAILED(signerNameDER)); + } + ByteString certs[] = { signerDER, ByteString() }; + return CreateEncodedOCSPSuccessfulResponse(certStatus, *endEntityCertID, + signerName, *signerKeyPair, + oneDayBeforeNow, + oneDayBeforeNow, + &oneDayAfterNow, + sha256WithRSAEncryption(), + certs); + } + + static ByteString CreateEncodedCertificate(uint32_t serialNumber, + const TestSignatureAlgorithm& signatureAlg, + const char* issuer, + time_t notBefore, + time_t notAfter, + const char* subject, + const TestKeyPair& subjectKeyPair, + /*optional*/ const ByteString* extensions, + const TestKeyPair& signerKeyPair) + { + ByteString serialNumberDER(CreateEncodedSerialNumber( + static_cast<long>(serialNumber))); + if (ENCODING_FAILED(serialNumberDER)) { + return ByteString(); + } + ByteString issuerDER(CNToDERName(issuer)); + if (ENCODING_FAILED(issuerDER)) { + return ByteString(); + } + ByteString subjectDER(CNToDERName(subject)); + if (ENCODING_FAILED(subjectDER)) { + return ByteString(); + } + return ::mozilla::pkix::test::CreateEncodedCertificate( + v3, signatureAlg, serialNumberDER, + issuerDER, notBefore, notAfter, + subjectDER, subjectKeyPair, extensions, + signerKeyPair, signatureAlg); + } + + static const Input OCSPSigningEKUDER; +}; + +/*static*/ const Input pkixocsp_VerifyEncodedResponse_DelegatedResponder:: + OCSPSigningEKUDER(tlv_id_kp_OCSPSigning); + +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_byKey) +{ + ByteString responseString( + CreateEncodedIndirectOCSPSuccessfulResponse( + "good_indirect_byKey", OCSPResponseContext::good, + byKey, sha256WithRSAEncryption())); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Success, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_byName) +{ + ByteString responseString( + CreateEncodedIndirectOCSPSuccessfulResponse( + "good_indirect_byName", OCSPResponseContext::good, + "good_indirect_byName", sha256WithRSAEncryption())); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Success, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, + good_byKey_missing_signer) +{ + ScopedTestKeyPair missingSignerKeyPair(GenerateKeyPair()); + ASSERT_TRUE(missingSignerKeyPair.get()); + + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, byKey, + *missingSignerKeyPair, oneDayBeforeNow, + oneDayBeforeNow, nullptr, + sha256WithRSAEncryption())); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, + good_byName_missing_signer) +{ + ScopedTestKeyPair missingSignerKeyPair(GenerateKeyPair()); + ASSERT_TRUE(missingSignerKeyPair.get()); + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, + "missing", *missingSignerKeyPair, + oneDayBeforeNow, oneDayBeforeNow, nullptr, + sha256WithRSAEncryption())); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_expired) +{ + static const char* signerName = "good_indirect_expired"; + + const ByteString extensions[] = { + CreateEncodedEKUExtension(OCSPSigningEKUDER, Critical::No), + ByteString() + }; + + ScopedTestKeyPair signerKeyPair(GenerateKeyPair()); + ByteString signerDER(CreateEncodedCertificate( + ++rootIssuedCount, sha256WithRSAEncryption(), + rootName, + tenDaysBeforeNow, + twoDaysBeforeNow, + signerName, *signerKeyPair, extensions, + *rootKeyPair)); + ASSERT_FALSE(ENCODING_FAILED(signerDER)); + + ByteString certs[] = { signerDER, ByteString() }; + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, + signerName, *signerKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + sha256WithRSAEncryption(), certs)); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_future) +{ + static const char* signerName = "good_indirect_future"; + + const ByteString extensions[] = { + CreateEncodedEKUExtension(OCSPSigningEKUDER, Critical::No), + ByteString() + }; + + ScopedTestKeyPair signerKeyPair(GenerateKeyPair()); + ByteString signerDER(CreateEncodedCertificate( + ++rootIssuedCount, sha256WithRSAEncryption(), + rootName, + twoDaysAfterNow, + tenDaysAfterNow, + signerName, *signerKeyPair, extensions, + *rootKeyPair)); + ASSERT_FALSE(ENCODING_FAILED(signerDER)); + + ByteString certs[] = { signerDER, ByteString() }; + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, + signerName, *signerKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + sha256WithRSAEncryption(), certs)); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_no_eku) +{ + ByteString responseString( + CreateEncodedIndirectOCSPSuccessfulResponse( + "good_indirect_wrong_eku", + OCSPResponseContext::good, byKey, + sha256WithRSAEncryption(), nullptr)); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +static const Input serverAuthEKUDER(tlv_id_kp_serverAuth); + +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, + good_indirect_wrong_eku) +{ + ByteString responseString( + CreateEncodedIndirectOCSPSuccessfulResponse( + "good_indirect_wrong_eku", + OCSPResponseContext::good, byKey, + sha256WithRSAEncryption(), &serverAuthEKUDER)); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +// Test that signature of OCSP response signer cert is verified +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_tampered_eku) +{ + ByteString tamperedResponse( + CreateEncodedIndirectOCSPSuccessfulResponse( + "good_indirect_tampered_eku", + OCSPResponseContext::good, byKey, + sha256WithRSAEncryption(), &serverAuthEKUDER)); + ASSERT_EQ(Success, + TamperOnce(tamperedResponse, + ByteString(tlv_id_kp_serverAuth, + sizeof(tlv_id_kp_serverAuth)), + ByteString(tlv_id_kp_OCSPSigning, + sizeof(tlv_id_kp_OCSPSigning)))); + Input tamperedResponseInput; + ASSERT_EQ(Success, tamperedResponseInput.Init(tamperedResponse.data(), + tamperedResponse.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + tamperedResponseInput, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_unknown_issuer) +{ + static const char* subCAName = "good_indirect_unknown_issuer sub-CA"; + static const char* signerName = "good_indirect_unknown_issuer OCSP signer"; + + // unknown issuer + ScopedTestKeyPair unknownKeyPair(GenerateKeyPair()); + ASSERT_TRUE(unknownKeyPair.get()); + + // Delegated responder cert signed by unknown issuer + const ByteString extensions[] = { + CreateEncodedEKUExtension(OCSPSigningEKUDER, Critical::No), + ByteString() + }; + ScopedTestKeyPair signerKeyPair(GenerateKeyPair()); + ByteString signerDER(CreateEncodedCertificate( + 1, sha256WithRSAEncryption(), subCAName, + oneDayBeforeNow, oneDayAfterNow, signerName, + *signerKeyPair, extensions, *unknownKeyPair)); + ASSERT_FALSE(ENCODING_FAILED(signerDER)); + + // OCSP response signed by that delegated responder + ByteString certs[] = { signerDER, ByteString() }; + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, + signerName, *signerKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + sha256WithRSAEncryption(), certs)); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +// The CA that issued the OCSP responder cert is a sub-CA of the issuer of +// the certificate that the OCSP response is for. That sub-CA cert is included +// in the OCSP response before the OCSP responder cert. +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, + good_indirect_subca_1_first) +{ + static const char* subCAName = "good_indirect_subca_1_first sub-CA"; + static const char* signerName = "good_indirect_subca_1_first OCSP signer"; + static const long zero = 0; + + // sub-CA of root (root is the direct issuer of endEntity) + const ByteString subCAExtensions[] = { + CreateEncodedBasicConstraints(true, &zero, Critical::No), + ByteString() + }; + ScopedTestKeyPair subCAKeyPair(GenerateKeyPair()); + ByteString subCADER(CreateEncodedCertificate( + ++rootIssuedCount, sha256WithRSAEncryption(), rootName, + oneDayBeforeNow, oneDayAfterNow, subCAName, + *subCAKeyPair, subCAExtensions, *rootKeyPair)); + ASSERT_FALSE(ENCODING_FAILED(subCADER)); + + // Delegated responder cert signed by that sub-CA + const ByteString extensions[] = { + CreateEncodedEKUExtension(OCSPSigningEKUDER, Critical::No), + ByteString(), + }; + ScopedTestKeyPair signerKeyPair(GenerateKeyPair()); + ByteString signerDER(CreateEncodedCertificate( + 1, sha256WithRSAEncryption(), subCAName, + oneDayBeforeNow, oneDayAfterNow, signerName, + *signerKeyPair, extensions, *subCAKeyPair)); + ASSERT_FALSE(ENCODING_FAILED(signerDER)); + + // OCSP response signed by the delegated responder issued by the sub-CA + // that is trying to impersonate the root. + ByteString certs[] = { subCADER, signerDER, ByteString() }; + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, + signerName, *signerKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + sha256WithRSAEncryption(), certs)); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +// The CA that issued the OCSP responder cert is a sub-CA of the issuer of +// the certificate that the OCSP response is for. That sub-CA cert is included +// in the OCSP response after the OCSP responder cert. +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, + good_indirect_subca_1_second) +{ + static const char* subCAName = "good_indirect_subca_1_second sub-CA"; + static const char* signerName = "good_indirect_subca_1_second OCSP signer"; + static const long zero = 0; + + // sub-CA of root (root is the direct issuer of endEntity) + const ByteString subCAExtensions[] = { + CreateEncodedBasicConstraints(true, &zero, Critical::No), + ByteString() + }; + ScopedTestKeyPair subCAKeyPair(GenerateKeyPair()); + ByteString subCADER(CreateEncodedCertificate(++rootIssuedCount, + sha256WithRSAEncryption(), + rootName, + oneDayBeforeNow, oneDayAfterNow, + subCAName, *subCAKeyPair, + subCAExtensions, *rootKeyPair)); + ASSERT_FALSE(ENCODING_FAILED(subCADER)); + + // Delegated responder cert signed by that sub-CA + const ByteString extensions[] = { + CreateEncodedEKUExtension(OCSPSigningEKUDER, Critical::No), + ByteString() + }; + ScopedTestKeyPair signerKeyPair(GenerateKeyPair()); + ByteString signerDER(CreateEncodedCertificate( + 1, sha256WithRSAEncryption(), subCAName, + oneDayBeforeNow, oneDayAfterNow, signerName, + *signerKeyPair, extensions, *subCAKeyPair)); + ASSERT_FALSE(ENCODING_FAILED(signerDER)); + + // OCSP response signed by the delegated responder issued by the sub-CA + // that is trying to impersonate the root. + ByteString certs[] = { signerDER, subCADER, ByteString() }; + ByteString responseString( + CreateEncodedOCSPSuccessfulResponse( + OCSPResponseContext::good, *endEntityCertID, + signerName, *signerKeyPair, oneDayBeforeNow, + oneDayBeforeNow, &oneDayAfterNow, + sha256WithRSAEncryption(), certs)); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, + good_unsupportedSignatureAlgorithmOnResponder) +{ + // Note that the algorithm ID (md5WithRSAEncryption) identifies the signature + // algorithm that will be used to sign the certificate that issues the OCSP + // responses, not the responses themselves. + ByteString responseString( + CreateEncodedIndirectOCSPSuccessfulResponse( + "good_indirect_unsupportedSignatureAlgorithm", + OCSPResponseContext::good, byKey, + md5WithRSAEncryption())); + Input response; + ASSERT_EQ(Success, + response.Init(responseString.data(), responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); +} + +class pkixocsp_VerifyEncodedResponse_GetCertTrust + : public pkixocsp_VerifyEncodedResponse_DelegatedResponder { +public: + void SetUp() + { + pkixocsp_VerifyEncodedResponse_DelegatedResponder::SetUp(); + + responseString = + CreateEncodedIndirectOCSPSuccessfulResponse( + "OCSPGetCertTrustTest Signer", OCSPResponseContext::good, + byKey, sha256WithRSAEncryption(), &OCSPSigningEKUDER, + &signerCertDER); + if (ENCODING_FAILED(responseString)) { + abort(); + } + if (response.Init(responseString.data(), responseString.length()) + != Success) { + abort(); + } + if (signerCertDER.length() == 0) { + abort(); + } + } + + class TrustDomain final : public OCSPTestTrustDomain + { + public: + TrustDomain() + : certTrustLevel(TrustLevel::InheritsTrust) + { + } + + bool SetCertTrust(const ByteString& aCertDER, TrustLevel aCertTrustLevel) + { + this->certDER = aCertDER; + this->certTrustLevel = aCertTrustLevel; + return true; + } + private: + Result GetCertTrust(EndEntityOrCA endEntityOrCA, const CertPolicyId&, + Input candidateCert, /*out*/ TrustLevel& trustLevel) + override + { + EXPECT_EQ(endEntityOrCA, EndEntityOrCA::MustBeEndEntity); + EXPECT_FALSE(certDER.empty()); + Input certDERInput; + EXPECT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length())); + EXPECT_TRUE(InputsAreEqual(certDERInput, candidateCert)); + trustLevel = certTrustLevel; + return Success; + } + + ByteString certDER; + TrustLevel certTrustLevel; + }; + +// trustDomain deliberately shadows the inherited field so that it isn't used +// by accident. See bug 1339921. +// Unfortunately GCC can't parse __has_warning("-Wshadow-field") even if it's +// the latter part of a conjunction that would evaluate to false, so we have to +// wrap it in a separate preprocessor conditional rather than using &&. +#if defined(__clang__) + #if __has_warning("-Wshadow-field") + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wshadow-field" + #endif +#endif + TrustDomain trustDomain; +#if defined(__clang__) + #if __has_warning("-Wshadow-field") + #pragma clang diagnostic pop + #endif +#endif + ByteString signerCertDER; + ByteString responseString; + Input response; // references data in responseString +}; + +TEST_F(pkixocsp_VerifyEncodedResponse_GetCertTrust, InheritTrust) +{ + ASSERT_TRUE(trustDomain.SetCertTrust(signerCertDER, + TrustLevel::InheritsTrust)); + bool expired; + ASSERT_EQ(Success, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_GetCertTrust, TrustAnchor) +{ + ASSERT_TRUE(trustDomain.SetCertTrust(signerCertDER, + TrustLevel::TrustAnchor)); + bool expired; + ASSERT_EQ(Success, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + response, expired)); + ASSERT_FALSE(expired); +} + +TEST_F(pkixocsp_VerifyEncodedResponse_GetCertTrust, ActivelyDistrusted) +{ + ASSERT_TRUE(trustDomain.SetCertTrust(signerCertDER, + TrustLevel::ActivelyDistrusted)); + Input responseInput; + ASSERT_EQ(Success, + responseInput.Init(responseString.data(), + responseString.length())); + bool expired; + ASSERT_EQ(Result::ERROR_OCSP_INVALID_SIGNING_CERT, + VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, Now(), + END_ENTITY_MAX_LIFETIME_IN_DAYS, + responseInput, expired)); + ASSERT_FALSE(expired); +} |