Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
*/
/* 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
*
*
* 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 "secoid.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 SetUpTestSuite()
{
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_SUITE_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 SetUpTestSuite()
{
pkixocsp_VerifyEncodedResponse::SetUpTestSuite();
}
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,
/*optional*/ const ByteString* trailingResponseData = nullptr,
/*optional*/ DigestAlgorithm certIDHashAlgorithm = DigestAlgorithm::sha1,
/*optional*/ ByteString certIDHashAlgorithmEncoded = ByteString())
{
OCSPResponseContext context(certID, producedAt);
context.certIDHashAlgorithm = certIDHashAlgorithm;
context.certIDHashAlgorithmEncoded = certIDHashAlgorithmEncoded;
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.trailingResponseData = trailingResponseData;
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)
{
PRUint32 policyMd5;
ASSERT_EQ(SECSuccess,NSS_GetAlgorithmPolicy(SEC_OID_MD5, &policyMd5));
/* our encode won't work if MD5 isn't allowed by policy */
ASSERT_EQ(SECSuccess,
NSS_SetAlgorithmPolicy(SEC_OID_MD5, NSS_USE_ALG_IN_SIGNATURE, 0));
ByteString responseString(
CreateEncodedOCSPSuccessfulResponse(
OCSPResponseContext::good, *endEntityCertID, byKey,
*rootKeyPair, oneDayBeforeNow,
oneDayBeforeNow, &oneDayAfterNow,
md5WithRSAEncryption()));
Input response;
ASSERT_EQ(Success,
response.Init(responseString.data(), responseString.length()));
/* now restore the existing policy */
ASSERT_EQ(SECSuccess,
NSS_SetAlgorithmPolicy(SEC_OID_MD5, policyMd5, NSS_USE_ALG_IN_SIGNATURE));
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);
}
// 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);
}
}
// 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
};
TEST_F(pkixocsp_VerifyEncodedResponse_successful, ct_extension)
{
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);
}
TEST_F(pkixocsp_VerifyEncodedResponse_successful, trailingResponseData)
{
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 trailingResponseData(3, 0x20);
ByteString responseString(
CreateEncodedOCSPSuccessfulResponse(
OCSPResponseContext::good, *endEntityCertID, byKey,
*rootKeyPair, oneDayBeforeNow,
oneDayBeforeNow, &oneDayAfterNow,
sha256WithRSAEncryption(),
/*certs*/ nullptr,
&ctExtension,
/*responseExtensions*/ nullptr,
&trailingResponseData));
Input response;
ASSERT_EQ(Success,
response.Init(responseString.data(), responseString.length()));
bool expired;
ASSERT_EQ(Result::ERROR_OCSP_MALFORMED_RESPONSE,
VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID,
Now(), END_ENTITY_MAX_LIFETIME_IN_DAYS,
response, expired));
}
struct CertIDHashAlgorithm
{
DigestAlgorithm hashAlgorithm;
ByteString encodedHashAlgorithm;
Result expectedResult;
};
// python DottedOIDToCode.py --alg id-sha1 1.3.14.3.2.26
static const uint8_t alg_id_sha1[] = {
0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a
};
// python DottedOIDToCode.py --alg id-sha256 2.16.840.1.101.3.4.2.1
static const uint8_t alg_id_sha256[] = {
0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01
};
static const uint8_t not_an_encoded_hash_oid[] = {
0x01, 0x02, 0x03, 0x04
};
static const CertIDHashAlgorithm CERTID_HASH_ALGORITHMS[] = {
{ DigestAlgorithm::sha1, ByteString(), Success },
{ DigestAlgorithm::sha256, ByteString(), Success },
{ DigestAlgorithm::sha384, ByteString(), Success },
{ DigestAlgorithm::sha512, ByteString(), Success },
{ DigestAlgorithm::sha256, BytesToByteString(alg_id_sha1),
Result::ERROR_OCSP_MALFORMED_RESPONSE },
{ DigestAlgorithm::sha1, BytesToByteString(alg_id_sha256),
Result::ERROR_OCSP_MALFORMED_RESPONSE },
{ DigestAlgorithm::sha1, BytesToByteString(not_an_encoded_hash_oid),
Result::ERROR_OCSP_MALFORMED_RESPONSE },
};
class pkixocsp_VerifyEncodedResponse_CertIDHashAlgorithm
: public pkixocsp_VerifyEncodedResponse_successful
, public ::testing::WithParamInterface<CertIDHashAlgorithm>
{
};
TEST_P(pkixocsp_VerifyEncodedResponse_CertIDHashAlgorithm, CertIDHashAlgorithm)
{
ByteString responseString(
CreateEncodedOCSPSuccessfulResponse(
OCSPResponseContext::good, *endEntityCertID, byKey,
*rootKeyPair, oneDayBeforeNow,
oneDayBeforeNow, &oneDayAfterNow,
sha256WithRSAEncryption(),
nullptr,
nullptr,
nullptr,
nullptr,
GetParam().hashAlgorithm,
GetParam().encodedHashAlgorithm));
Input response;
ASSERT_EQ(Success,
response.Init(responseString.data(), responseString.length()));
bool expired;
ASSERT_EQ(GetParam().expectedResult,
VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID,
Now(), END_ENTITY_MAX_LIFETIME_IN_DAYS,
response, expired));
ASSERT_FALSE(expired);
}
INSTANTIATE_TEST_SUITE_P(pkixocsp_VerifyEncodedResponse_CertIDHashAlgorithm,
pkixocsp_VerifyEncodedResponse_CertIDHashAlgorithm,
testing::ValuesIn(CERTID_HASH_ALGORITHMS));
///////////////////////////////////////////////////////////////////////////////
// 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.
PRUint32 policyMd5;
ASSERT_EQ(SECSuccess,NSS_GetAlgorithmPolicy(SEC_OID_MD5, &policyMd5));
/* our encode won't work if MD5 isn't allowed by policy */
ASSERT_EQ(SECSuccess,
NSS_SetAlgorithmPolicy(SEC_OID_MD5, NSS_USE_ALG_IN_SIGNATURE, 0));
ByteString responseString(
CreateEncodedIndirectOCSPSuccessfulResponse(
"good_indirect_unsupportedSignatureAlgorithm",
OCSPResponseContext::good, byKey,
md5WithRSAEncryption()));
Input response;
/* now restore the existing policy */
ASSERT_EQ(Success,
response.Init(responseString.data(), responseString.length()));
ASSERT_EQ(SECSuccess,
NSS_SetAlgorithmPolicy(SEC_OID_MD5, policyMd5, NSS_USE_ALG_IN_SIGNATURE));
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
// 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);
}