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; }
Result BackCert::RememberExtension(Reader& extnID, Input extnValue, bool critical, /*out*/ bool& understood) { understood = false; // python DottedOIDToCode.py id-ce-keyUsage 2.5.29.15 static const uint8_t id_ce_keyUsage[] = { 0x55, 0x1d, 0x0f }; // python DottedOIDToCode.py id-ce-subjectAltName 2.5.29.17 static const uint8_t id_ce_subjectAltName[] = { 0x55, 0x1d, 0x11 }; // python DottedOIDToCode.py id-ce-basicConstraints 2.5.29.19 static const uint8_t id_ce_basicConstraints[] = { 0x55, 0x1d, 0x13 }; // python DottedOIDToCode.py id-ce-nameConstraints 2.5.29.30 static const uint8_t id_ce_nameConstraints[] = { 0x55, 0x1d, 0x1e }; // python DottedOIDToCode.py id-ce-certificatePolicies 2.5.29.32 static const uint8_t id_ce_certificatePolicies[] = { 0x55, 0x1d, 0x20 }; // python DottedOIDToCode.py id-ce-policyConstraints 2.5.29.36 static const uint8_t id_ce_policyConstraints[] = { 0x55, 0x1d, 0x24 }; // python DottedOIDToCode.py id-ce-extKeyUsage 2.5.29.37 static const uint8_t id_ce_extKeyUsage[] = { 0x55, 0x1d, 0x25 }; // python DottedOIDToCode.py id-ce-inhibitAnyPolicy 2.5.29.54 static const uint8_t id_ce_inhibitAnyPolicy[] = { 0x55, 0x1d, 0x36 }; // python DottedOIDToCode.py id-pe-authorityInfoAccess 1.3.6.1.5.5.7.1.1 static const uint8_t id_pe_authorityInfoAccess[] = { 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01 }; // python DottedOIDToCode.py id-pkix-ocsp-nocheck 1.3.6.1.5.5.7.48.1.5 static const uint8_t id_pkix_ocsp_nocheck[] = { 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x05 }; // python DottedOIDToCode.py Netscape-certificate-type 2.16.840.1.113730.1.1 static const uint8_t Netscape_certificate_type[] = { 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x01 }; Input* out = nullptr; // We already enforce the maximum possible constraints for policies so we // can safely ignore even critical policy constraint extensions. // // XXX: Doing it this way won't allow us to detect duplicate // policyConstraints extensions, but that's OK because (and only because) we // ignore the extension. Input dummyPolicyConstraints; // We don't need to save the contents of this extension if it is present. We // just need to handle its presence (it is essentially ignored right now). Input dummyOCSPNocheck; // For compatibility reasons, for some extensions we have to allow empty // extension values. This would normally interfere with our duplicate // extension checking code. However, as long as the extensions we allow to // have empty values are also the ones we implicitly allow duplicates of, // this will work fine. bool emptyValueAllowed = false; // RFC says "Conforming CAs MUST mark this extension as non-critical" for // both authorityKeyIdentifier and subjectKeyIdentifier, and we do not use // them for anything, so we totally ignore them here. if (extnID.MatchRest(id_ce_keyUsage)) { out = &keyUsage; } else if (extnID.MatchRest(id_ce_subjectAltName)) { out = &subjectAltName; } else if (extnID.MatchRest(id_ce_basicConstraints)) { out = &basicConstraints; } else if (extnID.MatchRest(id_ce_nameConstraints)) { out = &nameConstraints; } else if (extnID.MatchRest(id_ce_certificatePolicies)) { out = &certificatePolicies; } else if (extnID.MatchRest(id_ce_policyConstraints)) { out = &dummyPolicyConstraints; } else if (extnID.MatchRest(id_ce_extKeyUsage)) { out = &extKeyUsage; } else if (extnID.MatchRest(id_ce_inhibitAnyPolicy)) { out = &inhibitAnyPolicy; } else if (extnID.MatchRest(id_pe_authorityInfoAccess)) { out = &authorityInfoAccess; } else if (extnID.MatchRest(id_pkix_ocsp_nocheck) && critical) { // We need to make sure we don't reject delegated OCSP response signing // certificates that contain the id-pkix-ocsp-nocheck extension marked as // critical when validating OCSP responses. Without this, an application // that implements soft-fail OCSP might ignore a valid Revoked or Unknown // response, and an application that implements hard-fail OCSP might fail // to connect to a server given a valid Good response. out = &dummyOCSPNocheck; // We allow this extension to have an empty value. // See http://comments.gmane.org/gmane.ietf.x509/30947 emptyValueAllowed = true; } else if (extnID.MatchRest(Netscape_certificate_type) && critical) { out = &criticalNetscapeCertificateType; } if (out) { // Don't allow an empty value for any extension we understand. This way, we // can test out->GetLength() != 0 or out->Init() to check for duplicates. if (extnValue.GetLength() == 0 && !emptyValueAllowed) { return Result::ERROR_EXTENSION_VALUE_INVALID; } if (out->Init(extnValue) != Success) { // Duplicate extension return Result::ERROR_EXTENSION_VALUE_INVALID; } understood = true; } return Success; }
// XXX: The second value is of type |const Input&| instead of type |Input| due // to limitations in our std::bind polyfill. Result BackCert::RememberExtension(Reader& extnID, const Input& extnValue, bool critical, /*out*/ bool& understood) { understood = false; // python DottedOIDToCode.py id-ce-keyUsage 2.5.29.15 static const uint8_t id_ce_keyUsage[] = { 0x55, 0x1d, 0x0f }; // python DottedOIDToCode.py id-ce-subjectAltName 2.5.29.17 static const uint8_t id_ce_subjectAltName[] = { 0x55, 0x1d, 0x11 }; // python DottedOIDToCode.py id-ce-basicConstraints 2.5.29.19 static const uint8_t id_ce_basicConstraints[] = { 0x55, 0x1d, 0x13 }; // python DottedOIDToCode.py id-ce-nameConstraints 2.5.29.30 static const uint8_t id_ce_nameConstraints[] = { 0x55, 0x1d, 0x1e }; // python DottedOIDToCode.py id-ce-certificatePolicies 2.5.29.32 static const uint8_t id_ce_certificatePolicies[] = { 0x55, 0x1d, 0x20 }; // python DottedOIDToCode.py id-ce-policyConstraints 2.5.29.36 static const uint8_t id_ce_policyConstraints[] = { 0x55, 0x1d, 0x24 }; // python DottedOIDToCode.py id-ce-extKeyUsage 2.5.29.37 static const uint8_t id_ce_extKeyUsage[] = { 0x55, 0x1d, 0x25 }; // python DottedOIDToCode.py id-ce-inhibitAnyPolicy 2.5.29.54 static const uint8_t id_ce_inhibitAnyPolicy[] = { 0x55, 0x1d, 0x36 }; // python DottedOIDToCode.py id-pe-authorityInfoAccess 1.3.6.1.5.5.7.1.1 static const uint8_t id_pe_authorityInfoAccess[] = { 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01 }; // python DottedOIDToCode.py Netscape-certificate-type 2.16.840.1.113730.1.1 static const uint8_t Netscape_certificate_type[] = { 0x60, 0x86, 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x01 }; Input* out = nullptr; // We already enforce the maximum possible constraints for policies so we // can safely ignore even critical policy constraint extensions. // // XXX: Doing it this way won't allow us to detect duplicate // policyConstraints extensions, but that's OK because (and only because) we // ignore the extension. Input dummyPolicyConstraints; // RFC says "Conforming CAs MUST mark this extension as non-critical" for // both authorityKeyIdentifier and subjectKeyIdentifier, and we do not use // them for anything, so we totally ignore them here. if (extnID.MatchRest(id_ce_keyUsage)) { out = &keyUsage; } else if (extnID.MatchRest(id_ce_subjectAltName)) { out = &subjectAltName; } else if (extnID.MatchRest(id_ce_basicConstraints)) { out = &basicConstraints; } else if (extnID.MatchRest(id_ce_nameConstraints)) { out = &nameConstraints; } else if (extnID.MatchRest(id_ce_certificatePolicies)) { out = &certificatePolicies; } else if (extnID.MatchRest(id_ce_policyConstraints)) { out = &dummyPolicyConstraints; } else if (extnID.MatchRest(id_ce_extKeyUsage)) { out = &extKeyUsage; } else if (extnID.MatchRest(id_ce_inhibitAnyPolicy)) { out = &inhibitAnyPolicy; } else if (extnID.MatchRest(id_pe_authorityInfoAccess)) { out = &authorityInfoAccess; } else if (extnID.MatchRest(Netscape_certificate_type) && critical) { out = &criticalNetscapeCertificateType; } if (out) { // Don't allow an empty value for any extension we understand. This way, we // can test out->GetLength() != 0 or out->Init() to check for duplicates. if (extnValue.GetLength() == 0) { return Result::ERROR_EXTENSION_VALUE_INVALID; } if (out->Init(extnValue) != Success) { // Duplicate extension return Result::ERROR_EXTENSION_VALUE_INVALID; } understood = true; } return Success; }
// certificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation Result CheckCertificatePolicies(EndEntityOrCA endEntityOrCA, const Input* encodedCertificatePolicies, const Input* encodedInhibitAnyPolicy, TrustLevel trustLevel, const CertPolicyId& requiredPolicy) { if (requiredPolicy.numBytes == 0 || requiredPolicy.numBytes > sizeof requiredPolicy.bytes) { return Result::FATAL_ERROR_INVALID_ARGS; } bool requiredPolicyFound = requiredPolicy.IsAnyPolicy(); if (requiredPolicyFound) { return Success; } // Bug 989051. Until we handle inhibitAnyPolicy we will fail close when // inhibitAnyPolicy extension is present and we are validating for a policy. if (!requiredPolicyFound && encodedInhibitAnyPolicy) { return Result::ERROR_POLICY_VALIDATION_FAILED; } // The root CA certificate may omit the policies that it has been // trusted for, so we cannot require the policies to be present in those // certificates. Instead, the determination of which roots are trusted for // which policies is made by the TrustDomain's GetCertTrust method. if (trustLevel == TrustLevel::TrustAnchor && endEntityOrCA == EndEntityOrCA::MustBeCA) { requiredPolicyFound = true; } Input requiredPolicyDER; if (requiredPolicyDER.Init(requiredPolicy.bytes, requiredPolicy.numBytes) != Success) { return Result::FATAL_ERROR_INVALID_ARGS; } if (encodedCertificatePolicies) { Reader extension(*encodedCertificatePolicies); Reader certificatePolicies; Result rv = der::ExpectTagAndGetValue(extension, der::SEQUENCE, certificatePolicies); if (rv != Success) { return Result::ERROR_POLICY_VALIDATION_FAILED; } if (!extension.AtEnd()) { return Result::ERROR_POLICY_VALIDATION_FAILED; } do { // PolicyInformation ::= SEQUENCE { // policyIdentifier CertPolicyId, // policyQualifiers SEQUENCE SIZE (1..MAX) OF // PolicyQualifierInfo OPTIONAL } Reader policyInformation; rv = der::ExpectTagAndGetValue(certificatePolicies, der::SEQUENCE, policyInformation); if (rv != Success) { return Result::ERROR_POLICY_VALIDATION_FAILED; } Reader policyIdentifier; rv = der::ExpectTagAndGetValue(policyInformation, der::OIDTag, policyIdentifier); if (rv != Success) { return rv; } if (policyIdentifier.MatchRest(requiredPolicyDER)) { requiredPolicyFound = true; } else if (endEntityOrCA == EndEntityOrCA::MustBeCA && policyIdentifier.MatchRest(anyPolicy)) { requiredPolicyFound = 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. } while (!requiredPolicyFound && !certificatePolicies.AtEnd()); } if (!requiredPolicyFound) { return Result::ERROR_POLICY_VALIDATION_FAILED; } return Success; }
Result CheckSubjectPublicKeyInfo(Reader& input, TrustDomain& trustDomain, EndEntityOrCA endEntityOrCA) { // Here, we validate the syntax and do very basic semantic validation of the // public key of the certificate. The intention here is to filter out the // types of bad inputs that are most likely to trigger non-mathematical // security vulnerabilities in the TrustDomain, like buffer overflows or the // use of unsafe elliptic curves. // // We don't check (all of) the mathematical properties of the public key here // because it is more efficient for the TrustDomain to do it during signature // verification and/or other use of the public key. In particular, we // delegate the arithmetic validation of the public key, as specified in // NIST SP800-56A section 5.6.2, to the TrustDomain, at least for now. Reader algorithm; Input subjectPublicKey; Result rv = der::ExpectTagAndGetValue(input, der::SEQUENCE, algorithm); if (rv != Success) { return rv; } rv = der::BitStringWithNoUnusedBits(input, subjectPublicKey); if (rv != Success) { return rv; } rv = der::End(input); if (rv != Success) { return rv; } Reader subjectPublicKeyReader(subjectPublicKey); Reader algorithmOID; rv = der::ExpectTagAndGetValue(algorithm, der::OIDTag, algorithmOID); if (rv != Success) { return rv; } // RFC 3279 Section 2.3.1 // python DottedOIDToCode.py rsaEncryption 1.2.840.113549.1.1.1 static const uint8_t rsaEncryption[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 }; // RFC 3279 Section 2.3.5 and RFC 5480 Section 2.1.1 // python DottedOIDToCode.py id-ecPublicKey 1.2.840.10045.2.1 static const uint8_t id_ecPublicKey[] = { 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01 }; if (algorithmOID.MatchRest(id_ecPublicKey)) { // An id-ecPublicKey AlgorithmIdentifier has a parameter that identifes // the curve being used. Although RFC 5480 specifies multiple forms, we // only supported the NamedCurve form, where the curve is identified by an // OID. Reader namedCurveOIDValue; rv = der::ExpectTagAndGetValue(algorithm, der::OIDTag, namedCurveOIDValue); if (rv != Success) { return rv; } // RFC 5480 // python DottedOIDToCode.py secp256r1 1.2.840.10045.3.1.7 static const uint8_t secp256r1[] = { 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07 }; // RFC 5480 // python DottedOIDToCode.py secp384r1 1.3.132.0.34 static const uint8_t secp384r1[] = { 0x2b, 0x81, 0x04, 0x00, 0x22 }; // RFC 5480 // python DottedOIDToCode.py secp521r1 1.3.132.0.35 static const uint8_t secp521r1[] = { 0x2b, 0x81, 0x04, 0x00, 0x23 }; // Matching is attempted based on a rough estimate of the commonality of the // elliptic curve, to minimize the number of MatchRest calls. NamedCurve curve; unsigned int bits; if (namedCurveOIDValue.MatchRest(secp256r1)) { curve = NamedCurve::secp256r1; bits = 256; } else if (namedCurveOIDValue.MatchRest(secp384r1)) { curve = NamedCurve::secp384r1; bits = 384; } else if (namedCurveOIDValue.MatchRest(secp521r1)) { curve = NamedCurve::secp521r1; bits = 521; } else { return Result::ERROR_UNSUPPORTED_ELLIPTIC_CURVE; } rv = trustDomain.CheckECDSACurveIsAcceptable(endEntityOrCA, curve); if (rv != Success) { return rv; } // RFC 5480 Section 2.2 says that the first octet will be 0x04 to indicate // an uncompressed point, which is the only encoding we support. uint8_t compressedOrUncompressed; rv = subjectPublicKeyReader.Read(compressedOrUncompressed); if (rv != Success) { return rv; } if (compressedOrUncompressed != 0x04) { return Result::ERROR_UNSUPPORTED_EC_POINT_FORM; } // The point is encoded as two raw (not DER-encoded) integers, each padded // to the bit length (rounded up to the nearest byte). Input point; rv = subjectPublicKeyReader.SkipToEnd(point); if (rv != Success) { return rv; } if (point.GetLength() != ((bits + 7) / 8u) * 2u) { return Result::ERROR_BAD_DER; } // XXX: We defer the mathematical verification of the validity of the point // until signature verification. This means that if we never verify a // signature, we'll never fully check whether the public key is valid. } else if (algorithmOID.MatchRest(rsaEncryption)) { // RFC 3279 Section 2.3.1 says "The parameters field MUST have ASN.1 type // NULL for this algorithm identifier." rv = der::ExpectTagAndEmptyValue(algorithm, der::NULLTag); if (rv != Success) { return rv; } // RSAPublicKey :: = SEQUENCE{ // modulus INTEGER, --n // publicExponent INTEGER } --e rv = der::Nested(subjectPublicKeyReader, der::SEQUENCE, [&trustDomain, endEntityOrCA](Reader& r) { Input modulus; Input::size_type modulusSignificantBytes; Result rv = der::PositiveInteger(r, modulus, &modulusSignificantBytes); if (rv != Success) { return rv; } // XXX: Should we do additional checks of the modulus? rv = trustDomain.CheckRSAPublicKeyModulusSizeInBits( endEntityOrCA, modulusSignificantBytes * 8u); if (rv != Success) { return rv; } // XXX: We don't allow the TrustDomain to validate the exponent. // XXX: We don't do our own sanity checking of the exponent. Input exponent; return der::PositiveInteger(r, exponent); }); if (rv != Success) { return rv; } } else { return Result::ERROR_UNSUPPORTED_KEYALG; } rv = der::End(algorithm); if (rv != Success) { return rv; } rv = der::End(subjectPublicKeyReader); if (rv != Success) { return rv; } return Success; }