// PolicyInformation ::= SEQUENCE { // policyIdentifier CertPolicyId, // policyQualifiers SEQUENCE SIZE (1..MAX) OF // PolicyQualifierInfo OPTIONAL } inline Result CheckPolicyInformation(Reader& input, EndEntityOrCA endEntityOrCA, const CertPolicyId& requiredPolicy, /*in/out*/ bool& found) { if (input.MatchTLV(der::OIDTag, requiredPolicy.numBytes, requiredPolicy.bytes)) { found = true; } else if (endEntityOrCA == EndEntityOrCA::MustBeCA && input.MatchTLV(der::OIDTag, CertPolicyId::anyPolicy.numBytes, CertPolicyId::anyPolicy.bytes)) { found = true; } // RFC 5280 Section 4.2.1.4 says "Optional qualifiers, which MAY be present, // are not expected to change the definition of the policy." Also, it seems // that Section 6, which defines validation, does not require any matching of // qualifiers. Thus, doing anything with the policy qualifiers would be a // waste of time and a source of potential incompatibilities, so we just // ignore them. // Skip unmatched OID and/or policyQualifiers input.SkipToEnd(); return Success; }
Result BitStringWithNoUnusedBits(Reader& input, /*out*/ Input& value) { Reader valueWithUnusedBits; Result rv = ExpectTagAndGetValue(input, BIT_STRING, valueWithUnusedBits); if (rv != Success) { return rv; } uint8_t unusedBitsAtEnd; if (valueWithUnusedBits.Read(unusedBitsAtEnd) != Success) { return Result::ERROR_BAD_DER; } // XXX: Really the constraint should be that unusedBitsAtEnd must be less // than 7. But, we suspect there are no real-world values in OCSP responses // or certificates with non-zero unused bits. It seems like NSS assumes this // in various places, so we enforce it too in order to simplify this code. If // we find compatibility issues, we'll know we're wrong and we'll have to // figure out how to shift the bits around. if (unusedBitsAtEnd != 0) { return Result::ERROR_BAD_DER; } Reader::Mark mark(valueWithUnusedBits.GetMark()); valueWithUnusedBits.SkipToEnd(); return valueWithUnusedBits.GetInput(mark, value); }
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); }
static Result MatchEKU(Reader& value, KeyPurposeId requiredEKU, EndEntityOrCA endEntityOrCA, /*in/out*/ bool& found, /*in/out*/ bool& foundOCSPSigning) { // See Section 5.9 of "A Layman's Guide to a Subset of ASN.1, BER, and DER" // for a description of ASN.1 DER encoding of OIDs. // id-pkix OBJECT IDENTIFIER ::= // { iso(1) identified-organization(3) dod(6) internet(1) // security(5) mechanisms(5) pkix(7) } // id-kp OBJECT IDENTIFIER ::= { id-pkix 3 } // id-kp-serverAuth OBJECT IDENTIFIER ::= { id-kp 1 } // id-kp-clientAuth OBJECT IDENTIFIER ::= { id-kp 2 } // id-kp-codeSigning OBJECT IDENTIFIER ::= { id-kp 3 } // id-kp-emailProtection OBJECT IDENTIFIER ::= { id-kp 4 } // id-kp-OCSPSigning OBJECT IDENTIFIER ::= { id-kp 9 } static const uint8_t server[] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 1 }; static const uint8_t client[] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 2 }; static const uint8_t code [] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 3 }; static const uint8_t email [] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 4 }; static const uint8_t ocsp [] = { (40*1)+3, 6, 1, 5, 5, 7, 3, 9 }; // id-Netscape OBJECT IDENTIFIER ::= { 2 16 840 1 113730 } // id-Netscape-policy OBJECT IDENTIFIER ::= { id-Netscape 4 } // id-Netscape-stepUp OBJECT IDENTIFIER ::= { id-Netscape-policy 1 } static const uint8_t serverStepUp[] = { (40*2)+16, 128+6,72, 1, 128+6,128+120,66, 4, 1 }; bool match = false; if (!found) { switch (requiredEKU) { case KeyPurposeId::id_kp_serverAuth: // Treat CA certs with step-up OID as also having SSL server type. // Comodo has issued certificates that require this behavior that don't // expire until June 2020! TODO(bug 982932): Limit this exception to // old certificates. match = value.MatchRest(server) || (endEntityOrCA == EndEntityOrCA::MustBeCA && value.MatchRest(serverStepUp)); break; case KeyPurposeId::id_kp_clientAuth: match = value.MatchRest(client); break; case KeyPurposeId::id_kp_codeSigning: match = value.MatchRest(code); break; case KeyPurposeId::id_kp_emailProtection: match = value.MatchRest(email); break; case KeyPurposeId::id_kp_OCSPSigning: match = value.MatchRest(ocsp); break; case KeyPurposeId::anyExtendedKeyUsage: PR_NOT_REACHED("anyExtendedKeyUsage should start with found==true"); return Result::FATAL_ERROR_LIBRARY_FAILURE; default: PR_NOT_REACHED("unrecognized EKU"); return Result::FATAL_ERROR_LIBRARY_FAILURE; } } if (match) { found = true; if (requiredEKU == KeyPurposeId::id_kp_OCSPSigning) { foundOCSPSigning = true; } } else if (value.MatchRest(ocsp)) { foundOCSPSigning = true; } value.SkipToEnd(); // ignore unmatched OIDs. 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 CertID(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() != TrustDomain::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[TrustDomain::DIGEST_LENGTH]; rv = context.trustDomain.DigestBuf(context.certID.issuer, 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); }
// 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, bind(CertID, _1, cref(context), ref(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; } // 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, bind(der::GeneralizedTime, _1, ref(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, ExtensionNotUnderstood); if (rv != Success) { return rv; } if (context.thisUpdate) { *context.thisUpdate = thisUpdate; } if (context.validThrough) { *context.validThrough = notAfterPlusSlop; } return Success; }