| /* -*- 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); |
| } |