// Extension ::= SEQUENCE { // extnID OBJECT IDENTIFIER, // critical BOOLEAN DEFAULT FALSE, // extnValue OCTET STRING // } static der::Result CheckExtensionForCriticality(der::Input& input) { uint16_t toSkip; if (ExpectTagAndGetLength(input, der::OIDTag, toSkip) != der::Success) { return der::Failure; } // TODO: maybe we should check the syntax of the OID value if (input.Skip(toSkip) != der::Success) { return der::Failure; } // The only valid explicit encoding of the value is TRUE, so don't even // bother parsing it, since we're going to fail either way. if (input.Peek(der::BOOLEAN)) { return der::Fail(SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION); } if (ExpectTagAndGetLength(input, der::OCTET_STRING, toSkip) != der::Success) { return der::Failure; } return input.Skip(toSkip); }
// 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 der::Result SingleResponse(der::Input& input, Context& context) { bool match = false; if (der::Nested(input, der::SEQUENCE, bind(CertID, _1, cref(context), ref(match))) != der::Success) { return der::Failure; } 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 der::Success; } // 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))) { if (ExpectTagAndLength(input, static_cast<uint8_t>(CertStatus::Good), 0) != der::Success) { return der::Failure; } 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. if (der::Skip(input, static_cast<uint8_t>(CertStatus::Revoked)) != der::Success) { return der::Failure; } context.certStatus = CertStatus::Revoked; } else if (ExpectTagAndLength(input, static_cast<uint8_t>(CertStatus::Unknown), 0) != der::Success) { return der::Failure; } // 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. // We won't accept any OCSP responses that are more than 10 days old, even if // the nextUpdate time is further in the future. static const PRTime OLDEST_ACCEPTABLE = INT64_C(10) * ONE_DAY; PRTime thisUpdate; if (der::GeneralizedTime(input, thisUpdate) != der::Success) { return der::Failure; } if (thisUpdate > context.time + SLOP) { return der::Fail(SEC_ERROR_OCSP_FUTURE_RESPONSE); } PRTime notAfter; static const uint8_t NEXT_UPDATE_TAG = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0; if (input.Peek(NEXT_UPDATE_TAG)) { PRTime nextUpdate; if (der::Nested(input, NEXT_UPDATE_TAG, bind(der::GeneralizedTime, _1, ref(nextUpdate))) != der::Success) { return der::Failure; } if (nextUpdate < thisUpdate) { return der::Fail(SEC_ERROR_OCSP_MALFORMED_RESPONSE); } if (nextUpdate - thisUpdate <= OLDEST_ACCEPTABLE) { notAfter = nextUpdate; } else { notAfter = thisUpdate + OLDEST_ACCEPTABLE; } } else { // NSS requires all OCSP responses without a nextUpdate to be recent. // Match that stricter behavior. notAfter = thisUpdate + ONE_DAY; } if (context.time < SLOP) { // prevent underflow return der::Fail(SEC_ERROR_INVALID_ARGS); } if (context.time - SLOP > notAfter) { return der::Fail(SEC_ERROR_OCSP_OLD_RESPONSE); } if (!input.AtEnd()) { if (CheckExtensionsForCriticality(input) != der::Success) { return der::Failure; } } if (context.thisUpdate) { *context.thisUpdate = thisUpdate; } if (context.validThrough) { *context.validThrough = notAfter; } return der::Success; }
// ResponseData ::= SEQUENCE { // version [0] EXPLICIT Version DEFAULT v1, // responderID ResponderID, // producedAt GeneralizedTime, // responses SEQUENCE OF SingleResponse, // responseExtensions [1] EXPLICIT Extensions OPTIONAL } static inline der::Result ResponseData(der::Input& input, Context& context, const CERTSignedData& signedResponseData, /*const*/ SECItem* certs, size_t numCerts) { uint8_t version; if (der::OptionalVersion(input, version) != der::Success) { return der::Failure; } if (version != der::v1) { // TODO: more specific error code for bad version? return der::Fail(SEC_ERROR_BAD_DER); } // ResponderID ::= CHOICE { // byName [1] Name, // byKey [2] KeyHash } SECItem responderID; uint16_t responderIDLength; ResponderIDType responderIDType = input.Peek(static_cast<uint8_t>(ResponderIDType::byName)) ? ResponderIDType::byName : ResponderIDType::byKey; if (ExpectTagAndGetLength(input, static_cast<uint8_t>(responderIDType), responderIDLength) != der::Success) { return der::Failure; } // TODO: responderID probably needs to have another level of ASN1 tag/length // checked and stripped. if (input.Skip(responderIDLength, responderID) != der::Success) { return der::Failure; } // 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. if (VerifySignature(context, responderIDType, responderID, certs, numCerts, signedResponseData) != SECSuccess) { return der::Failure; } // TODO: Do we even need to parse this? Should we just skip it? PRTime producedAt; if (der::GeneralizedTime(input, producedAt) != der::Success) { return der::Failure; } // 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. if (der::NestedOf(input, der::SEQUENCE, der::SEQUENCE, der::MustNotBeEmpty, bind(SingleResponse, _1, ref(context))) != der::Success) { return der::Failure; } if (!input.AtEnd()) { if (CheckExtensionsForCriticality(input) != der::Success) { return der::Failure; } } return der::Success; }