123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995 |
- /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
- /* 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/.
- */
- #include <limits>
- #include "pkix/pkix.h"
- #include "pkix/pkixcheck.h"
- #include "pkix/pkixutil.h"
- namespace {
- const size_t SHA1_DIGEST_LENGTH = 160 / 8;
- } // namespace
- namespace mozilla { namespace pkix {
- // These values correspond to the tag values in the ASN.1 CertStatus
- enum class CertStatus : uint8_t {
- Good = der::CONTEXT_SPECIFIC | 0,
- Revoked = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1,
- Unknown = der::CONTEXT_SPECIFIC | 2
- };
- class Context final
- {
- public:
- Context(TrustDomain& aTrustDomain, const CertID& aCertID, Time aTime,
- uint16_t aMaxLifetimeInDays, /*optional out*/ Time* aThisUpdate,
- /*optional out*/ Time* aValidThrough)
- : trustDomain(aTrustDomain)
- , certID(aCertID)
- , time(aTime)
- , maxLifetimeInDays(aMaxLifetimeInDays)
- , certStatus(CertStatus::Unknown)
- , thisUpdate(aThisUpdate)
- , validThrough(aValidThrough)
- , expired(false)
- , matchFound(false)
- {
- if (thisUpdate) {
- *thisUpdate = TimeFromElapsedSecondsAD(0);
- }
- if (validThrough) {
- *validThrough = TimeFromElapsedSecondsAD(0);
- }
- }
- TrustDomain& trustDomain;
- const CertID& certID;
- const Time time;
- const uint16_t maxLifetimeInDays;
- CertStatus certStatus;
- Time* thisUpdate;
- 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
- // indicate a server failure in those cases.
- bool matchFound;
- Context(const Context&) = delete;
- void operator=(const Context&) = delete;
- };
- // Verify that potentialSigner is a valid delegated OCSP response signing cert
- // according to RFC 6960 section 4.2.2.2.
- static Result
- CheckOCSPResponseSignerCert(TrustDomain& trustDomain,
- BackCert& potentialSigner,
- Input issuerSubject,
- Input issuerSubjectPublicKeyInfo,
- Time time)
- {
- Result rv;
- // We don't need to do a complete verification of the signer (i.e. we don't
- // have to call BuildCertChain to verify the entire chain) because we
- // already know that the issuer is valid, since revocation checking is done
- // from the root to the parent after we've built a complete chain that we
- // know is otherwise valid. Rather, we just need to do a one-step validation
- // from potentialSigner to the issuer.
- //
- // It seems reasonable to require the KU_DIGITAL_SIGNATURE key usage on the
- // OCSP responder certificate if the OCSP responder certificate has a
- // key usage extension. However, according to bug 240456, some OCSP responder
- // certificates may have only the nonRepudiation bit set. Also, the OCSP
- // specification (RFC 6960) does not mandate any particular key usage to be
- // asserted for OCSP responde signers. Oddly, the CABForum Baseline
- // Requirements v.1.1.5 do say "If the Root CA Private Key is used for
- // signing OCSP responses, then the digitalSignature bit MUST be set."
- //
- // Note that CheckIssuerIndependentProperties processes
- // SEC_OID_OCSP_RESPONDER in the way that the OCSP specification requires us
- // to--in particular, it doesn't allow SEC_OID_OCSP_RESPONDER to be implied
- // by a missing EKU extension, unlike other EKUs.
- //
- // TODO(bug 926261): If we're validating for a policy then the policy OID we
- // are validating for should be passed to CheckIssuerIndependentProperties.
- TrustLevel unusedTrustLevel;
- rv = CheckIssuerIndependentProperties(trustDomain, potentialSigner, time,
- KeyUsage::noParticularKeyUsageRequired,
- KeyPurposeId::id_kp_OCSPSigning,
- CertPolicyId::anyPolicy, 0,
- unusedTrustLevel);
- if (rv != Success) {
- return rv;
- }
- // It is possible that there exists a certificate with the same key as the
- // issuer but with a different name, so we need to compare names
- // XXX(bug 926270) XXX(bug 1008133) XXX(bug 980163): Improve name
- // comparison.
- // TODO: needs test
- if (!InputsAreEqual(potentialSigner.GetIssuer(), issuerSubject)) {
- return Result::ERROR_OCSP_RESPONDER_CERT_INVALID;
- }
- // TODO(bug 926260): check name constraints
- rv = VerifySignedData(trustDomain, potentialSigner.GetSignedData(),
- issuerSubjectPublicKeyInfo);
- // TODO: check for revocation of the OCSP responder certificate unless no-check
- // or the caller forcing no-check. To properly support the no-check policy, we'd
- // need to enforce policy constraints from the issuerChain.
- return rv;
- }
- enum class ResponderIDType : uint8_t
- {
- byName = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1,
- byKey = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 2
- };
- static inline Result OCSPResponse(Reader&, Context&);
- static inline Result ResponseBytes(Reader&, Context&);
- static inline Result BasicResponse(Reader&, Context&);
- static inline Result ResponseData(
- Reader& tbsResponseData,
- Context& context,
- const der::SignedDataWithSignature& signedResponseData,
- const DERArray& certs);
- 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);
- // It is convention to name the function after the part of the data structure
- // we're parsing from the RFC (e.g. OCSPResponse, ResponseBytes).
- // But since we also have a C++ type called CertID, this function doesn't
- // follow the convention to prevent shadowing.
- static inline Result MatchCertID(Reader& input,
- const Context& context,
- /*out*/ bool& match);
- static Result MatchKeyHash(TrustDomain& trustDomain,
- Input issuerKeyHash,
- Input issuerSubjectPublicKeyInfo,
- /*out*/ bool& match);
- static Result KeyHash(TrustDomain& trustDomain,
- Input subjectPublicKeyInfo,
- /*out*/ uint8_t* hashBuf, size_t hashBufSize);
- static Result
- MatchResponderID(TrustDomain& trustDomain,
- ResponderIDType responderIDType,
- Input responderID,
- Input potentialSignerSubject,
- Input potentialSignerSubjectPublicKeyInfo,
- /*out*/ bool& match)
- {
- match = false;
- switch (responderIDType) {
- case ResponderIDType::byName:
- // XXX(bug 926270) XXX(bug 1008133) XXX(bug 980163): Improve name
- // comparison.
- match = InputsAreEqual(responderID, potentialSignerSubject);
- return Success;
- case ResponderIDType::byKey:
- {
- Reader input(responderID);
- Input keyHash;
- Result rv = der::ExpectTagAndGetValue(input, der::OCTET_STRING, keyHash);
- if (rv != Success) {
- return rv;
- }
- return MatchKeyHash(trustDomain, keyHash,
- potentialSignerSubjectPublicKeyInfo, match);
- }
- MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
- }
- }
- static Result
- VerifyOCSPSignedData(TrustDomain& trustDomain,
- const der::SignedDataWithSignature& signedResponseData,
- Input spki)
- {
- Result rv = VerifySignedData(trustDomain, signedResponseData, spki);
- if (rv == Result::ERROR_BAD_SIGNATURE) {
- rv = Result::ERROR_OCSP_BAD_SIGNATURE;
- }
- return rv;
- }
- // RFC 6960 section 4.2.2.2: The OCSP responder must either be the issuer of
- // the cert or it must be a delegated OCSP response signing cert directly
- // issued by the issuer. If the OCSP responder is a delegated OCSP response
- // signer, then its certificate is (probably) embedded within the OCSP
- // response and we'll need to verify that it is a valid certificate that chains
- // *directly* to issuerCert.
- static Result
- VerifySignature(Context& context, ResponderIDType responderIDType,
- Input responderID, const DERArray& certs,
- const der::SignedDataWithSignature& signedResponseData)
- {
- bool match;
- Result rv = MatchResponderID(context.trustDomain, responderIDType,
- responderID, context.certID.issuer,
- context.certID.issuerSubjectPublicKeyInfo,
- match);
- if (rv != Success) {
- return rv;
- }
- if (match) {
- return VerifyOCSPSignedData(context.trustDomain, signedResponseData,
- context.certID.issuerSubjectPublicKeyInfo);
- }
- size_t numCerts = certs.GetLength();
- for (size_t i = 0; i < numCerts; ++i) {
- BackCert cert(*certs.GetDER(i), EndEntityOrCA::MustBeEndEntity, nullptr);
- rv = cert.Init();
- if (rv != Success) {
- return rv;
- }
- rv = MatchResponderID(context.trustDomain, responderIDType, responderID,
- cert.GetSubject(), cert.GetSubjectPublicKeyInfo(),
- match);
- if (rv != Success) {
- if (IsFatalError(rv)) {
- return rv;
- }
- continue;
- }
- if (match) {
- rv = CheckOCSPResponseSignerCert(context.trustDomain, cert,
- context.certID.issuer,
- context.certID.issuerSubjectPublicKeyInfo,
- context.time);
- if (rv != Success) {
- if (IsFatalError(rv)) {
- return rv;
- }
- continue;
- }
- return VerifyOCSPSignedData(context.trustDomain, signedResponseData,
- cert.GetSubjectPublicKeyInfo());
- }
- }
- return Result::ERROR_OCSP_INVALID_SIGNING_CERT;
- }
- static inline Result
- MapBadDERToMalformedOCSPResponse(Result rv)
- {
- if (rv == Result::ERROR_BAD_DER) {
- return Result::ERROR_OCSP_MALFORMED_RESPONSE;
- }
- return rv;
- }
- Result
- VerifyEncodedOCSPResponse(TrustDomain& trustDomain, const struct CertID& certID,
- Time time, uint16_t maxOCSPLifetimeInDays,
- Input encodedResponse,
- /*out*/ bool& expired,
- /*optional out*/ Time* thisUpdate,
- /*optional out*/ Time* validThrough)
- {
- // Always initialize this to something reasonable.
- expired = false;
- Context context(trustDomain, certID, time, maxOCSPLifetimeInDays,
- thisUpdate, validThrough);
- Reader input(encodedResponse);
- Result rv = der::Nested(input, der::SEQUENCE, [&context](Reader& r) {
- return OCSPResponse(r, context);
- });
- if (rv != Success) {
- return MapBadDERToMalformedOCSPResponse(rv);
- }
- rv = der::End(input);
- if (rv != Success) {
- return MapBadDERToMalformedOCSPResponse(rv);
- }
- if (!context.matchFound) {
- return Result::ERROR_OCSP_RESPONSE_FOR_CERT_MISSING;
- }
- expired = context.expired;
- switch (context.certStatus) {
- case CertStatus::Good:
- 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;
- case CertStatus::Unknown:
- return Result::ERROR_OCSP_UNKNOWN_CERT;
- MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
- }
- }
- // OCSPResponse ::= SEQUENCE {
- // responseStatus OCSPResponseStatus,
- // responseBytes [0] EXPLICIT ResponseBytes OPTIONAL }
- //
- static inline Result
- OCSPResponse(Reader& input, Context& context)
- {
- // OCSPResponseStatus ::= ENUMERATED {
- // successful (0), -- Response has valid confirmations
- // malformedRequest (1), -- Illegal confirmation request
- // internalError (2), -- Internal error in issuer
- // tryLater (3), -- Try again later
- // -- (4) is not used
- // sigRequired (5), -- Must sign the request
- // unauthorized (6) -- Request unauthorized
- // }
- uint8_t responseStatus;
- Result rv = der::Enumerated(input, responseStatus);
- if (rv != Success) {
- return rv;
- }
- switch (responseStatus) {
- case 0: break; // successful
- case 1: return Result::ERROR_OCSP_MALFORMED_REQUEST;
- case 2: return Result::ERROR_OCSP_SERVER_ERROR;
- case 3: return Result::ERROR_OCSP_TRY_SERVER_LATER;
- case 5: return Result::ERROR_OCSP_REQUEST_NEEDS_SIG;
- case 6: return Result::ERROR_OCSP_UNAUTHORIZED_REQUEST;
- default: return Result::ERROR_OCSP_UNKNOWN_RESPONSE_STATUS;
- }
- return der::Nested(input, der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0,
- der::SEQUENCE, [&context](Reader& r) {
- return ResponseBytes(r, context);
- });
- }
- // ResponseBytes ::= SEQUENCE {
- // responseType OBJECT IDENTIFIER,
- // response OCTET STRING }
- static inline Result
- ResponseBytes(Reader& input, Context& context)
- {
- static const uint8_t id_pkix_ocsp_basic[] = {
- 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01
- };
- Result rv = der::OID(input, id_pkix_ocsp_basic);
- if (rv != Success) {
- return rv;
- }
- return der::Nested(input, der::OCTET_STRING, der::SEQUENCE,
- [&context](Reader& r) {
- return BasicResponse(r, context);
- });
- }
- // BasicOCSPResponse ::= SEQUENCE {
- // tbsResponseData ResponseData,
- // signatureAlgorithm AlgorithmIdentifier,
- // signature BIT STRING,
- // certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
- Result
- BasicResponse(Reader& input, Context& context)
- {
- Reader tbsResponseData;
- der::SignedDataWithSignature signedData;
- Result rv = der::SignedData(input, tbsResponseData, signedData);
- if (rv != Success) {
- if (rv == Result::ERROR_BAD_SIGNATURE) {
- return Result::ERROR_OCSP_BAD_SIGNATURE;
- }
- return rv;
- }
- // Parse certificates, if any
- NonOwningDERArray certs;
- if (!input.AtEnd()) {
- rv = der::Nested(input, der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0,
- der::SEQUENCE, [&certs](Reader& certsDER) -> Result {
- while (!certsDER.AtEnd()) {
- Input cert;
- Result nestedRv =
- der::ExpectTagAndGetTLV(certsDER, der::SEQUENCE, cert);
- if (nestedRv != Success) {
- return nestedRv;
- }
- nestedRv = certs.Append(cert);
- if (nestedRv != Success) {
- return Result::ERROR_BAD_DER; // Too many certs
- }
- }
- return Success;
- });
- if (rv != Success) {
- return rv;
- }
- }
- return ResponseData(tbsResponseData, context, signedData, certs);
- }
- // ResponseData ::= SEQUENCE {
- // version [0] EXPLICIT Version DEFAULT v1,
- // responderID ResponderID,
- // producedAt GeneralizedTime,
- // responses SEQUENCE OF SingleResponse,
- // responseExtensions [1] EXPLICIT Extensions OPTIONAL }
- static inline Result
- ResponseData(Reader& input, Context& context,
- const der::SignedDataWithSignature& signedResponseData,
- const DERArray& certs)
- {
- der::Version version;
- Result rv = der::OptionalVersion(input, version);
- if (rv != Success) {
- return rv;
- }
- if (version != der::Version::v1) {
- // TODO: more specific error code for bad version?
- return Result::ERROR_BAD_DER;
- }
- // ResponderID ::= CHOICE {
- // byName [1] Name,
- // byKey [2] KeyHash }
- Input responderID;
- ResponderIDType responderIDType
- = input.Peek(static_cast<uint8_t>(ResponderIDType::byName))
- ? ResponderIDType::byName
- : ResponderIDType::byKey;
- rv = der::ExpectTagAndGetValue(input, static_cast<uint8_t>(responderIDType),
- responderID);
- if (rv != Success) {
- return rv;
- }
- // This is the soonest we can verify the signature. We verify the signature
- // right away to follow the principal of minimizing the processing of data
- // before verifying its signature.
- rv = VerifySignature(context, responderIDType, responderID, certs,
- signedResponseData);
- if (rv != Success) {
- return rv;
- }
- // TODO: Do we even need to parse this? Should we just skip it?
- Time producedAt(Time::uninitialized);
- rv = der::GeneralizedTime(input, producedAt);
- if (rv != Success) {
- return rv;
- }
- // We don't accept an empty sequence of responses. In practice, a legit OCSP
- // responder will never return an empty response, and handling the case of an
- // empty response makes things unnecessarily complicated.
- rv = der::NestedOf(input, der::SEQUENCE, der::SEQUENCE,
- der::EmptyAllowed::No, [&context](Reader& r) {
- return SingleResponse(r, context);
- });
- if (rv != Success) {
- return rv;
- }
- return der::OptionalExtensions(input,
- der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1,
- ExtensionNotUnderstood);
- }
- // SingleResponse ::= SEQUENCE {
- // certID CertID,
- // certStatus CertStatus,
- // thisUpdate GeneralizedTime,
- // nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL,
- // singleExtensions [1] EXPLICIT Extensions{{re-ocsp-crl |
- // re-ocsp-archive-cutoff |
- // CrlEntryExtensions, ...}
- // } OPTIONAL }
- static inline Result
- SingleResponse(Reader& input, Context& context)
- {
- bool match = false;
- Result rv = der::Nested(input, der::SEQUENCE, [&context, &match](Reader& r) {
- return MatchCertID(r, context, match);
- });
- if (rv != Success) {
- return rv;
- }
- if (!match) {
- // This response does not reference the certificate we're interested in.
- // By consuming the rest of our input and returning successfully, we can
- // continue processing and examine another response that might have what
- // we want.
- input.SkipToEnd();
- return Success;
- }
- // We found a response for the cert we're interested in.
- context.matchFound = true;
- // CertStatus ::= CHOICE {
- // good [0] IMPLICIT NULL,
- // revoked [1] IMPLICIT RevokedInfo,
- // unknown [2] IMPLICIT UnknownInfo }
- //
- // In the event of multiple SingleResponses for a cert that have conflicting
- // statuses, we use the following precedence rules:
- //
- // * revoked overrides good and unknown
- // * good overrides unknown
- if (input.Peek(static_cast<uint8_t>(CertStatus::Good))) {
- rv = der::ExpectTagAndEmptyValue(input,
- static_cast<uint8_t>(CertStatus::Good));
- if (rv != Success) {
- return rv;
- }
- if (context.certStatus != CertStatus::Revoked) {
- context.certStatus = CertStatus::Good;
- }
- } else if (input.Peek(static_cast<uint8_t>(CertStatus::Revoked))) {
- // We don't need any info from the RevokedInfo structure, so we don't even
- // parse it. TODO: We should mention issues like this in the explanation of
- // why we treat invalid OCSP responses equivalently to revoked for OCSP
- // stapling.
- rv = der::ExpectTagAndSkipValue(input,
- static_cast<uint8_t>(CertStatus::Revoked));
- if (rv != Success) {
- return rv;
- }
- context.certStatus = CertStatus::Revoked;
- } else {
- rv = der::ExpectTagAndEmptyValue(input,
- static_cast<uint8_t>(CertStatus::Unknown));
- if (rv != Success) {
- return rv;
- }
- }
- // http://tools.ietf.org/html/rfc6960#section-3.2
- // 5. The time at which the status being indicated is known to be
- // correct (thisUpdate) is sufficiently recent;
- // 6. When available, the time at or before which newer information will
- // be available about the status of the certificate (nextUpdate) is
- // greater than the current time.
- Time thisUpdate(Time::uninitialized);
- rv = der::GeneralizedTime(input, thisUpdate);
- if (rv != Success) {
- return rv;
- }
- static const uint64_t SLOP_SECONDS = Time::ONE_DAY_IN_SECONDS;
- Time timePlusSlop(context.time);
- rv = timePlusSlop.AddSeconds(SLOP_SECONDS);
- if (rv != Success) {
- return rv;
- }
- if (thisUpdate > timePlusSlop) {
- return Result::ERROR_OCSP_FUTURE_RESPONSE;
- }
- Time notAfter(Time::uninitialized);
- static const uint8_t NEXT_UPDATE_TAG =
- der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0;
- if (input.Peek(NEXT_UPDATE_TAG)) {
- Time nextUpdate(Time::uninitialized);
- rv = der::Nested(input, NEXT_UPDATE_TAG, [&nextUpdate](Reader& r) {
- return der::GeneralizedTime(r, nextUpdate);
- });
- if (rv != Success) {
- return rv;
- }
- if (nextUpdate < thisUpdate) {
- return Result::ERROR_OCSP_MALFORMED_RESPONSE;
- }
- notAfter = thisUpdate;
- if (notAfter.AddSeconds(context.maxLifetimeInDays *
- Time::ONE_DAY_IN_SECONDS) != Success) {
- // This could only happen if we're dealing with times beyond the year
- // 10,000AD.
- return Result::ERROR_OCSP_FUTURE_RESPONSE;
- }
- if (nextUpdate <= notAfter) {
- notAfter = nextUpdate;
- }
- } else {
- // NSS requires all OCSP responses without a nextUpdate to be recent.
- // Match that stricter behavior.
- notAfter = thisUpdate;
- if (notAfter.AddSeconds(Time::ONE_DAY_IN_SECONDS) != Success) {
- // This could only happen if we're dealing with times beyond the year
- // 10,000AD.
- return Result::ERROR_OCSP_FUTURE_RESPONSE;
- }
- }
- // Add some slop to hopefully handle clock-skew.
- Time notAfterPlusSlop(notAfter);
- rv = notAfterPlusSlop.AddSeconds(SLOP_SECONDS);
- if (rv != Success) {
- // This could only happen if we're dealing with times beyond the year
- // 10,000AD.
- return Result::ERROR_OCSP_FUTURE_RESPONSE;
- }
- if (context.time > notAfterPlusSlop) {
- context.expired = true;
- }
- 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;
- }
- if (context.thisUpdate) {
- *context.thisUpdate = thisUpdate;
- }
- if (context.validThrough) {
- *context.validThrough = notAfterPlusSlop;
- }
- return Success;
- }
- // CertID ::= SEQUENCE {
- // hashAlgorithm AlgorithmIdentifier,
- // issuerNameHash OCTET STRING, -- Hash of issuer's DN
- // issuerKeyHash OCTET STRING, -- Hash of issuer's public key
- // serialNumber CertificateSerialNumber }
- static inline Result
- MatchCertID(Reader& input, const Context& context, /*out*/ bool& match)
- {
- match = false;
- DigestAlgorithm hashAlgorithm;
- Result rv = der::DigestAlgorithmIdentifier(input, hashAlgorithm);
- if (rv != Success) {
- if (rv == Result::ERROR_INVALID_ALGORITHM) {
- // Skip entries that are hashed with algorithms we don't support.
- input.SkipToEnd();
- return Success;
- }
- return rv;
- }
- Input issuerNameHash;
- rv = der::ExpectTagAndGetValue(input, der::OCTET_STRING, issuerNameHash);
- if (rv != Success) {
- return rv;
- }
- Input issuerKeyHash;
- rv = der::ExpectTagAndGetValue(input, der::OCTET_STRING, issuerKeyHash);
- if (rv != Success) {
- return rv;
- }
- Input serialNumber;
- rv = der::CertificateSerialNumber(input, serialNumber);
- if (rv != Success) {
- return rv;
- }
- if (!InputsAreEqual(serialNumber, context.certID.serialNumber)) {
- // This does not reference the certificate we're interested in.
- // Consume the rest of the input and return successfully to
- // potentially continue processing other responses.
- input.SkipToEnd();
- return Success;
- }
- // TODO: support SHA-2 hashes.
- if (hashAlgorithm != DigestAlgorithm::sha1) {
- // Again, not interested in this response. Consume input, return success.
- input.SkipToEnd();
- return Success;
- }
- if (issuerNameHash.GetLength() != SHA1_DIGEST_LENGTH) {
- return Result::ERROR_OCSP_MALFORMED_RESPONSE;
- }
- // From http://tools.ietf.org/html/rfc6960#section-4.1.1:
- // "The hash shall be calculated over the DER encoding of the
- // issuer's name field in the certificate being checked."
- uint8_t hashBuf[SHA1_DIGEST_LENGTH];
- rv = context.trustDomain.DigestBuf(context.certID.issuer,
- DigestAlgorithm::sha1, hashBuf,
- sizeof(hashBuf));
- if (rv != Success) {
- return rv;
- }
- Input computed(hashBuf);
- if (!InputsAreEqual(computed, issuerNameHash)) {
- // Again, not interested in this response. Consume input, return success.
- input.SkipToEnd();
- return Success;
- }
- return MatchKeyHash(context.trustDomain, issuerKeyHash,
- context.certID.issuerSubjectPublicKeyInfo, match);
- }
- // From http://tools.ietf.org/html/rfc6960#section-4.1.1:
- // "The hash shall be calculated over the value (excluding tag and length) of
- // the subject public key field in the issuer's certificate."
- //
- // From http://tools.ietf.org/html/rfc6960#appendix-B.1:
- // KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key
- // -- (i.e., the SHA-1 hash of the value of the
- // -- BIT STRING subjectPublicKey [excluding
- // -- the tag, length, and number of unused
- // -- bits] in the responder's certificate)
- static Result
- MatchKeyHash(TrustDomain& trustDomain, Input keyHash,
- const Input subjectPublicKeyInfo, /*out*/ bool& match)
- {
- if (keyHash.GetLength() != SHA1_DIGEST_LENGTH) {
- return Result::ERROR_OCSP_MALFORMED_RESPONSE;
- }
- uint8_t hashBuf[SHA1_DIGEST_LENGTH];
- Result rv = KeyHash(trustDomain, subjectPublicKeyInfo, hashBuf,
- sizeof hashBuf);
- if (rv != Success) {
- return rv;
- }
- Input computed(hashBuf);
- match = InputsAreEqual(computed, keyHash);
- return Success;
- }
- // TODO(bug 966856): support SHA-2 hashes
- Result
- KeyHash(TrustDomain& trustDomain, const Input subjectPublicKeyInfo,
- /*out*/ uint8_t* hashBuf, size_t hashBufSize)
- {
- if (!hashBuf || hashBufSize != SHA1_DIGEST_LENGTH) {
- return Result::FATAL_ERROR_LIBRARY_FAILURE;
- }
- // RFC 5280 Section 4.1
- //
- // SubjectPublicKeyInfo ::= SEQUENCE {
- // algorithm AlgorithmIdentifier,
- // subjectPublicKey BIT STRING }
- Reader spki;
- Result rv = der::ExpectTagAndGetValueAtEnd(subjectPublicKeyInfo,
- der::SEQUENCE, spki);
- if (rv != Success) {
- return rv;
- }
- // Skip AlgorithmIdentifier
- rv = der::ExpectTagAndSkipValue(spki, der::SEQUENCE);
- if (rv != Success) {
- return rv;
- }
- Input subjectPublicKey;
- rv = der::BitStringWithNoUnusedBits(spki, subjectPublicKey);
- if (rv != Success) {
- return rv;
- }
- rv = der::End(spki);
- if (rv != Success) {
- return rv;
- }
- return trustDomain.DigestBuf(subjectPublicKey, DigestAlgorithm::sha1,
- hashBuf, hashBufSize);
- }
- Result
- ExtensionNotUnderstood(Reader& /*extnID*/, Input /*extnValue*/,
- bool /*critical*/, /*out*/ bool& understood)
- {
- understood = false;
- 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;
- // 3. The identity of the signer matches the intended recipient of the
- // request;
- // 4. The signer is currently authorized to provide a response for the
- // certificate in question;
- // 5. The time at which the status being indicated is known to be
- // correct (thisUpdate) is sufficiently recent;
- // 6. When available, the time at or before which newer information will
- // be available about the status of the certificate (nextUpdate) is
- // greater than the current time.
- //
- // Responses whose nextUpdate value is earlier than
- // the local system time value SHOULD be considered unreliable.
- // Responses whose thisUpdate time is later than the local system time
- // SHOULD be considered unreliable.
- //
- // If nextUpdate is not set, the responder is indicating that newer
- // revocation information is available all the time.
- //
- // http://tools.ietf.org/html/rfc5019#section-4
- Result
- CreateEncodedOCSPRequest(TrustDomain& trustDomain, const struct CertID& certID,
- /*out*/ uint8_t (&out)[OCSP_REQUEST_MAX_LENGTH],
- /*out*/ size_t& outLen)
- {
- // We do not add any extensions to the request.
- // RFC 6960 says "An OCSP client MAY wish to specify the kinds of response
- // types it understands. To do so, it SHOULD use an extension with the OID
- // id-pkix-ocsp-response." This use of MAY and SHOULD is unclear. MSIE11
- // on Windows 8.1 does not include any extensions, whereas NSS has always
- // included the id-pkix-ocsp-response extension. Avoiding the sending the
- // extension is better for OCSP GET because it makes the request smaller,
- // and thus more likely to fit within the 255 byte limit for OCSP GET that
- // is specified in RFC 5019 Section 5.
- // Bug 966856: Add the id-pkix-ocsp-pref-sig-algs extension.
- // Since we don't know whether the OCSP responder supports anything other
- // than SHA-1, we have no choice but to use SHA-1 for issuerNameHash and
- // issuerKeyHash.
- static const uint8_t hashAlgorithm[11] = {
- 0x30, 0x09, // SEQUENCE
- 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, // OBJECT IDENTIFIER id-sha1
- 0x05, 0x00, // NULL
- };
- static const uint8_t hashLen = 160 / 8;
- static const unsigned int totalLenWithoutSerialNumberData
- = 2 // OCSPRequest
- + 2 // tbsRequest
- + 2 // requestList
- + 2 // Request
- + 2 // reqCert (CertID)
- + sizeof(hashAlgorithm) // hashAlgorithm
- + 2 + hashLen // issuerNameHash
- + 2 + hashLen // issuerKeyHash
- + 2; // serialNumber (header)
- // The only way we could have a request this large is if the serialNumber was
- // ridiculously and unreasonably large. RFC 5280 says "Conforming CAs MUST
- // NOT use serialNumber values longer than 20 octets." With this restriction,
- // we allow for some amount of non-conformance with that requirement while
- // still ensuring we can encode the length values in the ASN.1 TLV structures
- // in a single byte.
- static_assert(totalLenWithoutSerialNumberData < OCSP_REQUEST_MAX_LENGTH,
- "totalLenWithoutSerialNumberData too big");
- if (certID.serialNumber.GetLength() >
- OCSP_REQUEST_MAX_LENGTH - totalLenWithoutSerialNumberData) {
- return Result::ERROR_BAD_DER;
- }
- outLen = totalLenWithoutSerialNumberData + certID.serialNumber.GetLength();
- uint8_t totalLen = static_cast<uint8_t>(outLen);
- uint8_t* d = out;
- *d++ = 0x30; *d++ = totalLen - 2u; // OCSPRequest (SEQUENCE)
- *d++ = 0x30; *d++ = totalLen - 4u; // tbsRequest (SEQUENCE)
- *d++ = 0x30; *d++ = totalLen - 6u; // requestList (SEQUENCE OF)
- *d++ = 0x30; *d++ = totalLen - 8u; // Request (SEQUENCE)
- *d++ = 0x30; *d++ = totalLen - 10u; // reqCert (CertID SEQUENCE)
- // reqCert.hashAlgorithm
- for (const uint8_t hashAlgorithmByte : hashAlgorithm) {
- *d++ = hashAlgorithmByte;
- }
- // reqCert.issuerNameHash (OCTET STRING)
- *d++ = 0x04;
- *d++ = hashLen;
- Result rv = trustDomain.DigestBuf(certID.issuer, DigestAlgorithm::sha1, d,
- hashLen);
- if (rv != Success) {
- return rv;
- }
- d += hashLen;
- // reqCert.issuerKeyHash (OCTET STRING)
- *d++ = 0x04;
- *d++ = hashLen;
- rv = KeyHash(trustDomain, certID.issuerSubjectPublicKeyInfo, d, hashLen);
- if (rv != Success) {
- return rv;
- }
- d += hashLen;
- // reqCert.serialNumber (INTEGER)
- *d++ = 0x02; // INTEGER
- *d++ = static_cast<uint8_t>(certID.serialNumber.GetLength());
- Reader serialNumber(certID.serialNumber);
- do {
- rv = serialNumber.Read(*d);
- if (rv != Success) {
- return rv;
- }
- ++d;
- } while (!serialNumber.AtEnd());
- assert(d == out + totalLen);
- return Success;
- }
- } } // namespace mozilla::pkix
|