Result MultiLogCTVerifier::Verify(Input cert, Input issuerSubjectPublicKeyInfo, Input sctListFromCert, Input sctListFromOCSPResponse, Input sctListFromTLSExtension, uint64_t time, CTVerifyResult& result) { MOZ_ASSERT(cert.GetLength() > 0); result.Reset(); Result rv; // Verify embedded SCTs if (issuerSubjectPublicKeyInfo.GetLength() > 0 && sctListFromCert.GetLength() > 0) { LogEntry precertEntry; rv = GetPrecertLogEntry(cert, issuerSubjectPublicKeyInfo, precertEntry); if (rv != Success) { return rv; } rv = VerifySCTs(sctListFromCert, precertEntry, SignedCertificateTimestamp::Origin::Embedded, time, result); if (rv != Success) { return rv; } } LogEntry x509Entry; rv = GetX509LogEntry(cert, x509Entry); if (rv != Success) { return rv; } // Verify SCTs from a stapled OCSP response if (sctListFromOCSPResponse.GetLength() > 0) { rv = VerifySCTs(sctListFromOCSPResponse, x509Entry, SignedCertificateTimestamp::Origin::OCSPResponse, time, result); if (rv != Success) { return rv; } } // Verify SCTs from a TLS extension if (sctListFromTLSExtension.GetLength() > 0) { rv = VerifySCTs(sctListFromTLSExtension, x509Entry, SignedCertificateTimestamp::Origin::TLSExtension, time, result); if (rv != Success) { return rv; } } return Success; }
// 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() != TrustDomain::DIGEST_LENGTH) { return Result::ERROR_OCSP_MALFORMED_RESPONSE; } static uint8_t hashBuf[TrustDomain::DIGEST_LENGTH]; Result rv = KeyHash(trustDomain, subjectPublicKeyInfo, hashBuf, sizeof hashBuf); if (rv != Success) { return rv; } Input computed(hashBuf); match = InputsAreEqual(computed, keyHash); 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; }
// 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); }
// 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; }
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; }