summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/mozpkix/include/pkix/pkixtypes.h22
-rw-r--r--lib/mozpkix/lib/pkixbuild.cpp14
-rw-r--r--lib/mozpkix/lib/pkixcert.cpp20
-rw-r--r--lib/mozpkix/lib/pkixocsp.cpp57
-rw-r--r--lib/mozpkix/lib/pkixutil.h11
-rw-r--r--lib/mozpkix/test/gtest/pkixbuild_tests.cpp118
-rw-r--r--lib/mozpkix/test/gtest/pkixgtest.h9
-rw-r--r--lib/mozpkix/test/gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp60
-rw-r--r--lib/mozpkix/test/lib/pkixtestutil.cpp24
-rw-r--r--lib/mozpkix/test/lib/pkixtestutil.h7
10 files changed, 324 insertions, 18 deletions
diff --git a/lib/mozpkix/include/pkix/pkixtypes.h b/lib/mozpkix/include/pkix/pkixtypes.h
index eeab664e0..0a8f770a1 100644
--- a/lib/mozpkix/include/pkix/pkixtypes.h
+++ b/lib/mozpkix/include/pkix/pkixtypes.h
@@ -105,6 +105,21 @@ enum class TrustLevel
InheritsTrust = 3 // certificate must chain to a trust anchor
};
+// Extensions extracted during the verification flow.
+// See TrustDomain::NoteAuxiliaryExtension.
+enum class AuxiliaryExtension
+{
+ // Certificate Transparency data, specifically Signed Certificate
+ // Timestamps (SCTs). See RFC 6962.
+
+ // SCT list embedded in the end entity certificate. Called by BuildCertChain
+ // after the certificate containing the SCTs has passed the revocation checks.
+ EmbeddedSCTList = 1,
+ // SCT list from OCSP response. Called by VerifyEncodedOCSPResponse
+ // when its result is a success and the SCT list is present.
+ SCTListFromOCSPResponse = 2
+};
+
// CertID references the information needed to do revocation checking for the
// certificate issued by the given issuer with the given serial number.
//
@@ -337,6 +352,13 @@ public:
virtual Result NetscapeStepUpMatchesServerAuth(Time notBefore,
/*out*/ bool& matches) = 0;
+ // Some certificate or OCSP response extensions do not directly participate
+ // in the verification flow, but might still be of interest to the clients
+ // (notably Certificate Transparency data, RFC 6962). Such extensions are
+ // extracted and passed to this function for further processing.
+ virtual void NoteAuxiliaryExtension(AuxiliaryExtension extension,
+ Input extensionData) = 0;
+
// Compute a digest of the data in item using the given digest algorithm.
//
// item contains the data to hash.
diff --git a/lib/mozpkix/lib/pkixbuild.cpp b/lib/mozpkix/lib/pkixbuild.cpp
index 99942cfa1..fdbd9b59f 100644
--- a/lib/mozpkix/lib/pkixbuild.cpp
+++ b/lib/mozpkix/lib/pkixbuild.cpp
@@ -244,6 +244,20 @@ PathBuildingStep::Check(Input potentialIssuerDER,
if (rv != Success) {
return RecordResult(rv, keepGoing);
}
+
+ if (subject.endEntityOrCA == EndEntityOrCA::MustBeEndEntity) {
+ const Input* sctExtension = subject.GetSignedCertificateTimestamps();
+ if (sctExtension) {
+ Input sctList;
+ rv = ExtractSignedCertificateTimestampListFromExtension(*sctExtension,
+ sctList);
+ if (rv != Success) {
+ return RecordResult(rv, keepGoing);
+ }
+ trustDomain.NoteAuxiliaryExtension(AuxiliaryExtension::EmbeddedSCTList,
+ sctList);
+ }
+ }
}
return RecordResult(Success, keepGoing);
diff --git a/lib/mozpkix/lib/pkixcert.cpp b/lib/mozpkix/lib/pkixcert.cpp
index 1cb452c20..ffa58fd3b 100644
--- a/lib/mozpkix/lib/pkixcert.cpp
+++ b/lib/mozpkix/lib/pkixcert.cpp
@@ -223,6 +223,11 @@ BackCert::RememberExtension(Reader& extnID, Input extnValue,
static const uint8_t id_pe_tlsfeature[] = {
0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x18
};
+ // python DottedOIDToCode.py id-embeddedSctList 1.3.6.1.4.1.11129.2.4.2
+ // See Section 3.3 of RFC 6962.
+ static const uint8_t id_embeddedSctList[] = {
+ 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x02
+ };
Input* out = nullptr;
@@ -269,6 +274,8 @@ BackCert::RememberExtension(Reader& extnID, Input extnValue,
out = &authorityInfoAccess;
} else if (extnID.MatchRest(id_pe_tlsfeature)) {
out = &requiredTLSFeatures;
+ } else if (extnID.MatchRest(id_embeddedSctList)) {
+ out = &signedCertificateTimestamps;
} else if (extnID.MatchRest(id_pkix_ocsp_nocheck) && critical) {
// We need to make sure we don't reject delegated OCSP response signing
// certificates that contain the id-pkix-ocsp-nocheck extension marked as
@@ -300,4 +307,17 @@ BackCert::RememberExtension(Reader& extnID, Input extnValue,
return Success;
}
+Result
+ExtractSignedCertificateTimestampListFromExtension(Input extnValue,
+ Input& sctList)
+{
+ Reader decodedValue;
+ Result rv = der::ExpectTagAndGetValueAtEnd(extnValue, der::OCTET_STRING,
+ decodedValue);
+ if (rv != Success) {
+ return rv;
+ }
+ return decodedValue.SkipToEnd(sctList);
+}
+
} } // namespace mozilla::pkix
diff --git a/lib/mozpkix/lib/pkixocsp.cpp b/lib/mozpkix/lib/pkixocsp.cpp
index b22fd285a..06cb53bc4 100644
--- a/lib/mozpkix/lib/pkixocsp.cpp
+++ b/lib/mozpkix/lib/pkixocsp.cpp
@@ -76,6 +76,8 @@ public:
Time* validThrough;
bool expired;
+ Input signedCertificateTimestamps;
+
// Keep track of whether the OCSP response contains the status of the
// certificate we're interested in. Responders might reply without
// including the status of any of the requested certs, we should
@@ -168,6 +170,9 @@ static inline Result ResponseData(
static inline Result SingleResponse(Reader& input, Context& context);
static Result ExtensionNotUnderstood(Reader& extnID, Input extnValue,
bool critical, /*out*/ bool& understood);
+static Result RememberSingleExtension(Context& context, Reader& extnID,
+ Input extnValue, bool critical,
+ /*out*/ bool& understood);
static inline Result CertID(Reader& input,
const Context& context,
/*out*/ bool& match);
@@ -330,6 +335,16 @@ VerifyEncodedOCSPResponse(TrustDomain& trustDomain, const struct CertID& certID,
if (expired) {
return Result::ERROR_OCSP_OLD_RESPONSE;
}
+ if (context.signedCertificateTimestamps.GetLength()) {
+ Input sctList;
+ rv = ExtractSignedCertificateTimestampListFromExtension(
+ context.signedCertificateTimestamps, sctList);
+ if (rv != Success) {
+ return MapBadDERToMalformedOCSPResponse(rv);
+ }
+ context.trustDomain.NoteAuxiliaryExtension(
+ AuxiliaryExtension::SCTListFromOCSPResponse, sctList);
+ }
return Success;
case CertStatus::Revoked:
return Result::ERROR_REVOKED_CERTIFICATE;
@@ -651,9 +666,15 @@ SingleResponse(Reader& input, Context& context)
context.expired = true;
}
- rv = der::OptionalExtensions(input,
- der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1,
- ExtensionNotUnderstood);
+ rv = der::OptionalExtensions(
+ input,
+ der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1,
+ [&context](Reader& extnID, const Input& extnValue, bool critical,
+ /*out*/ bool& understood) {
+ return RememberSingleExtension(context, extnID, extnValue, critical,
+ understood);
+ });
+
if (rv != Success) {
return rv;
}
@@ -826,6 +847,36 @@ ExtensionNotUnderstood(Reader& /*extnID*/, Input /*extnValue*/,
return Success;
}
+Result
+RememberSingleExtension(Context& context, Reader& extnID, Input extnValue,
+ bool /*critical*/, /*out*/ bool& understood)
+{
+ understood = false;
+
+ // SingleExtension for Signed Certificate Timestamp List.
+ // See Section 3.3 of RFC 6962.
+ // python DottedOIDToCode.py
+ // id_ocsp_singleExtensionSctList 1.3.6.1.4.1.11129.2.4.5
+ static const uint8_t id_ocsp_singleExtensionSctList[] = {
+ 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x05
+ };
+
+ if (extnID.MatchRest(id_ocsp_singleExtensionSctList)) {
+ // Empty values are not allowed for this extension. Note that
+ // we assume this later, when checking if the extension was present.
+ if (extnValue.GetLength() == 0) {
+ return Result::ERROR_EXTENSION_VALUE_INVALID;
+ }
+ if (context.signedCertificateTimestamps.Init(extnValue) != Success) {
+ // Duplicate extension.
+ return Result::ERROR_EXTENSION_VALUE_INVALID;
+ }
+ understood = true;
+ }
+
+ return Success;
+}
+
// 1. The certificate identified in a received response corresponds to
// the certificate that was identified in the corresponding request;
// 2. The signature on the response is valid;
diff --git a/lib/mozpkix/lib/pkixutil.h b/lib/mozpkix/lib/pkixutil.h
index 996ffe9b8..08581e943 100644
--- a/lib/mozpkix/lib/pkixutil.h
+++ b/lib/mozpkix/lib/pkixutil.h
@@ -106,6 +106,10 @@ public:
{
return MaybeInput(requiredTLSFeatures);
}
+ const Input* GetSignedCertificateTimestamps() const
+ {
+ return MaybeInput(signedCertificateTimestamps);
+ }
private:
const Input der;
@@ -149,6 +153,7 @@ private:
Input subjectAltName;
Input criticalNetscapeCertificateType;
Input requiredTLSFeatures;
+ Input signedCertificateTimestamps; // RFC 6962 (Certificate Transparency)
Result RememberExtension(Reader& extnID, Input extnValue, bool critical,
/*out*/ bool& understood);
@@ -197,6 +202,12 @@ private:
void operator=(const NonOwningDERArray&) = delete;
};
+// Extracts the SignedCertificateTimestampList structure which is encoded as an
+// OCTET STRING within the X.509v3 / OCSP extensions (see RFC 6962 section 3.3).
+Result
+ExtractSignedCertificateTimestampListFromExtension(Input extnValue,
+ Input& sctList);
+
inline unsigned int
DaysBeforeYear(unsigned int year)
{
diff --git a/lib/mozpkix/test/gtest/pkixbuild_tests.cpp b/lib/mozpkix/test/gtest/pkixbuild_tests.cpp
index 52aebad65..0b7750ef6 100644
--- a/lib/mozpkix/test/gtest/pkixbuild_tests.cpp
+++ b/lib/mozpkix/test/gtest/pkixbuild_tests.cpp
@@ -31,11 +31,13 @@
#endif
#include <map>
+#include <vector>
#if defined(_MSC_VER) && _MSC_VER < 1900
#pragma warning(pop)
#endif
+#include "pkixder.h"
#include "pkixgtest.h"
using namespace mozilla::pkix;
@@ -46,7 +48,8 @@ CreateCert(const char* issuerCN, // null means "empty name"
const char* subjectCN, // null means "empty name"
EndEntityOrCA endEntityOrCA,
/*optional modified*/ std::map<ByteString, ByteString>*
- subjectDERToCertDER = nullptr)
+ subjectDERToCertDER = nullptr,
+ /*optional*/ const ByteString* extension = nullptr)
{
static long serialNumberValue = 0;
++serialNumberValue;
@@ -56,18 +59,23 @@ CreateCert(const char* issuerCN, // null means "empty name"
ByteString issuerDER(issuerCN ? CNToDERName(issuerCN) : Name(ByteString()));
ByteString subjectDER(subjectCN ? CNToDERName(subjectCN) : Name(ByteString()));
- ByteString extensions[2];
+ std::vector<ByteString> extensions;
if (endEntityOrCA == EndEntityOrCA::MustBeCA) {
- extensions[0] =
+ ByteString basicConstraints =
CreateEncodedBasicConstraints(true, nullptr, Critical::Yes);
- EXPECT_FALSE(ENCODING_FAILED(extensions[0]));
+ EXPECT_FALSE(ENCODING_FAILED(basicConstraints));
+ extensions.push_back(basicConstraints);
}
+ if (extension) {
+ extensions.push_back(*extension);
+ }
+ extensions.push_back(ByteString()); // marks the end of the list
ScopedTestKeyPair reusedKey(CloneReusedKeyPair());
ByteString certDER(CreateEncodedCertificate(
v3, sha256WithRSAEncryption(), serialNumber, issuerDER,
oneDayBeforeNow, oneDayAfterNow, subjectDER,
- *reusedKey, extensions, *reusedKey,
+ *reusedKey, extensions.data(), *reusedKey,
sha256WithRSAEncryption()));
EXPECT_FALSE(ENCODING_FAILED(certDER));
@@ -239,15 +247,15 @@ TEST_F(pkixbuild, BeyondMaxAcceptableCertChainLength)
}
}
-// A TrustDomain that explicitly fails if CheckRevocation is called.
+// A TrustDomain that checks certificates against a given root certificate.
// It is initialized with the DER encoding of a root certificate that
// is treated as a trust anchor and is assumed to have issued all certificates
// (i.e. FindIssuer always attempts to build the next step in the chain with
// it).
-class ExpiredCertTrustDomain final : public DefaultCryptoTrustDomain
+class SingleRootTrustDomain : public DefaultCryptoTrustDomain
{
public:
- explicit ExpiredCertTrustDomain(ByteString rootDER)
+ explicit SingleRootTrustDomain(ByteString rootDER)
: rootDER(rootDER)
{
}
@@ -288,10 +296,36 @@ public:
return Success;
}
+ Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+ /*optional*/ const Input*, /*optional*/ const Input*)
+ override
+ {
+ return Success;
+ }
+
private:
ByteString rootDER;
};
+// A TrustDomain that explicitly fails if CheckRevocation is called.
+class ExpiredCertTrustDomain final : public SingleRootTrustDomain
+{
+public:
+ explicit ExpiredCertTrustDomain(ByteString rootDER)
+ : SingleRootTrustDomain(rootDER)
+ {
+ }
+
+ Result CheckRevocation(EndEntityOrCA, const CertID&, Time, Duration,
+ /*optional*/ const Input*, /*optional*/ const Input*)
+ override
+ {
+ ADD_FAILURE();
+ return NotReached("CheckRevocation should not be called",
+ Result::FATAL_ERROR_LIBRARY_FAILURE);
+ }
+};
+
TEST_F(pkixbuild, NoRevocationCheckingForExpiredCert)
{
const char* rootCN = "Root CA";
@@ -474,3 +508,71 @@ TEST_P(pkixbuild_IssuerNameCheck, MatchingName)
INSTANTIATE_TEST_CASE_P(pkixbuild_IssuerNameCheck, pkixbuild_IssuerNameCheck,
testing::ValuesIn(ISSUER_NAME_CHECK_PARAMS));
+
+
+// Records the embedded SCT list extension for later examination.
+class EmbeddedSCTListTestTrustDomain final : public SingleRootTrustDomain
+{
+public:
+ explicit EmbeddedSCTListTestTrustDomain(ByteString rootDER)
+ : SingleRootTrustDomain(rootDER)
+ {
+ }
+
+ virtual void NoteAuxiliaryExtension(AuxiliaryExtension extension,
+ Input extensionData) override
+ {
+ if (extension == AuxiliaryExtension::EmbeddedSCTList) {
+ signedCertificateTimestamps = InputToByteString(extensionData);
+ } else {
+ ADD_FAILURE();
+ }
+ }
+
+ ByteString signedCertificateTimestamps;
+};
+
+TEST_F(pkixbuild, CertificateTransparencyExtension)
+{
+ // python security/pkix/tools/DottedOIDToCode.py --tlv
+ // id-embeddedSctList 1.3.6.1.4.1.11129.2.4.2
+ static const uint8_t tlv_id_embeddedSctList[] = {
+ 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x04, 0x02
+ };
+ static const uint8_t dummySctList[] = {
+ 0x01, 0x02, 0x03, 0x04, 0x05
+ };
+
+ ByteString ctExtension = TLV(der::SEQUENCE,
+ BytesToByteString(tlv_id_embeddedSctList) +
+ Boolean(false) +
+ TLV(der::OCTET_STRING,
+ // SignedCertificateTimestampList structure is encoded as an OCTET STRING
+ // within the X.509v3 extension (see RFC 6962 section 3.3).
+ // pkix decodes it internally and returns the actual structure.
+ TLV(der::OCTET_STRING, BytesToByteString(dummySctList))));
+
+ const char* rootCN = "Root CA";
+ ByteString rootDER(CreateCert(rootCN, rootCN, EndEntityOrCA::MustBeCA));
+ ASSERT_FALSE(ENCODING_FAILED(rootDER));
+
+ ByteString certDER(CreateCert(rootCN, "Cert with SCT list",
+ EndEntityOrCA::MustBeEndEntity,
+ nullptr, /*subjectDERToCertDER*/
+ &ctExtension));
+ ASSERT_FALSE(ENCODING_FAILED(certDER));
+
+ Input certInput;
+ ASSERT_EQ(Success, certInput.Init(certDER.data(), certDER.length()));
+
+ EmbeddedSCTListTestTrustDomain extTrustDomain(rootDER);
+ ASSERT_EQ(Success,
+ BuildCertChain(extTrustDomain, certInput, Now(),
+ EndEntityOrCA::MustBeEndEntity,
+ KeyUsage::noParticularKeyUsageRequired,
+ KeyPurposeId::anyExtendedKeyUsage,
+ CertPolicyId::anyPolicy,
+ nullptr /*stapledOCSPResponse*/));
+ ASSERT_EQ(BytesToByteString(dummySctList),
+ extTrustDomain.signedCertificateTimestamps);
+}
diff --git a/lib/mozpkix/test/gtest/pkixgtest.h b/lib/mozpkix/test/gtest/pkixgtest.h
index cdc5b46e6..1ec8727e2 100644
--- a/lib/mozpkix/test/gtest/pkixgtest.h
+++ b/lib/mozpkix/test/gtest/pkixgtest.h
@@ -178,6 +178,11 @@ public:
return NotReached("NetscapeStepUpMatchesServerAuth should not be called",
Result::FATAL_ERROR_LIBRARY_FAILURE);
}
+
+ virtual void NoteAuxiliaryExtension(AuxiliaryExtension, Input) override
+ {
+ ADD_FAILURE();
+ }
};
class DefaultCryptoTrustDomain : public EverythingFailsByDefaultTrustDomain
@@ -228,6 +233,10 @@ class DefaultCryptoTrustDomain : public EverythingFailsByDefaultTrustDomain
matches = true;
return Success;
}
+
+ void NoteAuxiliaryExtension(AuxiliaryExtension, Input) override
+ {
+ }
};
class DefaultNameMatchingPolicy : public NameMatchingPolicy
diff --git a/lib/mozpkix/test/gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp b/lib/mozpkix/test/gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp
index be9f109d9..d7dab09d9 100644
--- a/lib/mozpkix/test/gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp
+++ b/lib/mozpkix/test/gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp
@@ -22,6 +22,7 @@
* limitations under the License.
*/
+#include "pkixder.h"
#include "pkixgtest.h"
using namespace mozilla::pkix;
@@ -43,6 +44,19 @@ public:
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 {
@@ -199,7 +213,9 @@ public:
time_t producedAt, time_t thisUpdate,
/*optional*/ const time_t* nextUpdate,
const TestSignatureAlgorithm& signatureAlgorithm,
- /*optional*/ const ByteString* certs = nullptr)
+ /*optional*/ const ByteString* certs = nullptr,
+ /*optional*/ OCSPResponseExtension* singleExtensions = nullptr,
+ /*optional*/ OCSPResponseExtension* responseExtensions = nullptr)
{
OCSPResponseContext context(certID, producedAt);
if (signerName) {
@@ -212,6 +228,8 @@ public:
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;
@@ -397,6 +415,46 @@ TEST_F(pkixocsp_VerifyEncodedResponse_successful, check_validThrough)
}
}
+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)
diff --git a/lib/mozpkix/test/lib/pkixtestutil.cpp b/lib/mozpkix/test/lib/pkixtestutil.cpp
index 7fd94192d..decdee09a 100644
--- a/lib/mozpkix/test/lib/pkixtestutil.cpp
+++ b/lib/mozpkix/test/lib/pkixtestutil.cpp
@@ -144,12 +144,21 @@ TLV(uint8_t tag, size_t length, const ByteString& value)
return result;
}
+OCSPResponseExtension::OCSPResponseExtension()
+ : id()
+ , critical(false)
+ , value()
+ , next(nullptr)
+{
+}
+
OCSPResponseContext::OCSPResponseContext(const CertID& certID, time_t time)
: certID(certID)
, responseStatus(successful)
, skipResponseBytes(false)
, producedAt(time)
- , extensions(nullptr)
+ , singleExtensions(nullptr)
+ , responseExtensions(nullptr)
, includeEmptyExtensions(false)
, signatureAlgorithm(sha256WithRSAEncryption())
, badSignature(false)
@@ -897,10 +906,10 @@ OCSPExtension(OCSPResponseExtension& extension)
// SEQUENCE OF Extension
// }
static ByteString
-Extensions(OCSPResponseContext& context)
+OCSPExtensions(OCSPResponseExtension* extensions)
{
ByteString value;
- for (OCSPResponseExtension* extension = context.extensions;
+ for (OCSPResponseExtension* extension = extensions;
extension; extension = extension->next) {
ByteString extensionEncoded(OCSPExtension(*extension));
if (ENCODING_FAILED(extensionEncoded)) {
@@ -935,8 +944,8 @@ ResponseData(OCSPResponseContext& context)
}
ByteString responses(TLV(der::SEQUENCE, response));
ByteString responseExtensions;
- if (context.extensions || context.includeEmptyExtensions) {
- responseExtensions = Extensions(context);
+ if (context.responseExtensions || context.includeEmptyExtensions) {
+ responseExtensions = OCSPExtensions(context.responseExtensions);
}
ByteString value;
@@ -1015,12 +1024,17 @@ SingleResponse(OCSPResponseContext& context)
nextUpdateEncodedNested = TLV(der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 0,
nextUpdateEncoded);
}
+ ByteString singleExtensions;
+ if (context.singleExtensions || context.includeEmptyExtensions) {
+ singleExtensions = OCSPExtensions(context.singleExtensions);
+ }
ByteString value;
value.append(certID);
value.append(certStatus);
value.append(thisUpdateEncoded);
value.append(nextUpdateEncodedNested);
+ value.append(singleExtensions);
return TLV(der::SEQUENCE, value);
}
diff --git a/lib/mozpkix/test/lib/pkixtestutil.h b/lib/mozpkix/test/lib/pkixtestutil.h
index 5d5746f73..b36f1f8ad 100644
--- a/lib/mozpkix/test/lib/pkixtestutil.h
+++ b/lib/mozpkix/test/lib/pkixtestutil.h
@@ -376,6 +376,8 @@ ByteString CreateEncodedEKUExtension(Input eku, Critical critical);
class OCSPResponseExtension final
{
public:
+ OCSPResponseExtension();
+
ByteString id;
bool critical;
ByteString value;
@@ -412,7 +414,10 @@ public:
std::time_t producedAt;
- OCSPResponseExtension* extensions;
+ // SingleResponse extensions (for the certID given in the constructor).
+ OCSPResponseExtension* singleExtensions;
+ // ResponseData extensions.
+ OCSPResponseExtension* responseExtensions;
bool includeEmptyExtensions; // If true, include the extension wrapper
// regardless of if there are any actual
// extensions.