| Index: net/cert/cert_verify_proc_mac.cc
|
| diff --git a/net/cert/cert_verify_proc_mac.cc b/net/cert/cert_verify_proc_mac.cc
|
| index 709922598eaf20985dd67a50661dd9692ab33efc..388d0fc41a93b81ba84d8565bec4996fbe0b31f7 100644
|
| --- a/net/cert/cert_verify_proc_mac.cc
|
| +++ b/net/cert/cert_verify_proc_mac.cc
|
| @@ -175,12 +175,21 @@ OSStatus CreateTrustPolicies(const std::string& hostname,
|
| return noErr;
|
| }
|
|
|
| -// Saves some information about the certificate chain |cert_chain| in
|
| -// |*verify_result|. The caller MUST initialize |*verify_result| before
|
| -// calling this function.
|
| +// Stores the constructed certificate chain |cert_chain| and information about
|
| +// the signature algorithms used into |*verify_result|. If the leaf cert in
|
| +// |cert_chain| contains a weak (MD2, MD4, MD5, SHA-1) signature, stores that
|
| +// in |*leaf_is_weak|.
|
| void GetCertChainInfo(CFArrayRef cert_chain,
|
| CSSM_TP_APPLE_EVIDENCE_INFO* chain_info,
|
| - CertVerifyResult* verify_result) {
|
| + CertVerifyResult* verify_result,
|
| + bool* leaf_is_weak) {
|
| + *leaf_is_weak = false;
|
| + verify_result->verified_cert = nullptr;
|
| + verify_result->has_md2 = false;
|
| + verify_result->has_md4 = false;
|
| + verify_result->has_md5 = false;
|
| + verify_result->has_sha1 = false;
|
| +
|
| SecCertificateRef verified_cert = NULL;
|
| std::vector<SecCertificateRef> verified_chain;
|
| for (CFIndex i = 0, count = CFArrayGetCount(cert_chain); i < count; ++i) {
|
| @@ -223,10 +232,16 @@ void GetCertChainInfo(CFArrayRef cert_chain,
|
| const CSSM_OID* alg_oid = &sig_algorithm->algorithm;
|
| if (CSSMOIDEqual(alg_oid, &CSSMOID_MD2WithRSA)) {
|
| verify_result->has_md2 = true;
|
| + if (i == 0)
|
| + *leaf_is_weak = true;
|
| } else if (CSSMOIDEqual(alg_oid, &CSSMOID_MD4WithRSA)) {
|
| verify_result->has_md4 = true;
|
| + if (i == 0)
|
| + *leaf_is_weak = true;
|
| } else if (CSSMOIDEqual(alg_oid, &CSSMOID_MD5WithRSA)) {
|
| verify_result->has_md5 = true;
|
| + if (i == 0)
|
| + *leaf_is_weak = true;
|
| } else if (CSSMOIDEqual(alg_oid, &CSSMOID_SHA1WithRSA) ||
|
| CSSMOIDEqual(alg_oid, &CSSMOID_SHA1WithRSA_OIW) ||
|
| CSSMOIDEqual(alg_oid, &CSSMOID_SHA1WithDSA) ||
|
| @@ -234,6 +249,8 @@ void GetCertChainInfo(CFArrayRef cert_chain,
|
| CSSMOIDEqual(alg_oid, &CSSMOID_SHA1WithDSA_JDK) ||
|
| CSSMOIDEqual(alg_oid, &CSSMOID_ECDSA_WithSHA1)) {
|
| verify_result->has_sha1 = true;
|
| + if (i == 0)
|
| + *leaf_is_weak = true;
|
| }
|
| }
|
| if (!verified_cert)
|
| @@ -446,80 +463,6 @@ int BuildAndEvaluateSecTrustRef(CFArrayRef cert_array,
|
| return OK;
|
| }
|
|
|
| -// OS X ships with both "GTE CyberTrust Global Root" and "Baltimore CyberTrust
|
| -// Root" as part of its trusted root store. However, a cross-certified version
|
| -// of the "Baltimore CyberTrust Root" exists that chains to "GTE CyberTrust
|
| -// Global Root". When OS X/Security.framework attempts to evaluate such a
|
| -// certificate chain, it disregards the "Baltimore CyberTrust Root" that exists
|
| -// within Keychain and instead attempts to terminate the chain in the "GTE
|
| -// CyberTrust Global Root". However, the GTE root is scheduled to be removed in
|
| -// a future OS X update (for sunsetting purposes), and once removed, such
|
| -// chains will fail validation, even though a trust anchor still exists.
|
| -//
|
| -// Rather than over-generalizing a solution that may mask a number of TLS
|
| -// misconfigurations, attempt to specifically match the affected
|
| -// cross-certified certificate and remove it from certificate chain processing.
|
| -bool IsBadBaltimoreGTECertificate(SecCertificateRef cert) {
|
| - // Matches the GTE-signed Baltimore CyberTrust Root
|
| - // https://cacert.omniroot.com/Baltimore-to-GTE-04-12.pem
|
| - static const SHA1HashValue kBadBaltimoreHashNew =
|
| - { { 0x4D, 0x34, 0xEA, 0x92, 0x76, 0x4B, 0x3A, 0x31, 0x49, 0x11,
|
| - 0x99, 0x52, 0xF4, 0x19, 0x30, 0xCA, 0x11, 0x34, 0x83, 0x61 } };
|
| - // Matches the legacy GTE-signed Baltimore CyberTrust Root
|
| - // https://cacert.omniroot.com/gte-2-2025.pem
|
| - static const SHA1HashValue kBadBaltimoreHashOld =
|
| - { { 0x54, 0xD8, 0xCB, 0x49, 0x1F, 0xA1, 0x6D, 0xF8, 0x87, 0xDC,
|
| - 0x94, 0xA9, 0x34, 0xCC, 0x83, 0x6B, 0xDA, 0xA8, 0xA3, 0x69 } };
|
| -
|
| - SHA1HashValue fingerprint = X509Certificate::CalculateFingerprint(cert);
|
| -
|
| - return fingerprint.Equals(kBadBaltimoreHashNew) ||
|
| - fingerprint.Equals(kBadBaltimoreHashOld);
|
| -}
|
| -
|
| -// Attempts to re-verify |cert_array| after adjusting the inputs to work around
|
| -// known issues in OS X. To be used if BuildAndEvaluateSecTrustRef fails to
|
| -// return a positive result for verification.
|
| -//
|
| -// This function should only be called while the Mac Security Services lock is
|
| -// held.
|
| -void RetrySecTrustEvaluateWithAdjustedChain(
|
| - CFArrayRef cert_array,
|
| - CFArrayRef trust_policies,
|
| - int flags,
|
| - ScopedCFTypeRef<SecTrustRef>* trust_ref,
|
| - SecTrustResultType* trust_result,
|
| - ScopedCFTypeRef<CFArrayRef>* verified_chain,
|
| - CSSM_TP_APPLE_EVIDENCE_INFO** chain_info) {
|
| - CFIndex count = CFArrayGetCount(*verified_chain);
|
| - CFIndex slice_point = 0;
|
| -
|
| - for (CFIndex i = 1; i < count; ++i) {
|
| - SecCertificateRef cert = reinterpret_cast<SecCertificateRef>(
|
| - const_cast<void*>(CFArrayGetValueAtIndex(*verified_chain, i)));
|
| - if (cert == NULL)
|
| - return; // Strange times; can't fix things up.
|
| -
|
| - if (IsBadBaltimoreGTECertificate(cert)) {
|
| - slice_point = i;
|
| - break;
|
| - }
|
| - }
|
| - if (slice_point == 0)
|
| - return; // Nothing to do.
|
| -
|
| - ScopedCFTypeRef<CFMutableArrayRef> adjusted_cert_array(
|
| - CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks));
|
| - // Note: This excludes the certificate at |slice_point|.
|
| - CFArrayAppendArray(adjusted_cert_array, cert_array,
|
| - CFRangeMake(0, slice_point));
|
| -
|
| - // Ignore the result; failure will preserve the old verification results.
|
| - BuildAndEvaluateSecTrustRef(
|
| - adjusted_cert_array, trust_policies, flags, trust_ref, trust_result,
|
| - verified_chain, chain_info);
|
| -}
|
| -
|
| } // namespace
|
|
|
| CertVerifyProcMac::CertVerifyProcMac() {}
|
| @@ -547,7 +490,8 @@ int CertVerifyProcMac::VerifyInternal(
|
| // array of certificates, the first of which is the certificate we're
|
| // verifying, and the subsequent (optional) certificates are used for
|
| // chain building.
|
| - ScopedCFTypeRef<CFArrayRef> cert_array(cert->CreateOSCertChainForCert());
|
| + ScopedCFTypeRef<CFMutableArrayRef> cert_array(
|
| + cert->CreateOSCertChainForCert());
|
|
|
| // Serialize all calls that may use the Keychain, to work around various
|
| // issues in OS X 10.6+ with multi-threaded access to Security.framework.
|
| @@ -557,17 +501,106 @@ int CertVerifyProcMac::VerifyInternal(
|
| SecTrustResultType trust_result = kSecTrustResultDeny;
|
| ScopedCFTypeRef<CFArrayRef> completed_chain;
|
| CSSM_TP_APPLE_EVIDENCE_INFO* chain_info = NULL;
|
| -
|
| - int rv = BuildAndEvaluateSecTrustRef(
|
| - cert_array, trust_policies, flags, &trust_ref, &trust_result,
|
| - &completed_chain, &chain_info);
|
| - if (rv != OK)
|
| - return rv;
|
| - if (trust_result != kSecTrustResultUnspecified &&
|
| - trust_result != kSecTrustResultProceed) {
|
| - RetrySecTrustEvaluateWithAdjustedChain(
|
| - cert_array, trust_policies, flags, &trust_ref, &trust_result,
|
| - &completed_chain, &chain_info);
|
| + bool candidate_untrusted = true;
|
| + bool candidate_weak = false;
|
| +
|
| + // OS X lacks proper path discovery; it will take the input certs and never
|
| + // backtrack the graph attempting to discover valid paths.
|
| + // This can create issues in some situations:
|
| + // - When OS X changes the trust store, there may be a chain
|
| + // A -> B -> C -> D
|
| + // where OS X trusts D (on some versions) and trusts C (on some versions).
|
| + // If a server supplies a chain A, B, C (cross-signed by D), then this chain
|
| + // will successfully validate on systems that trust D, but fail for systems
|
| + // that trust C. If the server supplies a chain of A -> B, then it forces
|
| + // all clients to fetch C (via AIA) if they trust D, and not all clients
|
| + // (notably, Firefox and Android) will do this, thus breaking them.
|
| + // An example of this is the Verizon Business Services root - GTE CyberTrust
|
| + // and Baltimore CyberTrust roots represent old and new roots that cause
|
| + // issues depending on which version of OS X being used.
|
| + //
|
| + // - A server may be (misconfigured) to send an expired intermediate
|
| + // certificate. On platforms with path discovery, the graph traversal
|
| + // will back up to immediately before this intermediate, and then
|
| + // attempt an AIA fetch or retrieval from local store. However, OS X
|
| + // does not do this, and thus prevents access. While this is ostensibly
|
| + // a server misconfiguration issue, the fact that it works on other
|
| + // platforms is a jarring inconsistency for users.
|
| + //
|
| + // - When OS X trusts both C and D (simultaneously), it's possible that the
|
| + // version of C signed by D is signed using a weak algorithm (e.g. SHA-1),
|
| + // while the version of C in the trust store's signature doesn't matter.
|
| + // Since a 'strong' chain exists, it would be desirable to prefer this
|
| + // chain.
|
| + //
|
| + // - A variant of the above example, it may be that the version of B sent by
|
| + // the server is signed using a weak algorithm, but the version of B
|
| + // present in the AIA of A is signed using a strong algorithm. Since a
|
| + // 'strong' chain exists, it would be desirable to prefer this chain.
|
| + //
|
| + // Because of this, the code below first attempts to validate the peer's
|
| + // identity using the supplied chain. If it is not trusted (e.g. the OS only
|
| + // trusts C, but the version of C signed by D was sent, and D is not trusted),
|
| + // or if it contains a weak chain, it will begin lopping off certificates
|
| + // from the end of the chain and attempting to verify. If a stronger, trusted
|
| + // chain is found, it is used, otherwise, the algorithm continues until only
|
| + // the peer's certificate remains.
|
| + //
|
| + // This does cause a performance hit for these users, but only in cases where
|
| + // OS X is building weaker chains than desired, or when it would otherwise
|
| + // fail the connection.
|
| + while (CFArrayGetCount(cert_array) > 0) {
|
| + ScopedCFTypeRef<SecTrustRef> temp_ref;
|
| + SecTrustResultType temp_trust_result = kSecTrustResultDeny;
|
| + ScopedCFTypeRef<CFArrayRef> temp_chain;
|
| + CSSM_TP_APPLE_EVIDENCE_INFO* temp_chain_info = NULL;
|
| +
|
| + int rv = BuildAndEvaluateSecTrustRef(cert_array, trust_policies, flags,
|
| + &temp_ref, &temp_trust_result,
|
| + &temp_chain, &temp_chain_info);
|
| + if (rv != OK)
|
| + return rv;
|
| +
|
| + CertVerifyResult temp_verify_result;
|
| + bool leaf_is_weak = false;
|
| + GetCertChainInfo(temp_chain, temp_chain_info, &temp_verify_result,
|
| + &leaf_is_weak);
|
| +
|
| + bool untrusted = (temp_trust_result != kSecTrustResultUnspecified &&
|
| + temp_trust_result != kSecTrustResultProceed);
|
| + bool weak_chain =
|
| + !leaf_is_weak &&
|
| + (temp_verify_result.has_md2 || temp_verify_result.has_md4 ||
|
| + temp_verify_result.has_md5 || temp_verify_result.has_sha1);
|
| + // Set the result to the current chain if:
|
| + // - This is the first verification attempt. This ensures that if
|
| + // everything is awful (e.g. it may just be an untrusted cert), that
|
| + // what is reported is exactly what was sent by the server
|
| + // - If the current chain is trusted, and the old chain was not trusted,
|
| + // then prefer this chain. This ensures that if there is at least a
|
| + // valid path to a trust anchor, it's preferred over reporting an error.
|
| + // - If the current chain is trusted, and the old chain is trusted, but
|
| + // the old chain contained weak algorithms while the current chain only
|
| + // contains strong algorithms, then prefer the current chain over the
|
| + // old chain.
|
| + //
|
| + // Note: If the leaf certificate itself is weak, then the only
|
| + // consideration is whether or not there is a trusted chain. That's
|
| + // because no amount of path discovery will fix a weak leaf.
|
| + if (!trust_ref || (!untrusted && (candidate_untrusted ||
|
| + (candidate_weak && !weak_chain)))) {
|
| + trust_ref = temp_ref;
|
| + trust_result = temp_trust_result;
|
| + completed_chain = temp_chain;
|
| + chain_info = temp_chain_info;
|
| +
|
| + candidate_untrusted = untrusted;
|
| + candidate_weak = weak_chain;
|
| + }
|
| + // Short-circuit when a current, trusted chain is found.
|
| + if (!untrusted && !weak_chain)
|
| + break;
|
| + CFArrayRemoveValueAtIndex(cert_array, CFArrayGetCount(cert_array) - 1);
|
| }
|
|
|
| if (flags & CertVerifier::VERIFY_REV_CHECKING_ENABLED)
|
| @@ -576,7 +609,9 @@ int CertVerifyProcMac::VerifyInternal(
|
| if (crl_set && !CheckRevocationWithCRLSet(completed_chain, crl_set))
|
| verify_result->cert_status |= CERT_STATUS_REVOKED;
|
|
|
| - GetCertChainInfo(completed_chain, chain_info, verify_result);
|
| + bool leaf_is_weak_unused = false;
|
| + GetCertChainInfo(completed_chain, chain_info, verify_result,
|
| + &leaf_is_weak_unused);
|
|
|
| // As of Security Update 2012-002/OS X 10.7.4, when an RSA key < 1024 bits
|
| // is encountered, CSSM returns CSSMERR_TP_VERIFY_ACTION_FAILED and adds
|
|
|