pkixbuild_tests.cpp (32871B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This code is made available to you under your choice of the following sets 4 * of licensing terms: 5 */ 6 /* This Source Code Form is subject to the terms of the Mozilla Public 7 * License, v. 2.0. If a copy of the MPL was not distributed with this 8 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 */ 10 /* Copyright 2013 Mozilla Contributors 11 * 12 * Licensed under the Apache License, Version 2.0 (the "License"); 13 * you may not use this file except in compliance with the License. 14 * You may obtain a copy of the License at 15 * 16 * http://www.apache.org/licenses/LICENSE-2.0 17 * 18 * Unless required by applicable law or agreed to in writing, software 19 * distributed under the License is distributed on an "AS IS" BASIS, 20 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 * See the License for the specific language governing permissions and 22 * limitations under the License. 23 */ 24 25 #if defined(_MSC_VER) && _MSC_VER < 1900 26 // When building with -D_HAS_EXCEPTIONS=0, MSVC's <xtree> header triggers 27 // warning C4702: unreachable code. 28 // https://connect.microsoft.com/VisualStudio/feedback/details/809962 29 #pragma warning(push) 30 #pragma warning(disable: 4702) 31 #endif 32 33 #include <map> 34 #include <vector> 35 36 #if defined(_MSC_VER) && _MSC_VER < 1900 37 #pragma warning(pop) 38 #endif 39 40 #include "pkixgtest.h" 41 42 #include "mozpkix/pkixder.h" 43 44 using namespace mozilla::pkix; 45 using namespace mozilla::pkix::test; 46 47 static ByteString 48 CreateCert(const char* issuerCN, // null means "empty name" 49 const char* subjectCN, // null means "empty name" 50 EndEntityOrCA endEntityOrCA, 51 /*optional modified*/ std::map<ByteString, ByteString>* 52 subjectDERToCertDER = nullptr, 53 /*optional*/ const ByteString* extension = nullptr, 54 /*optional*/ const TestKeyPair* issuerKeyPair = nullptr, 55 /*optional*/ const TestKeyPair* subjectKeyPair = nullptr) 56 { 57 static long serialNumberValue = 0; 58 ++serialNumberValue; 59 ByteString serialNumber(CreateEncodedSerialNumber(serialNumberValue)); 60 EXPECT_FALSE(ENCODING_FAILED(serialNumber)); 61 62 ByteString issuerDER(issuerCN ? CNToDERName(issuerCN) : Name(ByteString())); 63 ByteString subjectDER(subjectCN ? CNToDERName(subjectCN) : Name(ByteString())); 64 65 std::vector<ByteString> extensions; 66 if (endEntityOrCA == EndEntityOrCA::MustBeCA) { 67 ByteString basicConstraints = 68 CreateEncodedBasicConstraints(true, nullptr, Critical::Yes); 69 EXPECT_FALSE(ENCODING_FAILED(basicConstraints)); 70 extensions.push_back(basicConstraints); 71 } 72 if (extension) { 73 extensions.push_back(*extension); 74 } 75 extensions.push_back(ByteString()); // marks the end of the list 76 77 ScopedTestKeyPair reusedKey(CloneReusedKeyPair()); 78 ByteString certDER(CreateEncodedCertificate( 79 v3, sha256WithRSAEncryption(), serialNumber, issuerDER, 80 oneDayBeforeNow, oneDayAfterNow, subjectDER, 81 subjectKeyPair ? *subjectKeyPair : *reusedKey, 82 extensions.data(), 83 issuerKeyPair ? *issuerKeyPair : *reusedKey, 84 sha256WithRSAEncryption())); 85 EXPECT_FALSE(ENCODING_FAILED(certDER)); 86 87 if (subjectDERToCertDER) { 88 (*subjectDERToCertDER)[subjectDER] = certDER; 89 } 90 91 return certDER; 92 } 93 94 class TestTrustDomain final : public DefaultCryptoTrustDomain 95 { 96 public: 97 // The "cert chain tail" is a longish chain of certificates that is used by 98 // all of the tests here. We share this chain across all the tests in order 99 // to speed up the tests (generating keypairs for the certs is very slow). 100 bool SetUpCertChainTail() 101 { 102 static char const* const names[] = { 103 "CA1 (Root)", "CA2", "CA3", "CA4", "CA5", "CA6", "CA7" 104 }; 105 106 for (size_t i = 0; i < MOZILLA_PKIX_ARRAY_LENGTH(names); ++i) { 107 const char* issuerName = i == 0 ? names[0] : names[i-1]; 108 CreateCACert(issuerName, names[i]); 109 if (i == 0) { 110 rootCACertDER = leafCACertDER; 111 } 112 } 113 114 return true; 115 } 116 117 void CreateCACert(const char* issuerName, const char* subjectName) 118 { 119 leafCACertDER = CreateCert(issuerName, subjectName, 120 EndEntityOrCA::MustBeCA, &subjectDERToCertDER); 121 assert(!ENCODING_FAILED(leafCACertDER)); 122 } 123 124 ByteString GetLeafCACertDER() const { return leafCACertDER; } 125 126 private: 127 Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert, 128 /*out*/ TrustLevel& trustLevel) override 129 { 130 trustLevel = InputEqualsByteString(candidateCert, rootCACertDER) 131 ? TrustLevel::TrustAnchor 132 : TrustLevel::InheritsTrust; 133 return Success; 134 } 135 136 Result FindIssuer(Input encodedIssuerName, IssuerChecker& checker, Time) 137 override 138 { 139 ByteString subjectDER(InputToByteString(encodedIssuerName)); 140 ByteString certDER(subjectDERToCertDER[subjectDER]); 141 Input derCert; 142 Result rv = derCert.Init(certDER.data(), certDER.length()); 143 if (rv != Success) { 144 return rv; 145 } 146 bool keepGoing; 147 rv = checker.Check(derCert, nullptr/*additionalNameConstraints*/, 148 keepGoing); 149 if (rv != Success) { 150 return rv; 151 } 152 return Success; 153 } 154 155 Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, 156 /*optional*/ const Input*, /*optional*/ const Input*) 157 override 158 { 159 return Success; 160 } 161 162 Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override 163 { 164 return Success; 165 } 166 167 std::map<ByteString, ByteString> subjectDERToCertDER; 168 ByteString leafCACertDER; 169 ByteString rootCACertDER; 170 }; 171 172 class pkixbuild : public ::testing::Test 173 { 174 public: 175 static void SetUpTestSuite() 176 { 177 if (!trustDomain.SetUpCertChainTail()) { 178 abort(); 179 } 180 } 181 182 protected: 183 184 static TestTrustDomain trustDomain; 185 }; 186 187 /*static*/ TestTrustDomain pkixbuild::trustDomain; 188 189 TEST_F(pkixbuild, MaxAcceptableCertChainLength) 190 { 191 { 192 ByteString leafCACert(trustDomain.GetLeafCACertDER()); 193 Input certDER; 194 ASSERT_EQ(Success, certDER.Init(leafCACert.data(), leafCACert.length())); 195 ASSERT_EQ(Success, 196 BuildCertChain(trustDomain, certDER, Now(), 197 EndEntityOrCA::MustBeCA, 198 KeyUsage::noParticularKeyUsageRequired, 199 KeyPurposeId::id_kp_serverAuth, 200 CertPolicyId::anyPolicy, 201 nullptr/*stapledOCSPResponse*/)); 202 } 203 204 { 205 ByteString certDER(CreateCert("CA7", "Direct End-Entity", 206 EndEntityOrCA::MustBeEndEntity)); 207 ASSERT_FALSE(ENCODING_FAILED(certDER)); 208 Input certDERInput; 209 ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length())); 210 ASSERT_EQ(Success, 211 BuildCertChain(trustDomain, certDERInput, Now(), 212 EndEntityOrCA::MustBeEndEntity, 213 KeyUsage::noParticularKeyUsageRequired, 214 KeyPurposeId::id_kp_serverAuth, 215 CertPolicyId::anyPolicy, 216 nullptr/*stapledOCSPResponse*/)); 217 } 218 } 219 220 TEST_F(pkixbuild, BeyondMaxAcceptableCertChainLength) 221 { 222 static char const* const caCertName = "CA Too Far"; 223 224 trustDomain.CreateCACert("CA7", caCertName); 225 226 { 227 ByteString certDER(trustDomain.GetLeafCACertDER()); 228 Input certDERInput; 229 ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length())); 230 ASSERT_EQ(Result::ERROR_UNKNOWN_ISSUER, 231 BuildCertChain(trustDomain, certDERInput, Now(), 232 EndEntityOrCA::MustBeCA, 233 KeyUsage::noParticularKeyUsageRequired, 234 KeyPurposeId::id_kp_serverAuth, 235 CertPolicyId::anyPolicy, 236 nullptr/*stapledOCSPResponse*/)); 237 } 238 239 { 240 ByteString certDER(CreateCert(caCertName, "End-Entity Too Far", 241 EndEntityOrCA::MustBeEndEntity)); 242 ASSERT_FALSE(ENCODING_FAILED(certDER)); 243 Input certDERInput; 244 ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length())); 245 ASSERT_EQ(Result::ERROR_UNKNOWN_ISSUER, 246 BuildCertChain(trustDomain, certDERInput, Now(), 247 EndEntityOrCA::MustBeEndEntity, 248 KeyUsage::noParticularKeyUsageRequired, 249 KeyPurposeId::id_kp_serverAuth, 250 CertPolicyId::anyPolicy, 251 nullptr/*stapledOCSPResponse*/)); 252 } 253 } 254 255 // A TrustDomain that checks certificates against a given root certificate. 256 // It is initialized with the DER encoding of a root certificate that 257 // is treated as a trust anchor and is assumed to have issued all certificates 258 // (i.e. FindIssuer always attempts to build the next step in the chain with 259 // it). 260 class SingleRootTrustDomain : public DefaultCryptoTrustDomain 261 { 262 public: 263 explicit SingleRootTrustDomain(ByteString aRootDER) 264 : rootDER(aRootDER) 265 { 266 } 267 268 // The CertPolicyId argument is unused because we don't care about EV. 269 Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert, 270 /*out*/ TrustLevel& trustLevel) override 271 { 272 Input rootCert; 273 Result rv = rootCert.Init(rootDER.data(), rootDER.length()); 274 if (rv != Success) { 275 return rv; 276 } 277 if (InputsAreEqual(candidateCert, rootCert)) { 278 trustLevel = TrustLevel::TrustAnchor; 279 } else { 280 trustLevel = TrustLevel::InheritsTrust; 281 } 282 return Success; 283 } 284 285 Result FindIssuer(Input, IssuerChecker& checker, Time) override 286 { 287 // keepGoing is an out parameter from IssuerChecker.Check. It would tell us 288 // whether or not to continue attempting other potential issuers. We only 289 // know of one potential issuer, however, so we ignore it. 290 bool keepGoing; 291 Input rootCert; 292 Result rv = rootCert.Init(rootDER.data(), rootDER.length()); 293 if (rv != Success) { 294 return rv; 295 } 296 return checker.Check(rootCert, nullptr, keepGoing); 297 } 298 299 Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override 300 { 301 return Success; 302 } 303 304 Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, 305 /*optional*/ const Input*, /*optional*/ const Input*) 306 override 307 { 308 return Success; 309 } 310 311 private: 312 ByteString rootDER; 313 }; 314 315 // A TrustDomain that explicitly fails if CheckRevocation is called. 316 class ExpiredCertTrustDomain final : public SingleRootTrustDomain 317 { 318 public: 319 explicit ExpiredCertTrustDomain(ByteString aRootDER) 320 : SingleRootTrustDomain(aRootDER) 321 { 322 } 323 324 Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, 325 /*optional*/ const Input*, /*optional*/ const Input*) 326 override 327 { 328 ADD_FAILURE(); 329 return NotReached("CheckRevocation should not be called", 330 Result::FATAL_ERROR_LIBRARY_FAILURE); 331 } 332 }; 333 334 TEST_F(pkixbuild, NoRevocationCheckingForExpiredCert) 335 { 336 const char* rootCN = "Root CA"; 337 ByteString rootDER(CreateCert(rootCN, rootCN, EndEntityOrCA::MustBeCA, 338 nullptr)); 339 EXPECT_FALSE(ENCODING_FAILED(rootDER)); 340 ExpiredCertTrustDomain expiredCertTrustDomain(rootDER); 341 342 ByteString serialNumber(CreateEncodedSerialNumber(100)); 343 EXPECT_FALSE(ENCODING_FAILED(serialNumber)); 344 ByteString issuerDER(CNToDERName(rootCN)); 345 ByteString subjectDER(CNToDERName("Expired End-Entity Cert")); 346 ScopedTestKeyPair reusedKey(CloneReusedKeyPair()); 347 ByteString certDER(CreateEncodedCertificate( 348 v3, sha256WithRSAEncryption(), 349 serialNumber, issuerDER, 350 twoDaysBeforeNow, 351 oneDayBeforeNow, 352 subjectDER, *reusedKey, nullptr, *reusedKey, 353 sha256WithRSAEncryption())); 354 EXPECT_FALSE(ENCODING_FAILED(certDER)); 355 356 Input cert; 357 ASSERT_EQ(Success, cert.Init(certDER.data(), certDER.length())); 358 ASSERT_EQ(Result::ERROR_EXPIRED_CERTIFICATE, 359 BuildCertChain(expiredCertTrustDomain, cert, Now(), 360 EndEntityOrCA::MustBeEndEntity, 361 KeyUsage::noParticularKeyUsageRequired, 362 KeyPurposeId::id_kp_serverAuth, 363 CertPolicyId::anyPolicy, 364 nullptr)); 365 } 366 367 class DSSTrustDomain final : public EverythingFailsByDefaultTrustDomain 368 { 369 public: 370 Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, 371 Input, /*out*/ TrustLevel& trustLevel) override 372 { 373 trustLevel = TrustLevel::TrustAnchor; 374 return Success; 375 } 376 }; 377 378 class pkixbuild_DSS : public ::testing::Test { }; 379 380 TEST_F(pkixbuild_DSS, DSSEndEntityKeyNotAccepted) 381 { 382 DSSTrustDomain trustDomain; 383 384 ByteString serialNumber(CreateEncodedSerialNumber(1)); 385 ASSERT_FALSE(ENCODING_FAILED(serialNumber)); 386 387 ByteString subjectDER(CNToDERName("DSS")); 388 ASSERT_FALSE(ENCODING_FAILED(subjectDER)); 389 ScopedTestKeyPair subjectKey(GenerateDSSKeyPair()); 390 ASSERT_TRUE(subjectKey.get()); 391 392 ByteString issuerDER(CNToDERName("RSA")); 393 ASSERT_FALSE(ENCODING_FAILED(issuerDER)); 394 ScopedTestKeyPair issuerKey(CloneReusedKeyPair()); 395 ASSERT_TRUE(issuerKey.get()); 396 397 ByteString cert(CreateEncodedCertificate(v3, sha256WithRSAEncryption(), 398 serialNumber, issuerDER, 399 oneDayBeforeNow, oneDayAfterNow, 400 subjectDER, *subjectKey, nullptr, 401 *issuerKey, sha256WithRSAEncryption())); 402 ASSERT_FALSE(ENCODING_FAILED(cert)); 403 Input certDER; 404 ASSERT_EQ(Success, certDER.Init(cert.data(), cert.length())); 405 406 ASSERT_EQ(Result::ERROR_UNSUPPORTED_KEYALG, 407 BuildCertChain(trustDomain, certDER, Now(), 408 EndEntityOrCA::MustBeEndEntity, 409 KeyUsage::noParticularKeyUsageRequired, 410 KeyPurposeId::id_kp_serverAuth, 411 CertPolicyId::anyPolicy, 412 nullptr/*stapledOCSPResponse*/)); 413 } 414 415 class IssuerNameCheckTrustDomain final : public DefaultCryptoTrustDomain 416 { 417 public: 418 IssuerNameCheckTrustDomain(const ByteString& aIssuer, bool aExpectedKeepGoing) 419 : issuer(aIssuer) 420 , expectedKeepGoing(aExpectedKeepGoing) 421 { 422 } 423 424 Result GetCertTrust(EndEntityOrCA endEntityOrCA, const CertPolicyId&, Input, 425 /*out*/ TrustLevel& trustLevel) override 426 { 427 trustLevel = endEntityOrCA == EndEntityOrCA::MustBeCA 428 ? TrustLevel::TrustAnchor 429 : TrustLevel::InheritsTrust; 430 return Success; 431 } 432 433 Result FindIssuer(Input, IssuerChecker& checker, Time) override 434 { 435 Input issuerInput; 436 EXPECT_EQ(Success, issuerInput.Init(issuer.data(), issuer.length())); 437 bool keepGoing; 438 EXPECT_EQ(Success, 439 checker.Check(issuerInput, nullptr /*additionalNameConstraints*/, 440 keepGoing)); 441 EXPECT_EQ(expectedKeepGoing, keepGoing); 442 return Success; 443 } 444 445 Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, 446 /*optional*/ const Input*, /*optional*/ const Input*) 447 override 448 { 449 return Success; 450 } 451 452 Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override 453 { 454 return Success; 455 } 456 457 private: 458 const ByteString issuer; 459 const bool expectedKeepGoing; 460 }; 461 462 struct IssuerNameCheckParams 463 { 464 const char* subjectIssuerCN; // null means "empty name" 465 const char* issuerSubjectCN; // null means "empty name" 466 bool matches; 467 Result expectedError; 468 }; 469 470 static const IssuerNameCheckParams ISSUER_NAME_CHECK_PARAMS[] = 471 { 472 { "foo", "foo", true, Success }, 473 { "foo", "bar", false, Result::ERROR_UNKNOWN_ISSUER }, 474 { "f", "foo", false, Result::ERROR_UNKNOWN_ISSUER }, // prefix 475 { "foo", "f", false, Result::ERROR_UNKNOWN_ISSUER }, // prefix 476 { "foo", "Foo", false, Result::ERROR_UNKNOWN_ISSUER }, // case sensitive 477 { "", "", true, Success }, 478 { nullptr, nullptr, false, Result::ERROR_EMPTY_ISSUER_NAME }, // empty issuer 479 480 // check that certificate-related errors are deferred and superseded by 481 // ERROR_UNKNOWN_ISSUER when a chain can't be built due to name mismatches 482 { "foo", nullptr, false, Result::ERROR_UNKNOWN_ISSUER }, 483 { nullptr, "foo", false, Result::ERROR_UNKNOWN_ISSUER } 484 }; 485 486 class pkixbuild_IssuerNameCheck 487 : public ::testing::Test 488 , public ::testing::WithParamInterface<IssuerNameCheckParams> 489 { 490 }; 491 492 TEST_P(pkixbuild_IssuerNameCheck, MatchingName) 493 { 494 const IssuerNameCheckParams& params(GetParam()); 495 496 ByteString issuerCertDER(CreateCert(params.issuerSubjectCN, 497 params.issuerSubjectCN, 498 EndEntityOrCA::MustBeCA, nullptr)); 499 ASSERT_FALSE(ENCODING_FAILED(issuerCertDER)); 500 501 ByteString subjectCertDER(CreateCert(params.subjectIssuerCN, "end-entity", 502 EndEntityOrCA::MustBeEndEntity, 503 nullptr)); 504 ASSERT_FALSE(ENCODING_FAILED(subjectCertDER)); 505 506 Input subjectCertDERInput; 507 ASSERT_EQ(Success, subjectCertDERInput.Init(subjectCertDER.data(), 508 subjectCertDER.length())); 509 510 IssuerNameCheckTrustDomain trustDomain(issuerCertDER, !params.matches); 511 ASSERT_EQ(params.expectedError, 512 BuildCertChain(trustDomain, subjectCertDERInput, Now(), 513 EndEntityOrCA::MustBeEndEntity, 514 KeyUsage::noParticularKeyUsageRequired, 515 KeyPurposeId::id_kp_serverAuth, 516 CertPolicyId::anyPolicy, 517 nullptr/*stapledOCSPResponse*/)); 518 } 519 520 INSTANTIATE_TEST_SUITE_P(pkixbuild_IssuerNameCheck, pkixbuild_IssuerNameCheck, 521 testing::ValuesIn(ISSUER_NAME_CHECK_PARAMS)); 522 523 524 // Records the embedded SCT list extension for later examination. 525 class EmbeddedSCTListTestTrustDomain final : public SingleRootTrustDomain 526 { 527 public: 528 explicit EmbeddedSCTListTestTrustDomain(ByteString aRootDER) 529 : SingleRootTrustDomain(aRootDER) 530 { 531 } 532 533 virtual void NoteAuxiliaryExtension(AuxiliaryExtension extension, 534 Input extensionData) override 535 { 536 if (extension == AuxiliaryExtension::EmbeddedSCTList) { 537 signedCertificateTimestamps = InputToByteString(extensionData); 538 } else { 539 ADD_FAILURE(); 540 } 541 } 542 543 ByteString signedCertificateTimestamps; 544 }; 545 546 TEST_F(pkixbuild, CertificateTransparencyExtension) 547 { 548 // python security/pkix/tools/DottedOIDToCode.py --tlv 549 // id-embeddedSctList 1.3.6.1.4.1.11129.2.4.2 550 static const uint8_t tlv_id_embeddedSctList[] = { 551 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x02 552 }; 553 static const uint8_t dummySctList[] = { 554 0x01, 0x02, 0x03, 0x04, 0x05 555 }; 556 557 ByteString ctExtension = TLV(der::SEQUENCE, 558 BytesToByteString(tlv_id_embeddedSctList) + 559 Boolean(false) + 560 TLV(der::OCTET_STRING, 561 // SignedCertificateTimestampList structure is encoded as an OCTET STRING 562 // within the X.509v3 extension (see RFC 6962 section 3.3). 563 // pkix decodes it internally and returns the actual structure. 564 TLV(der::OCTET_STRING, BytesToByteString(dummySctList)))); 565 566 const char* rootCN = "Root CA"; 567 ByteString rootDER(CreateCert(rootCN, rootCN, EndEntityOrCA::MustBeCA)); 568 ASSERT_FALSE(ENCODING_FAILED(rootDER)); 569 570 ByteString certDER(CreateCert(rootCN, "Cert with SCT list", 571 EndEntityOrCA::MustBeEndEntity, 572 nullptr, /*subjectDERToCertDER*/ 573 &ctExtension)); 574 ASSERT_FALSE(ENCODING_FAILED(certDER)); 575 576 Input certInput; 577 ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length())); 578 579 EmbeddedSCTListTestTrustDomain extTrustDomain(rootDER); 580 ASSERT_EQ(Success, 581 BuildCertChain(extTrustDomain, certInput, Now(), 582 EndEntityOrCA::MustBeEndEntity, 583 KeyUsage::noParticularKeyUsageRequired, 584 KeyPurposeId::anyExtendedKeyUsage, 585 CertPolicyId::anyPolicy, 586 nullptr /*stapledOCSPResponse*/)); 587 ASSERT_EQ(BytesToByteString(dummySctList), 588 extTrustDomain.signedCertificateTimestamps); 589 } 590 591 // This TrustDomain implements a hierarchy like so: 592 // 593 // A B 594 // | | 595 // C D 596 // \ / 597 // E 598 // 599 // where A is a trust anchor, B is not a trust anchor and has no known issuer, C 600 // and D are intermediates with the same subject and subject public key, and E 601 // is an end-entity (in practice, the end-entity will be generated by the test 602 // functions using this trust domain). 603 class MultiplePathTrustDomain: public DefaultCryptoTrustDomain 604 { 605 public: 606 void SetUpCerts() 607 { 608 ASSERT_FALSE(ENCODING_FAILED(CreateCert("UntrustedRoot", "UntrustedRoot", 609 EndEntityOrCA::MustBeCA, 610 &subjectDERToCertDER))); 611 // The subject DER -> cert DER mapping would be overwritten for subject 612 // "Intermediate" when we create the second "Intermediate" certificate, so 613 // we keep a copy of this "Intermediate". 614 intermediateSignedByUntrustedRootCertDER = 615 CreateCert("UntrustedRoot", "Intermediate", EndEntityOrCA::MustBeCA); 616 ASSERT_FALSE(ENCODING_FAILED(intermediateSignedByUntrustedRootCertDER)); 617 rootCACertDER = CreateCert("TrustedRoot", "TrustedRoot", 618 EndEntityOrCA::MustBeCA, &subjectDERToCertDER); 619 ASSERT_FALSE(ENCODING_FAILED(rootCACertDER)); 620 ASSERT_FALSE(ENCODING_FAILED(CreateCert("TrustedRoot", "Intermediate", 621 EndEntityOrCA::MustBeCA, 622 &subjectDERToCertDER))); 623 } 624 625 private: 626 Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert, 627 /*out*/ TrustLevel& trustLevel) override 628 { 629 trustLevel = InputEqualsByteString(candidateCert, rootCACertDER) 630 ? TrustLevel::TrustAnchor 631 : TrustLevel::InheritsTrust; 632 return Success; 633 } 634 635 Result CheckCert(ByteString& certDER, IssuerChecker& checker, bool& keepGoing) 636 { 637 Input derCert; 638 Result rv = derCert.Init(certDER.data(), certDER.length()); 639 if (rv != Success) { 640 return rv; 641 } 642 return checker.Check(derCert, nullptr/*additionalNameConstraints*/, 643 keepGoing); 644 } 645 646 Result FindIssuer(Input encodedIssuerName, IssuerChecker& checker, Time) 647 override 648 { 649 ByteString subjectDER(InputToByteString(encodedIssuerName)); 650 ByteString certDER(subjectDERToCertDER[subjectDER]); 651 assert(!ENCODING_FAILED(certDER)); 652 bool keepGoing; 653 Result rv = CheckCert(certDER, checker, keepGoing); 654 if (rv != Success) { 655 return rv; 656 } 657 // Also try the other intermediate. 658 if (keepGoing) { 659 rv = CheckCert(intermediateSignedByUntrustedRootCertDER, checker, 660 keepGoing); 661 if (rv != Success) { 662 return rv; 663 } 664 } 665 return Success; 666 } 667 668 Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, 669 /*optional*/ const Input*, 670 /*optional*/ const Input*) override 671 { 672 return Success; 673 } 674 675 Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override 676 { 677 return Success; 678 } 679 680 std::map<ByteString, ByteString> subjectDERToCertDER; 681 ByteString rootCACertDER; 682 ByteString intermediateSignedByUntrustedRootCertDER; 683 }; 684 685 TEST_F(pkixbuild, BadEmbeddedSCTWithMultiplePaths) 686 { 687 MultiplePathTrustDomain localTrustDomain; 688 localTrustDomain.SetUpCerts(); 689 690 // python security/pkix/tools/DottedOIDToCode.py --tlv 691 // id-embeddedSctList 1.3.6.1.4.1.11129.2.4.2 692 static const uint8_t tlv_id_embeddedSctList[] = { 693 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x02 694 }; 695 static const uint8_t dummySctList[] = { 696 0x01, 0x02, 0x03, 0x04, 0x05 697 }; 698 ByteString ctExtension = TLV(der::SEQUENCE, 699 BytesToByteString(tlv_id_embeddedSctList) + 700 Boolean(false) + 701 // The contents of the OCTET STRING are supposed to consist of an OCTET 702 // STRING of useful data. We're testing what happens if it isn't, so shove 703 // some bogus (non-OCTET STRING) data in there. 704 TLV(der::OCTET_STRING, BytesToByteString(dummySctList))); 705 ByteString certDER(CreateCert("Intermediate", "Cert with bogus SCT list", 706 EndEntityOrCA::MustBeEndEntity, 707 nullptr, /*subjectDERToCertDER*/ 708 &ctExtension)); 709 ASSERT_FALSE(ENCODING_FAILED(certDER)); 710 Input certDERInput; 711 ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length())); 712 ASSERT_EQ(Result::ERROR_BAD_DER, 713 BuildCertChain(localTrustDomain, certDERInput, Now(), 714 EndEntityOrCA::MustBeEndEntity, 715 KeyUsage::noParticularKeyUsageRequired, 716 KeyPurposeId::id_kp_serverAuth, 717 CertPolicyId::anyPolicy, 718 nullptr/*stapledOCSPResponse*/)); 719 } 720 721 // Same as a MultiplePathTrustDomain, but the end-entity is revoked. 722 class RevokedEndEntityTrustDomain final : public MultiplePathTrustDomain 723 { 724 public: 725 Result CheckRevocation(EndEntityOrCA endEntityOrCA, const CertID&, Time, 726 Duration, /*optional*/ const Input*, 727 /*optional*/ const Input*) override 728 { 729 if (endEntityOrCA == EndEntityOrCA::MustBeEndEntity) { 730 return Result::ERROR_REVOKED_CERTIFICATE; 731 } 732 return Success; 733 } 734 }; 735 736 TEST_F(pkixbuild, RevokedEndEntityWithMultiplePaths) 737 { 738 RevokedEndEntityTrustDomain localTrustDomain; 739 localTrustDomain.SetUpCerts(); 740 ByteString certDER(CreateCert("Intermediate", "RevokedEndEntity", 741 EndEntityOrCA::MustBeEndEntity)); 742 ASSERT_FALSE(ENCODING_FAILED(certDER)); 743 Input certDERInput; 744 ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length())); 745 ASSERT_EQ(Result::ERROR_REVOKED_CERTIFICATE, 746 BuildCertChain(localTrustDomain, certDERInput, Now(), 747 EndEntityOrCA::MustBeEndEntity, 748 KeyUsage::noParticularKeyUsageRequired, 749 KeyPurposeId::id_kp_serverAuth, 750 CertPolicyId::anyPolicy, 751 nullptr/*stapledOCSPResponse*/)); 752 } 753 754 // This represents a collection of different certificates that all have the same 755 // subject and issuer distinguished name. 756 class SelfIssuedCertificatesTrustDomain final : public DefaultCryptoTrustDomain 757 { 758 public: 759 void SetUpCerts(size_t totalCerts) 760 { 761 ASSERT_TRUE(totalCerts > 0); 762 // First we generate a trust anchor. 763 ScopedTestKeyPair rootKeyPair(GenerateKeyPair()); 764 rootCACertDER = CreateCert("DN", "DN", EndEntityOrCA::MustBeCA, nullptr, 765 nullptr, rootKeyPair.get(), rootKeyPair.get()); 766 ASSERT_FALSE(ENCODING_FAILED(rootCACertDER)); 767 certs.push_back(rootCACertDER); 768 ScopedTestKeyPair issuerKeyPair(rootKeyPair.release()); 769 size_t subCAsGenerated; 770 // Then we generate 6 sub-CAs (given that we were requested to generate at 771 // least that many). 772 for (subCAsGenerated = 0; 773 subCAsGenerated < totalCerts - 1 && subCAsGenerated < 6; 774 subCAsGenerated++) { 775 // Each certificate has to have a unique SPKI (mozilla::pkix does loop 776 // detection and stops searching if it encounters two certificates in a 777 // path with the same subject and SPKI). 778 ScopedTestKeyPair keyPair(GenerateKeyPair()); 779 ByteString cert(CreateCert("DN", "DN", EndEntityOrCA::MustBeCA, nullptr, 780 nullptr, issuerKeyPair.get(), keyPair.get())); 781 ASSERT_FALSE(ENCODING_FAILED(cert)); 782 certs.push_back(cert); 783 issuerKeyPair.reset(keyPair.release()); 784 } 785 // We set firstIssuerKey here because we can't end up with a path that has 786 // more than 7 CAs in it (because mozilla::pkix limits the path length). 787 firstIssuerKey.reset(issuerKeyPair.release()); 788 // For any more sub CAs we generate, it doesn't matter what their keys are 789 // as long as they're different. 790 for (; subCAsGenerated < totalCerts - 1; subCAsGenerated++) { 791 ScopedTestKeyPair keyPair(GenerateKeyPair()); 792 ByteString cert(CreateCert("DN", "DN", EndEntityOrCA::MustBeCA, nullptr, 793 nullptr, keyPair.get(), keyPair.get())); 794 ASSERT_FALSE(ENCODING_FAILED(cert)); 795 certs.insert(certs.begin(), cert); 796 } 797 } 798 799 const TestKeyPair* GetFirstIssuerKey() 800 { 801 return firstIssuerKey.get(); 802 } 803 804 private: 805 Result GetCertTrust(EndEntityOrCA, const CertPolicyId&, Input candidateCert, 806 /*out*/ TrustLevel& trustLevel) override 807 { 808 trustLevel = InputEqualsByteString(candidateCert, rootCACertDER) 809 ? TrustLevel::TrustAnchor 810 : TrustLevel::InheritsTrust; 811 return Success; 812 } 813 814 Result FindIssuer(Input, IssuerChecker& checker, Time) override 815 { 816 bool keepGoing; 817 for (auto& cert: certs) { 818 Input certInput; 819 Result rv = certInput.Init(cert.data(), cert.length()); 820 if (rv != Success) { 821 return rv; 822 } 823 rv = checker.Check(certInput, nullptr, keepGoing); 824 if (rv != Success || !keepGoing) { 825 return rv; 826 } 827 } 828 return Success; 829 } 830 831 Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration, 832 /*optional*/ const Input*, /*optional*/ const Input*) 833 override 834 { 835 return Success; 836 } 837 838 Result IsChainValid(const DERArray&, Time, const CertPolicyId&) override 839 { 840 return Success; 841 } 842 843 std::vector<ByteString> certs; 844 ByteString rootCACertDER; 845 ScopedTestKeyPair firstIssuerKey; 846 }; 847 848 TEST_F(pkixbuild, AvoidUnboundedPathSearchingFailure) 849 { 850 SelfIssuedCertificatesTrustDomain localTrustDomain; 851 // This creates a few hundred million potential paths of length 8 (end entity 852 // + 6 sub-CAs + root). It would be prohibitively expensive to enumerate all 853 // of these, so we give mozilla::pkix a budget that is spent when searching 854 // paths. If the budget is exhausted, it simply returns an unknown issuer 855 // error. In the future it might be nice to return a specific error that would 856 // give the front-end a hint that maybe it shouldn't have so many certificates 857 // that all have the same subject and issuer DN but different SPKIs. 858 localTrustDomain.SetUpCerts(18); 859 ByteString certDER(CreateCert("DN", "DN", EndEntityOrCA::MustBeEndEntity, 860 nullptr, nullptr, 861 localTrustDomain.GetFirstIssuerKey())); 862 ASSERT_FALSE(ENCODING_FAILED(certDER)); 863 Input certDERInput; 864 ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length())); 865 ASSERT_EQ(Result::ERROR_UNKNOWN_ISSUER, 866 BuildCertChain(localTrustDomain, certDERInput, Now(), 867 EndEntityOrCA::MustBeEndEntity, 868 KeyUsage::noParticularKeyUsageRequired, 869 KeyPurposeId::id_kp_serverAuth, 870 CertPolicyId::anyPolicy, 871 nullptr/*stapledOCSPResponse*/)); 872 } 873 874 TEST_F(pkixbuild, AvoidUnboundedPathSearchingSuccess) 875 { 876 SelfIssuedCertificatesTrustDomain localTrustDomain; 877 // This creates a few hundred thousand possible potential paths of length 8 878 // (end entity + 6 sub-CAs + root). This will nearly exhaust mozilla::pkix's 879 // search budget, so this should succeed. 880 localTrustDomain.SetUpCerts(10); 881 ByteString certDER(CreateCert("DN", "DN", EndEntityOrCA::MustBeEndEntity, 882 nullptr, nullptr, 883 localTrustDomain.GetFirstIssuerKey())); 884 ASSERT_FALSE(ENCODING_FAILED(certDER)); 885 Input certDERInput; 886 ASSERT_EQ(Success, certDERInput.Init(certDER.data(), certDER.length())); 887 ASSERT_EQ(Success, 888 BuildCertChain(localTrustDomain, certDERInput, Now(), 889 EndEntityOrCA::MustBeEndEntity, 890 KeyUsage::noParticularKeyUsageRequired, 891 KeyPurposeId::id_kp_serverAuth, 892 CertPolicyId::anyPolicy, 893 nullptr/*stapledOCSPResponse*/)); 894 }