michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ michael@0: /* Copyright 2013 Mozilla Foundation michael@0: * michael@0: * Licensed under the Apache License, Version 2.0 (the "License"); michael@0: * you may not use this file except in compliance with the License. michael@0: * You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: */ michael@0: michael@0: #include michael@0: michael@0: #include "pkix/bind.h" michael@0: #include "pkix/pkix.h" michael@0: #include "pkixcheck.h" michael@0: #include "pkixder.h" michael@0: michael@0: #include "hasht.h" michael@0: #include "pk11pub.h" michael@0: #include "secder.h" michael@0: michael@0: #ifdef _MSC_VER michael@0: // C4480: nonstandard extension used: specifying underlying type for enum michael@0: #define ENUM_CLASS __pragma(warning(disable: 4480)) enum michael@0: #else michael@0: #define ENUM_CLASS enum class michael@0: #endif michael@0: michael@0: // TODO: use typed/qualified typedefs everywhere? michael@0: // TODO: When should we return SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE? michael@0: michael@0: namespace mozilla { namespace pkix { michael@0: michael@0: static const PRTime ONE_DAY michael@0: = INT64_C(24) * INT64_C(60) * INT64_C(60) * PR_USEC_PER_SEC; michael@0: static const PRTime SLOP = ONE_DAY; michael@0: michael@0: // These values correspond to the tag values in the ASN.1 CertStatus michael@0: ENUM_CLASS CertStatus : uint8_t { michael@0: Good = der::CONTEXT_SPECIFIC | 0, michael@0: Revoked = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1, michael@0: Unknown = der::CONTEXT_SPECIFIC | 2 michael@0: }; michael@0: michael@0: class Context michael@0: { michael@0: public: michael@0: Context(TrustDomain& trustDomain, michael@0: const CERTCertificate& cert, michael@0: CERTCertificate& issuerCert, michael@0: PRTime time, michael@0: uint16_t maxLifetimeInDays, michael@0: PRTime* thisUpdate, michael@0: PRTime* validThrough) michael@0: : trustDomain(trustDomain) michael@0: , cert(cert) michael@0: , issuerCert(issuerCert) michael@0: , time(time) michael@0: , maxLifetimeInDays(maxLifetimeInDays) michael@0: , certStatus(CertStatus::Unknown) michael@0: , thisUpdate(thisUpdate) michael@0: , validThrough(validThrough) michael@0: , expired(false) michael@0: { michael@0: if (thisUpdate) { michael@0: *thisUpdate = 0; michael@0: } michael@0: if (validThrough) { michael@0: *validThrough = 0; michael@0: } michael@0: } michael@0: michael@0: TrustDomain& trustDomain; michael@0: const CERTCertificate& cert; michael@0: CERTCertificate& issuerCert; michael@0: const PRTime time; michael@0: const uint16_t maxLifetimeInDays; michael@0: CertStatus certStatus; michael@0: PRTime* thisUpdate; michael@0: PRTime* validThrough; michael@0: bool expired; michael@0: michael@0: private: michael@0: Context(const Context&); // delete michael@0: void operator=(const Context&); // delete michael@0: }; michael@0: michael@0: // Verify that potentialSigner is a valid delegated OCSP response signing cert michael@0: // according to RFC 6960 section 4.2.2.2. michael@0: static Result michael@0: CheckOCSPResponseSignerCert(TrustDomain& trustDomain, michael@0: CERTCertificate& potentialSigner, michael@0: const CERTCertificate& issuerCert, PRTime time) michael@0: { michael@0: Result rv; michael@0: michael@0: BackCert cert(&potentialSigner, nullptr, BackCert::ExcludeCN); michael@0: rv = cert.Init(); michael@0: if (rv != Success) { michael@0: return rv; michael@0: } michael@0: michael@0: // We don't need to do a complete verification of the signer (i.e. we don't michael@0: // have to call BuildCertChain to verify the entire chain) because we michael@0: // already know that the issuerCert is valid, since revocation checking is michael@0: // done from the root to the parent after we've built a complete chain that michael@0: // we know is otherwise valid. Rather, we just need to do a one-step michael@0: // validation from potentialSigner to issuerCert. michael@0: // michael@0: // It seems reasonable to require the KU_DIGITAL_SIGNATURE key usage on the michael@0: // OCSP responder certificate if the OCSP responder certificate has a michael@0: // key usage extension. However, according to bug 240456, some OCSP responder michael@0: // certificates may have only the nonRepudiation bit set. Also, the OCSP michael@0: // specification (RFC 6960) does not mandate any particular key usage to be michael@0: // asserted for OCSP responde signers. Oddly, the CABForum Baseline michael@0: // Requirements v.1.1.5 do say "If the Root CA Private Key is used for michael@0: // signing OCSP responses, then the digitalSignature bit MUST be set." michael@0: // michael@0: // Note that CheckIssuerIndependentProperties processes michael@0: // SEC_OID_OCSP_RESPONDER in the way that the OCSP specification requires us michael@0: // to--in particular, it doesn't allow SEC_OID_OCSP_RESPONDER to be implied michael@0: // by a missing EKU extension, unlike other EKUs. michael@0: // michael@0: // TODO(bug 926261): If we're validating for a policy then the policy OID we michael@0: // are validating for should be passed to CheckIssuerIndependentProperties. michael@0: rv = CheckIssuerIndependentProperties(trustDomain, cert, time, michael@0: MustBeEndEntity, michael@0: KeyUsage::noParticularKeyUsageRequired, michael@0: SEC_OID_OCSP_RESPONDER, michael@0: SEC_OID_X509_ANY_POLICY, 0); michael@0: if (rv != Success) { michael@0: return rv; michael@0: } michael@0: michael@0: // It is possible that there exists a certificate with the same key as the michael@0: // issuer but with a different name, so we need to compare names michael@0: // TODO: needs test michael@0: if (!SECITEM_ItemsAreEqual(&cert.GetNSSCert()->derIssuer, michael@0: &issuerCert.derSubject) && michael@0: CERT_CompareName(&cert.GetNSSCert()->issuer, michael@0: &issuerCert.subject) != SECEqual) { michael@0: return Fail(RecoverableError, SEC_ERROR_OCSP_RESPONDER_CERT_INVALID); michael@0: } michael@0: michael@0: // TODO(bug 926260): check name constraints michael@0: michael@0: if (trustDomain.VerifySignedData(&potentialSigner.signatureWrap, michael@0: &issuerCert) != SECSuccess) { michael@0: return MapSECStatus(SECFailure); michael@0: } michael@0: michael@0: // TODO: check for revocation of the OCSP responder certificate unless no-check michael@0: // or the caller forcing no-check. To properly support the no-check policy, we'd michael@0: // need to enforce policy constraints from the issuerChain. michael@0: michael@0: return Success; michael@0: } michael@0: michael@0: //typedef enum { michael@0: // ocspResponderID_byName = 1, michael@0: // ocspResponderID_byKey = 2 michael@0: //} ResponderIDType; michael@0: michael@0: ENUM_CLASS ResponderIDType : uint8_t michael@0: { michael@0: byName = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1, michael@0: byKey = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 2 michael@0: }; michael@0: michael@0: static inline der::Result OCSPResponse(der::Input&, Context&); michael@0: static inline der::Result ResponseBytes(der::Input&, Context&); michael@0: static inline der::Result BasicResponse(der::Input&, Context&); michael@0: static inline der::Result ResponseData( michael@0: der::Input& tbsResponseData, Context& context, michael@0: const CERTSignedData& signedResponseData, michael@0: /*const*/ SECItem* certs, size_t numCerts); michael@0: static inline der::Result SingleResponse(der::Input& input, michael@0: Context& context); michael@0: static inline der::Result CheckExtensionsForCriticality(der::Input&); michael@0: static inline der::Result CertID(der::Input& input, michael@0: const Context& context, michael@0: /*out*/ bool& match); michael@0: static der::Result MatchIssuerKey(const SECItem& issuerKeyHash, michael@0: const CERTCertificate& issuer, michael@0: /*out*/ bool& match); michael@0: michael@0: // RFC 6960 section 4.2.2.2: The OCSP responder must either be the issuer of michael@0: // the cert or it must be a delegated OCSP response signing cert directly michael@0: // issued by the issuer. If the OCSP responder is a delegated OCSP response michael@0: // signer, then its certificate is (probably) embedded within the OCSP michael@0: // response and we'll need to verify that it is a valid certificate that chains michael@0: // *directly* to issuerCert. michael@0: static CERTCertificate* michael@0: GetOCSPSignerCertificate(TrustDomain& trustDomain, michael@0: ResponderIDType responderIDType, michael@0: const SECItem& responderIDItem, michael@0: const SECItem* certs, size_t numCerts, michael@0: CERTCertificate& issuerCert, PRTime time) michael@0: { michael@0: bool isIssuer = true; michael@0: size_t i = 0; michael@0: for (;;) { michael@0: ScopedCERTCertificate potentialSigner; michael@0: if (isIssuer) { michael@0: potentialSigner = CERT_DupCertificate(&issuerCert); michael@0: } else if (i < numCerts) { michael@0: potentialSigner = CERT_NewTempCertificate( michael@0: CERT_GetDefaultCertDB(), michael@0: /*TODO*/const_cast(&certs[i]), nullptr, michael@0: false, false); michael@0: if (!potentialSigner) { michael@0: return nullptr; michael@0: } michael@0: ++i; michael@0: } else { michael@0: PR_SetError(SEC_ERROR_OCSP_INVALID_SIGNING_CERT, 0); michael@0: return nullptr; michael@0: } michael@0: michael@0: bool match; michael@0: switch (responderIDType) { michael@0: case ResponderIDType::byName: michael@0: // The CA is very likely to have encoded the name in the OCSP response michael@0: // exactly the same as the name is encoded in the signing certificate. michael@0: // Consequently, most of the time we will avoid parsing the name michael@0: // completely. We're assuming here that the signer's subject name is michael@0: // correctly formatted. michael@0: // TODO: need test for exact name michael@0: // TODO: need test for non-exact name match michael@0: match = SECITEM_ItemsAreEqual(&responderIDItem, michael@0: &potentialSigner->derSubject); michael@0: if (!match) { michael@0: ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE)); michael@0: if (!arena) { michael@0: return nullptr; michael@0: } michael@0: CERTName name; michael@0: if (SEC_QuickDERDecodeItem(arena.get(), &name, michael@0: SEC_ASN1_GET(CERT_NameTemplate), michael@0: &responderIDItem) != SECSuccess) { michael@0: return nullptr; michael@0: } michael@0: match = CERT_CompareName(&name, &potentialSigner->subject) == SECEqual; michael@0: } michael@0: break; michael@0: michael@0: case ResponderIDType::byKey: michael@0: { michael@0: der::Input responderID; michael@0: if (responderID.Init(responderIDItem.data, responderIDItem.len) michael@0: != der::Success) { michael@0: return nullptr; michael@0: } michael@0: SECItem issuerKeyHash; michael@0: if (der::Skip(responderID, der::OCTET_STRING, issuerKeyHash) != der::Success) { michael@0: return nullptr; michael@0: } michael@0: if (MatchIssuerKey(issuerKeyHash, *potentialSigner.get(), match) michael@0: != der::Success) { michael@0: return nullptr; michael@0: } michael@0: break; michael@0: } michael@0: michael@0: default: michael@0: PR_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE, 0); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (match && !isIssuer) { michael@0: Result rv = CheckOCSPResponseSignerCert(trustDomain, michael@0: *potentialSigner.get(), michael@0: issuerCert, time); michael@0: if (rv == RecoverableError) { michael@0: match = false; michael@0: } else if (rv != Success) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: if (match) { michael@0: return potentialSigner.release(); michael@0: } michael@0: michael@0: isIssuer = false; michael@0: } michael@0: } michael@0: michael@0: static SECStatus michael@0: VerifySignature(Context& context, ResponderIDType responderIDType, michael@0: const SECItem& responderID, const SECItem* certs, michael@0: size_t numCerts, const CERTSignedData& signedResponseData) michael@0: { michael@0: ScopedCERTCertificate signer( michael@0: GetOCSPSignerCertificate(context.trustDomain, responderIDType, responderID, michael@0: certs, numCerts, context.issuerCert, michael@0: context.time)); michael@0: if (!signer) { michael@0: return SECFailure; michael@0: } michael@0: michael@0: if (context.trustDomain.VerifySignedData(&signedResponseData, signer.get()) michael@0: != SECSuccess) { michael@0: if (PR_GetError() == SEC_ERROR_BAD_SIGNATURE) { michael@0: PR_SetError(SEC_ERROR_OCSP_BAD_SIGNATURE, 0); michael@0: } michael@0: return SECFailure; michael@0: } michael@0: michael@0: return SECSuccess; michael@0: } michael@0: michael@0: static inline void michael@0: SetErrorToMalformedResponseOnBadDERError() michael@0: { michael@0: if (PR_GetError() == SEC_ERROR_BAD_DER) { michael@0: PR_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE, 0); michael@0: } michael@0: } michael@0: michael@0: SECStatus michael@0: VerifyEncodedOCSPResponse(TrustDomain& trustDomain, michael@0: const CERTCertificate* cert, michael@0: CERTCertificate* issuerCert, PRTime time, michael@0: uint16_t maxOCSPLifetimeInDays, michael@0: const SECItem* encodedResponse, michael@0: bool& expired, michael@0: PRTime* thisUpdate, michael@0: PRTime* validThrough) michael@0: { michael@0: PR_ASSERT(cert); michael@0: PR_ASSERT(issuerCert); michael@0: // TODO: PR_Assert(pinArg) michael@0: PR_ASSERT(encodedResponse); michael@0: if (!cert || !issuerCert || !encodedResponse || !encodedResponse->data) { michael@0: PR_SetError(SEC_ERROR_INVALID_ARGS, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: // Always initialize this to something reasonable. michael@0: expired = false; michael@0: michael@0: der::Input input; michael@0: if (input.Init(encodedResponse->data, encodedResponse->len) != der::Success) { michael@0: SetErrorToMalformedResponseOnBadDERError(); michael@0: return SECFailure; michael@0: } michael@0: Context context(trustDomain, *cert, *issuerCert, time, maxOCSPLifetimeInDays, michael@0: thisUpdate, validThrough); michael@0: michael@0: if (der::Nested(input, der::SEQUENCE, michael@0: bind(OCSPResponse, _1, ref(context))) != der::Success) { michael@0: SetErrorToMalformedResponseOnBadDERError(); michael@0: return SECFailure; michael@0: } michael@0: michael@0: if (der::End(input) != der::Success) { michael@0: SetErrorToMalformedResponseOnBadDERError(); michael@0: return SECFailure; michael@0: } michael@0: michael@0: expired = context.expired; michael@0: michael@0: switch (context.certStatus) { michael@0: case CertStatus::Good: michael@0: if (expired) { michael@0: PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0); michael@0: return SECFailure; michael@0: } michael@0: return SECSuccess; michael@0: case CertStatus::Revoked: michael@0: PR_SetError(SEC_ERROR_REVOKED_CERTIFICATE, 0); michael@0: return SECFailure; michael@0: case CertStatus::Unknown: michael@0: PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: PR_NOT_REACHED("unknown CertStatus"); michael@0: PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: // OCSPResponse ::= SEQUENCE { michael@0: // responseStatus OCSPResponseStatus, michael@0: // responseBytes [0] EXPLICIT ResponseBytes OPTIONAL } michael@0: // michael@0: static inline der::Result michael@0: OCSPResponse(der::Input& input, Context& context) michael@0: { michael@0: // OCSPResponseStatus ::= ENUMERATED { michael@0: // successful (0), -- Response has valid confirmations michael@0: // malformedRequest (1), -- Illegal confirmation request michael@0: // internalError (2), -- Internal error in issuer michael@0: // tryLater (3), -- Try again later michael@0: // -- (4) is not used michael@0: // sigRequired (5), -- Must sign the request michael@0: // unauthorized (6) -- Request unauthorized michael@0: // } michael@0: uint8_t responseStatus; michael@0: michael@0: if (der::Enumerated(input, responseStatus) != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: switch (responseStatus) { michael@0: case 0: break; // successful michael@0: case 1: return der::Fail(SEC_ERROR_OCSP_MALFORMED_REQUEST); michael@0: case 2: return der::Fail(SEC_ERROR_OCSP_SERVER_ERROR); michael@0: case 3: return der::Fail(SEC_ERROR_OCSP_TRY_SERVER_LATER); michael@0: case 5: return der::Fail(SEC_ERROR_OCSP_REQUEST_NEEDS_SIG); michael@0: case 6: return der::Fail(SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST); michael@0: default: return der::Fail(SEC_ERROR_OCSP_UNKNOWN_RESPONSE_STATUS); michael@0: } michael@0: michael@0: return der::Nested(input, der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0, michael@0: der::SEQUENCE, bind(ResponseBytes, _1, ref(context))); michael@0: } michael@0: michael@0: // ResponseBytes ::= SEQUENCE { michael@0: // responseType OBJECT IDENTIFIER, michael@0: // response OCTET STRING } michael@0: static inline der::Result michael@0: ResponseBytes(der::Input& input, Context& context) michael@0: { michael@0: static const uint8_t id_pkix_ocsp_basic[] = { michael@0: 0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01 michael@0: }; michael@0: michael@0: if (der::OID(input, id_pkix_ocsp_basic) != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: michael@0: return der::Nested(input, der::OCTET_STRING, der::SEQUENCE, michael@0: bind(BasicResponse, _1, ref(context))); michael@0: } michael@0: michael@0: // BasicOCSPResponse ::= SEQUENCE { michael@0: // tbsResponseData ResponseData, michael@0: // signatureAlgorithm AlgorithmIdentifier, michael@0: // signature BIT STRING, michael@0: // certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL } michael@0: der::Result michael@0: BasicResponse(der::Input& input, Context& context) michael@0: { michael@0: der::Input::Mark mark(input.GetMark()); michael@0: michael@0: uint16_t length; michael@0: if (der::ExpectTagAndGetLength(input, der::SEQUENCE, length) michael@0: != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: michael@0: // The signature covers the entire DER encoding of tbsResponseData, including michael@0: // the beginning tag and length. However, when we're parsing tbsResponseData, michael@0: // we want to strip off the tag and length because we don't need it after michael@0: // we've confirmed it's there and figured out what length it is. michael@0: michael@0: der::Input tbsResponseData; michael@0: michael@0: if (input.Skip(length, tbsResponseData) != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: michael@0: CERTSignedData signedData; michael@0: michael@0: input.GetSECItem(siBuffer, mark, signedData.data); michael@0: michael@0: if (der::Nested(input, der::SEQUENCE, michael@0: bind(der::AlgorithmIdentifier, _1, michael@0: ref(signedData.signatureAlgorithm))) != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: michael@0: if (der::Skip(input, der::BIT_STRING, signedData.signature) != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: if (signedData.signature.len == 0) { michael@0: return der::Fail(SEC_ERROR_OCSP_BAD_SIGNATURE); michael@0: } michael@0: unsigned int unusedBitsAtEnd = signedData.signature.data[0]; michael@0: // XXX: Really the constraint should be that unusedBitsAtEnd must be less michael@0: // than 7. But, we suspect there are no valid OCSP response signatures with michael@0: // non-zero unused bits. It seems like NSS assumes this in various places, so michael@0: // we enforce it. If we find compatibility issues, we'll know we're wrong. michael@0: if (unusedBitsAtEnd != 0) { michael@0: return der::Fail(SEC_ERROR_OCSP_BAD_SIGNATURE); michael@0: } michael@0: ++signedData.signature.data; michael@0: --signedData.signature.len; michael@0: signedData.signature.len = (signedData.signature.len << 3); // Bytes to bits michael@0: michael@0: // Parse certificates, if any michael@0: michael@0: SECItem certs[8]; michael@0: size_t numCerts = 0; michael@0: michael@0: if (!input.AtEnd()) { michael@0: // We ignore the lengths of the wrappers because we'll detect bad lengths michael@0: // during parsing--too short and we'll run out of input for parsing a cert, michael@0: // and too long and we'll have leftover data that won't parse as a cert. michael@0: michael@0: // [0] wrapper michael@0: if (der::ExpectTagAndIgnoreLength( michael@0: input, der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 0) michael@0: != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: michael@0: // SEQUENCE wrapper michael@0: if (der::ExpectTagAndIgnoreLength(input, der::SEQUENCE) != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: michael@0: // sequence of certificates michael@0: while (!input.AtEnd()) { michael@0: if (numCerts == PR_ARRAY_SIZE(certs)) { michael@0: return der::Fail(SEC_ERROR_BAD_DER); michael@0: } michael@0: michael@0: // Unwrap the SEQUENCE that contains the certificate, which is itself a michael@0: // SEQUENCE. michael@0: der::Input::Mark mark(input.GetMark()); michael@0: if (der::Skip(input, der::SEQUENCE) != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: michael@0: input.GetSECItem(siBuffer, mark, certs[numCerts]); michael@0: ++numCerts; michael@0: } michael@0: } michael@0: michael@0: return ResponseData(tbsResponseData, context, signedData, certs, numCerts); michael@0: } michael@0: michael@0: // ResponseData ::= SEQUENCE { michael@0: // version [0] EXPLICIT Version DEFAULT v1, michael@0: // responderID ResponderID, michael@0: // producedAt GeneralizedTime, michael@0: // responses SEQUENCE OF SingleResponse, michael@0: // responseExtensions [1] EXPLICIT Extensions OPTIONAL } michael@0: static inline der::Result michael@0: ResponseData(der::Input& input, Context& context, michael@0: const CERTSignedData& signedResponseData, michael@0: /*const*/ SECItem* certs, size_t numCerts) michael@0: { michael@0: uint8_t version; michael@0: if (der::OptionalVersion(input, version) != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: if (version != der::v1) { michael@0: // TODO: more specific error code for bad version? michael@0: return der::Fail(SEC_ERROR_BAD_DER); michael@0: } michael@0: michael@0: // ResponderID ::= CHOICE { michael@0: // byName [1] Name, michael@0: // byKey [2] KeyHash } michael@0: SECItem responderID; michael@0: uint16_t responderIDLength; michael@0: ResponderIDType responderIDType michael@0: = input.Peek(static_cast(ResponderIDType::byName)) michael@0: ? ResponderIDType::byName michael@0: : ResponderIDType::byKey; michael@0: if (ExpectTagAndGetLength(input, static_cast(responderIDType), michael@0: responderIDLength) != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: // TODO: responderID probably needs to have another level of ASN1 tag/length michael@0: // checked and stripped. michael@0: if (input.Skip(responderIDLength, responderID) != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: michael@0: // This is the soonest we can verify the signature. We verify the signature michael@0: // right away to follow the principal of minimizing the processing of data michael@0: // before verifying its signature. michael@0: if (VerifySignature(context, responderIDType, responderID, certs, numCerts, michael@0: signedResponseData) != SECSuccess) { michael@0: return der::Failure; michael@0: } michael@0: michael@0: // TODO: Do we even need to parse this? Should we just skip it? michael@0: PRTime producedAt; michael@0: if (der::GeneralizedTime(input, producedAt) != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: michael@0: // We don't accept an empty sequence of responses. In practice, a legit OCSP michael@0: // responder will never return an empty response, and handling the case of an michael@0: // empty response makes things unnecessarily complicated. michael@0: if (der::NestedOf(input, der::SEQUENCE, der::SEQUENCE, michael@0: der::MustNotBeEmpty, michael@0: bind(SingleResponse, _1, ref(context))) != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: michael@0: if (!input.AtEnd()) { michael@0: if (der::Nested(input, der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1, michael@0: CheckExtensionsForCriticality) != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: } michael@0: michael@0: return der::Success; michael@0: } michael@0: michael@0: // SingleResponse ::= SEQUENCE { michael@0: // certID CertID, michael@0: // certStatus CertStatus, michael@0: // thisUpdate GeneralizedTime, michael@0: // nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL, michael@0: // singleExtensions [1] EXPLICIT Extensions{{re-ocsp-crl | michael@0: // re-ocsp-archive-cutoff | michael@0: // CrlEntryExtensions, ...} michael@0: // } OPTIONAL } michael@0: static inline der::Result michael@0: SingleResponse(der::Input& input, Context& context) michael@0: { michael@0: bool match = false; michael@0: if (der::Nested(input, der::SEQUENCE, michael@0: bind(CertID, _1, cref(context), ref(match))) michael@0: != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: michael@0: if (!match) { michael@0: // This response does not reference the certificate we're interested in. michael@0: // By consuming the rest of our input and returning successfully, we can michael@0: // continue processing and examine another response that might have what michael@0: // we want. michael@0: input.SkipToEnd(); michael@0: return der::Success; michael@0: } michael@0: michael@0: // CertStatus ::= CHOICE { michael@0: // good [0] IMPLICIT NULL, michael@0: // revoked [1] IMPLICIT RevokedInfo, michael@0: // unknown [2] IMPLICIT UnknownInfo } michael@0: // michael@0: // In the event of multiple SingleResponses for a cert that have conflicting michael@0: // statuses, we use the following precedence rules: michael@0: // michael@0: // * revoked overrides good and unknown michael@0: // * good overrides unknown michael@0: if (input.Peek(static_cast(CertStatus::Good))) { michael@0: if (ExpectTagAndLength(input, static_cast(CertStatus::Good), 0) michael@0: != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: if (context.certStatus != CertStatus::Revoked) { michael@0: context.certStatus = CertStatus::Good; michael@0: } michael@0: } else if (input.Peek(static_cast(CertStatus::Revoked))) { michael@0: // We don't need any info from the RevokedInfo structure, so we don't even michael@0: // parse it. TODO: We should mention issues like this in the explanation of michael@0: // why we treat invalid OCSP responses equivalently to revoked for OCSP michael@0: // stapling. michael@0: if (der::Skip(input, static_cast(CertStatus::Revoked)) michael@0: != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: context.certStatus = CertStatus::Revoked; michael@0: } else if (ExpectTagAndLength(input, michael@0: static_cast(CertStatus::Unknown), michael@0: 0) != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: michael@0: // http://tools.ietf.org/html/rfc6960#section-3.2 michael@0: // 5. The time at which the status being indicated is known to be michael@0: // correct (thisUpdate) is sufficiently recent; michael@0: // 6. When available, the time at or before which newer information will michael@0: // be available about the status of the certificate (nextUpdate) is michael@0: // greater than the current time. michael@0: michael@0: const PRTime maxLifetime = michael@0: context.maxLifetimeInDays * ONE_DAY; michael@0: michael@0: PRTime thisUpdate; michael@0: if (der::GeneralizedTime(input, thisUpdate) != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: michael@0: if (thisUpdate > context.time + SLOP) { michael@0: return der::Fail(SEC_ERROR_OCSP_FUTURE_RESPONSE); michael@0: } michael@0: michael@0: PRTime notAfter; michael@0: static const uint8_t NEXT_UPDATE_TAG = michael@0: der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0; michael@0: if (input.Peek(NEXT_UPDATE_TAG)) { michael@0: PRTime nextUpdate; michael@0: if (der::Nested(input, NEXT_UPDATE_TAG, michael@0: bind(der::GeneralizedTime, _1, ref(nextUpdate))) michael@0: != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: michael@0: if (nextUpdate < thisUpdate) { michael@0: return der::Fail(SEC_ERROR_OCSP_MALFORMED_RESPONSE); michael@0: } michael@0: if (nextUpdate - thisUpdate <= maxLifetime) { michael@0: notAfter = nextUpdate; michael@0: } else { michael@0: notAfter = thisUpdate + maxLifetime; michael@0: } michael@0: } else { michael@0: // NSS requires all OCSP responses without a nextUpdate to be recent. michael@0: // Match that stricter behavior. michael@0: notAfter = thisUpdate + ONE_DAY; michael@0: } michael@0: michael@0: if (context.time < SLOP) { // prevent underflow michael@0: return der::Fail(SEC_ERROR_INVALID_ARGS); michael@0: } michael@0: michael@0: if (context.time - SLOP > notAfter) { michael@0: context.expired = true; michael@0: } michael@0: michael@0: if (!input.AtEnd()) { michael@0: if (der::Nested(input, der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1, michael@0: CheckExtensionsForCriticality) != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: } michael@0: michael@0: if (context.thisUpdate) { michael@0: *context.thisUpdate = thisUpdate; michael@0: } michael@0: if (context.validThrough) { michael@0: *context.validThrough = notAfter; michael@0: } michael@0: michael@0: return der::Success; michael@0: } michael@0: michael@0: // CertID ::= SEQUENCE { michael@0: // hashAlgorithm AlgorithmIdentifier, michael@0: // issuerNameHash OCTET STRING, -- Hash of issuer's DN michael@0: // issuerKeyHash OCTET STRING, -- Hash of issuer's public key michael@0: // serialNumber CertificateSerialNumber } michael@0: static inline der::Result michael@0: CertID(der::Input& input, const Context& context, /*out*/ bool& match) michael@0: { michael@0: match = false; michael@0: michael@0: SECAlgorithmID hashAlgorithm; michael@0: if (der::Nested(input, der::SEQUENCE, michael@0: bind(der::AlgorithmIdentifier, _1, ref(hashAlgorithm))) michael@0: != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: michael@0: SECItem issuerNameHash; michael@0: if (der::Skip(input, der::OCTET_STRING, issuerNameHash) != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: michael@0: SECItem issuerKeyHash; michael@0: if (der::Skip(input, der::OCTET_STRING, issuerKeyHash) != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: michael@0: SECItem serialNumber; michael@0: if (der::CertificateSerialNumber(input, serialNumber) != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: michael@0: const CERTCertificate& cert = context.cert; michael@0: const CERTCertificate& issuerCert = context.issuerCert; michael@0: michael@0: if (!SECITEM_ItemsAreEqual(&serialNumber, &cert.serialNumber)) { michael@0: // This does not reference the certificate we're interested in. michael@0: // Consume the rest of the input and return successfully to michael@0: // potentially continue processing other responses. michael@0: input.SkipToEnd(); michael@0: return der::Success; michael@0: } michael@0: michael@0: // TODO: support SHA-2 hashes. michael@0: michael@0: SECOidTag hashAlg = SECOID_GetAlgorithmTag(&hashAlgorithm); michael@0: if (hashAlg != SEC_OID_SHA1) { michael@0: // Again, not interested in this response. Consume input, return success. michael@0: input.SkipToEnd(); michael@0: return der::Success; michael@0: } michael@0: michael@0: if (issuerNameHash.len != SHA1_LENGTH) { michael@0: return der::Fail(SEC_ERROR_OCSP_MALFORMED_RESPONSE); michael@0: } michael@0: michael@0: // From http://tools.ietf.org/html/rfc6960#section-4.1.1: michael@0: // "The hash shall be calculated over the DER encoding of the michael@0: // issuer's name field in the certificate being checked." michael@0: uint8_t hashBuf[SHA1_LENGTH]; michael@0: if (PK11_HashBuf(SEC_OID_SHA1, hashBuf, cert.derIssuer.data, michael@0: cert.derIssuer.len) != SECSuccess) { michael@0: return der::Failure; michael@0: } michael@0: if (memcmp(hashBuf, issuerNameHash.data, issuerNameHash.len)) { michael@0: // Again, not interested in this response. Consume input, return success. michael@0: input.SkipToEnd(); michael@0: return der::Success; michael@0: } michael@0: michael@0: return MatchIssuerKey(issuerKeyHash, issuerCert, match); michael@0: } michael@0: michael@0: // From http://tools.ietf.org/html/rfc6960#section-4.1.1: michael@0: // "The hash shall be calculated over the value (excluding tag and length) of michael@0: // the subject public key field in the issuer's certificate." michael@0: static der::Result michael@0: MatchIssuerKey(const SECItem& issuerKeyHash, const CERTCertificate& issuer, michael@0: /*out*/ bool& match) michael@0: { michael@0: if (issuerKeyHash.len != SHA1_LENGTH) { michael@0: return der::Fail(SEC_ERROR_OCSP_MALFORMED_RESPONSE); michael@0: } michael@0: michael@0: // TODO(bug 966856): support SHA-2 hashes michael@0: michael@0: // Copy just the length and data pointer (nothing needs to be freed) of the michael@0: // subject public key so we can convert the length from bits to bytes, which michael@0: // is what the digest function expects. michael@0: SECItem spk = issuer.subjectPublicKeyInfo.subjectPublicKey; michael@0: DER_ConvertBitString(&spk); michael@0: michael@0: static uint8_t hashBuf[SHA1_LENGTH]; michael@0: if (PK11_HashBuf(SEC_OID_SHA1, hashBuf, spk.data, spk.len) != SECSuccess) { michael@0: return der::Failure; michael@0: } michael@0: michael@0: match = !memcmp(hashBuf, issuerKeyHash.data, issuerKeyHash.len); michael@0: return der::Success; michael@0: } michael@0: michael@0: // Extension ::= SEQUENCE { michael@0: // extnID OBJECT IDENTIFIER, michael@0: // critical BOOLEAN DEFAULT FALSE, michael@0: // extnValue OCTET STRING michael@0: // } michael@0: static der::Result michael@0: CheckExtensionForCriticality(der::Input& input) michael@0: { michael@0: uint16_t toSkip; michael@0: if (ExpectTagAndGetLength(input, der::OIDTag, toSkip) != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: michael@0: // TODO: maybe we should check the syntax of the OID value michael@0: if (input.Skip(toSkip) != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: michael@0: // The only valid explicit encoding of the value is TRUE, so don't even michael@0: // bother parsing it, since we're going to fail either way. michael@0: if (input.Peek(der::BOOLEAN)) { michael@0: return der::Fail(SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION); michael@0: } michael@0: michael@0: if (ExpectTagAndGetLength(input, der::OCTET_STRING, toSkip) michael@0: != der::Success) { michael@0: return der::Failure; michael@0: } michael@0: return input.Skip(toSkip); michael@0: } michael@0: michael@0: // Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension michael@0: static der::Result michael@0: CheckExtensionsForCriticality(der::Input& input) michael@0: { michael@0: // TODO(bug 997994): some responders include an empty SEQUENCE OF michael@0: // Extension, which is invalid (der::MayBeEmpty should really be michael@0: // der::MustNotBeEmpty). michael@0: return der::NestedOf(input, der::SEQUENCE, der::SEQUENCE, michael@0: der::MayBeEmpty, CheckExtensionForCriticality); michael@0: } michael@0: michael@0: // 1. The certificate identified in a received response corresponds to michael@0: // the certificate that was identified in the corresponding request; michael@0: // 2. The signature on the response is valid; michael@0: // 3. The identity of the signer matches the intended recipient of the michael@0: // request; michael@0: // 4. The signer is currently authorized to provide a response for the michael@0: // certificate in question; michael@0: // 5. The time at which the status being indicated is known to be michael@0: // correct (thisUpdate) is sufficiently recent; michael@0: // 6. When available, the time at or before which newer information will michael@0: // be available about the status of the certificate (nextUpdate) is michael@0: // greater than the current time. michael@0: // michael@0: // Responses whose nextUpdate value is earlier than michael@0: // the local system time value SHOULD be considered unreliable. michael@0: // Responses whose thisUpdate time is later than the local system time michael@0: // SHOULD be considered unreliable. michael@0: // michael@0: // If nextUpdate is not set, the responder is indicating that newer michael@0: // revocation information is available all the time. michael@0: // michael@0: // http://tools.ietf.org/html/rfc5019#section-4 michael@0: michael@0: SECItem* michael@0: CreateEncodedOCSPRequest(PLArenaPool* arena, michael@0: const CERTCertificate* cert, michael@0: const CERTCertificate* issuerCert) michael@0: { michael@0: if (!arena || !cert || !issuerCert) { michael@0: PR_SetError(SEC_ERROR_INVALID_ARGS, 0); michael@0: return nullptr; michael@0: } michael@0: michael@0: // We do not add any extensions to the request. michael@0: michael@0: // RFC 6960 says "An OCSP client MAY wish to specify the kinds of response michael@0: // types it understands. To do so, it SHOULD use an extension with the OID michael@0: // id-pkix-ocsp-response." This use of MAY and SHOULD is unclear. MSIE11 michael@0: // on Windows 8.1 does not include any extensions, whereas NSS has always michael@0: // included the id-pkix-ocsp-response extension. Avoiding the sending the michael@0: // extension is better for OCSP GET because it makes the request smaller, michael@0: // and thus more likely to fit within the 255 byte limit for OCSP GET that michael@0: // is specified in RFC 5019 Section 5. michael@0: michael@0: // Bug 966856: Add the id-pkix-ocsp-pref-sig-algs extension. michael@0: michael@0: // Since we don't know whether the OCSP responder supports anything other michael@0: // than SHA-1, we have no choice but to use SHA-1 for issuerNameHash and michael@0: // issuerKeyHash. michael@0: static const uint8_t hashAlgorithm[11] = { michael@0: 0x30, 0x09, // SEQUENCE michael@0: 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, // OBJECT IDENTIFIER id-sha1 michael@0: 0x05, 0x00, // NULL michael@0: }; michael@0: static const uint8_t hashLen = SHA1_LENGTH; michael@0: michael@0: static const unsigned int totalLenWithoutSerialNumberData michael@0: = 2 // OCSPRequest michael@0: + 2 // tbsRequest michael@0: + 2 // requestList michael@0: + 2 // Request michael@0: + 2 // reqCert (CertID) michael@0: + PR_ARRAY_SIZE(hashAlgorithm) // hashAlgorithm michael@0: + 2 + hashLen // issuerNameHash michael@0: + 2 + hashLen // issuerKeyHash michael@0: + 2; // serialNumber (header) michael@0: michael@0: // The only way we could have a request this large is if the serialNumber was michael@0: // ridiculously and unreasonably large. RFC 5280 says "Conforming CAs MUST michael@0: // NOT use serialNumber values longer than 20 octets." With this restriction, michael@0: // we allow for some amount of non-conformance with that requirement while michael@0: // still ensuring we can encode the length values in the ASN.1 TLV structures michael@0: // in a single byte. michael@0: if (issuerCert->serialNumber.len > 127u - totalLenWithoutSerialNumberData) { michael@0: PR_SetError(SEC_ERROR_BAD_DATA, 0); michael@0: return nullptr; michael@0: } michael@0: michael@0: uint8_t totalLen = static_cast(totalLenWithoutSerialNumberData + michael@0: cert->serialNumber.len); michael@0: michael@0: SECItem* encodedRequest = SECITEM_AllocItem(arena, nullptr, totalLen); michael@0: if (!encodedRequest) { michael@0: return nullptr; michael@0: } michael@0: michael@0: uint8_t* d = encodedRequest->data; michael@0: *d++ = 0x30; *d++ = totalLen - 2; // OCSPRequest (SEQUENCE) michael@0: *d++ = 0x30; *d++ = totalLen - 4; // tbsRequest (SEQUENCE) michael@0: *d++ = 0x30; *d++ = totalLen - 6; // requestList (SEQUENCE OF) michael@0: *d++ = 0x30; *d++ = totalLen - 8; // Request (SEQUENCE) michael@0: *d++ = 0x30; *d++ = totalLen - 10; // reqCert (CertID SEQUENCE) michael@0: michael@0: // reqCert.hashAlgorithm michael@0: for (size_t i = 0; i < PR_ARRAY_SIZE(hashAlgorithm); ++i) { michael@0: *d++ = hashAlgorithm[i]; michael@0: } michael@0: michael@0: // reqCert.issuerNameHash (OCTET STRING) michael@0: *d++ = 0x04; michael@0: *d++ = hashLen; michael@0: if (PK11_HashBuf(SEC_OID_SHA1, d, issuerCert->derSubject.data, michael@0: issuerCert->derSubject.len) != SECSuccess) { michael@0: return nullptr; michael@0: } michael@0: d += hashLen; michael@0: michael@0: // reqCert.issuerKeyHash (OCTET STRING) michael@0: *d++ = 0x04; michael@0: *d++ = hashLen; michael@0: SECItem key = issuerCert->subjectPublicKeyInfo.subjectPublicKey; michael@0: DER_ConvertBitString(&key); michael@0: if (PK11_HashBuf(SEC_OID_SHA1, d, key.data, key.len) != SECSuccess) { michael@0: return nullptr; michael@0: } michael@0: d += hashLen; michael@0: michael@0: // reqCert.serialNumber (INTEGER) michael@0: *d++ = 0x02; // INTEGER michael@0: *d++ = static_cast(cert->serialNumber.len); michael@0: for (size_t i = 0; i < cert->serialNumber.len; ++i) { michael@0: *d++ = cert->serialNumber.data[i]; michael@0: } michael@0: michael@0: PR_ASSERT(d == encodedRequest->data + totalLen); michael@0: michael@0: return encodedRequest; michael@0: } michael@0: michael@0: } } // namespace mozilla::pkix