diff options
author | rsleevi@chromium.org <rsleevi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-22 04:50:24 +0000 |
---|---|---|
committer | rsleevi@chromium.org <rsleevi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-03-22 04:50:24 +0000 |
commit | 62b23c2fcde411a2198af403161050906f16f079 (patch) | |
tree | 7d537427f11deef843478e72efc210ca4e613e4b | |
parent | 305a8b341c1f7e73b48f9f01d0b5cadec5cd7c36 (diff) | |
download | chromium_src-62b23c2fcde411a2198af403161050906f16f079.zip chromium_src-62b23c2fcde411a2198af403161050906f16f079.tar.gz chromium_src-62b23c2fcde411a2198af403161050906f16f079.tar.bz2 |
Move X509Certificate::Verify into CertVerifyProc
With this split, CertVerifyProc is responsible for
interacting with the underlying PKIX path building and
verification library, while X509Certificate is responsible
for parsing certificates with the underlying crypto library
and exposing a common interface for higher-level code such
as UI.
BUG=114343
TEST=net_unittests
Review URL: https://chromiumcodereview.appspot.com/9691054
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@128172 0039d316-1c4b-4281-b951-d872f2087c98
25 files changed, 3186 insertions, 2867 deletions
diff --git a/chrome/browser/ui/cocoa/certificate_viewer_mac.mm b/chrome/browser/ui/cocoa/certificate_viewer_mac.mm index 0a65a31..82ca94b 100644 --- a/chrome/browser/ui/cocoa/certificate_viewer_mac.mm +++ b/chrome/browser/ui/cocoa/certificate_viewer_mac.mm @@ -12,6 +12,7 @@ #include "base/mac/foundation_util.h" #include "base/mac/scoped_cftyperef.h" #include "net/base/x509_certificate.h" +#include "net/base/x509_util_mac.h" void ShowCertificateViewer(gfx::NativeWindow parent, net::X509Certificate* cert) { @@ -42,7 +43,7 @@ void ShowCertificateViewer(gfx::NativeWindow parent, // Add a basic X.509 policy, in order to match the behaviour of // SFCertificatePanel when no policies are specified. SecPolicyRef basic_policy = NULL; - OSStatus status = net::X509Certificate::CreateBasicX509Policy(&basic_policy); + OSStatus status = net::x509_util::CreateBasicX509Policy(&basic_policy); if (status != noErr) { NOTREACHED(); return; @@ -50,7 +51,7 @@ void ShowCertificateViewer(gfx::NativeWindow parent, CFArrayAppendValue(policies, basic_policy); CFRelease(basic_policy); - status = net::X509Certificate::CreateRevocationPolicies(false, policies); + status = net::x509_util::CreateRevocationPolicies(false, policies); if (status != noErr) { NOTREACHED(); return; diff --git a/chrome/browser/ui/cocoa/ssl_client_certificate_selector.mm b/chrome/browser/ui/cocoa/ssl_client_certificate_selector.mm index 7a06059..fe359ba 100644 --- a/chrome/browser/ui/cocoa/ssl_client_certificate_selector.mm +++ b/chrome/browser/ui/cocoa/ssl_client_certificate_selector.mm @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -21,6 +21,7 @@ #include "grit/generated_resources.h" #include "net/base/ssl_cert_request_info.h" #include "net/base/x509_certificate.h" +#include "net/base/x509_util_mac.h" #include "ui/base/l10n/l10n_util_mac.h" using content::BrowserThread; @@ -218,7 +219,7 @@ void ShowSSLClientCertificateSelector( [panel setDefaultButtonTitle:l10n_util::GetNSString(IDS_OK)]; [panel setAlternateButtonTitle:l10n_util::GetNSString(IDS_CANCEL)]; SecPolicyRef sslPolicy; - if (net::X509Certificate::CreateSSLClientPolicy(&sslPolicy) == noErr) { + if (net::x509_util::CreateSSLClientPolicy(&sslPolicy) == noErr) { [panel setPolicies:(id)sslPolicy]; CFRelease(sslPolicy); } diff --git a/net/base/cert_verify_proc.cc b/net/base/cert_verify_proc.cc index 0b98199..7039efc 100644 --- a/net/base/cert_verify_proc.cc +++ b/net/base/cert_verify_proc.cc @@ -4,32 +4,61 @@ #include "net/base/cert_verify_proc.h" +#include "base/metrics/histogram.h" +#include "base/sha1.h" #include "build/build_config.h" +#include "net/base/cert_status_flags.h" +#include "net/base/cert_verify_result.h" +#include "net/base/crl_set.h" +#include "net/base/net_errors.h" #include "net/base/x509_certificate.h" -namespace net { +#if defined(USE_NSS) +#include "net/base/cert_verify_proc_nss.h" +#elif defined(USE_OPENSSL) +#include "net/base/cert_verify_proc_openssl.h" +#elif defined(OS_MACOSX) +#include "net/base/cert_verify_proc_mac.h" +#elif defined(OS_WIN) +#include "net/base/cert_verify_proc_win.h" +#else +#error Implement certificate verification. +#endif + -// TODO(rsleevi): Temporary refactoring - http://crbug.com/114343 -class CertVerifyProcStub : public CertVerifyProc { - public: - CertVerifyProcStub() {} +namespace net { - private: - virtual ~CertVerifyProcStub() {} +namespace { - // CertVerifyProc implementation - virtual int VerifyInternal(X509Certificate* cert, - const std::string& hostname, - int flags, - CRLSet* crl_set, - CertVerifyResult* verify_result) OVERRIDE { - return cert->Verify(hostname, flags, crl_set, verify_result); +// Returns true if |type| is |kPublicKeyTypeRSA| or |kPublicKeyTypeDSA|, and +// if |size_bits| is < 1024. Note that this means there may be false +// negatives: keys for other algorithms and which are weak will pass this +// test. +bool IsWeakKey(X509Certificate::PublicKeyType type, size_t size_bits) { + switch (type) { + case X509Certificate::kPublicKeyTypeRSA: + case X509Certificate::kPublicKeyTypeDSA: + return size_bits < 1024; + default: + return false; } -}; +} + +} // namespace // static CertVerifyProc* CertVerifyProc::CreateDefault() { - return new CertVerifyProcStub(); +#if defined(USE_NSS) + return new CertVerifyProcNSS(); +#elif defined(USE_OPENSSL) + return new CertVerifyProcOpenSSL(); +#elif defined(OS_MACOSX) + return new CertVerifyProcMac(); +#elif defined(OS_WIN) + return new CertVerifyProcWin(); +#else + return NULL; +#endif } CertVerifyProc::CertVerifyProc() {} @@ -41,7 +70,203 @@ int CertVerifyProc::Verify(X509Certificate* cert, int flags, CRLSet* crl_set, CertVerifyResult* verify_result) { - return VerifyInternal(cert, hostname, flags, crl_set, verify_result); + verify_result->Reset(); + verify_result->verified_cert = cert; + + if (IsBlacklisted(cert)) { + verify_result->cert_status |= CERT_STATUS_REVOKED; + return ERR_CERT_REVOKED; + } + + // If EV verification was requested and no CRLSet is present, or if the + // CRLSet has expired, then enable online revocation checks. If the online + // check fails, EV status won't be shown. + // + // A possible optimisation is to only enable online revocation checking in + // the event that the leaf certificate appears to include a EV policy ID. + // However, it's expected that having a current CRLSet will be very common. + if ((flags & X509Certificate::VERIFY_EV_CERT) && + (!crl_set || crl_set->IsExpired())) { + flags |= X509Certificate::VERIFY_REV_CHECKING_ENABLED; + } + + int rv = VerifyInternal(cert, hostname, flags, crl_set, verify_result); + + // This check is done after VerifyInternal so that VerifyInternal can fill + // in the list of public key hashes. + if (IsPublicKeyBlacklisted(verify_result->public_key_hashes)) { + verify_result->cert_status |= CERT_STATUS_REVOKED; + rv = MapCertStatusToNetError(verify_result->cert_status); + } + + // Check for weak keys in the entire verified chain. + size_t size_bits = 0; + X509Certificate::PublicKeyType type = + X509Certificate::kPublicKeyTypeUnknown; + bool weak_key = false; + + X509Certificate::GetPublicKeyInfo( + verify_result->verified_cert->os_cert_handle(), &size_bits, &type); + if (IsWeakKey(type, size_bits)) { + weak_key = true; + } else { + const X509Certificate::OSCertHandles& intermediates = + verify_result->verified_cert->GetIntermediateCertificates(); + for (size_t i = 0; i < intermediates.size(); ++i) { + X509Certificate::GetPublicKeyInfo(intermediates[i], &size_bits, &type); + if (IsWeakKey(type, size_bits)) + weak_key = true; + } + } + + if (weak_key) { + verify_result->cert_status |= CERT_STATUS_WEAK_KEY; + // Avoid replacing a more serious error, such as an OS/library failure, + // by ensuring that if verification failed, it failed with a certificate + // error. + if (rv == OK || IsCertificateError(rv)) + rv = MapCertStatusToNetError(verify_result->cert_status); + } + + // Treat certificates signed using broken signature algorithms as invalid. + if (verify_result->has_md2 || verify_result->has_md4) { + verify_result->cert_status |= CERT_STATUS_INVALID; + rv = MapCertStatusToNetError(verify_result->cert_status); + } + + // Flag certificates using weak signature algorithms. + if (verify_result->has_md5) { + verify_result->cert_status |= CERT_STATUS_WEAK_SIGNATURE_ALGORITHM; + // Avoid replacing a more serious error, such as an OS/library failure, + // by ensuring that if verification failed, it failed with a certificate + // error. + if (rv == OK || IsCertificateError(rv)) + rv = MapCertStatusToNetError(verify_result->cert_status); + } + + return rv; +} + +// static +bool CertVerifyProc::IsBlacklisted(X509Certificate* cert) { + static const unsigned kComodoSerialBytes = 16; + static const uint8 kComodoSerials[][kComodoSerialBytes] = { + // Not a real certificate. For testing only. + {0x07,0x7a,0x59,0xbc,0xd5,0x34,0x59,0x60,0x1c,0xa6,0x90,0x72,0x67,0xa6,0xdd,0x1c}, + + // The next nine certificates all expire on Fri Mar 14 23:59:59 2014. + // Some serial numbers actually have a leading 0x00 byte required to + // encode a positive integer in DER if the most significant bit is 0. + // We omit the leading 0x00 bytes to make all serial numbers 16 bytes. + + // Subject: CN=mail.google.com + // subjectAltName dNSName: mail.google.com, www.mail.google.com + {0x04,0x7e,0xcb,0xe9,0xfc,0xa5,0x5f,0x7b,0xd0,0x9e,0xae,0x36,0xe1,0x0c,0xae,0x1e}, + // Subject: CN=global trustee + // subjectAltName dNSName: global trustee + // Note: not a CA certificate. + {0xd8,0xf3,0x5f,0x4e,0xb7,0x87,0x2b,0x2d,0xab,0x06,0x92,0xe3,0x15,0x38,0x2f,0xb0}, + // Subject: CN=login.live.com + // subjectAltName dNSName: login.live.com, www.login.live.com + {0xb0,0xb7,0x13,0x3e,0xd0,0x96,0xf9,0xb5,0x6f,0xae,0x91,0xc8,0x74,0xbd,0x3a,0xc0}, + // Subject: CN=addons.mozilla.org + // subjectAltName dNSName: addons.mozilla.org, www.addons.mozilla.org + {0x92,0x39,0xd5,0x34,0x8f,0x40,0xd1,0x69,0x5a,0x74,0x54,0x70,0xe1,0xf2,0x3f,0x43}, + // Subject: CN=login.skype.com + // subjectAltName dNSName: login.skype.com, www.login.skype.com + {0xe9,0x02,0x8b,0x95,0x78,0xe4,0x15,0xdc,0x1a,0x71,0x0a,0x2b,0x88,0x15,0x44,0x47}, + // Subject: CN=login.yahoo.com + // subjectAltName dNSName: login.yahoo.com, www.login.yahoo.com + {0xd7,0x55,0x8f,0xda,0xf5,0xf1,0x10,0x5b,0xb2,0x13,0x28,0x2b,0x70,0x77,0x29,0xa3}, + // Subject: CN=www.google.com + // subjectAltName dNSName: www.google.com, google.com + {0xf5,0xc8,0x6a,0xf3,0x61,0x62,0xf1,0x3a,0x64,0xf5,0x4f,0x6d,0xc9,0x58,0x7c,0x06}, + // Subject: CN=login.yahoo.com + // subjectAltName dNSName: login.yahoo.com + {0x39,0x2a,0x43,0x4f,0x0e,0x07,0xdf,0x1f,0x8a,0xa3,0x05,0xde,0x34,0xe0,0xc2,0x29}, + // Subject: CN=login.yahoo.com + // subjectAltName dNSName: login.yahoo.com + {0x3e,0x75,0xce,0xd4,0x6b,0x69,0x30,0x21,0x21,0x88,0x30,0xae,0x86,0xa8,0x2a,0x71}, + }; + + const std::string& serial_number = cert->serial_number(); + if (!serial_number.empty() && (serial_number[0] & 0x80) != 0) { + // This is a negative serial number, which isn't technically allowed but + // which probably happens. In order to avoid confusing a negative serial + // number with a positive one once the leading zeros have been removed, we + // disregard it. + return false; + } + + base::StringPiece serial(serial_number); + // Remove leading zeros. + while (serial.size() > 1 && serial[0] == 0) + serial.remove_prefix(1); + + if (serial.size() == kComodoSerialBytes) { + for (unsigned i = 0; i < arraysize(kComodoSerials); i++) { + if (memcmp(kComodoSerials[i], serial.data(), kComodoSerialBytes) == 0) { + UMA_HISTOGRAM_ENUMERATION("Net.SSLCertBlacklisted", i, + arraysize(kComodoSerials) + 1); + return true; + } + } + } + + return false; +} + +// static +bool CertVerifyProc::IsPublicKeyBlacklisted( + const std::vector<SHA1Fingerprint>& public_key_hashes) { + static const unsigned kNumHashes = 8; + static const uint8 kHashes[kNumHashes][base::kSHA1Length] = { + // Subject: CN=DigiNotar Root CA + // Issuer: CN=Entrust.net x2 and self-signed + {0x41, 0x0f, 0x36, 0x36, 0x32, 0x58, 0xf3, 0x0b, 0x34, 0x7d, + 0x12, 0xce, 0x48, 0x63, 0xe4, 0x33, 0x43, 0x78, 0x06, 0xa8}, + // Subject: CN=DigiNotar Cyber CA + // Issuer: CN=GTE CyberTrust Global Root + {0xc4, 0xf9, 0x66, 0x37, 0x16, 0xcd, 0x5e, 0x71, 0xd6, 0x95, + 0x0b, 0x5f, 0x33, 0xce, 0x04, 0x1c, 0x95, 0xb4, 0x35, 0xd1}, + // Subject: CN=DigiNotar Services 1024 CA + // Issuer: CN=Entrust.net + {0xe2, 0x3b, 0x8d, 0x10, 0x5f, 0x87, 0x71, 0x0a, 0x68, 0xd9, + 0x24, 0x80, 0x50, 0xeb, 0xef, 0xc6, 0x27, 0xbe, 0x4c, 0xa6}, + // Subject: CN=DigiNotar PKIoverheid CA Organisatie - G2 + // Issuer: CN=Staat der Nederlanden Organisatie CA - G2 + {0x7b, 0x2e, 0x16, 0xbc, 0x39, 0xbc, 0xd7, 0x2b, 0x45, 0x6e, + 0x9f, 0x05, 0x5d, 0x1d, 0xe6, 0x15, 0xb7, 0x49, 0x45, 0xdb}, + // Subject: CN=DigiNotar PKIoverheid CA Overheid en Bedrijven + // Issuer: CN=Staat der Nederlanden Overheid CA + {0xe8, 0xf9, 0x12, 0x00, 0xc6, 0x5c, 0xee, 0x16, 0xe0, 0x39, + 0xb9, 0xf8, 0x83, 0x84, 0x16, 0x61, 0x63, 0x5f, 0x81, 0xc5}, + // Subject: O=Digicert Sdn. Bhd. + // Issuer: CN=GTE CyberTrust Global Root + // Expires: Jul 17 15:16:54 2012 GMT + {0x01, 0x29, 0xbc, 0xd5, 0xb4, 0x48, 0xae, 0x8d, 0x24, 0x96, + 0xd1, 0xc3, 0xe1, 0x97, 0x23, 0x91, 0x90, 0x88, 0xe1, 0x52}, + // Subject: O=Digicert Sdn. Bhd. + // Issuer: CN=Entrust.net Certification Authority (2048) + // Expires: Jul 16 17:53:37 2015 GMT + {0xd3, 0x3c, 0x5b, 0x41, 0xe4, 0x5c, 0xc4, 0xb3, 0xbe, 0x9a, + 0xd6, 0x95, 0x2c, 0x4e, 0xcc, 0x25, 0x28, 0x03, 0x29, 0x81}, + // Issuer: CN=Trustwave Organization Issuing CA, Level 2 + // Covers two certificates, the latter of which expires Apr 15 21:09:30 + // 2021 GMT. + {0xe1, 0x2d, 0x89, 0xf5, 0x6d, 0x22, 0x76, 0xf8, 0x30, 0xe6, + 0xce, 0xaf, 0xa6, 0x6c, 0x72, 0x5c, 0x0b, 0x41, 0xa9, 0x32}, + }; + + for (unsigned i = 0; i < kNumHashes; i++) { + for (std::vector<SHA1Fingerprint>::const_iterator + j = public_key_hashes.begin(); j != public_key_hashes.end(); ++j) { + if (memcmp(j->data, kHashes[i], base::kSHA1Length) == 0) + return true; + } + } + + return false; } } // namespace net diff --git a/net/base/cert_verify_proc.h b/net/base/cert_verify_proc.h index ee737e1..d379f16 100644 --- a/net/base/cert_verify_proc.h +++ b/net/base/cert_verify_proc.h @@ -7,9 +7,12 @@ #pragma once #include <string> +#include <vector> +#include "base/gtest_prod_util.h" #include "base/memory/ref_counted.h" #include "net/base/net_export.h" +#include "net/base/x509_cert_types.h" namespace net { @@ -55,6 +58,7 @@ class NET_EXPORT CertVerifyProc protected: friend class base::RefCountedThreadSafe<CertVerifyProc>; + FRIEND_TEST_ALL_PREFIXES(CertVerifyProcTest, DigiNotarCerts); CertVerifyProc(); virtual ~CertVerifyProc(); @@ -67,6 +71,14 @@ class NET_EXPORT CertVerifyProc int flags, CRLSet* crl_set, CertVerifyResult* verify_result) = 0; + + // Returns true if |cert| is explicitly blacklisted. + static bool IsBlacklisted(X509Certificate* cert); + + // IsPublicKeyBlacklisted returns true iff one of |public_key_hashes| (which + // are SHA1 hashes of SubjectPublicKeyInfo structures) is explicitly blocked. + static bool IsPublicKeyBlacklisted( + const std::vector<SHA1Fingerprint>& public_key_hashes); }; } // namespace net diff --git a/net/base/cert_verify_proc_mac.cc b/net/base/cert_verify_proc_mac.cc new file mode 100644 index 0000000..959378e --- /dev/null +++ b/net/base/cert_verify_proc_mac.cc @@ -0,0 +1,550 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/base/cert_verify_proc_mac.h" + +#include <CommonCrypto/CommonDigest.h> +#include <CoreServices/CoreServices.h> +#include <Security/Security.h> + +#include "base/logging.h" +#include "base/mac/mac_logging.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/sha1.h" +#include "base/string_piece.h" +#include "crypto/nss_util.h" +#include "crypto/sha2.h" +#include "net/base/asn1_util.h" +#include "net/base/cert_status_flags.h" +#include "net/base/cert_verify_result.h" +#include "net/base/crl_set.h" +#include "net/base/net_errors.h" +#include "net/base/test_root_certs.h" +#include "net/base/x509_certificate.h" +#include "net/base/x509_certificate_known_roots_mac.h" +#include "net/base/x509_util_mac.h" + +// From 10.7.2 libsecurity_keychain-55035/lib/SecTrustPriv.h, for use with +// SecTrustCopyExtendedResult. +#ifndef kSecEVOrganizationName +#define kSecEVOrganizationName CFSTR("Organization") +#endif + +using base::mac::ScopedCFTypeRef; + +namespace net { + +namespace { + +typedef OSStatus (*SecTrustCopyExtendedResultFuncPtr)(SecTrustRef, + CFDictionaryRef*); + +int NetErrorFromOSStatus(OSStatus status) { + switch (status) { + case noErr: + return OK; + case errSecNotAvailable: + case errSecNoCertificateModule: + case errSecNoPolicyModule: + return ERR_NOT_IMPLEMENTED; + case errSecAuthFailed: + return ERR_ACCESS_DENIED; + default: { + OSSTATUS_LOG(ERROR, status) << "Unknown error mapped to ERR_FAILED"; + return ERR_FAILED; + } + } +} + +CertStatus CertStatusFromOSStatus(OSStatus status) { + switch (status) { + case noErr: + return 0; + + case CSSMERR_TP_INVALID_ANCHOR_CERT: + case CSSMERR_TP_NOT_TRUSTED: + case CSSMERR_TP_INVALID_CERT_AUTHORITY: + return CERT_STATUS_AUTHORITY_INVALID; + + case CSSMERR_TP_CERT_EXPIRED: + case CSSMERR_TP_CERT_NOT_VALID_YET: + // "Expired" and "not yet valid" collapse into a single status. + return CERT_STATUS_DATE_INVALID; + + case CSSMERR_TP_CERT_REVOKED: + case CSSMERR_TP_CERT_SUSPENDED: + return CERT_STATUS_REVOKED; + + case CSSMERR_APPLETP_HOSTNAME_MISMATCH: + return CERT_STATUS_COMMON_NAME_INVALID; + + case CSSMERR_APPLETP_CRL_NOT_FOUND: + case CSSMERR_APPLETP_OCSP_UNAVAILABLE: + case CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK: + return CERT_STATUS_NO_REVOCATION_MECHANISM; + + case CSSMERR_APPLETP_CRL_EXPIRED: + case CSSMERR_APPLETP_CRL_NOT_VALID_YET: + case CSSMERR_APPLETP_CRL_SERVER_DOWN: + case CSSMERR_APPLETP_CRL_NOT_TRUSTED: + case CSSMERR_APPLETP_CRL_INVALID_ANCHOR_CERT: + case CSSMERR_APPLETP_CRL_POLICY_FAIL: + case CSSMERR_APPLETP_OCSP_BAD_RESPONSE: + case CSSMERR_APPLETP_OCSP_BAD_REQUEST: + case CSSMERR_APPLETP_OCSP_STATUS_UNRECOGNIZED: + case CSSMERR_APPLETP_NETWORK_FAILURE: + case CSSMERR_APPLETP_OCSP_NOT_TRUSTED: + case CSSMERR_APPLETP_OCSP_INVALID_ANCHOR_CERT: + case CSSMERR_APPLETP_OCSP_SIG_ERROR: + case CSSMERR_APPLETP_OCSP_NO_SIGNER: + case CSSMERR_APPLETP_OCSP_RESP_MALFORMED_REQ: + case CSSMERR_APPLETP_OCSP_RESP_INTERNAL_ERR: + case CSSMERR_APPLETP_OCSP_RESP_TRY_LATER: + case CSSMERR_APPLETP_OCSP_RESP_SIG_REQUIRED: + case CSSMERR_APPLETP_OCSP_RESP_UNAUTHORIZED: + case CSSMERR_APPLETP_OCSP_NONCE_MISMATCH: + // We asked for a revocation check, but didn't get it. + return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION; + + case CSSMERR_APPLETP_CRL_BAD_URI: + case CSSMERR_APPLETP_IDP_FAIL: + return CERT_STATUS_INVALID; + + default: { + // Failure was due to something Chromium doesn't define a + // specific status for (such as basic constraints violation, or + // unknown critical extension) + OSSTATUS_LOG(WARNING, status) + << "Unknown error mapped to CERT_STATUS_INVALID"; + return CERT_STATUS_INVALID; + } + } +} + +// Creates a series of SecPolicyRefs to be added to a SecTrustRef used to +// validate a certificate for an SSL server. |hostname| contains the name of +// the SSL server that the certificate should be verified against. |flags| is +// a bitwise-OR of VerifyFlags that can further alter how trust is validated, +// such as how revocation is checked. If successful, returns noErr, and +// stores the resultant array of SecPolicyRefs in |policies|. +OSStatus CreateTrustPolicies(const std::string& hostname, + int flags, + ScopedCFTypeRef<CFArrayRef>* policies) { + ScopedCFTypeRef<CFMutableArrayRef> local_policies( + CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks)); + if (!local_policies) + return memFullErr; + + SecPolicyRef ssl_policy; + OSStatus status = x509_util::CreateSSLServerPolicy(hostname, &ssl_policy); + if (status) + return status; + CFArrayAppendValue(local_policies, ssl_policy); + CFRelease(ssl_policy); + + // Explicitly add revocation policies, in order to override system + // revocation checking policies and instead respect the application-level + // revocation preference. + status = x509_util::CreateRevocationPolicies( + (flags & X509Certificate::VERIFY_REV_CHECKING_ENABLED), + local_policies); + if (status) + return status; + + policies->reset(local_policies.release()); + return noErr; +} + +// Saves some information about the certificate chain |cert_chain| in +// |*verify_result|. The caller MUST initialize |*verify_result| before +// calling this function. +void GetCertChainInfo(CFArrayRef cert_chain, + CSSM_TP_APPLE_EVIDENCE_INFO* chain_info, + CertVerifyResult* verify_result) { + SecCertificateRef verified_cert = NULL; + std::vector<SecCertificateRef> verified_chain; + for (CFIndex i = 0, count = CFArrayGetCount(cert_chain); i < count; ++i) { + SecCertificateRef chain_cert = reinterpret_cast<SecCertificateRef>( + const_cast<void*>(CFArrayGetValueAtIndex(cert_chain, i))); + if (i == 0) { + verified_cert = chain_cert; + } else { + verified_chain.push_back(chain_cert); + } + + if ((chain_info[i].StatusBits & CSSM_CERT_STATUS_IS_IN_ANCHORS) || + (chain_info[i].StatusBits & CSSM_CERT_STATUS_IS_ROOT)) { + // The current certificate is either in the user's trusted store or is + // a root (self-signed) certificate. Ignore the signature algorithm for + // these certificates, as it is meaningless for security. We allow + // self-signed certificates (i == 0 & IS_ROOT), since we accept that + // any security assertions by such a cert are inherently meaningless. + continue; + } + + x509_util::CSSMCachedCertificate cached_cert; + OSStatus status = cached_cert.Init(chain_cert); + if (status) + continue; + x509_util::CSSMFieldValue signature_field; + status = cached_cert.GetField(&CSSMOID_X509V1SignatureAlgorithm, + &signature_field); + if (status || !signature_field.field()) + continue; + // Match the behaviour of OS X system tools and defensively check that + // sizes are appropriate. This would indicate a critical failure of the + // OS X certificate library, but based on history, it is best to play it + // safe. + const CSSM_X509_ALGORITHM_IDENTIFIER* sig_algorithm = + signature_field.GetAs<CSSM_X509_ALGORITHM_IDENTIFIER>(); + if (!sig_algorithm) + continue; + + const CSSM_OID* alg_oid = &sig_algorithm->algorithm; + if (CSSMOIDEqual(alg_oid, &CSSMOID_MD2WithRSA)) { + verify_result->has_md2 = true; + if (i != 0) + verify_result->has_md2_ca = true; + } else if (CSSMOIDEqual(alg_oid, &CSSMOID_MD4WithRSA)) { + verify_result->has_md4 = true; + } else if (CSSMOIDEqual(alg_oid, &CSSMOID_MD5WithRSA)) { + verify_result->has_md5 = true; + if (i != 0) + verify_result->has_md5_ca = true; + } + } + if (!verified_cert) + return; + + verify_result->verified_cert = + X509Certificate::CreateFromHandle(verified_cert, verified_chain); +} + +void AppendPublicKeyHashes(CFArrayRef chain, + std::vector<SHA1Fingerprint>* hashes) { + const CFIndex n = CFArrayGetCount(chain); + for (CFIndex i = 0; i < n; i++) { + SecCertificateRef cert = reinterpret_cast<SecCertificateRef>( + const_cast<void*>(CFArrayGetValueAtIndex(chain, i))); + + CSSM_DATA cert_data; + OSStatus err = SecCertificateGetData(cert, &cert_data); + DCHECK_EQ(err, noErr); + base::StringPiece der_bytes(reinterpret_cast<const char*>(cert_data.Data), + cert_data.Length); + base::StringPiece spki_bytes; + if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki_bytes)) + continue; + + SHA1Fingerprint hash; + CC_SHA1(spki_bytes.data(), spki_bytes.size(), hash.data); + hashes->push_back(hash); + } +} + +bool CheckRevocationWithCRLSet(CFArrayRef chain, CRLSet* crl_set) { + if (CFArrayGetCount(chain) == 0) + return true; + + // We iterate from the root certificate down to the leaf, keeping track of + // the issuer's SPKI at each step. + std::string issuer_spki_hash; + for (CFIndex i = CFArrayGetCount(chain) - 1; i >= 0; i--) { + SecCertificateRef cert = reinterpret_cast<SecCertificateRef>( + const_cast<void*>(CFArrayGetValueAtIndex(chain, i))); + + CSSM_DATA cert_data; + OSStatus err = SecCertificateGetData(cert, &cert_data); + if (err != noErr) { + NOTREACHED(); + continue; + } + base::StringPiece der_bytes(reinterpret_cast<const char*>(cert_data.Data), + cert_data.Length); + base::StringPiece spki; + if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki)) { + NOTREACHED(); + continue; + } + + const std::string spki_hash = crypto::SHA256HashString(spki); + x509_util::CSSMCachedCertificate cached_cert; + if (cached_cert.Init(cert) != CSSM_OK) { + NOTREACHED(); + continue; + } + x509_util::CSSMFieldValue serial_number; + err = cached_cert.GetField(&CSSMOID_X509V1SerialNumber, &serial_number); + if (err || !serial_number.field()) { + NOTREACHED(); + continue; + } + + base::StringPiece serial( + reinterpret_cast<const char*>(serial_number.field()->Data), + serial_number.field()->Length); + + CRLSet::Result result = crl_set->CheckSPKI(spki_hash); + + if (result != CRLSet::REVOKED && !issuer_spki_hash.empty()) + result = crl_set->CheckSerial(serial, issuer_spki_hash); + + issuer_spki_hash = spki_hash; + + switch (result) { + case CRLSet::REVOKED: + return false; + case CRLSet::UNKNOWN: + case CRLSet::GOOD: + continue; + default: + NOTREACHED(); + return false; + } + } + + return true; +} + +// IsIssuedByKnownRoot returns true if the given chain is rooted at a root CA +// that we recognise as a standard root. +// static +bool IsIssuedByKnownRoot(CFArrayRef chain) { + int n = CFArrayGetCount(chain); + if (n < 1) + return false; + SecCertificateRef root_ref = reinterpret_cast<SecCertificateRef>( + const_cast<void*>(CFArrayGetValueAtIndex(chain, n - 1))); + SHA1Fingerprint hash = X509Certificate::CalculateFingerprint(root_ref); + return IsSHA1HashInSortedArray( + hash, &kKnownRootCertSHA1Hashes[0][0], sizeof(kKnownRootCertSHA1Hashes)); +} + +} // namespace + +CertVerifyProcMac::CertVerifyProcMac() {} + +CertVerifyProcMac::~CertVerifyProcMac() {} + +int CertVerifyProcMac::VerifyInternal(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + CertVerifyResult* verify_result) { + ScopedCFTypeRef<CFArrayRef> trust_policies; + OSStatus status = CreateTrustPolicies(hostname, flags, &trust_policies); + if (status) + return NetErrorFromOSStatus(status); + + // Create and configure a SecTrustRef, which takes our certificate(s) + // and our SSL SecPolicyRef. SecTrustCreateWithCertificates() takes an + // 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()); + + // From here on, only one thread can be active at a time. We have had a number + // of sporadic crashes in the SecTrustEvaluate call below, way down inside + // Apple's cert code, which we suspect are caused by a thread-safety issue. + // So as a speculative fix allow only one thread to use SecTrust on this cert. + base::AutoLock lock(verification_lock_); + + SecTrustRef trust_ref = NULL; + status = SecTrustCreateWithCertificates(cert_array, trust_policies, + &trust_ref); + if (status) + return NetErrorFromOSStatus(status); + ScopedCFTypeRef<SecTrustRef> scoped_trust_ref(trust_ref); + + if (TestRootCerts::HasInstance()) { + status = TestRootCerts::GetInstance()->FixupSecTrustRef(trust_ref); + if (status) + return NetErrorFromOSStatus(status); + } + + CSSM_APPLE_TP_ACTION_DATA tp_action_data; + memset(&tp_action_data, 0, sizeof(tp_action_data)); + tp_action_data.Version = CSSM_APPLE_TP_ACTION_VERSION; + // Allow CSSM to download any missing intermediate certificates if an + // authorityInfoAccess extension or issuerAltName extension is present. + tp_action_data.ActionFlags = CSSM_TP_ACTION_FETCH_CERT_FROM_NET | + CSSM_TP_ACTION_TRUST_SETTINGS; + + if (flags & X509Certificate::VERIFY_REV_CHECKING_ENABLED) { + // Require a positive result from an OCSP responder or a CRL (or both) + // for every certificate in the chain. The Apple TP automatically + // excludes the self-signed root from this requirement. If a certificate + // is missing both a crlDistributionPoints extension and an + // authorityInfoAccess extension with an OCSP responder URL, then we + // will get a kSecTrustResultRecoverableTrustFailure back from + // SecTrustEvaluate(), with a + // CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK error code. In that case, + // we'll set our own result to include + // CERT_STATUS_NO_REVOCATION_MECHANISM. If one or both extensions are + // present, and a check fails (server unavailable, OCSP retry later, + // signature mismatch), then we'll set our own result to include + // CERT_STATUS_UNABLE_TO_CHECK_REVOCATION. + tp_action_data.ActionFlags |= CSSM_TP_ACTION_REQUIRE_REV_PER_CERT; + verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED; + + // Note, even if revocation checking is disabled, SecTrustEvaluate() will + // modify the OCSP options so as to attempt OCSP checking if it believes a + // certificate may chain to an EV root. However, because network fetches + // are disabled in CreateTrustPolicies() when revocation checking is + // disabled, these will only go against the local cache. + } + + CFDataRef action_data_ref = + CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, + reinterpret_cast<UInt8*>(&tp_action_data), + sizeof(tp_action_data), kCFAllocatorNull); + if (!action_data_ref) + return ERR_OUT_OF_MEMORY; + ScopedCFTypeRef<CFDataRef> scoped_action_data_ref(action_data_ref); + status = SecTrustSetParameters(trust_ref, CSSM_TP_ACTION_DEFAULT, + action_data_ref); + if (status) + return NetErrorFromOSStatus(status); + + // Verify the certificate. A non-zero result from SecTrustGetResult() + // indicates that some fatal error occurred and the chain couldn't be + // processed, not that the chain contains no errors. We need to examine the + // output of SecTrustGetResult() to determine that. + SecTrustResultType trust_result; + status = SecTrustEvaluate(trust_ref, &trust_result); + if (status) + return NetErrorFromOSStatus(status); + CFArrayRef completed_chain = NULL; + CSSM_TP_APPLE_EVIDENCE_INFO* chain_info; + status = SecTrustGetResult(trust_ref, &trust_result, &completed_chain, + &chain_info); + if (status) + return NetErrorFromOSStatus(status); + ScopedCFTypeRef<CFArrayRef> scoped_completed_chain(completed_chain); + + if (crl_set && !CheckRevocationWithCRLSet(completed_chain, crl_set)) + verify_result->cert_status |= CERT_STATUS_REVOKED; + + GetCertChainInfo(scoped_completed_chain.get(), chain_info, verify_result); + + // Evaluate the results + OSStatus cssm_result; + switch (trust_result) { + case kSecTrustResultUnspecified: + case kSecTrustResultProceed: + // Certificate chain is valid and trusted ("unspecified" indicates that + // the user has not explicitly set a trust setting) + break; + + case kSecTrustResultDeny: + case kSecTrustResultConfirm: + // Certificate chain is explicitly untrusted. For kSecTrustResultConfirm, + // we're following what Secure Transport does and treating it as + // "deny". + verify_result->cert_status |= CERT_STATUS_AUTHORITY_INVALID; + break; + + case kSecTrustResultRecoverableTrustFailure: + // Certificate chain has a failure that can be overridden by the user. + status = SecTrustGetCssmResultCode(trust_ref, &cssm_result); + if (status) + return NetErrorFromOSStatus(status); + verify_result->cert_status |= CertStatusFromOSStatus(cssm_result); + // Walk the chain of error codes in the CSSM_TP_APPLE_EVIDENCE_INFO + // structure which can catch multiple errors from each certificate. + for (CFIndex index = 0, chain_count = CFArrayGetCount(completed_chain); + index < chain_count; ++index) { + if (chain_info[index].StatusBits & CSSM_CERT_STATUS_EXPIRED || + chain_info[index].StatusBits & CSSM_CERT_STATUS_NOT_VALID_YET) + verify_result->cert_status |= CERT_STATUS_DATE_INVALID; + if (!IsCertStatusError(verify_result->cert_status) && + chain_info[index].NumStatusCodes == 0) { + LOG(WARNING) << "chain_info[" << index << "].NumStatusCodes is 0" + ", chain_info[" << index << "].StatusBits is " + << chain_info[index].StatusBits; + } + for (uint32 status_code_index = 0; + status_code_index < chain_info[index].NumStatusCodes; + ++status_code_index) { + verify_result->cert_status |= CertStatusFromOSStatus( + chain_info[index].StatusCodes[status_code_index]); + } + } + if (!IsCertStatusError(verify_result->cert_status)) { + LOG(ERROR) << "cssm_result=" << cssm_result; + verify_result->cert_status |= CERT_STATUS_INVALID; + NOTREACHED(); + } + break; + + default: + status = SecTrustGetCssmResultCode(trust_ref, &cssm_result); + if (status) + return NetErrorFromOSStatus(status); + verify_result->cert_status |= CertStatusFromOSStatus(cssm_result); + if (!IsCertStatusError(verify_result->cert_status)) { + LOG(WARNING) << "trust_result=" << trust_result; + verify_result->cert_status |= CERT_STATUS_INVALID; + } + break; + } + + // Perform hostname verification independent of SecTrustEvaluate. In order to + // do so, mask off any reported name errors first. + verify_result->cert_status &= ~CERT_STATUS_COMMON_NAME_INVALID; + if (!cert->VerifyNameMatch(hostname)) + verify_result->cert_status |= CERT_STATUS_COMMON_NAME_INVALID; + + // TODO(wtc): Suppress CERT_STATUS_NO_REVOCATION_MECHANISM for now to be + // compatible with Windows, which in turn implements this behavior to be + // compatible with WinHTTP, which doesn't report this error (bug 3004). + verify_result->cert_status &= ~CERT_STATUS_NO_REVOCATION_MECHANISM; + + if (IsCertStatusError(verify_result->cert_status)) + return MapCertStatusToNetError(verify_result->cert_status); + + if (flags & X509Certificate::VERIFY_EV_CERT) { + // Determine the certificate's EV status using SecTrustCopyExtendedResult(), + // which we need to look up because the function wasn't added until + // Mac OS X 10.5.7. + // Note: "ExtendedResult" means extended validation results. + CFBundleRef bundle = + CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security")); + if (bundle) { + SecTrustCopyExtendedResultFuncPtr copy_extended_result = + reinterpret_cast<SecTrustCopyExtendedResultFuncPtr>( + CFBundleGetFunctionPointerForName(bundle, + CFSTR("SecTrustCopyExtendedResult"))); + if (copy_extended_result) { + CFDictionaryRef ev_dict_temp = NULL; + status = copy_extended_result(trust_ref, &ev_dict_temp); + ScopedCFTypeRef<CFDictionaryRef> ev_dict(ev_dict_temp); + ev_dict_temp = NULL; + if (status == noErr && ev_dict) { + // In 10.7.3, SecTrustCopyExtendedResult returns noErr and populates + // ev_dict even for non-EV certificates, but only EV certificates + // will cause ev_dict to contain kSecEVOrganizationName. In previous + // releases, SecTrustCopyExtendedResult would only return noErr and + // populate ev_dict for EV certificates, but would always include + // kSecEVOrganizationName in that case, so checking for this key is + // appropriate for all known versions of SecTrustCopyExtendedResult. + // The actual organization name is unneeded here and can be accessed + // through other means. All that matters here is the OS' conception + // of whether or not the certificate is EV. + if (CFDictionaryContainsKey(ev_dict, + kSecEVOrganizationName)) { + verify_result->cert_status |= CERT_STATUS_IS_EV; + } + } + } + } + } + + AppendPublicKeyHashes(completed_chain, &verify_result->public_key_hashes); + verify_result->is_issued_by_known_root = IsIssuedByKnownRoot(completed_chain); + + return OK; +} + +} // namespace net diff --git a/net/base/cert_verify_proc_mac.h b/net/base/cert_verify_proc_mac.h new file mode 100644 index 0000000..b60bf28 --- /dev/null +++ b/net/base/cert_verify_proc_mac.h @@ -0,0 +1,38 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_BASE_CERT_VERIFY_PROC_MAC_H_ +#define NET_BASE_CERT_VERIFY_PROC_MAC_H_ +#pragma once + +#include "net/base/cert_verify_proc.h" + +#include "base/synchronization/lock.h" + +namespace net { + +// Performs certificate path construction and validation using OS X's +// Security.framework. +class CertVerifyProcMac : public CertVerifyProc { + public: + CertVerifyProcMac(); + + protected: + virtual ~CertVerifyProcMac(); + + private: + virtual int VerifyInternal(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + CertVerifyResult* verify_result) OVERRIDE; + + // Blocks multiple threads from verifying the cert simultaneously. + // (Marked mutable because it's used in a const method.) + mutable base::Lock verification_lock_; +}; + +} // namespace net + +#endif // NET_BASE_CERT_VERIFY_PROC_MAC_H_ diff --git a/net/base/cert_verify_proc_nss.cc b/net/base/cert_verify_proc_nss.cc new file mode 100644 index 0000000..3108555 --- /dev/null +++ b/net/base/cert_verify_proc_nss.cc @@ -0,0 +1,781 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/base/cert_verify_proc_nss.h" + +#include <cert.h> +#include <nss.h> +#include <prerror.h> +#include <secerr.h> +#include <sechash.h> +#include <sslerr.h> + +#include "base/logging.h" +#include "crypto/nss_util.h" +#include "crypto/scoped_nss_types.h" +#include "crypto/sha2.h" +#include "net/base/asn1_util.h" +#include "net/base/cert_status_flags.h" +#include "net/base/cert_verify_result.h" +#include "net/base/crl_set.h" +#include "net/base/ev_root_ca_metadata.h" +#include "net/base/net_errors.h" +#include "net/base/x509_certificate.h" +#include "net/base/x509_util_nss.h" + +namespace net { + +namespace { + +class ScopedCERTCertificatePolicies { + public: + explicit ScopedCERTCertificatePolicies(CERTCertificatePolicies* policies) + : policies_(policies) {} + + ~ScopedCERTCertificatePolicies() { + if (policies_) + CERT_DestroyCertificatePoliciesExtension(policies_); + } + + private: + CERTCertificatePolicies* policies_; + + DISALLOW_COPY_AND_ASSIGN(ScopedCERTCertificatePolicies); +}; + +// ScopedCERTValOutParam manages destruction of values in the CERTValOutParam +// array that cvout points to. cvout must be initialized as passed to +// CERT_PKIXVerifyCert, so that the array must be terminated with +// cert_po_end type. +// When it goes out of scope, it destroys values of cert_po_trustAnchor +// and cert_po_certList types, but doesn't release the array itself. +class ScopedCERTValOutParam { + public: + explicit ScopedCERTValOutParam(CERTValOutParam* cvout) + : cvout_(cvout) {} + + ~ScopedCERTValOutParam() { + if (cvout_ == NULL) + return; + for (CERTValOutParam *p = cvout_; p->type != cert_po_end; p++) { + switch (p->type) { + case cert_po_trustAnchor: + if (p->value.pointer.cert) { + CERT_DestroyCertificate(p->value.pointer.cert); + p->value.pointer.cert = NULL; + } + break; + case cert_po_certList: + if (p->value.pointer.chain) { + CERT_DestroyCertList(p->value.pointer.chain); + p->value.pointer.chain = NULL; + } + break; + default: + break; + } + } + } + + private: + CERTValOutParam* cvout_; + + DISALLOW_COPY_AND_ASSIGN(ScopedCERTValOutParam); +}; + +// Map PORT_GetError() return values to our network error codes. +int MapSecurityError(int err) { + switch (err) { + case PR_DIRECTORY_LOOKUP_ERROR: // DNS lookup error. + return ERR_NAME_NOT_RESOLVED; + case SEC_ERROR_INVALID_ARGS: + return ERR_INVALID_ARGUMENT; + case SSL_ERROR_BAD_CERT_DOMAIN: + return ERR_CERT_COMMON_NAME_INVALID; + case SEC_ERROR_INVALID_TIME: + case SEC_ERROR_EXPIRED_CERTIFICATE: + case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: + return ERR_CERT_DATE_INVALID; + case SEC_ERROR_UNKNOWN_ISSUER: + case SEC_ERROR_UNTRUSTED_ISSUER: + case SEC_ERROR_CA_CERT_INVALID: + return ERR_CERT_AUTHORITY_INVALID; + case SEC_ERROR_REVOKED_CERTIFICATE: + case SEC_ERROR_UNTRUSTED_CERT: // Treat as revoked. + return ERR_CERT_REVOKED; + case SEC_ERROR_BAD_DER: + case SEC_ERROR_BAD_SIGNATURE: + case SEC_ERROR_CERT_NOT_VALID: + // TODO(port): add an ERR_CERT_WRONG_USAGE error code. + case SEC_ERROR_CERT_USAGES_INVALID: + case SEC_ERROR_INADEQUATE_KEY_USAGE: + case SEC_ERROR_INADEQUATE_CERT_TYPE: + case SEC_ERROR_POLICY_VALIDATION_FAILED: + case SEC_ERROR_CERT_NOT_IN_NAME_SPACE: + case SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID: + case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION: + case SEC_ERROR_EXTENSION_VALUE_INVALID: + return ERR_CERT_INVALID; + default: + LOG(WARNING) << "Unknown error " << err << " mapped to net::ERR_FAILED"; + return ERR_FAILED; + } +} + +// Map PORT_GetError() return values to our cert status flags. +CertStatus MapCertErrorToCertStatus(int err) { + switch (err) { + case SSL_ERROR_BAD_CERT_DOMAIN: + return CERT_STATUS_COMMON_NAME_INVALID; + case SEC_ERROR_INVALID_TIME: + case SEC_ERROR_EXPIRED_CERTIFICATE: + case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: + return CERT_STATUS_DATE_INVALID; + case SEC_ERROR_UNKNOWN_ISSUER: + case SEC_ERROR_UNTRUSTED_ISSUER: + case SEC_ERROR_CA_CERT_INVALID: + return CERT_STATUS_AUTHORITY_INVALID; + // TODO(port): map CERT_STATUS_NO_REVOCATION_MECHANISM. + case SEC_ERROR_OCSP_BAD_HTTP_RESPONSE: + case SEC_ERROR_OCSP_SERVER_ERROR: + return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION; + case SEC_ERROR_REVOKED_CERTIFICATE: + case SEC_ERROR_UNTRUSTED_CERT: // Treat as revoked. + return CERT_STATUS_REVOKED; + case SEC_ERROR_BAD_DER: + case SEC_ERROR_BAD_SIGNATURE: + case SEC_ERROR_CERT_NOT_VALID: + // TODO(port): add a CERT_STATUS_WRONG_USAGE error code. + case SEC_ERROR_CERT_USAGES_INVALID: + case SEC_ERROR_INADEQUATE_KEY_USAGE: // Key usage. + case SEC_ERROR_INADEQUATE_CERT_TYPE: // Extended key usage and whether + // the certificate is a CA. + case SEC_ERROR_POLICY_VALIDATION_FAILED: + case SEC_ERROR_CERT_NOT_IN_NAME_SPACE: + case SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID: + case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION: + case SEC_ERROR_EXTENSION_VALUE_INVALID: + return CERT_STATUS_INVALID; + default: + return 0; + } +} + +// Saves some information about the certificate chain cert_list in +// *verify_result. The caller MUST initialize *verify_result before calling +// this function. +// Note that cert_list[0] is the end entity certificate. +void GetCertChainInfo(CERTCertList* cert_list, + CERTCertificate* root_cert, + CertVerifyResult* verify_result) { + // NOTE: Using a NSS library before 3.12.3.1 will crash below. To see the + // NSS version currently in use: + // 1. use ldd on the chrome executable for NSS's location (ie. libnss3.so*) + // 2. use ident libnss3.so* for the library's version + DCHECK(cert_list); + + CERTCertificate* verified_cert = NULL; + std::vector<CERTCertificate*> verified_chain; + int i = 0; + for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list); + !CERT_LIST_END(node, cert_list); + node = CERT_LIST_NEXT(node), ++i) { + if (i == 0) { + verified_cert = node->cert; + } else { + // Because of an NSS bug, CERT_PKIXVerifyCert may chain a self-signed + // certificate of a root CA to another certificate of the same root CA + // key. Detect that error and ignore the root CA certificate. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=721288. + if (node->cert->isRoot) { + // NOTE: isRoot doesn't mean the certificate is a trust anchor. It + // means the certificate is self-signed. Here we assume isRoot only + // implies the certificate is self-issued. + CERTCertListNode* next_node = CERT_LIST_NEXT(node); + CERTCertificate* next_cert; + if (!CERT_LIST_END(next_node, cert_list)) { + next_cert = next_node->cert; + } else { + next_cert = root_cert; + } + // Test that |node->cert| is actually a self-signed certificate + // whose key is equal to |next_cert|, and not a self-issued + // certificate signed by another key of the same CA. + if (next_cert && SECITEM_ItemsAreEqual(&node->cert->derPublicKey, + &next_cert->derPublicKey)) { + continue; + } + } + verified_chain.push_back(node->cert); + } + + SECAlgorithmID& signature = node->cert->signature; + SECOidTag oid_tag = SECOID_FindOIDTag(&signature.algorithm); + switch (oid_tag) { + case SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION: + verify_result->has_md5 = true; + if (i != 0) + verify_result->has_md5_ca = true; + break; + case SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION: + verify_result->has_md2 = true; + if (i != 0) + verify_result->has_md2_ca = true; + break; + case SEC_OID_PKCS1_MD4_WITH_RSA_ENCRYPTION: + verify_result->has_md4 = true; + break; + default: + break; + } + } + + if (root_cert) + verified_chain.push_back(root_cert); + verify_result->verified_cert = + X509Certificate::CreateFromHandle(verified_cert, verified_chain); +} + +// IsKnownRoot returns true if the given certificate is one that we believe +// is a standard (as opposed to user-installed) root. +bool IsKnownRoot(CERTCertificate* root) { + if (!root || !root->slot) + return false; + + // This magic name is taken from + // http://bonsai.mozilla.org/cvsblame.cgi?file=mozilla/security/nss/lib/ckfw/builtins/constants.c&rev=1.13&mark=86,89#79 + return 0 == strcmp(PK11_GetSlotName(root->slot), + "NSS Builtin Objects"); +} + +enum CRLSetResult { + kCRLSetRevoked, + kCRLSetOk, + kCRLSetError, +}; + +// CheckRevocationWithCRLSet attempts to check each element of |cert_list| +// against |crl_set|. It returns: +// kCRLSetRevoked: if any element of the chain is known to have been revoked. +// kCRLSetError: if an error occurs in processing. +// kCRLSetOk: if no element in the chain is known to have been revoked. +CRLSetResult CheckRevocationWithCRLSet(CERTCertList* cert_list, + CERTCertificate* root, + CRLSet* crl_set) { + std::vector<CERTCertificate*> certs; + + if (cert_list) { + for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list); + !CERT_LIST_END(node, cert_list); + node = CERT_LIST_NEXT(node)) { + certs.push_back(node->cert); + } + } + if (root) + certs.push_back(root); + + // We iterate from the root certificate down to the leaf, keeping track of + // the issuer's SPKI at each step. + std::string issuer_spki_hash; + for (std::vector<CERTCertificate*>::reverse_iterator i = certs.rbegin(); + i != certs.rend(); ++i) { + CERTCertificate* cert = *i; + + base::StringPiece der(reinterpret_cast<char*>(cert->derCert.data), + cert->derCert.len); + + base::StringPiece spki; + if (!asn1::ExtractSPKIFromDERCert(der, &spki)) { + NOTREACHED(); + return kCRLSetError; + } + const std::string spki_hash = crypto::SHA256HashString(spki); + + base::StringPiece serial_number = base::StringPiece( + reinterpret_cast<char*>(cert->serialNumber.data), + cert->serialNumber.len); + + CRLSet::Result result = crl_set->CheckSPKI(spki_hash); + + if (result != CRLSet::REVOKED && !issuer_spki_hash.empty()) + result = crl_set->CheckSerial(serial_number, issuer_spki_hash); + + issuer_spki_hash = spki_hash; + + switch (result) { + case CRLSet::REVOKED: + return kCRLSetRevoked; + case CRLSet::UNKNOWN: + case CRLSet::GOOD: + continue; + default: + NOTREACHED(); + return kCRLSetError; + } + } + + return kCRLSetOk; +} + +// Forward declarations. +SECStatus RetryPKIXVerifyCertWithWorkarounds( + X509Certificate::OSCertHandle cert_handle, int num_policy_oids, + bool cert_io_enabled, std::vector<CERTValInParam>* cvin, + CERTValOutParam* cvout); +SECOidTag GetFirstCertPolicy(X509Certificate::OSCertHandle cert_handle); + +// Call CERT_PKIXVerifyCert for the cert_handle. +// Verification results are stored in an array of CERTValOutParam. +// If policy_oids is not NULL and num_policy_oids is positive, policies +// are also checked. +// Caller must initialize cvout before calling this function. +SECStatus PKIXVerifyCert(X509Certificate::OSCertHandle cert_handle, + bool check_revocation, + bool cert_io_enabled, + const SECOidTag* policy_oids, + int num_policy_oids, + CERTValOutParam* cvout) { + bool use_crl = check_revocation; + bool use_ocsp = check_revocation; + + // These CAs have multiple keys, which trigger two bugs in NSS's CRL code. + // 1. NSS may use one key to verify a CRL signed with another key, + // incorrectly concluding that the CRL's signature is invalid. + // Hopefully this bug will be fixed in NSS 3.12.9. + // 2. NSS considers all certificates issued by the CA as revoked when it + // receives a CRL with an invalid signature. This overly strict policy + // has been relaxed in NSS 3.12.7. See + // https://bugzilla.mozilla.org/show_bug.cgi?id=562542. + // So we have to turn off CRL checking for these CAs. See + // http://crbug.com/55695. + static const char* const kMultipleKeyCA[] = { + "CN=Microsoft Secure Server Authority," + "DC=redmond,DC=corp,DC=microsoft,DC=com", + "CN=Microsoft Secure Server Authority", + }; + + if (!NSS_VersionCheck("3.12.7")) { + for (size_t i = 0; i < arraysize(kMultipleKeyCA); ++i) { + if (strcmp(cert_handle->issuerName, kMultipleKeyCA[i]) == 0) { + use_crl = false; + break; + } + } + } + + PRUint64 revocation_method_flags = + CERT_REV_M_DO_NOT_TEST_USING_THIS_METHOD | + CERT_REV_M_ALLOW_NETWORK_FETCHING | + CERT_REV_M_IGNORE_IMPLICIT_DEFAULT_SOURCE | + CERT_REV_M_IGNORE_MISSING_FRESH_INFO | + CERT_REV_M_STOP_TESTING_ON_FRESH_INFO; + PRUint64 revocation_method_independent_flags = + CERT_REV_MI_TEST_ALL_LOCAL_INFORMATION_FIRST; + if (check_revocation && policy_oids && num_policy_oids > 0) { + // EV verification requires revocation checking. Consider the certificate + // revoked if we don't have revocation info. + // TODO(wtc): Add a bool parameter to expressly specify we're doing EV + // verification or we want strict revocation flags. + revocation_method_flags |= CERT_REV_M_REQUIRE_INFO_ON_MISSING_SOURCE; + revocation_method_independent_flags |= + CERT_REV_MI_REQUIRE_SOME_FRESH_INFO_AVAILABLE; + } else { + revocation_method_flags |= CERT_REV_M_SKIP_TEST_ON_MISSING_SOURCE; + revocation_method_independent_flags |= + CERT_REV_MI_NO_OVERALL_INFO_REQUIREMENT; + } + PRUint64 method_flags[2]; + method_flags[cert_revocation_method_crl] = revocation_method_flags; + method_flags[cert_revocation_method_ocsp] = revocation_method_flags; + + if (use_crl) { + method_flags[cert_revocation_method_crl] |= + CERT_REV_M_TEST_USING_THIS_METHOD; + } + if (use_ocsp) { + method_flags[cert_revocation_method_ocsp] |= + CERT_REV_M_TEST_USING_THIS_METHOD; + } + + CERTRevocationMethodIndex preferred_revocation_methods[1]; + if (use_ocsp) { + preferred_revocation_methods[0] = cert_revocation_method_ocsp; + } else { + preferred_revocation_methods[0] = cert_revocation_method_crl; + } + + CERTRevocationFlags revocation_flags; + revocation_flags.leafTests.number_of_defined_methods = + arraysize(method_flags); + revocation_flags.leafTests.cert_rev_flags_per_method = method_flags; + revocation_flags.leafTests.number_of_preferred_methods = + arraysize(preferred_revocation_methods); + revocation_flags.leafTests.preferred_methods = preferred_revocation_methods; + revocation_flags.leafTests.cert_rev_method_independent_flags = + revocation_method_independent_flags; + + revocation_flags.chainTests.number_of_defined_methods = + arraysize(method_flags); + revocation_flags.chainTests.cert_rev_flags_per_method = method_flags; + revocation_flags.chainTests.number_of_preferred_methods = + arraysize(preferred_revocation_methods); + revocation_flags.chainTests.preferred_methods = preferred_revocation_methods; + revocation_flags.chainTests.cert_rev_method_independent_flags = + revocation_method_independent_flags; + + std::vector<CERTValInParam> cvin; + cvin.reserve(5); + CERTValInParam in_param; + // No need to set cert_pi_trustAnchors here. + in_param.type = cert_pi_revocationFlags; + in_param.value.pointer.revocation = &revocation_flags; + cvin.push_back(in_param); + if (policy_oids && num_policy_oids > 0) { + in_param.type = cert_pi_policyOID; + in_param.value.arraySize = num_policy_oids; + in_param.value.array.oids = policy_oids; + cvin.push_back(in_param); + } + in_param.type = cert_pi_end; + cvin.push_back(in_param); + + SECStatus rv = CERT_PKIXVerifyCert(cert_handle, certificateUsageSSLServer, + &cvin[0], cvout, NULL); + if (rv != SECSuccess) { + rv = RetryPKIXVerifyCertWithWorkarounds(cert_handle, num_policy_oids, + cert_io_enabled, &cvin, cvout); + } + return rv; +} + +// PKIXVerifyCert calls this function to work around some bugs in +// CERT_PKIXVerifyCert. All the arguments of this function are either the +// arguments or local variables of PKIXVerifyCert. +SECStatus RetryPKIXVerifyCertWithWorkarounds( + X509Certificate::OSCertHandle cert_handle, int num_policy_oids, + bool cert_io_enabled, std::vector<CERTValInParam>* cvin, + CERTValOutParam* cvout) { + // We call this function when the first CERT_PKIXVerifyCert call in + // PKIXVerifyCert failed, so we initialize |rv| to SECFailure. + SECStatus rv = SECFailure; + int nss_error = PORT_GetError(); + CERTValInParam in_param; + + // If we get SEC_ERROR_UNKNOWN_ISSUER, we may be missing an intermediate + // CA certificate, so we retry with cert_pi_useAIACertFetch. + // cert_pi_useAIACertFetch has several bugs in its error handling and + // error reporting (NSS bug 528743), so we don't use it by default. + // Note: When building a certificate chain, CERT_PKIXVerifyCert may + // incorrectly pick a CA certificate with the same subject name as the + // missing intermediate CA certificate, and fail with the + // SEC_ERROR_BAD_SIGNATURE error (NSS bug 524013), so we also retry with + // cert_pi_useAIACertFetch on SEC_ERROR_BAD_SIGNATURE. + if (cert_io_enabled && + (nss_error == SEC_ERROR_UNKNOWN_ISSUER || + nss_error == SEC_ERROR_BAD_SIGNATURE)) { + DCHECK_EQ(cvin->back().type, cert_pi_end); + cvin->pop_back(); + in_param.type = cert_pi_useAIACertFetch; + in_param.value.scalar.b = PR_TRUE; + cvin->push_back(in_param); + in_param.type = cert_pi_end; + cvin->push_back(in_param); + rv = CERT_PKIXVerifyCert(cert_handle, certificateUsageSSLServer, + &(*cvin)[0], cvout, NULL); + if (rv == SECSuccess) + return rv; + int new_nss_error = PORT_GetError(); + if (new_nss_error == SEC_ERROR_INVALID_ARGS || + new_nss_error == SEC_ERROR_UNKNOWN_AIA_LOCATION_TYPE || + new_nss_error == SEC_ERROR_BAD_INFO_ACCESS_LOCATION || + new_nss_error == SEC_ERROR_BAD_HTTP_RESPONSE || + new_nss_error == SEC_ERROR_BAD_LDAP_RESPONSE || + !IS_SEC_ERROR(new_nss_error)) { + // Use the original error code because of cert_pi_useAIACertFetch's + // bad error reporting. + PORT_SetError(nss_error); + return rv; + } + nss_error = new_nss_error; + } + + // If an intermediate CA certificate has requireExplicitPolicy in its + // policyConstraints extension, CERT_PKIXVerifyCert fails with + // SEC_ERROR_POLICY_VALIDATION_FAILED because we didn't specify any + // certificate policy (NSS bug 552775). So we retry with the certificate + // policy found in the server certificate. + if (nss_error == SEC_ERROR_POLICY_VALIDATION_FAILED && + num_policy_oids == 0) { + SECOidTag policy = GetFirstCertPolicy(cert_handle); + if (policy != SEC_OID_UNKNOWN) { + DCHECK_EQ(cvin->back().type, cert_pi_end); + cvin->pop_back(); + in_param.type = cert_pi_policyOID; + in_param.value.arraySize = 1; + in_param.value.array.oids = &policy; + cvin->push_back(in_param); + in_param.type = cert_pi_end; + cvin->push_back(in_param); + rv = CERT_PKIXVerifyCert(cert_handle, certificateUsageSSLServer, + &(*cvin)[0], cvout, NULL); + if (rv != SECSuccess) { + // Use the original error code. + PORT_SetError(nss_error); + } + } + } + + return rv; +} + +// Decodes the certificatePolicies extension of the certificate. Returns +// NULL if the certificate doesn't have the extension or the extension can't +// be decoded. The returned value must be freed with a +// CERT_DestroyCertificatePoliciesExtension call. +CERTCertificatePolicies* DecodeCertPolicies( + X509Certificate::OSCertHandle cert_handle) { + SECItem policy_ext; + SECStatus rv = CERT_FindCertExtension(cert_handle, + SEC_OID_X509_CERTIFICATE_POLICIES, + &policy_ext); + if (rv != SECSuccess) + return NULL; + CERTCertificatePolicies* policies = + CERT_DecodeCertificatePoliciesExtension(&policy_ext); + SECITEM_FreeItem(&policy_ext, PR_FALSE); + return policies; +} + +// Returns the OID tag for the first certificate policy in the certificate's +// certificatePolicies extension. Returns SEC_OID_UNKNOWN if the certificate +// has no certificate policy. +SECOidTag GetFirstCertPolicy(X509Certificate::OSCertHandle cert_handle) { + CERTCertificatePolicies* policies = DecodeCertPolicies(cert_handle); + if (!policies) + return SEC_OID_UNKNOWN; + ScopedCERTCertificatePolicies scoped_policies(policies); + CERTPolicyInfo* policy_info = policies->policyInfos[0]; + if (!policy_info) + return SEC_OID_UNKNOWN; + if (policy_info->oid != SEC_OID_UNKNOWN) + return policy_info->oid; + + // The certificate policy is unknown to NSS. We need to create a dynamic + // OID tag for the policy. + SECOidData od; + od.oid.len = policy_info->policyID.len; + od.oid.data = policy_info->policyID.data; + od.offset = SEC_OID_UNKNOWN; + // NSS doesn't allow us to pass an empty description, so I use a hardcoded, + // default description here. The description doesn't need to be unique for + // each OID. + od.desc = "a certificate policy"; + od.mechanism = CKM_INVALID_MECHANISM; + od.supportedExtension = INVALID_CERT_EXTENSION; + return SECOID_AddEntry(&od); +} + +bool CheckCertPolicies(X509Certificate::OSCertHandle cert_handle, + SECOidTag ev_policy_tag) { + CERTCertificatePolicies* policies = DecodeCertPolicies(cert_handle); + if (!policies) { + LOG(ERROR) << "Cert has no policies extension or extension couldn't be " + "decoded."; + return false; + } + ScopedCERTCertificatePolicies scoped_policies(policies); + CERTPolicyInfo** policy_infos = policies->policyInfos; + while (*policy_infos != NULL) { + CERTPolicyInfo* policy_info = *policy_infos++; + SECOidTag oid_tag = policy_info->oid; + if (oid_tag == SEC_OID_UNKNOWN) + continue; + if (oid_tag == ev_policy_tag) + return true; + } + return false; +} + +SHA1Fingerprint CertPublicKeyHash(CERTCertificate* cert) { + SHA1Fingerprint hash; + SECStatus rv = HASH_HashBuf(HASH_AlgSHA1, hash.data, + cert->derPublicKey.data, cert->derPublicKey.len); + DCHECK_EQ(rv, SECSuccess); + return hash; +} + +void AppendPublicKeyHashes(CERTCertList* cert_list, + CERTCertificate* root_cert, + std::vector<SHA1Fingerprint>* hashes) { + for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list); + !CERT_LIST_END(node, cert_list); + node = CERT_LIST_NEXT(node)) { + hashes->push_back(CertPublicKeyHash(node->cert)); + } + if (root_cert) + hashes->push_back(CertPublicKeyHash(root_cert)); +} + +// Studied Mozilla's code (esp. security/manager/ssl/src/nsIdentityChecking.cpp +// and nsNSSCertHelper.cpp) to learn how to verify EV certificate. +// TODO(wtc): A possible optimization is that we get the trust anchor from +// the first PKIXVerifyCert call. We look up the EV policy for the trust +// anchor. If the trust anchor has no EV policy, we know the cert isn't EV. +// Otherwise, we pass just that EV policy (as opposed to all the EV policies) +// to the second PKIXVerifyCert call. +bool VerifyEV(CERTCertificate* cert_handle, int flags, CRLSet* crl_set) { + EVRootCAMetadata* metadata = EVRootCAMetadata::GetInstance(); + + CERTValOutParam cvout[3]; + int cvout_index = 0; + cvout[cvout_index].type = cert_po_certList; + cvout[cvout_index].value.pointer.chain = NULL; + int cvout_cert_list_index = cvout_index; + cvout_index++; + cvout[cvout_index].type = cert_po_trustAnchor; + cvout[cvout_index].value.pointer.cert = NULL; + int cvout_trust_anchor_index = cvout_index; + cvout_index++; + cvout[cvout_index].type = cert_po_end; + ScopedCERTValOutParam scoped_cvout(cvout); + + SECStatus status = PKIXVerifyCert( + cert_handle, + flags & X509Certificate::VERIFY_REV_CHECKING_ENABLED, + flags & X509Certificate::VERIFY_CERT_IO_ENABLED, + metadata->GetPolicyOIDs(), + metadata->NumPolicyOIDs(), + cvout); + if (status != SECSuccess) + return false; + + CERTCertificate* root_ca = + cvout[cvout_trust_anchor_index].value.pointer.cert; + if (root_ca == NULL) + return false; + + // This second PKIXVerifyCert call could have found a different certification + // path and one or more of the certificates on this new path, that weren't on + // the old path, might have been revoked. + if (crl_set) { + CRLSetResult crl_set_result = CheckRevocationWithCRLSet( + cvout[cvout_cert_list_index].value.pointer.chain, + cvout[cvout_trust_anchor_index].value.pointer.cert, + crl_set); + if (crl_set_result == kCRLSetRevoked) + return false; + } + + SHA1Fingerprint fingerprint = + X509Certificate::CalculateFingerprint(root_ca); + std::vector<SECOidTag> ev_policy_tags; + if (!metadata->GetPolicyOIDsForCA(fingerprint, &ev_policy_tags)) + return false; + DCHECK(!ev_policy_tags.empty()); + + for (std::vector<SECOidTag>::const_iterator + i = ev_policy_tags.begin(); i != ev_policy_tags.end(); ++i) { + if (CheckCertPolicies(cert_handle, *i)) + return true; + } + return false; +} + +} // namespace + +CertVerifyProcNSS::CertVerifyProcNSS() {} + +CertVerifyProcNSS::~CertVerifyProcNSS() {} + +int CertVerifyProcNSS::VerifyInternal(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + CertVerifyResult* verify_result) { + CERTCertificate* cert_handle = cert->os_cert_handle(); + // Make sure that the hostname matches with the common name of the cert. + SECStatus status = CERT_VerifyCertName(cert_handle, hostname.c_str()); + if (status != SECSuccess) + verify_result->cert_status |= CERT_STATUS_COMMON_NAME_INVALID; + + // Make sure that the cert is valid now. + SECCertTimeValidity validity = CERT_CheckCertValidTimes( + cert_handle, PR_Now(), PR_TRUE); + if (validity != secCertTimeValid) + verify_result->cert_status |= CERT_STATUS_DATE_INVALID; + + CERTValOutParam cvout[3]; + int cvout_index = 0; + cvout[cvout_index].type = cert_po_certList; + cvout[cvout_index].value.pointer.chain = NULL; + int cvout_cert_list_index = cvout_index; + cvout_index++; + cvout[cvout_index].type = cert_po_trustAnchor; + cvout[cvout_index].value.pointer.cert = NULL; + int cvout_trust_anchor_index = cvout_index; + cvout_index++; + cvout[cvout_index].type = cert_po_end; + ScopedCERTValOutParam scoped_cvout(cvout); + + bool cert_io_enabled = flags & X509Certificate::VERIFY_CERT_IO_ENABLED; + bool check_revocation = + (flags & X509Certificate::VERIFY_REV_CHECKING_ENABLED) && + cert_io_enabled; + if (check_revocation) + verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED; + + status = PKIXVerifyCert(cert_handle, check_revocation, cert_io_enabled, + NULL, 0, cvout); + + if (crl_set) { + CRLSetResult crl_set_result = CheckRevocationWithCRLSet( + cvout[cvout_cert_list_index].value.pointer.chain, + cvout[cvout_trust_anchor_index].value.pointer.cert, + crl_set); + if (crl_set_result == kCRLSetRevoked) { + PORT_SetError(SEC_ERROR_REVOKED_CERTIFICATE); + status = SECFailure; + } + } + + if (status != SECSuccess) { + int err = PORT_GetError(); + LOG(ERROR) << "CERT_PKIXVerifyCert for " << hostname + << " failed err=" << err; + // CERT_PKIXVerifyCert rerports the wrong error code for + // expired certificates (NSS bug 491174) + if (err == SEC_ERROR_CERT_NOT_VALID && + (verify_result->cert_status & CERT_STATUS_DATE_INVALID)) + err = SEC_ERROR_EXPIRED_CERTIFICATE; + CertStatus cert_status = MapCertErrorToCertStatus(err); + if (cert_status) { + verify_result->cert_status |= cert_status; + return MapCertStatusToNetError(verify_result->cert_status); + } + // |err| is not a certificate error. + return MapSecurityError(err); + } + + GetCertChainInfo(cvout[cvout_cert_list_index].value.pointer.chain, + cvout[cvout_trust_anchor_index].value.pointer.cert, + verify_result); + if (IsCertStatusError(verify_result->cert_status)) + return MapCertStatusToNetError(verify_result->cert_status); + + AppendPublicKeyHashes(cvout[cvout_cert_list_index].value.pointer.chain, + cvout[cvout_trust_anchor_index].value.pointer.cert, + &verify_result->public_key_hashes); + + verify_result->is_issued_by_known_root = + IsKnownRoot(cvout[cvout_trust_anchor_index].value.pointer.cert); + + if ((flags & X509Certificate::VERIFY_EV_CERT) && + VerifyEV(cert_handle, flags, crl_set)) { + verify_result->cert_status |= CERT_STATUS_IS_EV; + } + + return OK; +} + +} // namespace net diff --git a/net/base/cert_verify_proc_nss.h b/net/base/cert_verify_proc_nss.h new file mode 100644 index 0000000..7496eaf --- /dev/null +++ b/net/base/cert_verify_proc_nss.h @@ -0,0 +1,31 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_BASE_CERT_VERIFY_PROC_NSS_H_ +#define NET_BASE_CERT_VERIFY_PROC_NSS_H_ +#pragma once + +#include "net/base/cert_verify_proc.h" + +namespace net { + +// Performs certificate path construction and validation using NSS's libpkix. +class CertVerifyProcNSS : public CertVerifyProc { + public: + CertVerifyProcNSS(); + + protected: + virtual ~CertVerifyProcNSS(); + + private: + virtual int VerifyInternal(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + CertVerifyResult* verify_result) OVERRIDE; +}; + +} // namespace net + +#endif // NET_BASE_CERT_VERIFY_PROC_NSS_H_ diff --git a/net/base/cert_verify_proc_openssl.cc b/net/base/cert_verify_proc_openssl.cc new file mode 100644 index 0000000..31ac27d --- /dev/null +++ b/net/base/cert_verify_proc_openssl.cc @@ -0,0 +1,288 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/base/cert_verify_proc_openssl.h" + +#include <openssl/x509v3.h> + +#include "base/logging.h" +#include "base/sha1.h" +#include "crypto/openssl_util.h" +#include "net/base/asn1_util.h" +#include "net/base/cert_status_flags.h" +#include "net/base/cert_verify_result.h" +#include "net/base/net_errors.h" +#include "net/base/x509_certificate.h" + +#if defined(OS_ANDROID) +#include "net/android/network_library.h" +#endif + +namespace net { + +namespace { + +// Maps X509_STORE_CTX_get_error() return values to our cert status flags. +CertStatus MapCertErrorToCertStatus(int err) { + switch (err) { + case X509_V_ERR_SUBJECT_ISSUER_MISMATCH: + return CERT_STATUS_COMMON_NAME_INVALID; + case X509_V_ERR_CERT_NOT_YET_VALID: + case X509_V_ERR_CERT_HAS_EXPIRED: + case X509_V_ERR_CRL_NOT_YET_VALID: + case X509_V_ERR_CRL_HAS_EXPIRED: + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: + case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: + return CERT_STATUS_DATE_INVALID; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + case X509_V_ERR_UNABLE_TO_GET_CRL: + case X509_V_ERR_INVALID_CA: + case X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER: + case X509_V_ERR_INVALID_NON_CA: + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + return CERT_STATUS_AUTHORITY_INVALID; +#if 0 +// TODO(bulach): what should we map to these status? + return CERT_STATUS_NO_REVOCATION_MECHANISM; + return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION; + return CERT_STATUS_NOT_IN_DNS; +#endif + case X509_V_ERR_CERT_REVOKED: + return CERT_STATUS_REVOKED; + // All these status are mapped to CERT_STATUS_INVALID. + case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: + case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: + case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: + case X509_V_ERR_CERT_SIGNATURE_FAILURE: + case X509_V_ERR_CRL_SIGNATURE_FAILURE: + case X509_V_ERR_OUT_OF_MEM: + case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: + case X509_V_ERR_CERT_CHAIN_TOO_LONG: + case X509_V_ERR_PATH_LENGTH_EXCEEDED: + case X509_V_ERR_INVALID_PURPOSE: + case X509_V_ERR_CERT_UNTRUSTED: + case X509_V_ERR_CERT_REJECTED: + case X509_V_ERR_AKID_SKID_MISMATCH: + case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH: + case X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION: + case X509_V_ERR_KEYUSAGE_NO_CERTSIGN: + case X509_V_ERR_KEYUSAGE_NO_CRL_SIGN: + case X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION: + case X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED: + case X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE: + case X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED: + case X509_V_ERR_INVALID_EXTENSION: + case X509_V_ERR_INVALID_POLICY_EXTENSION: + case X509_V_ERR_NO_EXPLICIT_POLICY: + case X509_V_ERR_UNNESTED_RESOURCE: + case X509_V_ERR_APPLICATION_VERIFICATION: + return CERT_STATUS_INVALID; + default: + NOTREACHED() << "Invalid X509 err " << err; + return CERT_STATUS_INVALID; + } +} + +// sk_X509_free is a function-style macro, so can't be used as a template +// param directly. +void sk_X509_free_fn(STACK_OF(X509)* st) { + sk_X509_free(st); +} + +void GetCertChainInfo(X509_STORE_CTX* store_ctx, + CertVerifyResult* verify_result) { + STACK_OF(X509)* chain = X509_STORE_CTX_get_chain(store_ctx); + X509* verified_cert = NULL; + std::vector<X509*> verified_chain; + for (int i = 0; i < sk_X509_num(chain); ++i) { + X509* cert = sk_X509_value(chain, i); + if (i == 0) { + verified_cert = cert; + } else { + verified_chain.push_back(cert); + } + + // Only check the algorithm status for certificates that are not in the + // trust store. + if (i < store_ctx->last_untrusted) { + int sig_alg = OBJ_obj2nid(cert->sig_alg->algorithm); + if (sig_alg == NID_md2WithRSAEncryption) { + verify_result->has_md2 = true; + if (i != 0) + verify_result->has_md2_ca = true; + } else if (sig_alg == NID_md4WithRSAEncryption) { + verify_result->has_md4 = true; + } else if (sig_alg == NID_md5WithRSAEncryption) { + verify_result->has_md5 = true; + if (i != 0) + verify_result->has_md5_ca = true; + } + } + } + + if (verified_cert) { + verify_result->verified_cert = + X509Certificate::CreateFromHandle(verified_cert, verified_chain); + } +} + +void AppendPublicKeyHashes(X509_STORE_CTX* store_ctx, + std::vector<SHA1Fingerprint>* hashes) { + STACK_OF(X509)* chain = X509_STORE_CTX_get_chain(store_ctx); + for (int i = 0; i < sk_X509_num(chain); ++i) { + X509* cert = sk_X509_value(chain, i); + + std::string der_data; + if (!X509Certificate::GetDEREncoded(cert, &der_data)) + continue; + + base::StringPiece der_bytes(der_data); + base::StringPiece spki_bytes; + if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki_bytes)) + continue; + + SHA1Fingerprint hash; + base::SHA1HashBytes(reinterpret_cast<const uint8*>(spki_bytes.data()), + spki_bytes.size(), hash.data); + hashes->push_back(hash); + } +} + +#if defined(OS_ANDROID) +// Returns true if we have verification result in |verify_result| from Android +// Trust Manager. Otherwise returns false. +bool VerifyFromAndroidTrustManager(const std::vector<std::string>& cert_bytes, + CertVerifyResult* verify_result) { + // TODO(joth): Fetch the authentication type from SSL rather than hardcode. + // TODO(jnd): Remove unused |hostname| from net::android::VerifyX509CertChain. + bool verified = true; +#if 0 + android::VerifyResult result = + android::VerifyX509CertChain(cert_bytes, hostname, "RSA"); +#else + // TODO(jingzhao): Recover the original implementation once we support JNI. + android::VerifyResult result = android::VERIFY_INVOCATION_ERROR; + NOTIMPLEMENTED(); +#endif + switch (result) { + case android::VERIFY_OK: + break; + case android::VERIFY_BAD_HOSTNAME: + verify_result->cert_status |= CERT_STATUS_COMMON_NAME_INVALID; + break; + case android::VERIFY_NO_TRUSTED_ROOT: + verify_result->cert_status |= CERT_STATUS_AUTHORITY_INVALID; + break; + case android::VERIFY_INVOCATION_ERROR: + verified = false; + break; + default: + verify_result->cert_status |= CERT_STATUS_INVALID; + break; + } + return verified; +} + +void GetChainDEREncodedBytes(X509Certificate* cert, + std::vector<std::string>* chain_bytes) { + X509Certificate::OSCertHandle cert_handle = cert->os_cert_handle(); + X509Certificate::OSCertHandles cert_handles = + cert->GetIntermediateCertificates(); + + // Make sure the peer's own cert is the first in the chain, if it's not + // already there. + if (cert_handles.empty() || cert_handles[0] != cert_handle) + cert_handles.insert(cert_handles.begin(), cert_handle); + + chain_bytes->reserve(cert_handles.size()); + for (X509Certificate::OSCertHandles::const_iterator it = + cert_handles.begin(); it != cert_handles.end(); ++it) { + std::string cert_bytes; + X509Certificate::X509Certificate::GetDEREncoded(*it, &cert_bytes); + chain_bytes->push_back(cert_bytes); + } +} +#endif // defined(OS_ANDROID) + +} // namespace + +CertVerifyProcOpenSSL::CertVerifyProcOpenSSL() {} + +CertVerifyProcOpenSSL::~CertVerifyProcOpenSSL() {} + +int CertVerifyProcOpenSSL::VerifyInternal(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + CertVerifyResult* verify_result) { + crypto::EnsureOpenSSLInit(); + + if (!cert->VerifyNameMatch(hostname)) + verify_result->cert_status |= CERT_STATUS_COMMON_NAME_INVALID; + + bool verify_attempted = false; + +#if defined(OS_ANDROID) + std::vector<std::string> cert_bytes; + GetChainDEREncodedBytes(&cert_bytes); + + verify_attempted = VerifyFromAndroidTrustManager(cert_bytes, verify_result); +#endif + + if (verify_attempted) { + if (IsCertStatusError(verify_result->cert_status)) + return MapCertStatusToNetError(verify_result->cert_status); + } else { + crypto::ScopedOpenSSL<X509_STORE_CTX, X509_STORE_CTX_free> ctx( + X509_STORE_CTX_new()); + + crypto::ScopedOpenSSL<STACK_OF(X509), sk_X509_free_fn> intermediates( + sk_X509_new_null()); + if (!intermediates.get()) + return ERR_OUT_OF_MEMORY; + + const X509Certificate::OSCertHandles& os_intermediates = + cert->GetIntermediateCertificates(); + for (X509Certificate::OSCertHandles::const_iterator it = + os_intermediates.begin(); it != os_intermediates.end(); ++it) { + if (!sk_X509_push(intermediates.get(), *it)) + return ERR_OUT_OF_MEMORY; + } + int rv = X509_STORE_CTX_init(ctx.get(), X509Certificate::cert_store(), + cert->os_cert_handle(), intermediates.get()); + CHECK_EQ(1, rv); + + if (X509_verify_cert(ctx.get()) != 1) { + int x509_error = X509_STORE_CTX_get_error(ctx.get()); + CertStatus cert_status = MapCertErrorToCertStatus(x509_error); + LOG(ERROR) << "X509 Verification error " + << X509_verify_cert_error_string(x509_error) + << " : " << x509_error + << " : " << X509_STORE_CTX_get_error_depth(ctx.get()) + << " : " << cert_status; + verify_result->cert_status |= cert_status; + } + + GetCertChainInfo(ctx.get(), verify_result); + if (IsCertStatusError(verify_result->cert_status)) + return MapCertStatusToNetError(verify_result->cert_status); + AppendPublicKeyHashes(ctx.get(), &verify_result->public_key_hashes); + } + + // Currently we only ues OpenSSL's default root CA paths, so treat all + // correctly verified certs as being from a known root. + // TODO(joth): if the motivations described in + // http://src.chromium.org/viewvc/chrome?view=rev&revision=80778 become an + // issue on OpenSSL builds, we will need to embed a hardcoded list of well + // known root CAs, as per the _mac and _win versions. + verify_result->is_issued_by_known_root = true; + + return OK; +} + +} // namespace net diff --git a/net/base/cert_verify_proc_openssl.h b/net/base/cert_verify_proc_openssl.h new file mode 100644 index 0000000..200451c --- /dev/null +++ b/net/base/cert_verify_proc_openssl.h @@ -0,0 +1,31 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_BASE_CERT_VERIFY_PROC_OPENSSL_H_ +#define NET_BASE_CERT_VERIFY_PROC_OPENSSL_H_ +#pragma once + +#include "net/base/cert_verify_proc.h" + +namespace net { + +// Performs certificate path construction and validation using OpenSSL. +class CertVerifyProcOpenSSL : public CertVerifyProc { + public: + CertVerifyProcOpenSSL(); + + protected: + virtual ~CertVerifyProcOpenSSL(); + + private: + virtual int VerifyInternal(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + CertVerifyResult* verify_result) OVERRIDE; +}; + +} // namespace net + +#endif // NET_BASE_CERT_VERIFY_PROC_OPENSSL_H_ diff --git a/net/base/cert_verify_proc_unittest.cc b/net/base/cert_verify_proc_unittest.cc index 3c6de2b7..3d860c6 100644 --- a/net/base/cert_verify_proc_unittest.cc +++ b/net/base/cert_verify_proc_unittest.cc @@ -9,6 +9,7 @@ #include "base/file_path.h" #include "base/string_number_conversions.h" #include "base/sha1.h" +#include "net/base/asn1_util.h" #include "net/base/cert_status_flags.h" #include "net/base/cert_test_util.h" #include "net/base/cert_verify_result.h" @@ -372,6 +373,41 @@ TEST_F(CertVerifyProcTest, GoogleDigiNotarTest) { EXPECT_NE(OK, error); } +TEST_F(CertVerifyProcTest, DigiNotarCerts) { + static const char* const kDigiNotarFilenames[] = { + "diginotar_root_ca.pem", + "diginotar_cyber_ca.pem", + "diginotar_services_1024_ca.pem", + "diginotar_pkioverheid.pem", + "diginotar_pkioverheid_g2.pem", + NULL, + }; + + FilePath certs_dir = GetTestCertsDirectory(); + + for (size_t i = 0; kDigiNotarFilenames[i]; i++) { + scoped_refptr<X509Certificate> diginotar_cert = + ImportCertFromFile(certs_dir, kDigiNotarFilenames[i]); + std::string der_bytes; + ASSERT_TRUE(X509Certificate::GetDEREncoded( + diginotar_cert->os_cert_handle(), &der_bytes)); + + base::StringPiece spki; + ASSERT_TRUE(asn1::ExtractSPKIFromDERCert(der_bytes, &spki)); + + std::string spki_sha1 = base::SHA1HashString(spki.as_string()); + + std::vector<SHA1Fingerprint> public_keys; + SHA1Fingerprint fingerprint; + ASSERT_EQ(sizeof(fingerprint.data), spki_sha1.size()); + memcpy(fingerprint.data, spki_sha1.data(), spki_sha1.size()); + public_keys.push_back(fingerprint); + + EXPECT_TRUE(CertVerifyProc::IsPublicKeyBlacklisted(public_keys)) << + "Public key not blocked for " << kDigiNotarFilenames[i]; + } +} + // Bug 111893: This test needs a new certificate. TEST_F(CertVerifyProcTest, DISABLED_TestKnownRoot) { FilePath certs_dir = GetTestCertsDirectory(); diff --git a/net/base/cert_verify_proc_win.cc b/net/base/cert_verify_proc_win.cc new file mode 100644 index 0000000..7e1aa43 --- /dev/null +++ b/net/base/cert_verify_proc_win.cc @@ -0,0 +1,722 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/base/cert_verify_proc_win.h" + +#include "base/memory/scoped_ptr.h" +#include "base/sha1.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "crypto/capi_util.h" +#include "crypto/scoped_capi_types.h" +#include "crypto/sha2.h" +#include "net/base/asn1_util.h" +#include "net/base/cert_status_flags.h" +#include "net/base/cert_verify_result.h" +#include "net/base/crl_set.h" +#include "net/base/ev_root_ca_metadata.h" +#include "net/base/net_errors.h" +#include "net/base/test_root_certs.h" +#include "net/base/x509_certificate.h" +#include "net/base/x509_certificate_known_roots_win.h" + +#pragma comment(lib, "crypt32.lib") + +namespace net { + +namespace { + +struct FreeChainEngineFunctor { + void operator()(HCERTCHAINENGINE engine) const { + if (engine) + CertFreeCertificateChainEngine(engine); + } +}; + +struct FreeCertChainContextFunctor { + void operator()(PCCERT_CHAIN_CONTEXT chain_context) const { + if (chain_context) + CertFreeCertificateChain(chain_context); + } +}; + +struct FreeCertContextFunctor { + void operator()(PCCERT_CONTEXT context) const { + if (context) + CertFreeCertificateContext(context); + } +}; + +typedef crypto::ScopedCAPIHandle<HCERTCHAINENGINE, FreeChainEngineFunctor> + ScopedHCERTCHAINENGINE; + +typedef scoped_ptr_malloc<const CERT_CHAIN_CONTEXT, + FreeCertChainContextFunctor> + ScopedPCCERT_CHAIN_CONTEXT; + +typedef scoped_ptr_malloc<const CERT_CONTEXT, + FreeCertContextFunctor> ScopedPCCERT_CONTEXT; + +//----------------------------------------------------------------------------- + +// TODO(wtc): This is a copy of the MapSecurityError function in +// ssl_client_socket_win.cc. Another function that maps Windows error codes +// to our network error codes is WinInetUtil::OSErrorToNetError. We should +// eliminate the code duplication. +int MapSecurityError(SECURITY_STATUS err) { + // There are numerous security error codes, but these are the ones we thus + // far find interesting. + switch (err) { + case SEC_E_WRONG_PRINCIPAL: // Schannel + case CERT_E_CN_NO_MATCH: // CryptoAPI + return ERR_CERT_COMMON_NAME_INVALID; + case SEC_E_UNTRUSTED_ROOT: // Schannel + case CERT_E_UNTRUSTEDROOT: // CryptoAPI + return ERR_CERT_AUTHORITY_INVALID; + case SEC_E_CERT_EXPIRED: // Schannel + case CERT_E_EXPIRED: // CryptoAPI + return ERR_CERT_DATE_INVALID; + case CRYPT_E_NO_REVOCATION_CHECK: + return ERR_CERT_NO_REVOCATION_MECHANISM; + case CRYPT_E_REVOCATION_OFFLINE: + return ERR_CERT_UNABLE_TO_CHECK_REVOCATION; + case CRYPT_E_REVOKED: // Schannel and CryptoAPI + return ERR_CERT_REVOKED; + case SEC_E_CERT_UNKNOWN: + case CERT_E_ROLE: + return ERR_CERT_INVALID; + case CERT_E_WRONG_USAGE: + // TODO(wtc): Should we add ERR_CERT_WRONG_USAGE? + return ERR_CERT_INVALID; + // We received an unexpected_message or illegal_parameter alert message + // from the server. + case SEC_E_ILLEGAL_MESSAGE: + return ERR_SSL_PROTOCOL_ERROR; + case SEC_E_ALGORITHM_MISMATCH: + return ERR_SSL_VERSION_OR_CIPHER_MISMATCH; + case SEC_E_INVALID_HANDLE: + return ERR_UNEXPECTED; + case SEC_E_OK: + return OK; + default: + LOG(WARNING) << "Unknown error " << err << " mapped to net::ERR_FAILED"; + return ERR_FAILED; + } +} + +// Map the errors in the chain_context->TrustStatus.dwErrorStatus returned by +// CertGetCertificateChain to our certificate status flags. +int MapCertChainErrorStatusToCertStatus(DWORD error_status) { + CertStatus cert_status = 0; + + // We don't include CERT_TRUST_IS_NOT_TIME_NESTED because it's obsolete and + // we wouldn't consider it an error anyway + const DWORD kDateInvalidErrors = CERT_TRUST_IS_NOT_TIME_VALID | + CERT_TRUST_CTL_IS_NOT_TIME_VALID; + if (error_status & kDateInvalidErrors) + cert_status |= CERT_STATUS_DATE_INVALID; + + const DWORD kAuthorityInvalidErrors = CERT_TRUST_IS_UNTRUSTED_ROOT | + CERT_TRUST_IS_EXPLICIT_DISTRUST | + CERT_TRUST_IS_PARTIAL_CHAIN; + if (error_status & kAuthorityInvalidErrors) + cert_status |= CERT_STATUS_AUTHORITY_INVALID; + + if ((error_status & CERT_TRUST_REVOCATION_STATUS_UNKNOWN) && + !(error_status & CERT_TRUST_IS_OFFLINE_REVOCATION)) + cert_status |= CERT_STATUS_NO_REVOCATION_MECHANISM; + + if (error_status & CERT_TRUST_IS_OFFLINE_REVOCATION) + cert_status |= CERT_STATUS_UNABLE_TO_CHECK_REVOCATION; + + if (error_status & CERT_TRUST_IS_REVOKED) + cert_status |= CERT_STATUS_REVOKED; + + const DWORD kWrongUsageErrors = CERT_TRUST_IS_NOT_VALID_FOR_USAGE | + CERT_TRUST_CTL_IS_NOT_VALID_FOR_USAGE; + if (error_status & kWrongUsageErrors) { + // TODO(wtc): Should we add CERT_STATUS_WRONG_USAGE? + cert_status |= CERT_STATUS_INVALID; + } + + // The rest of the errors. + const DWORD kCertInvalidErrors = + CERT_TRUST_IS_NOT_SIGNATURE_VALID | + CERT_TRUST_IS_CYCLIC | + CERT_TRUST_INVALID_EXTENSION | + CERT_TRUST_INVALID_POLICY_CONSTRAINTS | + CERT_TRUST_INVALID_BASIC_CONSTRAINTS | + CERT_TRUST_INVALID_NAME_CONSTRAINTS | + CERT_TRUST_CTL_IS_NOT_SIGNATURE_VALID | + CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT | + CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT | + CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT | + CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT | + CERT_TRUST_NO_ISSUANCE_CHAIN_POLICY | + CERT_TRUST_HAS_NOT_SUPPORTED_CRITICAL_EXT; + if (error_status & kCertInvalidErrors) + cert_status |= CERT_STATUS_INVALID; + + return cert_status; +} + +// Returns true if any common name in the certificate's Subject field contains +// a NULL character. +bool CertSubjectCommonNameHasNull(PCCERT_CONTEXT cert) { + CRYPT_DECODE_PARA decode_para; + decode_para.cbSize = sizeof(decode_para); + decode_para.pfnAlloc = crypto::CryptAlloc; + decode_para.pfnFree = crypto::CryptFree; + CERT_NAME_INFO* name_info = NULL; + DWORD name_info_size = 0; + BOOL rv; + rv = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + X509_NAME, + cert->pCertInfo->Subject.pbData, + cert->pCertInfo->Subject.cbData, + CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, + &decode_para, + &name_info, + &name_info_size); + if (rv) { + scoped_ptr_malloc<CERT_NAME_INFO> scoped_name_info(name_info); + + // The Subject field may have multiple common names. According to the + // "PKI Layer Cake" paper, CryptoAPI uses every common name in the + // Subject field, so we inspect every common name. + // + // From RFC 5280: + // X520CommonName ::= CHOICE { + // teletexString TeletexString (SIZE (1..ub-common-name)), + // printableString PrintableString (SIZE (1..ub-common-name)), + // universalString UniversalString (SIZE (1..ub-common-name)), + // utf8String UTF8String (SIZE (1..ub-common-name)), + // bmpString BMPString (SIZE (1..ub-common-name)) } + // + // We also check IA5String and VisibleString. + for (DWORD i = 0; i < name_info->cRDN; ++i) { + PCERT_RDN rdn = &name_info->rgRDN[i]; + for (DWORD j = 0; j < rdn->cRDNAttr; ++j) { + PCERT_RDN_ATTR rdn_attr = &rdn->rgRDNAttr[j]; + if (strcmp(rdn_attr->pszObjId, szOID_COMMON_NAME) == 0) { + switch (rdn_attr->dwValueType) { + // After the CryptoAPI ASN.1 security vulnerabilities described in + // http://www.microsoft.com/technet/security/Bulletin/MS09-056.mspx + // were patched, we get CERT_RDN_ENCODED_BLOB for a common name + // that contains a NULL character. + case CERT_RDN_ENCODED_BLOB: + break; + // Array of 8-bit characters. + case CERT_RDN_PRINTABLE_STRING: + case CERT_RDN_TELETEX_STRING: + case CERT_RDN_IA5_STRING: + case CERT_RDN_VISIBLE_STRING: + for (DWORD k = 0; k < rdn_attr->Value.cbData; ++k) { + if (rdn_attr->Value.pbData[k] == '\0') + return true; + } + break; + // Array of 16-bit characters. + case CERT_RDN_BMP_STRING: + case CERT_RDN_UTF8_STRING: { + DWORD num_wchars = rdn_attr->Value.cbData / 2; + wchar_t* common_name = + reinterpret_cast<wchar_t*>(rdn_attr->Value.pbData); + for (DWORD k = 0; k < num_wchars; ++k) { + if (common_name[k] == L'\0') + return true; + } + break; + } + // Array of ints (32-bit). + case CERT_RDN_UNIVERSAL_STRING: { + DWORD num_ints = rdn_attr->Value.cbData / 4; + int* common_name = + reinterpret_cast<int*>(rdn_attr->Value.pbData); + for (DWORD k = 0; k < num_ints; ++k) { + if (common_name[k] == 0) + return true; + } + break; + } + default: + NOTREACHED(); + break; + } + } + } + } + } + return false; +} + +// IsIssuedByKnownRoot returns true if the given chain is rooted at a root CA +// which we recognise as a standard root. +// static +bool IsIssuedByKnownRoot(PCCERT_CHAIN_CONTEXT chain_context) { + PCERT_SIMPLE_CHAIN first_chain = chain_context->rgpChain[0]; + int num_elements = first_chain->cElement; + if (num_elements < 1) + return false; + PCERT_CHAIN_ELEMENT* element = first_chain->rgpElement; + PCCERT_CONTEXT cert = element[num_elements - 1]->pCertContext; + + SHA1Fingerprint hash = X509Certificate::CalculateFingerprint(cert); + return IsSHA1HashInSortedArray( + hash, &kKnownRootCertSHA1Hashes[0][0], sizeof(kKnownRootCertSHA1Hashes)); +} + +// Saves some information about the certificate chain |chain_context| in +// |*verify_result|. The caller MUST initialize |*verify_result| before +// calling this function. +void GetCertChainInfo(PCCERT_CHAIN_CONTEXT chain_context, + CertVerifyResult* verify_result) { + if (chain_context->cChain == 0) + return; + + PCERT_SIMPLE_CHAIN first_chain = chain_context->rgpChain[0]; + int num_elements = first_chain->cElement; + PCERT_CHAIN_ELEMENT* element = first_chain->rgpElement; + + PCCERT_CONTEXT verified_cert = NULL; + std::vector<PCCERT_CONTEXT> verified_chain; + + bool has_root_ca = num_elements > 1 && + !(chain_context->TrustStatus.dwErrorStatus & + CERT_TRUST_IS_PARTIAL_CHAIN); + + // Each chain starts with the end entity certificate (i = 0) and ends with + // either the root CA certificate or the last available intermediate. If a + // root CA certificate is present, do not inspect the signature algorithm of + // the root CA certificate because the signature on the trust anchor is not + // important. + if (has_root_ca) { + // If a full chain was constructed, regardless of whether it was trusted, + // don't inspect the root's signature algorithm. + num_elements -= 1; + } + + for (int i = 0; i < num_elements; ++i) { + PCCERT_CONTEXT cert = element[i]->pCertContext; + if (i == 0) { + verified_cert = cert; + } else { + verified_chain.push_back(cert); + } + + const char* algorithm = cert->pCertInfo->SignatureAlgorithm.pszObjId; + if (strcmp(algorithm, szOID_RSA_MD5RSA) == 0) { + // md5WithRSAEncryption: 1.2.840.113549.1.1.4 + verify_result->has_md5 = true; + if (i != 0) + verify_result->has_md5_ca = true; + } else if (strcmp(algorithm, szOID_RSA_MD2RSA) == 0) { + // md2WithRSAEncryption: 1.2.840.113549.1.1.2 + verify_result->has_md2 = true; + if (i != 0) + verify_result->has_md2_ca = true; + } else if (strcmp(algorithm, szOID_RSA_MD4RSA) == 0) { + // md4WithRSAEncryption: 1.2.840.113549.1.1.3 + verify_result->has_md4 = true; + } + } + + if (verified_cert) { + // Add the root certificate, if present, as it was not added above. + if (has_root_ca) + verified_chain.push_back(element[num_elements]->pCertContext); + verify_result->verified_cert = + X509Certificate::CreateFromHandle(verified_cert, verified_chain); + } +} + +// Decodes the cert's certificatePolicies extension into a CERT_POLICIES_INFO +// structure and stores it in *output. +void GetCertPoliciesInfo(PCCERT_CONTEXT cert, + scoped_ptr_malloc<CERT_POLICIES_INFO>* output) { + PCERT_EXTENSION extension = CertFindExtension(szOID_CERT_POLICIES, + cert->pCertInfo->cExtension, + cert->pCertInfo->rgExtension); + if (!extension) + return; + + CRYPT_DECODE_PARA decode_para; + decode_para.cbSize = sizeof(decode_para); + decode_para.pfnAlloc = crypto::CryptAlloc; + decode_para.pfnFree = crypto::CryptFree; + CERT_POLICIES_INFO* policies_info = NULL; + DWORD policies_info_size = 0; + BOOL rv; + rv = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + szOID_CERT_POLICIES, + extension->Value.pbData, + extension->Value.cbData, + CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, + &decode_para, + &policies_info, + &policies_info_size); + if (rv) + output->reset(policies_info); +} + +bool CheckRevocationWithCRLSet(PCCERT_CHAIN_CONTEXT chain, + CRLSet* crl_set) { + if (chain->cChain == 0) + return true; + + const PCERT_SIMPLE_CHAIN first_chain = chain->rgpChain[0]; + const PCERT_CHAIN_ELEMENT* element = first_chain->rgpElement; + + const int num_elements = first_chain->cElement; + if (num_elements == 0) + return true; + + // We iterate from the root certificate down to the leaf, keeping track of + // the issuer's SPKI at each step. + std::string issuer_spki_hash; + for (int i = num_elements - 1; i >= 0; i--) { + PCCERT_CONTEXT cert = element[i]->pCertContext; + + base::StringPiece der_bytes( + reinterpret_cast<const char*>(cert->pbCertEncoded), + cert->cbCertEncoded); + + base::StringPiece spki; + if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki)) { + NOTREACHED(); + continue; + } + + const std::string spki_hash = crypto::SHA256HashString(spki); + + const CRYPT_INTEGER_BLOB* serial_blob = &cert->pCertInfo->SerialNumber; + scoped_array<uint8> serial_bytes(new uint8[serial_blob->cbData]); + // The bytes of the serial number are stored little-endian. + for (unsigned j = 0; j < serial_blob->cbData; j++) + serial_bytes[j] = serial_blob->pbData[serial_blob->cbData - j - 1]; + base::StringPiece serial(reinterpret_cast<const char*>(serial_bytes.get()), + serial_blob->cbData); + + CRLSet::Result result = crl_set->CheckSPKI(spki_hash); + + if (result != CRLSet::REVOKED && !issuer_spki_hash.empty()) + result = crl_set->CheckSerial(serial, issuer_spki_hash); + + issuer_spki_hash = spki_hash; + + switch (result) { + case CRLSet::REVOKED: + return false; + case CRLSet::UNKNOWN: + case CRLSet::GOOD: + continue; + default: + NOTREACHED(); + continue; + } + } + + return true; +} + +void AppendPublicKeyHashes(PCCERT_CHAIN_CONTEXT chain, + std::vector<SHA1Fingerprint>* hashes) { + if (chain->cChain == 0) + return; + + PCERT_SIMPLE_CHAIN first_chain = chain->rgpChain[0]; + PCERT_CHAIN_ELEMENT* const element = first_chain->rgpElement; + + const DWORD num_elements = first_chain->cElement; + for (DWORD i = 0; i < num_elements; i++) { + PCCERT_CONTEXT cert = element[i]->pCertContext; + + base::StringPiece der_bytes( + reinterpret_cast<const char*>(cert->pbCertEncoded), + cert->cbCertEncoded); + base::StringPiece spki_bytes; + if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki_bytes)) + continue; + + SHA1Fingerprint hash; + base::SHA1HashBytes(reinterpret_cast<const uint8*>(spki_bytes.data()), + spki_bytes.size(), hash.data); + hashes->push_back(hash); + } +} + +// Returns true if the certificate is an extended-validation certificate. +// +// This function checks the certificatePolicies extensions of the +// certificates in the certificate chain according to Section 7 (pp. 11-12) +// of the EV Certificate Guidelines Version 1.0 at +// http://cabforum.org/EV_Certificate_Guidelines.pdf. +bool CheckEV(PCCERT_CHAIN_CONTEXT chain_context, + bool rev_checking_enabled, + const char* policy_oid) { + DCHECK_NE(static_cast<DWORD>(0), chain_context->cChain); + // If the cert doesn't match any of the policies, the + // CERT_TRUST_IS_NOT_VALID_FOR_USAGE bit (0x10) in + // chain_context->TrustStatus.dwErrorStatus is set. + DWORD error_status = chain_context->TrustStatus.dwErrorStatus; + + if (!rev_checking_enabled) { + // If online revocation checking is disabled then we will have still + // requested that the revocation cache be checked. However, that will often + // cause the following two error bits to be set. These error bits mean that + // the local OCSP/CRL is stale or missing entries for these certificates. + // Since they are expected, we mask them away. + error_status &= ~(CERT_TRUST_IS_OFFLINE_REVOCATION | + CERT_TRUST_REVOCATION_STATUS_UNKNOWN); + } + if (!chain_context->cChain || error_status != CERT_TRUST_NO_ERROR) + return false; + + // Check the end certificate simple chain (chain_context->rgpChain[0]). + // If the end certificate's certificatePolicies extension contains the + // EV policy OID of the root CA, return true. + PCERT_CHAIN_ELEMENT* element = chain_context->rgpChain[0]->rgpElement; + int num_elements = chain_context->rgpChain[0]->cElement; + if (num_elements < 2) + return false; + + // Look up the EV policy OID of the root CA. + PCCERT_CONTEXT root_cert = element[num_elements - 1]->pCertContext; + SHA1Fingerprint fingerprint = + X509Certificate::CalculateFingerprint(root_cert); + EVRootCAMetadata* metadata = EVRootCAMetadata::GetInstance(); + return metadata->HasEVPolicyOID(fingerprint, policy_oid); +} + +} // namespace + +CertVerifyProcWin::CertVerifyProcWin() {} + +CertVerifyProcWin::~CertVerifyProcWin() {} + +int CertVerifyProcWin::VerifyInternal(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + CertVerifyResult* verify_result) { + PCCERT_CONTEXT cert_handle = cert->os_cert_handle(); + if (!cert_handle) + return ERR_UNEXPECTED; + + // Build and validate certificate chain. + CERT_CHAIN_PARA chain_para; + memset(&chain_para, 0, sizeof(chain_para)); + chain_para.cbSize = sizeof(chain_para); + // ExtendedKeyUsage. + // We still need to request szOID_SERVER_GATED_CRYPTO and szOID_SGC_NETSCAPE + // today because some certificate chains need them. IE also requests these + // two usages. + static const LPSTR usage[] = { + szOID_PKIX_KP_SERVER_AUTH, + szOID_SERVER_GATED_CRYPTO, + szOID_SGC_NETSCAPE + }; + chain_para.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; + chain_para.RequestedUsage.Usage.cUsageIdentifier = arraysize(usage); + chain_para.RequestedUsage.Usage.rgpszUsageIdentifier = + const_cast<LPSTR*>(usage); + // We can set CERT_CHAIN_RETURN_LOWER_QUALITY_CONTEXTS to get more chains. + DWORD chain_flags = CERT_CHAIN_CACHE_END_CERT | + CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT; + const bool rev_checking_enabled = + flags & X509Certificate::VERIFY_REV_CHECKING_ENABLED; + + if (rev_checking_enabled) { + verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED; + } else { + chain_flags |= CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY; + } + + // Get the certificatePolicies extension of the certificate. + scoped_ptr_malloc<CERT_POLICIES_INFO> policies_info; + LPSTR ev_policy_oid = NULL; + if (flags & X509Certificate::VERIFY_EV_CERT) { + GetCertPoliciesInfo(cert_handle, &policies_info); + if (policies_info.get()) { + EVRootCAMetadata* metadata = EVRootCAMetadata::GetInstance(); + for (DWORD i = 0; i < policies_info->cPolicyInfo; ++i) { + LPSTR policy_oid = policies_info->rgPolicyInfo[i].pszPolicyIdentifier; + if (metadata->IsEVPolicyOID(policy_oid)) { + ev_policy_oid = policy_oid; + chain_para.RequestedIssuancePolicy.dwType = USAGE_MATCH_TYPE_AND; + chain_para.RequestedIssuancePolicy.Usage.cUsageIdentifier = 1; + chain_para.RequestedIssuancePolicy.Usage.rgpszUsageIdentifier = + &ev_policy_oid; + break; + } + } + } + } + + // For non-test scenarios, use the default HCERTCHAINENGINE, NULL, which + // corresponds to HCCE_CURRENT_USER and is is initialized as needed by + // crypt32. However, when testing, it is necessary to create a new + // HCERTCHAINENGINE and use that instead. This is because each + // HCERTCHAINENGINE maintains a cache of information about certificates + // encountered, and each test run may modify the trust status of a + // certificate. + ScopedHCERTCHAINENGINE chain_engine(NULL); + if (TestRootCerts::HasInstance()) + chain_engine.reset(TestRootCerts::GetInstance()->GetChainEngine()); + + ScopedPCCERT_CONTEXT cert_list(cert->CreateOSCertChainForCert()); + PCCERT_CHAIN_CONTEXT chain_context; + // IE passes a non-NULL pTime argument that specifies the current system + // time. IE passes CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT as the + // chain_flags argument. + if (!CertGetCertificateChain( + chain_engine, + cert_list.get(), + NULL, // current system time + cert_list->hCertStore, + &chain_para, + chain_flags, + NULL, // reserved + &chain_context)) { + verify_result->cert_status |= CERT_STATUS_INVALID; + return MapSecurityError(GetLastError()); + } + + if (chain_context->TrustStatus.dwErrorStatus & + CERT_TRUST_IS_NOT_VALID_FOR_USAGE) { + ev_policy_oid = NULL; + chain_para.RequestedIssuancePolicy.Usage.cUsageIdentifier = 0; + chain_para.RequestedIssuancePolicy.Usage.rgpszUsageIdentifier = NULL; + CertFreeCertificateChain(chain_context); + if (!CertGetCertificateChain( + chain_engine, + cert_list.get(), + NULL, // current system time + cert_list->hCertStore, + &chain_para, + chain_flags, + NULL, // reserved + &chain_context)) { + verify_result->cert_status |= CERT_STATUS_INVALID; + return MapSecurityError(GetLastError()); + } + } + + ScopedPCCERT_CHAIN_CONTEXT scoped_chain_context(chain_context); + + GetCertChainInfo(chain_context, verify_result); + verify_result->cert_status |= MapCertChainErrorStatusToCertStatus( + chain_context->TrustStatus.dwErrorStatus); + + // Flag certificates that have a Subject common name with a NULL character. + if (CertSubjectCommonNameHasNull(cert_handle)) + verify_result->cert_status |= CERT_STATUS_INVALID; + + if (crl_set && !CheckRevocationWithCRLSet(chain_context, crl_set)) + verify_result->cert_status |= CERT_STATUS_REVOKED; + + std::wstring wstr_hostname = ASCIIToWide(hostname); + + SSL_EXTRA_CERT_CHAIN_POLICY_PARA extra_policy_para; + memset(&extra_policy_para, 0, sizeof(extra_policy_para)); + extra_policy_para.cbSize = sizeof(extra_policy_para); + extra_policy_para.dwAuthType = AUTHTYPE_SERVER; + extra_policy_para.fdwChecks = 0; + extra_policy_para.pwszServerName = + const_cast<wchar_t*>(wstr_hostname.c_str()); + + CERT_CHAIN_POLICY_PARA policy_para; + memset(&policy_para, 0, sizeof(policy_para)); + policy_para.cbSize = sizeof(policy_para); + policy_para.dwFlags = 0; + policy_para.pvExtraPolicyPara = &extra_policy_para; + + CERT_CHAIN_POLICY_STATUS policy_status; + memset(&policy_status, 0, sizeof(policy_status)); + policy_status.cbSize = sizeof(policy_status); + + if (!CertVerifyCertificateChainPolicy( + CERT_CHAIN_POLICY_SSL, + chain_context, + &policy_para, + &policy_status)) { + return MapSecurityError(GetLastError()); + } + + if (policy_status.dwError) { + verify_result->cert_status |= MapNetErrorToCertStatus( + MapSecurityError(policy_status.dwError)); + + // CertVerifyCertificateChainPolicy reports only one error (in + // policy_status.dwError) if the certificate has multiple errors. + // CertGetCertificateChain doesn't report certificate name mismatch, so + // CertVerifyCertificateChainPolicy is the only function that can report + // certificate name mismatch. + // + // To prevent a potential certificate name mismatch from being hidden by + // some other certificate error, if we get any other certificate error, + // we call CertVerifyCertificateChainPolicy again, ignoring all other + // certificate errors. Both extra_policy_para.fdwChecks and + // policy_para.dwFlags allow us to ignore certificate errors, so we set + // them both. + if (policy_status.dwError != CERT_E_CN_NO_MATCH) { + const DWORD extra_ignore_flags = + 0x00000080 | // SECURITY_FLAG_IGNORE_REVOCATION + 0x00000100 | // SECURITY_FLAG_IGNORE_UNKNOWN_CA + 0x00002000 | // SECURITY_FLAG_IGNORE_CERT_DATE_INVALID + 0x00000200; // SECURITY_FLAG_IGNORE_WRONG_USAGE + extra_policy_para.fdwChecks = extra_ignore_flags; + const DWORD ignore_flags = + CERT_CHAIN_POLICY_IGNORE_ALL_NOT_TIME_VALID_FLAGS | + CERT_CHAIN_POLICY_IGNORE_INVALID_BASIC_CONSTRAINTS_FLAG | + CERT_CHAIN_POLICY_ALLOW_UNKNOWN_CA_FLAG | + CERT_CHAIN_POLICY_IGNORE_WRONG_USAGE_FLAG | + CERT_CHAIN_POLICY_IGNORE_INVALID_NAME_FLAG | + CERT_CHAIN_POLICY_IGNORE_INVALID_POLICY_FLAG | + CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS | + CERT_CHAIN_POLICY_ALLOW_TESTROOT_FLAG | + CERT_CHAIN_POLICY_TRUST_TESTROOT_FLAG | + CERT_CHAIN_POLICY_IGNORE_NOT_SUPPORTED_CRITICAL_EXT_FLAG | + CERT_CHAIN_POLICY_IGNORE_PEER_TRUST_FLAG; + policy_para.dwFlags = ignore_flags; + if (!CertVerifyCertificateChainPolicy( + CERT_CHAIN_POLICY_SSL, + chain_context, + &policy_para, + &policy_status)) { + return MapSecurityError(GetLastError()); + } + if (policy_status.dwError) { + verify_result->cert_status |= MapNetErrorToCertStatus( + MapSecurityError(policy_status.dwError)); + } + } + } + + // TODO(wtc): Suppress CERT_STATUS_NO_REVOCATION_MECHANISM for now to be + // compatible with WinHTTP, which doesn't report this error (bug 3004). + verify_result->cert_status &= ~CERT_STATUS_NO_REVOCATION_MECHANISM; + + if (!rev_checking_enabled) { + // If we didn't do online revocation checking then Windows will report + // CERT_UNABLE_TO_CHECK_REVOCATION unless it had cached OCSP or CRL + // information for every certificate. We only want to put up revoked + // statuses from the offline checks so we squash this error. + verify_result->cert_status &= ~CERT_STATUS_UNABLE_TO_CHECK_REVOCATION; + } + + if (IsCertStatusError(verify_result->cert_status)) + return MapCertStatusToNetError(verify_result->cert_status); + + AppendPublicKeyHashes(chain_context, &verify_result->public_key_hashes); + verify_result->is_issued_by_known_root = IsIssuedByKnownRoot(chain_context); + + if (ev_policy_oid && + CheckEV(chain_context, rev_checking_enabled, ev_policy_oid)) { + verify_result->cert_status |= CERT_STATUS_IS_EV; + } + return OK; +} + +} // namespace net diff --git a/net/base/cert_verify_proc_win.h b/net/base/cert_verify_proc_win.h new file mode 100644 index 0000000..7984f5e --- /dev/null +++ b/net/base/cert_verify_proc_win.h @@ -0,0 +1,32 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_BASE_CERT_VERIFY_PROC_WIN_H_ +#define NET_BASE_CERT_VERIFY_PROC_WIN_H_ +#pragma once + +#include "net/base/cert_verify_proc.h" + +namespace net { + +// Performs certificate path construction and validation using Windows' +// CryptoAPI. +class CertVerifyProcWin : public CertVerifyProc { + public: + CertVerifyProcWin(); + + protected: + virtual ~CertVerifyProcWin(); + + private: + virtual int VerifyInternal(X509Certificate* cert, + const std::string& hostname, + int flags, + CRLSet* crl_set, + CertVerifyResult* verify_result) OVERRIDE; +}; + +} // namespace net + +#endif // NET_BASE_CERT_VERIFY_PROC_WIN_H_ diff --git a/net/base/x509_cert_types.cc b/net/base/x509_cert_types.cc index 7578ccd..174000a 100644 --- a/net/base/x509_cert_types.cc +++ b/net/base/x509_cert_types.cc @@ -1,14 +1,18 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/base/x509_cert_types.h" -#include "net/base/x509_certificate.h" +#include <cstdlib> +#include <cstring> + #include "base/logging.h" +#include "base/sha1.h" #include "base/string_number_conversions.h" #include "base/string_piece.h" #include "base/time.h" +#include "net/base/x509_certificate.h" namespace net { @@ -25,8 +29,24 @@ int ParseIntAndAdvance(const char** field, size_t field_len, bool* ok) { return result; } +// CompareSHA1Hashes is a helper function for using bsearch() with an array of +// SHA1 hashes. +int CompareSHA1Hashes(const void* a, const void* b) { + return memcmp(a, b, base::kSHA1Length); +} + } // namespace +// static +bool IsSHA1HashInSortedArray(const SHA1Fingerprint& hash, + const uint8* array, + size_t array_byte_len) { + DCHECK_EQ(0u, array_byte_len % base::kSHA1Length); + const size_t arraylen = array_byte_len / base::kSHA1Length; + return NULL != bsearch(hash.data, array, arraylen, base::kSHA1Length, + CompareSHA1Hashes); +} + CertPrincipal::CertPrincipal() { } diff --git a/net/base/x509_cert_types.h b/net/base/x509_cert_types.h index d11092d2..61af236 100644 --- a/net/base/x509_cert_types.h +++ b/net/base/x509_cert_types.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -29,7 +29,7 @@ namespace net { class X509Certificate; // SHA-1 fingerprint (160 bits) of a certificate. -struct SHA1Fingerprint { +struct NET_EXPORT SHA1Fingerprint { bool Equals(const SHA1Fingerprint& other) const { return memcmp(data, other.data, sizeof(data)) == 0; } @@ -37,7 +37,7 @@ struct SHA1Fingerprint { unsigned char data[20]; }; -class SHA1FingerprintLessThan { +class NET_EXPORT SHA1FingerprintLessThan { public: bool operator() (const SHA1Fingerprint& lhs, const SHA1Fingerprint& rhs) const { @@ -45,6 +45,12 @@ class SHA1FingerprintLessThan { } }; +// IsSHA1HashInSortedArray returns true iff |hash| is in |array|, a sorted +// array of SHA1 hashes. +bool NET_EXPORT IsSHA1HashInSortedArray(const SHA1Fingerprint& hash, + const uint8* array, + size_t array_byte_len); + // CertPrincipal represents the issuer or subject field of an X.509 certificate. struct NET_EXPORT CertPrincipal { CertPrincipal(); diff --git a/net/base/x509_certificate.cc b/net/base/x509_certificate.cc index b42bd87..b30e2f8 100644 --- a/net/base/x509_certificate.cc +++ b/net/base/x509_certificate.cc @@ -23,10 +23,6 @@ #include "base/synchronization/lock.h" #include "base/time.h" #include "googleurl/src/url_canon_ip.h" -#include "net/base/cert_status_flags.h" -#include "net/base/cert_verify_result.h" -#include "net/base/crl_set.h" -#include "net/base/net_errors.h" #include "net/base/net_util.h" #include "net/base/pem_tokenizer.h" @@ -202,12 +198,6 @@ void RemoveFromCache(X509Certificate::OSCertHandle cert_handle) { #endif } -// CompareSHA1Hashes is a helper function for using bsearch() with an array of -// SHA1 hashes. -int CompareSHA1Hashes(const void* a, const void* b) { - return memcmp(a, b, base::kSHA1Length); -} - // Utility to split |src| on the first occurrence of |c|, if any. |right| will // either be empty if |c| was not found, or will contain the remainder of the // string including the split character itself. @@ -225,20 +215,6 @@ void SplitOnChar(const base::StringPiece& src, } } -// Returns true if |type| is |kPublicKeyTypeRSA| or |kPublicKeyTypeDSA|, and -// if |size_bits| is < 1024. Note that this means there may be false -// negatives: keys for other algorithms and which are weak will pass this -// test. -bool IsWeakKey(X509Certificate::PublicKeyType type, size_t size_bits) { - switch (type) { - case X509Certificate::kPublicKeyTypeRSA: - case X509Certificate::kPublicKeyTypeDSA: - return size_bits < 1024; - default: - return false; - } -} - } // namespace bool X509Certificate::LessThan::operator()(X509Certificate* lhs, @@ -636,85 +612,6 @@ bool X509Certificate::VerifyHostname( return false; } -int X509Certificate::Verify(const std::string& hostname, - int flags, - CRLSet* crl_set, - CertVerifyResult* verify_result) const { - verify_result->Reset(); - verify_result->verified_cert = const_cast<X509Certificate*>(this); - - if (IsBlacklisted()) { - verify_result->cert_status |= CERT_STATUS_REVOKED; - return ERR_CERT_REVOKED; - } - - // If EV verification was requested and no CRLSet is present, or if the - // CRLSet has expired, then enable online revocation checks. If the online - // check fails, EV status won't be shown. - // - // A possible optimisation is to only enable online revocation checking in - // the event that the leaf certificate appears to include a EV policy ID. - // However, it's expected that having a current CRLSet will be very common. - if ((flags & VERIFY_EV_CERT) && (!crl_set || crl_set->IsExpired())) - flags |= VERIFY_REV_CHECKING_ENABLED; - - int rv = VerifyInternal(hostname, flags, crl_set, verify_result); - - // This check is done after VerifyInternal so that VerifyInternal can fill in - // the list of public key hashes. - if (IsPublicKeyBlacklisted(verify_result->public_key_hashes)) { - verify_result->cert_status |= CERT_STATUS_REVOKED; - rv = MapCertStatusToNetError(verify_result->cert_status); - } - - // Check for weak keys in the entire verified chain. - size_t size_bits = 0; - PublicKeyType type = kPublicKeyTypeUnknown; - bool weak_key = false; - - GetPublicKeyInfo(verify_result->verified_cert->os_cert_handle(), &size_bits, - &type); - if (IsWeakKey(type, size_bits)) { - weak_key = true; - } else { - const OSCertHandles& intermediates = - verify_result->verified_cert->GetIntermediateCertificates(); - for (OSCertHandles::const_iterator i = intermediates.begin(); - i != intermediates.end(); ++i) { - GetPublicKeyInfo(*i, &size_bits, &type); - if (IsWeakKey(type, size_bits)) - weak_key = true; - } - } - - if (weak_key) { - verify_result->cert_status |= CERT_STATUS_WEAK_KEY; - // Avoid replacing a more serious error, such as an OS/library failure, - // by ensuring that if verification failed, it failed with a certificate - // error. - if (rv == OK || IsCertificateError(rv)) - rv = MapCertStatusToNetError(verify_result->cert_status); - } - - // Treat certificates signed using broken signature algorithms as invalid. - if (verify_result->has_md2 || verify_result->has_md4) { - verify_result->cert_status |= CERT_STATUS_INVALID; - rv = MapCertStatusToNetError(verify_result->cert_status); - } - - // Flag certificates using weak signature algorithms. - if (verify_result->has_md5) { - verify_result->cert_status |= CERT_STATUS_WEAK_SIGNATURE_ALGORITHM; - // Avoid replacing a more serious error, such as an OS/library failure, - // by ensuring that if verification failed, it failed with a certificate - // error. - if (rv == OK || IsCertificateError(rv)) - rv = MapCertStatusToNetError(verify_result->cert_status); - } - - return rv; -} - #if !defined(USE_NSS) bool X509Certificate::VerifyNameMatch(const std::string& hostname) const { std::vector<std::string> dns_names, ip_addrs; @@ -791,134 +688,4 @@ X509Certificate::~X509Certificate() { } } -bool X509Certificate::IsBlacklisted() const { - static const unsigned kComodoSerialBytes = 16; - static const uint8 kComodoSerials[][kComodoSerialBytes] = { - // Not a real certificate. For testing only. - {0x07,0x7a,0x59,0xbc,0xd5,0x34,0x59,0x60,0x1c,0xa6,0x90,0x72,0x67,0xa6,0xdd,0x1c}, - - // The next nine certificates all expire on Fri Mar 14 23:59:59 2014. - // Some serial numbers actually have a leading 0x00 byte required to - // encode a positive integer in DER if the most significant bit is 0. - // We omit the leading 0x00 bytes to make all serial numbers 16 bytes. - - // Subject: CN=mail.google.com - // subjectAltName dNSName: mail.google.com, www.mail.google.com - {0x04,0x7e,0xcb,0xe9,0xfc,0xa5,0x5f,0x7b,0xd0,0x9e,0xae,0x36,0xe1,0x0c,0xae,0x1e}, - // Subject: CN=global trustee - // subjectAltName dNSName: global trustee - // Note: not a CA certificate. - {0xd8,0xf3,0x5f,0x4e,0xb7,0x87,0x2b,0x2d,0xab,0x06,0x92,0xe3,0x15,0x38,0x2f,0xb0}, - // Subject: CN=login.live.com - // subjectAltName dNSName: login.live.com, www.login.live.com - {0xb0,0xb7,0x13,0x3e,0xd0,0x96,0xf9,0xb5,0x6f,0xae,0x91,0xc8,0x74,0xbd,0x3a,0xc0}, - // Subject: CN=addons.mozilla.org - // subjectAltName dNSName: addons.mozilla.org, www.addons.mozilla.org - {0x92,0x39,0xd5,0x34,0x8f,0x40,0xd1,0x69,0x5a,0x74,0x54,0x70,0xe1,0xf2,0x3f,0x43}, - // Subject: CN=login.skype.com - // subjectAltName dNSName: login.skype.com, www.login.skype.com - {0xe9,0x02,0x8b,0x95,0x78,0xe4,0x15,0xdc,0x1a,0x71,0x0a,0x2b,0x88,0x15,0x44,0x47}, - // Subject: CN=login.yahoo.com - // subjectAltName dNSName: login.yahoo.com, www.login.yahoo.com - {0xd7,0x55,0x8f,0xda,0xf5,0xf1,0x10,0x5b,0xb2,0x13,0x28,0x2b,0x70,0x77,0x29,0xa3}, - // Subject: CN=www.google.com - // subjectAltName dNSName: www.google.com, google.com - {0xf5,0xc8,0x6a,0xf3,0x61,0x62,0xf1,0x3a,0x64,0xf5,0x4f,0x6d,0xc9,0x58,0x7c,0x06}, - // Subject: CN=login.yahoo.com - // subjectAltName dNSName: login.yahoo.com - {0x39,0x2a,0x43,0x4f,0x0e,0x07,0xdf,0x1f,0x8a,0xa3,0x05,0xde,0x34,0xe0,0xc2,0x29}, - // Subject: CN=login.yahoo.com - // subjectAltName dNSName: login.yahoo.com - {0x3e,0x75,0xce,0xd4,0x6b,0x69,0x30,0x21,0x21,0x88,0x30,0xae,0x86,0xa8,0x2a,0x71}, - }; - - if (!serial_number_.empty() && (serial_number_[0] & 0x80) != 0) { - // This is a negative serial number, which isn't technically allowed but - // which probably happens. In order to avoid confusing a negative serial - // number with a positive one once the leading zeros have been removed, we - // disregard it. - return false; - } - - base::StringPiece serial(serial_number_); - // Remove leading zeros. - while (serial.size() > 1 && serial[0] == 0) - serial.remove_prefix(1); - - if (serial.size() == kComodoSerialBytes) { - for (unsigned i = 0; i < arraysize(kComodoSerials); i++) { - if (memcmp(kComodoSerials[i], serial.data(), kComodoSerialBytes) == 0) { - UMA_HISTOGRAM_ENUMERATION("Net.SSLCertBlacklisted", i, - arraysize(kComodoSerials) + 1); - return true; - } - } - } - - return false; -} - -// static -bool X509Certificate::IsPublicKeyBlacklisted( - const std::vector<SHA1Fingerprint>& public_key_hashes) { - static const unsigned kNumHashes = 8; - static const uint8 kHashes[kNumHashes][base::kSHA1Length] = { - // Subject: CN=DigiNotar Root CA - // Issuer: CN=Entrust.net x2 and self-signed - {0x41, 0x0f, 0x36, 0x36, 0x32, 0x58, 0xf3, 0x0b, 0x34, 0x7d, - 0x12, 0xce, 0x48, 0x63, 0xe4, 0x33, 0x43, 0x78, 0x06, 0xa8}, - // Subject: CN=DigiNotar Cyber CA - // Issuer: CN=GTE CyberTrust Global Root - {0xc4, 0xf9, 0x66, 0x37, 0x16, 0xcd, 0x5e, 0x71, 0xd6, 0x95, - 0x0b, 0x5f, 0x33, 0xce, 0x04, 0x1c, 0x95, 0xb4, 0x35, 0xd1}, - // Subject: CN=DigiNotar Services 1024 CA - // Issuer: CN=Entrust.net - {0xe2, 0x3b, 0x8d, 0x10, 0x5f, 0x87, 0x71, 0x0a, 0x68, 0xd9, - 0x24, 0x80, 0x50, 0xeb, 0xef, 0xc6, 0x27, 0xbe, 0x4c, 0xa6}, - // Subject: CN=DigiNotar PKIoverheid CA Organisatie - G2 - // Issuer: CN=Staat der Nederlanden Organisatie CA - G2 - {0x7b, 0x2e, 0x16, 0xbc, 0x39, 0xbc, 0xd7, 0x2b, 0x45, 0x6e, - 0x9f, 0x05, 0x5d, 0x1d, 0xe6, 0x15, 0xb7, 0x49, 0x45, 0xdb}, - // Subject: CN=DigiNotar PKIoverheid CA Overheid en Bedrijven - // Issuer: CN=Staat der Nederlanden Overheid CA - {0xe8, 0xf9, 0x12, 0x00, 0xc6, 0x5c, 0xee, 0x16, 0xe0, 0x39, - 0xb9, 0xf8, 0x83, 0x84, 0x16, 0x61, 0x63, 0x5f, 0x81, 0xc5}, - // Subject: O=Digicert Sdn. Bhd. - // Issuer: CN=GTE CyberTrust Global Root - // Expires: Jul 17 15:16:54 2012 GMT - {0x01, 0x29, 0xbc, 0xd5, 0xb4, 0x48, 0xae, 0x8d, 0x24, 0x96, - 0xd1, 0xc3, 0xe1, 0x97, 0x23, 0x91, 0x90, 0x88, 0xe1, 0x52}, - // Subject: O=Digicert Sdn. Bhd. - // Issuer: CN=Entrust.net Certification Authority (2048) - // Expires: Jul 16 17:53:37 2015 GMT - {0xd3, 0x3c, 0x5b, 0x41, 0xe4, 0x5c, 0xc4, 0xb3, 0xbe, 0x9a, - 0xd6, 0x95, 0x2c, 0x4e, 0xcc, 0x25, 0x28, 0x03, 0x29, 0x81}, - // Issuer: CN=Trustwave Organization Issuing CA, Level 2 - // Covers two certificates, the latter of which expires Apr 15 21:09:30 - // 2021 GMT. - {0xe1, 0x2d, 0x89, 0xf5, 0x6d, 0x22, 0x76, 0xf8, 0x30, 0xe6, - 0xce, 0xaf, 0xa6, 0x6c, 0x72, 0x5c, 0x0b, 0x41, 0xa9, 0x32}, - }; - - for (unsigned i = 0; i < kNumHashes; i++) { - for (std::vector<SHA1Fingerprint>::const_iterator - j = public_key_hashes.begin(); j != public_key_hashes.end(); ++j) { - if (memcmp(j->data, kHashes[i], base::kSHA1Length) == 0) - return true; - } - } - - return false; -} - -// static -bool X509Certificate::IsSHA1HashInSortedArray(const SHA1Fingerprint& hash, - const uint8* array, - size_t array_byte_len) { - DCHECK_EQ(0u, array_byte_len % base::kSHA1Length); - const size_t arraylen = array_byte_len / base::kSHA1Length; - return NULL != bsearch(hash.data, array, arraylen, base::kSHA1Length, - CompareSHA1Hashes); -} - } // namespace net diff --git a/net/base/x509_certificate.h b/net/base/x509_certificate.h index 4dc4f68..e3523d1 100644 --- a/net/base/x509_certificate.h +++ b/net/base/x509_certificate.h @@ -26,7 +26,6 @@ #include <CoreFoundation/CFArray.h> #include <Security/SecBase.h> -#include "base/synchronization/lock.h" #elif defined(USE_OPENSSL) // Forward declaration; real one in <x509.h> typedef struct x509_st X509; @@ -224,6 +223,9 @@ class NET_EXPORT X509Certificate // Appends a representation of this object to the given pickle. void Persist(Pickle* pickle); + // The serial number, DER encoded, possibly including a leading 00 byte. + const std::string& serial_number() const { return serial_number_; } + // The subject of the certificate. For HTTPS server certificates, this // represents the web server. The common name of the subject should match // the host name of the web server. @@ -282,26 +284,6 @@ class NET_EXPORT X509Certificate // Do any of the given issuer names appear in this cert's chain of trust? bool IsIssuedBy(const std::vector<CertPrincipal>& valid_issuers); - // Creates a security policy for certificates used as client certificates - // in SSL. - // If a policy is successfully created, it will be stored in - // |*policy| and ownership transferred to the caller. - static OSStatus CreateSSLClientPolicy(SecPolicyRef* policy); - - // Creates a security policy for basic X.509 validation. If the policy is - // successfully created, it will be stored in |*policy| and ownership - // transferred to the caller. - static OSStatus CreateBasicX509Policy(SecPolicyRef* policy); - - // Creates security policies to control revocation checking (OCSP and CRL). - // If |enable_revocation_checking| is false, the policies returned will be - // explicitly disabled from accessing the network or the cache. This may be - // used to override system settings regarding revocation checking. - // If the policies are successfully created, they will be appended to - // |policies|. - static OSStatus CreateRevocationPolicies(bool enable_revocation_checking, - CFMutableArrayRef policies); - // Adds all available SSL client identity certs to the given vector. // |server_domain| is a hint for which domain the cert is to be sent to // (a cert previously specified as the default for that domain will be given @@ -363,12 +345,6 @@ class NET_EXPORT X509Certificate PCCERT_CONTEXT CreateOSCertChainForCert() const; #endif -#if defined(OS_ANDROID) - // |chain_bytes| will contain the chain (including this certificate) encoded - // using GetChainDEREncodedBytes below. - void GetChainDEREncodedBytes(std::vector<std::string>* chain_bytes) const; -#endif - #if defined(USE_OPENSSL) // Returns a handle to a global, in-memory certificate store. We // use it for test code, e.g. importing the test server's certificate. @@ -455,11 +431,8 @@ class NET_EXPORT X509Certificate private: friend class base::RefCountedThreadSafe<X509Certificate>; friend class TestRootCerts; // For unit tests - // TODO(rsleevi): Temporary refactoring - http://crbug.com/114343 - friend class CertVerifyProcStub; FRIEND_TEST_ALL_PREFIXES(X509CertificateNameVerifyTest, VerifyHostname); - FRIEND_TEST_ALL_PREFIXES(X509CertificateTest, DigiNotarCerts); FRIEND_TEST_ALL_PREFIXES(X509CertificateTest, SerialNumbers); // Construct an X509Certificate from a handle to the certificate object @@ -472,44 +445,6 @@ class NET_EXPORT X509Certificate // Common object initialization code. Called by the constructors only. void Initialize(); - // Verifies the certificate against the given hostname. Returns OK if - // successful or an error code upon failure. - // - // The |*verify_result| structure, including the |verify_result->cert_status| - // bitmask, is always filled out regardless of the return value. If the - // certificate has multiple errors, the corresponding status flags are set in - // |verify_result->cert_status|, and the error code for the most serious - // error is returned. - // - // |flags| is bitwise OR'd of VerifyFlags: - // - // If VERIFY_REV_CHECKING_ENABLED is set in |flags|, online certificate - // revocation checking is performed (i.e. OCSP and downloading CRLs). CRLSet - // based revocation checking is always enabled, regardless of this flag, if - // |crl_set| is given. - // - // If VERIFY_EV_CERT is set in |flags| too, EV certificate verification is - // performed. - // - // |crl_set| points to an optional CRLSet structure which can be used to - // avoid revocation checks over the network. - int Verify(const std::string& hostname, - int flags, - CRLSet* crl_set, - CertVerifyResult* verify_result) const; - -#if defined(OS_WIN) - bool CheckEV(PCCERT_CHAIN_CONTEXT chain_context, - bool rev_checking_enabled, - const char* policy_oid) const; - static bool IsIssuedByKnownRoot(PCCERT_CHAIN_CONTEXT chain_context); -#endif -#if defined(OS_MACOSX) - static bool IsIssuedByKnownRoot(CFArrayRef chain); -#endif -#if defined(USE_NSS) - bool VerifyEV(int flags, CRLSet* crl_set) const; -#endif #if defined(USE_OPENSSL) // Resets the store returned by cert_store() to default state. Used by // TestRootCerts to undo modifications. @@ -530,32 +465,6 @@ class NET_EXPORT X509Certificate const std::vector<std::string>& cert_san_dns_names, const std::vector<std::string>& cert_san_ip_addrs); - // Performs the platform-dependent part of the Verify() method, verifiying - // this certificate against the platform's root CA certificates. - // - // Parameters and return value are as per Verify(). - int VerifyInternal(const std::string& hostname, - int flags, - CRLSet* crl_set, - CertVerifyResult* verify_result) const; - - // The serial number, DER encoded, possibly including a leading 00 byte. - const std::string& serial_number() const { return serial_number_; } - - // IsBlacklisted returns true if this certificate is explicitly blacklisted. - bool IsBlacklisted() const; - - // IsPublicKeyBlacklisted returns true iff one of |public_key_hashes| (which - // are SHA1 hashes of SubjectPublicKeyInfo structures) is explicitly blocked. - static bool IsPublicKeyBlacklisted( - const std::vector<SHA1Fingerprint>& public_key_hashes); - - // IsSHA1HashInSortedArray returns true iff |hash| is in |array|, a sorted - // array of SHA1 hashes. - static bool IsSHA1HashInSortedArray(const SHA1Fingerprint& hash, - const uint8* array, - size_t array_byte_len); - // Reads a single certificate from |pickle| and returns a platform-specific // certificate handle. The format of the certificate stored in |pickle| is // not guaranteed to be the same across different underlying cryptographic @@ -603,12 +512,6 @@ class NET_EXPORT X509Certificate std::string default_nickname_; #endif -#if defined(OS_MACOSX) - // Blocks multiple threads from verifying the cert simultaneously. - // (Marked mutable because it's used in a const method.) - mutable base::Lock verification_lock_; -#endif - DISALLOW_COPY_AND_ASSIGN(X509Certificate); }; diff --git a/net/base/x509_certificate_mac.cc b/net/base/x509_certificate_mac.cc index af72451..c427655 100644 --- a/net/base/x509_certificate_mac.cc +++ b/net/base/x509_certificate_mac.cc @@ -22,23 +22,9 @@ #include "crypto/cssm_init.h" #include "crypto/nss_util.h" #include "crypto/rsa_private_key.h" -#include "crypto/sha2.h" -#include "net/base/asn1_util.h" -#include "net/base/cert_status_flags.h" -#include "net/base/cert_verify_result.h" -#include "net/base/crl_set.h" -#include "net/base/net_errors.h" -#include "net/base/test_root_certs.h" -#include "net/base/x509_certificate_known_roots_mac.h" -#include "third_party/apple_apsl/cssmapplePriv.h" +#include "net/base/x509_util_mac.h" #include "third_party/nss/mozilla/security/nss/lib/certdb/cert.h" -// From 10.7.2 libsecurity_keychain-55035/lib/SecTrustPriv.h, for use with -// SecTrustCopyExtendedResult. -#ifndef kSecEVOrganizationName -#define kSecEVOrganizationName CFSTR("Organization") -#endif - using base::mac::ScopedCFTypeRef; using base::Time; @@ -46,224 +32,11 @@ namespace net { namespace { -typedef OSStatus (*SecTrustCopyExtendedResultFuncPtr)(SecTrustRef, - CFDictionaryRef*); - -int NetErrorFromOSStatus(OSStatus status) { - switch (status) { - case noErr: - return OK; - case errSecNotAvailable: - case errSecNoCertificateModule: - case errSecNoPolicyModule: - return ERR_NOT_IMPLEMENTED; - case errSecAuthFailed: - return ERR_ACCESS_DENIED; - default: { - OSSTATUS_LOG(ERROR, status) << "Unknown error mapped to ERR_FAILED"; - return ERR_FAILED; - } - } -} - -CertStatus CertStatusFromOSStatus(OSStatus status) { - switch (status) { - case noErr: - return 0; - - case CSSMERR_TP_INVALID_ANCHOR_CERT: - case CSSMERR_TP_NOT_TRUSTED: - case CSSMERR_TP_INVALID_CERT_AUTHORITY: - return CERT_STATUS_AUTHORITY_INVALID; - - case CSSMERR_TP_CERT_EXPIRED: - case CSSMERR_TP_CERT_NOT_VALID_YET: - // "Expired" and "not yet valid" collapse into a single status. - return CERT_STATUS_DATE_INVALID; - - case CSSMERR_TP_CERT_REVOKED: - case CSSMERR_TP_CERT_SUSPENDED: - return CERT_STATUS_REVOKED; - - case CSSMERR_APPLETP_HOSTNAME_MISMATCH: - return CERT_STATUS_COMMON_NAME_INVALID; - - case CSSMERR_APPLETP_CRL_NOT_FOUND: - case CSSMERR_APPLETP_OCSP_UNAVAILABLE: - case CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK: - return CERT_STATUS_NO_REVOCATION_MECHANISM; - - case CSSMERR_APPLETP_CRL_EXPIRED: - case CSSMERR_APPLETP_CRL_NOT_VALID_YET: - case CSSMERR_APPLETP_CRL_SERVER_DOWN: - case CSSMERR_APPLETP_CRL_NOT_TRUSTED: - case CSSMERR_APPLETP_CRL_INVALID_ANCHOR_CERT: - case CSSMERR_APPLETP_CRL_POLICY_FAIL: - case CSSMERR_APPLETP_OCSP_BAD_RESPONSE: - case CSSMERR_APPLETP_OCSP_BAD_REQUEST: - case CSSMERR_APPLETP_OCSP_STATUS_UNRECOGNIZED: - case CSSMERR_APPLETP_NETWORK_FAILURE: - case CSSMERR_APPLETP_OCSP_NOT_TRUSTED: - case CSSMERR_APPLETP_OCSP_INVALID_ANCHOR_CERT: - case CSSMERR_APPLETP_OCSP_SIG_ERROR: - case CSSMERR_APPLETP_OCSP_NO_SIGNER: - case CSSMERR_APPLETP_OCSP_RESP_MALFORMED_REQ: - case CSSMERR_APPLETP_OCSP_RESP_INTERNAL_ERR: - case CSSMERR_APPLETP_OCSP_RESP_TRY_LATER: - case CSSMERR_APPLETP_OCSP_RESP_SIG_REQUIRED: - case CSSMERR_APPLETP_OCSP_RESP_UNAUTHORIZED: - case CSSMERR_APPLETP_OCSP_NONCE_MISMATCH: - // We asked for a revocation check, but didn't get it. - return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION; - - case CSSMERR_APPLETP_CRL_BAD_URI: - case CSSMERR_APPLETP_IDP_FAIL: - return CERT_STATUS_INVALID; - - default: { - // Failure was due to something Chromium doesn't define a - // specific status for (such as basic constraints violation, or - // unknown critical extension) - OSSTATUS_LOG(WARNING, status) - << "Unknown error mapped to CERT_STATUS_INVALID"; - return CERT_STATUS_INVALID; - } - } -} - -// Wrapper for a CSSM_DATA_PTR that was obtained via one of the CSSM field -// accessors (such as CSSM_CL_CertGet[First/Next]Value or -// CSSM_CL_CertGet[First/Next]CachedValue). -class CSSMFieldValue { - public: - CSSMFieldValue() : cl_handle_(NULL), oid_(NULL), field_(NULL) {} - CSSMFieldValue(CSSM_CL_HANDLE cl_handle, - const CSSM_OID* oid, - CSSM_DATA_PTR field) - : cl_handle_(cl_handle), - oid_(const_cast<CSSM_OID_PTR>(oid)), - field_(field) { - } - - ~CSSMFieldValue() { - Reset(NULL, NULL, NULL); - } - - CSSM_OID_PTR oid() const { return oid_; } - CSSM_DATA_PTR field() const { return field_; } - - // Returns the field as if it was an arbitrary type - most commonly, by - // interpreting the field as a specific CSSM/CDSA parsed type, such as - // CSSM_X509_SUBJECT_PUBLIC_KEY_INFO or CSSM_X509_ALGORITHM_IDENTIFIER. - // An added check is applied to ensure that the current field is large - // enough to actually contain the requested type. - template <typename T> const T* GetAs() const { - if (!field_ || field_->Length < sizeof(T)) - return NULL; - return reinterpret_cast<const T*>(field_->Data); - } - - void Reset(CSSM_CL_HANDLE cl_handle, - CSSM_OID_PTR oid, - CSSM_DATA_PTR field) { - if (cl_handle_ && oid_ && field_) - CSSM_CL_FreeFieldValue(cl_handle_, oid_, field_); - cl_handle_ = cl_handle; - oid_ = oid; - field_ = field; - } - - private: - CSSM_CL_HANDLE cl_handle_; - CSSM_OID_PTR oid_; - CSSM_DATA_PTR field_; - - DISALLOW_COPY_AND_ASSIGN(CSSMFieldValue); -}; - -// CSSMCachedCertificate is a container class that is used to wrap the -// CSSM_CL_CertCache APIs and provide safe and efficient access to -// certificate fields in their CSSM form. -// -// To provide efficient access to certificate/CRL fields, CSSM provides an -// API/SPI to "cache" a certificate/CRL. The exact meaning of a cached -// certificate is not defined by CSSM, but is documented to generally be some -// intermediate or parsed form of the certificate. In the case of Apple's -// CSSM CL implementation, the intermediate form is the parsed certificate -// stored in an internal format (which happens to be NSS). By caching the -// certificate, callers that wish to access multiple fields (such as subject, -// issuer, and validity dates) do not need to repeatedly parse the entire -// certificate, nor are they forced to convert all fields from their NSS types -// to their CSSM equivalents. This latter point is especially helpful when -// running on OS X 10.5, as it will fail to convert some fields that reference -// unsupported algorithms, such as ECC. -class CSSMCachedCertificate { - public: - CSSMCachedCertificate() : cl_handle_(NULL), cached_cert_handle_(NULL) {} - ~CSSMCachedCertificate() { - if (cl_handle_ && cached_cert_handle_) - CSSM_CL_CertAbortCache(cl_handle_, cached_cert_handle_); - } - - // Initializes the CSSMCachedCertificate by caching the specified - // |os_cert_handle|. On success, returns noErr. - // Note: Once initialized, the cached certificate should only be accessed - // from a single thread. - OSStatus Init(SecCertificateRef os_cert_handle) { - DCHECK(!cl_handle_ && !cached_cert_handle_); - DCHECK(os_cert_handle); - CSSM_DATA cert_data; - OSStatus status = SecCertificateGetData(os_cert_handle, &cert_data); - if (status) - return status; - status = SecCertificateGetCLHandle(os_cert_handle, &cl_handle_); - if (status) { - DCHECK(!cl_handle_); - return status; - } - - status = CSSM_CL_CertCache(cl_handle_, &cert_data, &cached_cert_handle_); - if (status) - DCHECK(!cached_cert_handle_); - return status; - } - - // Fetches the first value for the field associated with |field_oid|. - // If |field_oid| is a valid OID and is present in the current certificate, - // returns CSSM_OK and stores the first value in |field|. If additional - // values are associated with |field_oid|, they are ignored. - OSStatus GetField(const CSSM_OID* field_oid, - CSSMFieldValue* field) const { - DCHECK(cl_handle_); - DCHECK(cached_cert_handle_); - - CSSM_OID_PTR oid = const_cast<CSSM_OID_PTR>(field_oid); - CSSM_DATA_PTR field_ptr = NULL; - CSSM_HANDLE results_handle = NULL; - uint32 field_value_count = 0; - CSSM_RETURN status = CSSM_CL_CertGetFirstCachedFieldValue( - cl_handle_, cached_cert_handle_, oid, &results_handle, - &field_value_count, &field_ptr); - if (status) - return status; - - // Note: |field_value_count| may be > 1, indicating that more than one - // value is present. This may happen with extensions, but for current - // usages, only the first value is returned. - CSSM_CL_CertAbortQuery(cl_handle_, results_handle); - field->Reset(cl_handle_, oid, field_ptr); - return CSSM_OK; - } - - private: - CSSM_CL_HANDLE cl_handle_; - CSSM_HANDLE cached_cert_handle_; -}; - -void GetCertDistinguishedName(const CSSMCachedCertificate& cached_cert, - const CSSM_OID* oid, - CertPrincipal* result) { - CSSMFieldValue distinguished_name; +void GetCertDistinguishedName( + const x509_util::CSSMCachedCertificate& cached_cert, + const CSSM_OID* oid, + CertPrincipal* result) { + x509_util::CSSMFieldValue distinguished_name; OSStatus status = cached_cert.GetField(oid, &distinguished_name); if (status || !distinguished_name.field()) return; @@ -271,12 +44,12 @@ void GetCertDistinguishedName(const CSSMCachedCertificate& cached_cert, distinguished_name.field()->Length); } -void GetCertDateForOID(const CSSMCachedCertificate& cached_cert, +void GetCertDateForOID(const x509_util::CSSMCachedCertificate& cached_cert, const CSSM_OID* oid, Time* result) { *result = Time::Time(); - CSSMFieldValue field; + x509_util::CSSMFieldValue field; OSStatus status = cached_cert.GetField(oid, &field); if (status) return; @@ -298,8 +71,9 @@ void GetCertDateForOID(const CSSMCachedCertificate& cached_cert, LOG(ERROR) << "Invalid certificate date/time " << time_string; } -std::string GetCertSerialNumber(const CSSMCachedCertificate& cached_cert) { - CSSMFieldValue serial_number; +std::string GetCertSerialNumber( + const x509_util::CSSMCachedCertificate& cached_cert) { + x509_util::CSSMFieldValue serial_number; OSStatus status = cached_cert.GetField(&CSSMOID_X509V1SerialNumber, &serial_number); if (status || !serial_number.field()) @@ -310,151 +84,6 @@ std::string GetCertSerialNumber(const CSSMCachedCertificate& cached_cert) { serial_number.field()->Length); } -// Creates a SecPolicyRef for the given OID, with optional value. -OSStatus CreatePolicy(const CSSM_OID* policy_oid, - void* option_data, - size_t option_length, - SecPolicyRef* policy) { - SecPolicySearchRef search; - OSStatus err = SecPolicySearchCreate(CSSM_CERT_X_509v3, policy_oid, NULL, - &search); - if (err) - return err; - err = SecPolicySearchCopyNext(search, policy); - CFRelease(search); - if (err) - return err; - - if (option_data) { - CSSM_DATA options_data = { - option_length, - reinterpret_cast<uint8_t*>(option_data) - }; - err = SecPolicySetValue(*policy, &options_data); - if (err) { - CFRelease(*policy); - return err; - } - } - return noErr; -} - -// Creates a series of SecPolicyRefs to be added to a SecTrustRef used to -// validate a certificate for an SSL server. |hostname| contains the name of -// the SSL server that the certificate should be verified against. |flags| is -// a bitwise-OR of VerifyFlags that can further alter how trust is validated, -// such as how revocation is checked. If successful, returns noErr, and -// stores the resultant array of SecPolicyRefs in |policies|. -OSStatus CreateTrustPolicies(const std::string& hostname, - int flags, - ScopedCFTypeRef<CFArrayRef>* policies) { - ScopedCFTypeRef<CFMutableArrayRef> local_policies( - CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks)); - if (!local_policies) - return memFullErr; - - // Create an SSL server policy. While certificate name validation will be - // performed by SecTrustEvaluate(), it has the following limitations: - // - Doesn't support IP addresses in dotted-quad literals (127.0.0.1) - // - Doesn't support IPv6 addresses - // - Doesn't support the iPAddress subjectAltName - // Providing the hostname is necessary in order to locate certain user or - // system trust preferences, such as those created by Safari. Preferences - // created by Keychain Access do not share this requirement. - CSSM_APPLE_TP_SSL_OPTIONS tp_ssl_options; - memset(&tp_ssl_options, 0, sizeof(tp_ssl_options)); - tp_ssl_options.Version = CSSM_APPLE_TP_SSL_OPTS_VERSION; - if (!hostname.empty()) { - tp_ssl_options.ServerName = hostname.data(); - tp_ssl_options.ServerNameLen = hostname.size(); - } - - SecPolicyRef ssl_policy; - OSStatus status = CreatePolicy(&CSSMOID_APPLE_TP_SSL, &tp_ssl_options, - sizeof(tp_ssl_options), &ssl_policy); - if (status) - return status; - CFArrayAppendValue(local_policies, ssl_policy); - CFRelease(ssl_policy); - - // Explicitly add revocation policies, in order to override system - // revocation checking policies and instead respect the application-level - // revocation preference. - status = X509Certificate::CreateRevocationPolicies( - (flags & X509Certificate::VERIFY_REV_CHECKING_ENABLED), - local_policies); - if (status) - return status; - - policies->reset(local_policies.release()); - return noErr; -} - -// Saves some information about the certificate chain |cert_chain| in -// |*verify_result|. The caller MUST initialize |*verify_result| before -// calling this function. -void GetCertChainInfo(CFArrayRef cert_chain, - CSSM_TP_APPLE_EVIDENCE_INFO* chain_info, - CertVerifyResult* verify_result) { - SecCertificateRef verified_cert = NULL; - std::vector<SecCertificateRef> verified_chain; - for (CFIndex i = 0, count = CFArrayGetCount(cert_chain); i < count; ++i) { - SecCertificateRef chain_cert = reinterpret_cast<SecCertificateRef>( - const_cast<void*>(CFArrayGetValueAtIndex(cert_chain, i))); - if (i == 0) { - verified_cert = chain_cert; - } else { - verified_chain.push_back(chain_cert); - } - - if ((chain_info[i].StatusBits & CSSM_CERT_STATUS_IS_IN_ANCHORS) || - (chain_info[i].StatusBits & CSSM_CERT_STATUS_IS_ROOT)) { - // The current certificate is either in the user's trusted store or is - // a root (self-signed) certificate. Ignore the signature algorithm for - // these certificates, as it is meaningless for security. We allow - // self-signed certificates (i == 0 & IS_ROOT), since we accept that - // any security assertions by such a cert are inherently meaningless. - continue; - } - - CSSMCachedCertificate cached_cert; - OSStatus status = cached_cert.Init(chain_cert); - if (status) - continue; - CSSMFieldValue signature_field; - status = cached_cert.GetField(&CSSMOID_X509V1SignatureAlgorithm, - &signature_field); - if (status || !signature_field.field()) - continue; - // Match the behaviour of OS X system tools and defensively check that - // sizes are appropriate. This would indicate a critical failure of the - // OS X certificate library, but based on history, it is best to play it - // safe. - const CSSM_X509_ALGORITHM_IDENTIFIER* sig_algorithm = - signature_field.GetAs<CSSM_X509_ALGORITHM_IDENTIFIER>(); - if (!sig_algorithm) - continue; - - const CSSM_OID* alg_oid = &sig_algorithm->algorithm; - if (CSSMOIDEqual(alg_oid, &CSSMOID_MD2WithRSA)) { - verify_result->has_md2 = true; - if (i != 0) - verify_result->has_md2_ca = true; - } else if (CSSMOIDEqual(alg_oid, &CSSMOID_MD4WithRSA)) { - verify_result->has_md4 = true; - } else if (CSSMOIDEqual(alg_oid, &CSSMOID_MD5WithRSA)) { - verify_result->has_md5 = true; - if (i != 0) - verify_result->has_md5_ca = true; - } - } - if (!verified_cert) - return; - - verify_result->verified_cert = - X509Certificate::CreateFromHandle(verified_cert, verified_chain); -} - // Gets the issuer for a given cert, starting with the cert itself and // including the intermediate and finally root certificates (if any). // This function calls SecTrust but doesn't actually pay attention to the trust @@ -466,7 +95,7 @@ OSStatus CopyCertChain(SecCertificateRef cert_handle, DCHECK(out_cert_chain); // Create an SSL policy ref configured for client cert evaluation. SecPolicyRef ssl_policy; - OSStatus result = X509Certificate::CreateSSLClientPolicy(&ssl_policy); + OSStatus result = x509_util::CreateSSLClientPolicy(&ssl_policy); if (result) return result; ScopedCFTypeRef<SecPolicyRef> scoped_ssl_policy(ssl_policy); @@ -665,87 +294,10 @@ class ScopedEncodedCertResults { CSSM_TP_RESULT_SET* results_; }; -void AppendPublicKeyHashes(CFArrayRef chain, - std::vector<SHA1Fingerprint>* hashes) { - const CFIndex n = CFArrayGetCount(chain); - for (CFIndex i = 0; i < n; i++) { - SecCertificateRef cert = reinterpret_cast<SecCertificateRef>( - const_cast<void*>(CFArrayGetValueAtIndex(chain, i))); - - CSSM_DATA cert_data; - OSStatus err = SecCertificateGetData(cert, &cert_data); - DCHECK_EQ(err, noErr); - base::StringPiece der_bytes(reinterpret_cast<const char*>(cert_data.Data), - cert_data.Length); - base::StringPiece spki_bytes; - if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki_bytes)) - continue; - - SHA1Fingerprint hash; - CC_SHA1(spki_bytes.data(), spki_bytes.size(), hash.data); - hashes->push_back(hash); - } -} - -bool CheckRevocationWithCRLSet(CFArrayRef chain, CRLSet* crl_set) { - if (CFArrayGetCount(chain) == 0) - return true; - - // We iterate from the root certificate down to the leaf, keeping track of - // the issuer's SPKI at each step. - std::string issuer_spki_hash; - for (CFIndex i = CFArrayGetCount(chain) - 1; i >= 0; i--) { - SecCertificateRef cert = reinterpret_cast<SecCertificateRef>( - const_cast<void*>(CFArrayGetValueAtIndex(chain, i))); - - CSSM_DATA cert_data; - OSStatus err = SecCertificateGetData(cert, &cert_data); - if (err != noErr) { - NOTREACHED(); - continue; - } - base::StringPiece der_bytes(reinterpret_cast<const char*>(cert_data.Data), - cert_data.Length); - base::StringPiece spki; - if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki)) { - NOTREACHED(); - continue; - } - - const std::string spki_hash = crypto::SHA256HashString(spki); - CSSMCachedCertificate cached_cert; - if (cached_cert.Init(cert) != CSSM_OK) { - NOTREACHED(); - continue; - } - const std::string serial = GetCertSerialNumber(cached_cert); - - CRLSet::Result result = crl_set->CheckSPKI(spki_hash); - - if (result != CRLSet::REVOKED && !issuer_spki_hash.empty()) - result = crl_set->CheckSerial(serial, issuer_spki_hash); - - issuer_spki_hash = spki_hash; - - switch (result) { - case CRLSet::REVOKED: - return false; - case CRLSet::UNKNOWN: - case CRLSet::GOOD: - continue; - default: - NOTREACHED(); - return false; - } - } - - return true; -} - } // namespace void X509Certificate::Initialize() { - CSSMCachedCertificate cached_cert; + x509_util::CSSMCachedCertificate cached_cert; if (cached_cert.Init(cert_handle_) == CSSM_OK) { GetCertDistinguishedName(cached_cert, &CSSMOID_X509V1SubjectNameStd, &subject_); @@ -762,20 +314,6 @@ void X509Certificate::Initialize() { ca_fingerprint_ = CalculateCAFingerprint(intermediate_ca_certs_); } -// IsIssuedByKnownRoot returns true if the given chain is rooted at a root CA -// that we recognise as a standard root. -// static -bool X509Certificate::IsIssuedByKnownRoot(CFArrayRef chain) { - int n = CFArrayGetCount(chain); - if (n < 1) - return false; - SecCertificateRef root_ref = reinterpret_cast<SecCertificateRef>( - const_cast<void*>(CFArrayGetValueAtIndex(chain, n - 1))); - SHA1Fingerprint hash = X509Certificate::CalculateFingerprint(root_ref); - return IsSHA1HashInSortedArray( - hash, &kKnownRootCertSHA1Hashes[0][0], sizeof(kKnownRootCertSHA1Hashes)); -} - // static X509Certificate* X509Certificate::CreateSelfSigned( crypto::RSAPrivateKey* key, @@ -922,11 +460,11 @@ void X509Certificate::GetSubjectAltName( if (ip_addrs) ip_addrs->clear(); - CSSMCachedCertificate cached_cert; + x509_util::CSSMCachedCertificate cached_cert; OSStatus status = cached_cert.Init(cert_handle_); if (status) return; - CSSMFieldValue subject_alt_name; + x509_util::CSSMFieldValue subject_alt_name; status = cached_cert.GetField(&CSSMOID_SubjectAltName, &subject_alt_name); if (status || !subject_alt_name.field()) return; @@ -955,225 +493,6 @@ void X509Certificate::GetSubjectAltName( } } -int X509Certificate::VerifyInternal(const std::string& hostname, - int flags, - CRLSet* crl_set, - CertVerifyResult* verify_result) const { - ScopedCFTypeRef<CFArrayRef> trust_policies; - OSStatus status = CreateTrustPolicies(hostname, flags, &trust_policies); - if (status) - return NetErrorFromOSStatus(status); - - // Create and configure a SecTrustRef, which takes our certificate(s) - // and our SSL SecPolicyRef. SecTrustCreateWithCertificates() takes an - // 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(CreateOSCertChainForCert()); - - // From here on, only one thread can be active at a time. We have had a number - // of sporadic crashes in the SecTrustEvaluate call below, way down inside - // Apple's cert code, which we suspect are caused by a thread-safety issue. - // So as a speculative fix allow only one thread to use SecTrust on this cert. - base::AutoLock lock(verification_lock_); - - SecTrustRef trust_ref = NULL; - status = SecTrustCreateWithCertificates(cert_array, trust_policies, - &trust_ref); - if (status) - return NetErrorFromOSStatus(status); - ScopedCFTypeRef<SecTrustRef> scoped_trust_ref(trust_ref); - - if (TestRootCerts::HasInstance()) { - status = TestRootCerts::GetInstance()->FixupSecTrustRef(trust_ref); - if (status) - return NetErrorFromOSStatus(status); - } - - CSSM_APPLE_TP_ACTION_DATA tp_action_data; - memset(&tp_action_data, 0, sizeof(tp_action_data)); - tp_action_data.Version = CSSM_APPLE_TP_ACTION_VERSION; - // Allow CSSM to download any missing intermediate certificates if an - // authorityInfoAccess extension or issuerAltName extension is present. - tp_action_data.ActionFlags = CSSM_TP_ACTION_FETCH_CERT_FROM_NET | - CSSM_TP_ACTION_TRUST_SETTINGS; - - if (flags & VERIFY_REV_CHECKING_ENABLED) { - // Require a positive result from an OCSP responder or a CRL (or both) - // for every certificate in the chain. The Apple TP automatically - // excludes the self-signed root from this requirement. If a certificate - // is missing both a crlDistributionPoints extension and an - // authorityInfoAccess extension with an OCSP responder URL, then we - // will get a kSecTrustResultRecoverableTrustFailure back from - // SecTrustEvaluate(), with a - // CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK error code. In that case, - // we'll set our own result to include - // CERT_STATUS_NO_REVOCATION_MECHANISM. If one or both extensions are - // present, and a check fails (server unavailable, OCSP retry later, - // signature mismatch), then we'll set our own result to include - // CERT_STATUS_UNABLE_TO_CHECK_REVOCATION. - tp_action_data.ActionFlags |= CSSM_TP_ACTION_REQUIRE_REV_PER_CERT; - verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED; - - // Note, even if revocation checking is disabled, SecTrustEvaluate() will - // modify the OCSP options so as to attempt OCSP checking if it believes a - // certificate may chain to an EV root. However, because network fetches - // are disabled in CreateTrustPolicies() when revocation checking is - // disabled, these will only go against the local cache. - } - - CFDataRef action_data_ref = - CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, - reinterpret_cast<UInt8*>(&tp_action_data), - sizeof(tp_action_data), kCFAllocatorNull); - if (!action_data_ref) - return ERR_OUT_OF_MEMORY; - ScopedCFTypeRef<CFDataRef> scoped_action_data_ref(action_data_ref); - status = SecTrustSetParameters(trust_ref, CSSM_TP_ACTION_DEFAULT, - action_data_ref); - if (status) - return NetErrorFromOSStatus(status); - - // Verify the certificate. A non-zero result from SecTrustGetResult() - // indicates that some fatal error occurred and the chain couldn't be - // processed, not that the chain contains no errors. We need to examine the - // output of SecTrustGetResult() to determine that. - SecTrustResultType trust_result; - status = SecTrustEvaluate(trust_ref, &trust_result); - if (status) - return NetErrorFromOSStatus(status); - CFArrayRef completed_chain = NULL; - CSSM_TP_APPLE_EVIDENCE_INFO* chain_info; - status = SecTrustGetResult(trust_ref, &trust_result, &completed_chain, - &chain_info); - if (status) - return NetErrorFromOSStatus(status); - ScopedCFTypeRef<CFArrayRef> scoped_completed_chain(completed_chain); - - if (crl_set && !CheckRevocationWithCRLSet(completed_chain, crl_set)) - verify_result->cert_status |= CERT_STATUS_REVOKED; - - GetCertChainInfo(scoped_completed_chain.get(), chain_info, verify_result); - - // Evaluate the results - OSStatus cssm_result; - switch (trust_result) { - case kSecTrustResultUnspecified: - case kSecTrustResultProceed: - // Certificate chain is valid and trusted ("unspecified" indicates that - // the user has not explicitly set a trust setting) - break; - - case kSecTrustResultDeny: - case kSecTrustResultConfirm: - // Certificate chain is explicitly untrusted. For kSecTrustResultConfirm, - // we're following what Secure Transport does and treating it as - // "deny". - verify_result->cert_status |= CERT_STATUS_AUTHORITY_INVALID; - break; - - case kSecTrustResultRecoverableTrustFailure: - // Certificate chain has a failure that can be overridden by the user. - status = SecTrustGetCssmResultCode(trust_ref, &cssm_result); - if (status) - return NetErrorFromOSStatus(status); - verify_result->cert_status |= CertStatusFromOSStatus(cssm_result); - // Walk the chain of error codes in the CSSM_TP_APPLE_EVIDENCE_INFO - // structure which can catch multiple errors from each certificate. - for (CFIndex index = 0, chain_count = CFArrayGetCount(completed_chain); - index < chain_count; ++index) { - if (chain_info[index].StatusBits & CSSM_CERT_STATUS_EXPIRED || - chain_info[index].StatusBits & CSSM_CERT_STATUS_NOT_VALID_YET) - verify_result->cert_status |= CERT_STATUS_DATE_INVALID; - if (!IsCertStatusError(verify_result->cert_status) && - chain_info[index].NumStatusCodes == 0) { - LOG(WARNING) << "chain_info[" << index << "].NumStatusCodes is 0" - ", chain_info[" << index << "].StatusBits is " - << chain_info[index].StatusBits; - } - for (uint32 status_code_index = 0; - status_code_index < chain_info[index].NumStatusCodes; - ++status_code_index) { - verify_result->cert_status |= CertStatusFromOSStatus( - chain_info[index].StatusCodes[status_code_index]); - } - } - if (!IsCertStatusError(verify_result->cert_status)) { - LOG(ERROR) << "cssm_result=" << cssm_result; - verify_result->cert_status |= CERT_STATUS_INVALID; - NOTREACHED(); - } - break; - - default: - status = SecTrustGetCssmResultCode(trust_ref, &cssm_result); - if (status) - return NetErrorFromOSStatus(status); - verify_result->cert_status |= CertStatusFromOSStatus(cssm_result); - if (!IsCertStatusError(verify_result->cert_status)) { - LOG(WARNING) << "trust_result=" << trust_result; - verify_result->cert_status |= CERT_STATUS_INVALID; - } - break; - } - - // Perform hostname verification independent of SecTrustEvaluate. In order to - // do so, mask off any reported name errors first. - verify_result->cert_status &= ~CERT_STATUS_COMMON_NAME_INVALID; - if (!VerifyNameMatch(hostname)) - verify_result->cert_status |= CERT_STATUS_COMMON_NAME_INVALID; - - // TODO(wtc): Suppress CERT_STATUS_NO_REVOCATION_MECHANISM for now to be - // compatible with Windows, which in turn implements this behavior to be - // compatible with WinHTTP, which doesn't report this error (bug 3004). - verify_result->cert_status &= ~CERT_STATUS_NO_REVOCATION_MECHANISM; - - if (IsCertStatusError(verify_result->cert_status)) - return MapCertStatusToNetError(verify_result->cert_status); - - if (flags & VERIFY_EV_CERT) { - // Determine the certificate's EV status using SecTrustCopyExtendedResult(), - // which we need to look up because the function wasn't added until - // Mac OS X 10.5.7. - // Note: "ExtendedResult" means extended validation results. - CFBundleRef bundle = - CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security")); - if (bundle) { - SecTrustCopyExtendedResultFuncPtr copy_extended_result = - reinterpret_cast<SecTrustCopyExtendedResultFuncPtr>( - CFBundleGetFunctionPointerForName(bundle, - CFSTR("SecTrustCopyExtendedResult"))); - if (copy_extended_result) { - CFDictionaryRef ev_dict_temp = NULL; - status = copy_extended_result(trust_ref, &ev_dict_temp); - ScopedCFTypeRef<CFDictionaryRef> ev_dict(ev_dict_temp); - ev_dict_temp = NULL; - if (status == noErr && ev_dict) { - // In 10.7.3, SecTrustCopyExtendedResult returns noErr and populates - // ev_dict even for non-EV certificates, but only EV certificates - // will cause ev_dict to contain kSecEVOrganizationName. In previous - // releases, SecTrustCopyExtendedResult would only return noErr and - // populate ev_dict for EV certificates, but would always include - // kSecEVOrganizationName in that case, so checking for this key is - // appropriate for all known versions of SecTrustCopyExtendedResult. - // The actual organization name is unneeded here and can be accessed - // through other means. All that matters here is the OS' conception - // of whether or not the certificate is EV. - if (CFDictionaryContainsKey(ev_dict, - kSecEVOrganizationName)) { - verify_result->cert_status |= CERT_STATUS_IS_EV; - } - } - } - } - } - - AppendPublicKeyHashes(completed_chain, &verify_result->public_key_hashes); - verify_result->is_issued_by_known_root = IsIssuedByKnownRoot(completed_chain); - - return OK; -} - // static bool X509Certificate::GetDEREncoded(X509Certificate::OSCertHandle cert_handle, std::string* encoded) { @@ -1299,7 +618,7 @@ SHA1Fingerprint X509Certificate::CalculateCAFingerprint( } bool X509Certificate::SupportsSSLClientAuth() const { - CSSMCachedCertificate cached_cert; + x509_util::CSSMCachedCertificate cached_cert; OSStatus status = cached_cert.Init(cert_handle_); if (status) return false; @@ -1313,7 +632,7 @@ bool X509Certificate::SupportsSSLClientAuth() const { // // In particular, if a key has the nonRepudiation bit and not the // digitalSignature one, we will not offer it to the user. - CSSMFieldValue key_usage; + x509_util::CSSMFieldValue key_usage; status = cached_cert.GetField(&CSSMOID_KeyUsage, &key_usage); if (status == CSSM_OK && key_usage.field()) { const CSSM_X509_EXTENSION* ext = key_usage.GetAs<CSSM_X509_EXTENSION>(); @@ -1359,84 +678,6 @@ bool X509Certificate::IsIssuedBy( } // static -OSStatus X509Certificate::CreateSSLClientPolicy(SecPolicyRef* policy) { - CSSM_APPLE_TP_SSL_OPTIONS tp_ssl_options; - memset(&tp_ssl_options, 0, sizeof(tp_ssl_options)); - tp_ssl_options.Version = CSSM_APPLE_TP_SSL_OPTS_VERSION; - tp_ssl_options.Flags |= CSSM_APPLE_TP_SSL_CLIENT; - - return CreatePolicy(&CSSMOID_APPLE_TP_SSL, &tp_ssl_options, - sizeof(tp_ssl_options), policy); -} - -// static -OSStatus X509Certificate::CreateBasicX509Policy(SecPolicyRef* policy) { - return CreatePolicy(&CSSMOID_APPLE_X509_BASIC, NULL, 0, policy); -} - -// static -OSStatus X509Certificate::CreateRevocationPolicies( - bool enable_revocation_checking, - CFMutableArrayRef policies) { - // In order to actually disable revocation checking, the SecTrustRef must - // have at least one revocation policy associated with it. If none are - // present, the Apple TP will add policies according to the system - // preferences, which will enable revocation checking even if the caller - // explicitly disabled it. An OCSP policy is used, rather than a CRL policy, - // because the Apple TP will force an OCSP policy to be present and enabled - // if it believes the certificate may chain to an EV root. By explicitly - // disabling network and OCSP cache access, then even if the Apple TP - // enables OCSP checking, no revocation checking will actually succeed. - CSSM_APPLE_TP_OCSP_OPTIONS tp_ocsp_options; - memset(&tp_ocsp_options, 0, sizeof(tp_ocsp_options)); - tp_ocsp_options.Version = CSSM_APPLE_TP_OCSP_OPTS_VERSION; - - if (enable_revocation_checking) { - // The default for the OCSP policy is to fetch responses via the network, - // unlike the CRL policy default. The policy is further modified to - // prefer OCSP over CRLs, if both are specified on the certificate. This - // is because an OCSP response is both sufficient and typically - // significantly smaller than the CRL counterpart. - tp_ocsp_options.Flags = CSSM_TP_ACTION_OCSP_SUFFICIENT; - } else { - // Effectively disable OCSP checking by making it impossible to get an - // OCSP response. Even if the Apple TP forces OCSP, no checking will - // be able to succeed. If this happens, the Apple TP will report an error - // that OCSP was unavailable, but this will be handled and suppressed in - // X509Certificate::Verify(). - tp_ocsp_options.Flags = CSSM_TP_ACTION_OCSP_DISABLE_NET | - CSSM_TP_ACTION_OCSP_CACHE_READ_DISABLE; - } - - SecPolicyRef ocsp_policy; - OSStatus status = CreatePolicy(&CSSMOID_APPLE_TP_REVOCATION_OCSP, - &tp_ocsp_options, sizeof(tp_ocsp_options), - &ocsp_policy); - if (status) - return status; - CFArrayAppendValue(policies, ocsp_policy); - CFRelease(ocsp_policy); - - if (enable_revocation_checking) { - CSSM_APPLE_TP_CRL_OPTIONS tp_crl_options; - memset(&tp_crl_options, 0, sizeof(tp_crl_options)); - tp_crl_options.Version = CSSM_APPLE_TP_CRL_OPTS_VERSION; - tp_crl_options.CrlFlags = CSSM_TP_ACTION_FETCH_CRL_FROM_NET; - - SecPolicyRef crl_policy; - status = CreatePolicy(&CSSMOID_APPLE_TP_REVOCATION_CRL, &tp_crl_options, - sizeof(tp_crl_options), &crl_policy); - if (status) - return status; - CFArrayAppendValue(policies, crl_policy); - CFRelease(crl_policy); - } - - return status; -} - - -// static bool X509Certificate::GetSSLClientCertificates( const std::string& server_domain, const std::vector<CertPrincipal>& valid_issuers, diff --git a/net/base/x509_certificate_nss.cc b/net/base/x509_certificate_nss.cc index 2b8ad84..36779bd 100644 --- a/net/base/x509_certificate_nss.cc +++ b/net/base/x509_certificate_nss.cc @@ -9,12 +9,9 @@ #include <keyhi.h> #include <nss.h> #include <pk11pub.h> -#include <prerror.h> #include <prtime.h> #include <secder.h> -#include <secerr.h> #include <sechash.h> -#include <sslerr.h> #include "base/logging.h" #include "base/memory/scoped_ptr.h" @@ -23,309 +20,12 @@ #include "crypto/nss_util.h" #include "crypto/rsa_private_key.h" #include "crypto/scoped_nss_types.h" -#include "crypto/sha2.h" -#include "net/base/asn1_util.h" -#include "net/base/cert_status_flags.h" -#include "net/base/cert_verify_result.h" -#include "net/base/crl_set.h" -#include "net/base/ev_root_ca_metadata.h" -#include "net/base/net_errors.h" #include "net/base/x509_util_nss.h" namespace net { namespace { -class ScopedCERTCertificatePolicies { - public: - explicit ScopedCERTCertificatePolicies(CERTCertificatePolicies* policies) - : policies_(policies) {} - - ~ScopedCERTCertificatePolicies() { - if (policies_) - CERT_DestroyCertificatePoliciesExtension(policies_); - } - - private: - CERTCertificatePolicies* policies_; - - DISALLOW_COPY_AND_ASSIGN(ScopedCERTCertificatePolicies); -}; - -// ScopedCERTValOutParam manages destruction of values in the CERTValOutParam -// array that cvout points to. cvout must be initialized as passed to -// CERT_PKIXVerifyCert, so that the array must be terminated with -// cert_po_end type. -// When it goes out of scope, it destroys values of cert_po_trustAnchor -// and cert_po_certList types, but doesn't release the array itself. -class ScopedCERTValOutParam { - public: - explicit ScopedCERTValOutParam(CERTValOutParam* cvout) - : cvout_(cvout) {} - - ~ScopedCERTValOutParam() { - if (cvout_ == NULL) - return; - for (CERTValOutParam *p = cvout_; p->type != cert_po_end; p++) { - switch (p->type) { - case cert_po_trustAnchor: - if (p->value.pointer.cert) { - CERT_DestroyCertificate(p->value.pointer.cert); - p->value.pointer.cert = NULL; - } - break; - case cert_po_certList: - if (p->value.pointer.chain) { - CERT_DestroyCertList(p->value.pointer.chain); - p->value.pointer.chain = NULL; - } - break; - default: - break; - } - } - } - - private: - CERTValOutParam* cvout_; - - DISALLOW_COPY_AND_ASSIGN(ScopedCERTValOutParam); -}; - -// Map PORT_GetError() return values to our network error codes. -int MapSecurityError(int err) { - switch (err) { - case PR_DIRECTORY_LOOKUP_ERROR: // DNS lookup error. - return ERR_NAME_NOT_RESOLVED; - case SEC_ERROR_INVALID_ARGS: - return ERR_INVALID_ARGUMENT; - case SSL_ERROR_BAD_CERT_DOMAIN: - return ERR_CERT_COMMON_NAME_INVALID; - case SEC_ERROR_INVALID_TIME: - case SEC_ERROR_EXPIRED_CERTIFICATE: - case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: - return ERR_CERT_DATE_INVALID; - case SEC_ERROR_UNKNOWN_ISSUER: - case SEC_ERROR_UNTRUSTED_ISSUER: - case SEC_ERROR_CA_CERT_INVALID: - return ERR_CERT_AUTHORITY_INVALID; - case SEC_ERROR_REVOKED_CERTIFICATE: - case SEC_ERROR_UNTRUSTED_CERT: // Treat as revoked. - return ERR_CERT_REVOKED; - case SEC_ERROR_BAD_DER: - case SEC_ERROR_BAD_SIGNATURE: - case SEC_ERROR_CERT_NOT_VALID: - // TODO(port): add an ERR_CERT_WRONG_USAGE error code. - case SEC_ERROR_CERT_USAGES_INVALID: - case SEC_ERROR_INADEQUATE_KEY_USAGE: - case SEC_ERROR_INADEQUATE_CERT_TYPE: - case SEC_ERROR_POLICY_VALIDATION_FAILED: - case SEC_ERROR_CERT_NOT_IN_NAME_SPACE: - case SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID: - case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION: - case SEC_ERROR_EXTENSION_VALUE_INVALID: - return ERR_CERT_INVALID; - default: - LOG(WARNING) << "Unknown error " << err << " mapped to net::ERR_FAILED"; - return ERR_FAILED; - } -} - -// Map PORT_GetError() return values to our cert status flags. -CertStatus MapCertErrorToCertStatus(int err) { - switch (err) { - case SSL_ERROR_BAD_CERT_DOMAIN: - return CERT_STATUS_COMMON_NAME_INVALID; - case SEC_ERROR_INVALID_TIME: - case SEC_ERROR_EXPIRED_CERTIFICATE: - case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: - return CERT_STATUS_DATE_INVALID; - case SEC_ERROR_UNKNOWN_ISSUER: - case SEC_ERROR_UNTRUSTED_ISSUER: - case SEC_ERROR_CA_CERT_INVALID: - return CERT_STATUS_AUTHORITY_INVALID; - // TODO(port): map CERT_STATUS_NO_REVOCATION_MECHANISM. - case SEC_ERROR_OCSP_BAD_HTTP_RESPONSE: - case SEC_ERROR_OCSP_SERVER_ERROR: - return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION; - case SEC_ERROR_REVOKED_CERTIFICATE: - case SEC_ERROR_UNTRUSTED_CERT: // Treat as revoked. - return CERT_STATUS_REVOKED; - case SEC_ERROR_BAD_DER: - case SEC_ERROR_BAD_SIGNATURE: - case SEC_ERROR_CERT_NOT_VALID: - // TODO(port): add a CERT_STATUS_WRONG_USAGE error code. - case SEC_ERROR_CERT_USAGES_INVALID: - case SEC_ERROR_INADEQUATE_KEY_USAGE: // Key usage. - case SEC_ERROR_INADEQUATE_CERT_TYPE: // Extended key usage and whether - // the certificate is a CA. - case SEC_ERROR_POLICY_VALIDATION_FAILED: - case SEC_ERROR_CERT_NOT_IN_NAME_SPACE: - case SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID: - case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION: - case SEC_ERROR_EXTENSION_VALUE_INVALID: - return CERT_STATUS_INVALID; - default: - return 0; - } -} - -// Saves some information about the certificate chain cert_list in -// *verify_result. The caller MUST initialize *verify_result before calling -// this function. -// Note that cert_list[0] is the end entity certificate. -void GetCertChainInfo(CERTCertList* cert_list, - CERTCertificate* root_cert, - CertVerifyResult* verify_result) { - // NOTE: Using a NSS library before 3.12.3.1 will crash below. To see the - // NSS version currently in use: - // 1. use ldd on the chrome executable for NSS's location (ie. libnss3.so*) - // 2. use ident libnss3.so* for the library's version - DCHECK(cert_list); - - CERTCertificate* verified_cert = NULL; - std::vector<CERTCertificate*> verified_chain; - int i = 0; - for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list); - !CERT_LIST_END(node, cert_list); - node = CERT_LIST_NEXT(node), ++i) { - if (i == 0) { - verified_cert = node->cert; - } else { - // Because of an NSS bug, CERT_PKIXVerifyCert may chain a self-signed - // certificate of a root CA to another certificate of the same root CA - // key. Detect that error and ignore the root CA certificate. - // See https://bugzilla.mozilla.org/show_bug.cgi?id=721288. - if (node->cert->isRoot) { - // NOTE: isRoot doesn't mean the certificate is a trust anchor. It - // means the certificate is self-signed. Here we assume isRoot only - // implies the certificate is self-issued. - CERTCertListNode* next_node = CERT_LIST_NEXT(node); - CERTCertificate* next_cert; - if (!CERT_LIST_END(next_node, cert_list)) { - next_cert = next_node->cert; - } else { - next_cert = root_cert; - } - // Test that |node->cert| is actually a self-signed certificate - // whose key is equal to |next_cert|, and not a self-issued - // certificate signed by another key of the same CA. - if (next_cert && SECITEM_ItemsAreEqual(&node->cert->derPublicKey, - &next_cert->derPublicKey)) { - continue; - } - } - verified_chain.push_back(node->cert); - } - - SECAlgorithmID& signature = node->cert->signature; - SECOidTag oid_tag = SECOID_FindOIDTag(&signature.algorithm); - switch (oid_tag) { - case SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION: - verify_result->has_md5 = true; - if (i != 0) - verify_result->has_md5_ca = true; - break; - case SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION: - verify_result->has_md2 = true; - if (i != 0) - verify_result->has_md2_ca = true; - break; - case SEC_OID_PKCS1_MD4_WITH_RSA_ENCRYPTION: - verify_result->has_md4 = true; - break; - default: - break; - } - } - - if (root_cert) - verified_chain.push_back(root_cert); - verify_result->verified_cert = - X509Certificate::CreateFromHandle(verified_cert, verified_chain); -} - -// IsKnownRoot returns true if the given certificate is one that we believe -// is a standard (as opposed to user-installed) root. -bool IsKnownRoot(CERTCertificate* root) { - if (!root || !root->slot) - return false; - - // This magic name is taken from - // http://bonsai.mozilla.org/cvsblame.cgi?file=mozilla/security/nss/lib/ckfw/builtins/constants.c&rev=1.13&mark=86,89#79 - return 0 == strcmp(PK11_GetSlotName(root->slot), - "NSS Builtin Objects"); -} - -enum CRLSetResult { - kCRLSetRevoked, - kCRLSetOk, - kCRLSetError, -}; - -// CheckRevocationWithCRLSet attempts to check each element of |cert_list| -// against |crl_set|. It returns: -// kCRLSetRevoked: if any element of the chain is known to have been revoked. -// kCRLSetError: if an error occurs in processing. -// kCRLSetOk: if no element in the chain is known to have been revoked. -CRLSetResult CheckRevocationWithCRLSet(CERTCertList* cert_list, - CERTCertificate* root, - CRLSet* crl_set) { - std::vector<CERTCertificate*> certs; - - if (cert_list) { - for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list); - !CERT_LIST_END(node, cert_list); - node = CERT_LIST_NEXT(node)) { - certs.push_back(node->cert); - } - } - if (root) - certs.push_back(root); - - // We iterate from the root certificate down to the leaf, keeping track of - // the issuer's SPKI at each step. - std::string issuer_spki_hash; - for (std::vector<CERTCertificate*>::reverse_iterator i = certs.rbegin(); - i != certs.rend(); ++i) { - CERTCertificate* cert = *i; - - base::StringPiece der(reinterpret_cast<char*>(cert->derCert.data), - cert->derCert.len); - - base::StringPiece spki; - if (!asn1::ExtractSPKIFromDERCert(der, &spki)) { - NOTREACHED(); - return kCRLSetError; - } - const std::string spki_hash = crypto::SHA256HashString(spki); - - base::StringPiece serial_number = base::StringPiece( - reinterpret_cast<char*>(cert->serialNumber.data), - cert->serialNumber.len); - - CRLSet::Result result = crl_set->CheckSPKI(spki_hash); - - if (result != CRLSet::REVOKED && !issuer_spki_hash.empty()) - result = crl_set->CheckSerial(serial_number, issuer_spki_hash); - - issuer_spki_hash = spki_hash; - - switch (result) { - case CRLSet::REVOKED: - return kCRLSetRevoked; - case CRLSet::UNKNOWN: - case CRLSet::GOOD: - continue; - default: - NOTREACHED(); - return kCRLSetError; - } - } - - return kCRLSetOk; -} - void ParsePrincipal(CERTName* name, CertPrincipal* principal) { typedef char* (*CERTGetNameFunc)(CERTName* name); @@ -392,285 +92,6 @@ void ParseDate(SECItem* der_date, base::Time* result) { *result = crypto::PRTimeToBaseTime(prtime); } -// Forward declarations. -SECStatus RetryPKIXVerifyCertWithWorkarounds( - X509Certificate::OSCertHandle cert_handle, int num_policy_oids, - bool cert_io_enabled, std::vector<CERTValInParam>* cvin, - CERTValOutParam* cvout); -SECOidTag GetFirstCertPolicy(X509Certificate::OSCertHandle cert_handle); - -// Call CERT_PKIXVerifyCert for the cert_handle. -// Verification results are stored in an array of CERTValOutParam. -// If policy_oids is not NULL and num_policy_oids is positive, policies -// are also checked. -// Caller must initialize cvout before calling this function. -SECStatus PKIXVerifyCert(X509Certificate::OSCertHandle cert_handle, - bool check_revocation, - bool cert_io_enabled, - const SECOidTag* policy_oids, - int num_policy_oids, - CERTValOutParam* cvout) { - bool use_crl = check_revocation; - bool use_ocsp = check_revocation; - - // These CAs have multiple keys, which trigger two bugs in NSS's CRL code. - // 1. NSS may use one key to verify a CRL signed with another key, - // incorrectly concluding that the CRL's signature is invalid. - // Hopefully this bug will be fixed in NSS 3.12.9. - // 2. NSS considers all certificates issued by the CA as revoked when it - // receives a CRL with an invalid signature. This overly strict policy - // has been relaxed in NSS 3.12.7. See - // https://bugzilla.mozilla.org/show_bug.cgi?id=562542. - // So we have to turn off CRL checking for these CAs. See - // http://crbug.com/55695. - static const char* const kMultipleKeyCA[] = { - "CN=Microsoft Secure Server Authority," - "DC=redmond,DC=corp,DC=microsoft,DC=com", - "CN=Microsoft Secure Server Authority", - }; - - if (!NSS_VersionCheck("3.12.7")) { - for (size_t i = 0; i < arraysize(kMultipleKeyCA); ++i) { - if (strcmp(cert_handle->issuerName, kMultipleKeyCA[i]) == 0) { - use_crl = false; - break; - } - } - } - - PRUint64 revocation_method_flags = - CERT_REV_M_DO_NOT_TEST_USING_THIS_METHOD | - CERT_REV_M_ALLOW_NETWORK_FETCHING | - CERT_REV_M_IGNORE_IMPLICIT_DEFAULT_SOURCE | - CERT_REV_M_IGNORE_MISSING_FRESH_INFO | - CERT_REV_M_STOP_TESTING_ON_FRESH_INFO; - PRUint64 revocation_method_independent_flags = - CERT_REV_MI_TEST_ALL_LOCAL_INFORMATION_FIRST; - if (check_revocation && policy_oids && num_policy_oids > 0) { - // EV verification requires revocation checking. Consider the certificate - // revoked if we don't have revocation info. - // TODO(wtc): Add a bool parameter to expressly specify we're doing EV - // verification or we want strict revocation flags. - revocation_method_flags |= CERT_REV_M_REQUIRE_INFO_ON_MISSING_SOURCE; - revocation_method_independent_flags |= - CERT_REV_MI_REQUIRE_SOME_FRESH_INFO_AVAILABLE; - } else { - revocation_method_flags |= CERT_REV_M_SKIP_TEST_ON_MISSING_SOURCE; - revocation_method_independent_flags |= - CERT_REV_MI_NO_OVERALL_INFO_REQUIREMENT; - } - PRUint64 method_flags[2]; - method_flags[cert_revocation_method_crl] = revocation_method_flags; - method_flags[cert_revocation_method_ocsp] = revocation_method_flags; - - if (use_crl) { - method_flags[cert_revocation_method_crl] |= - CERT_REV_M_TEST_USING_THIS_METHOD; - } - if (use_ocsp) { - method_flags[cert_revocation_method_ocsp] |= - CERT_REV_M_TEST_USING_THIS_METHOD; - } - - CERTRevocationMethodIndex preferred_revocation_methods[1]; - if (use_ocsp) { - preferred_revocation_methods[0] = cert_revocation_method_ocsp; - } else { - preferred_revocation_methods[0] = cert_revocation_method_crl; - } - - CERTRevocationFlags revocation_flags; - revocation_flags.leafTests.number_of_defined_methods = - arraysize(method_flags); - revocation_flags.leafTests.cert_rev_flags_per_method = method_flags; - revocation_flags.leafTests.number_of_preferred_methods = - arraysize(preferred_revocation_methods); - revocation_flags.leafTests.preferred_methods = preferred_revocation_methods; - revocation_flags.leafTests.cert_rev_method_independent_flags = - revocation_method_independent_flags; - - revocation_flags.chainTests.number_of_defined_methods = - arraysize(method_flags); - revocation_flags.chainTests.cert_rev_flags_per_method = method_flags; - revocation_flags.chainTests.number_of_preferred_methods = - arraysize(preferred_revocation_methods); - revocation_flags.chainTests.preferred_methods = preferred_revocation_methods; - revocation_flags.chainTests.cert_rev_method_independent_flags = - revocation_method_independent_flags; - - std::vector<CERTValInParam> cvin; - cvin.reserve(5); - CERTValInParam in_param; - // No need to set cert_pi_trustAnchors here. - in_param.type = cert_pi_revocationFlags; - in_param.value.pointer.revocation = &revocation_flags; - cvin.push_back(in_param); - if (policy_oids && num_policy_oids > 0) { - in_param.type = cert_pi_policyOID; - in_param.value.arraySize = num_policy_oids; - in_param.value.array.oids = policy_oids; - cvin.push_back(in_param); - } - in_param.type = cert_pi_end; - cvin.push_back(in_param); - - SECStatus rv = CERT_PKIXVerifyCert(cert_handle, certificateUsageSSLServer, - &cvin[0], cvout, NULL); - if (rv != SECSuccess) { - rv = RetryPKIXVerifyCertWithWorkarounds(cert_handle, num_policy_oids, - cert_io_enabled, &cvin, cvout); - } - return rv; -} - -// PKIXVerifyCert calls this function to work around some bugs in -// CERT_PKIXVerifyCert. All the arguments of this function are either the -// arguments or local variables of PKIXVerifyCert. -SECStatus RetryPKIXVerifyCertWithWorkarounds( - X509Certificate::OSCertHandle cert_handle, int num_policy_oids, - bool cert_io_enabled, std::vector<CERTValInParam>* cvin, - CERTValOutParam* cvout) { - // We call this function when the first CERT_PKIXVerifyCert call in - // PKIXVerifyCert failed, so we initialize |rv| to SECFailure. - SECStatus rv = SECFailure; - int nss_error = PORT_GetError(); - CERTValInParam in_param; - - // If we get SEC_ERROR_UNKNOWN_ISSUER, we may be missing an intermediate - // CA certificate, so we retry with cert_pi_useAIACertFetch. - // cert_pi_useAIACertFetch has several bugs in its error handling and - // error reporting (NSS bug 528743), so we don't use it by default. - // Note: When building a certificate chain, CERT_PKIXVerifyCert may - // incorrectly pick a CA certificate with the same subject name as the - // missing intermediate CA certificate, and fail with the - // SEC_ERROR_BAD_SIGNATURE error (NSS bug 524013), so we also retry with - // cert_pi_useAIACertFetch on SEC_ERROR_BAD_SIGNATURE. - if (cert_io_enabled && - (nss_error == SEC_ERROR_UNKNOWN_ISSUER || - nss_error == SEC_ERROR_BAD_SIGNATURE)) { - DCHECK_EQ(cvin->back().type, cert_pi_end); - cvin->pop_back(); - in_param.type = cert_pi_useAIACertFetch; - in_param.value.scalar.b = PR_TRUE; - cvin->push_back(in_param); - in_param.type = cert_pi_end; - cvin->push_back(in_param); - rv = CERT_PKIXVerifyCert(cert_handle, certificateUsageSSLServer, - &(*cvin)[0], cvout, NULL); - if (rv == SECSuccess) - return rv; - int new_nss_error = PORT_GetError(); - if (new_nss_error == SEC_ERROR_INVALID_ARGS || - new_nss_error == SEC_ERROR_UNKNOWN_AIA_LOCATION_TYPE || - new_nss_error == SEC_ERROR_BAD_INFO_ACCESS_LOCATION || - new_nss_error == SEC_ERROR_BAD_HTTP_RESPONSE || - new_nss_error == SEC_ERROR_BAD_LDAP_RESPONSE || - !IS_SEC_ERROR(new_nss_error)) { - // Use the original error code because of cert_pi_useAIACertFetch's - // bad error reporting. - PORT_SetError(nss_error); - return rv; - } - nss_error = new_nss_error; - } - - // If an intermediate CA certificate has requireExplicitPolicy in its - // policyConstraints extension, CERT_PKIXVerifyCert fails with - // SEC_ERROR_POLICY_VALIDATION_FAILED because we didn't specify any - // certificate policy (NSS bug 552775). So we retry with the certificate - // policy found in the server certificate. - if (nss_error == SEC_ERROR_POLICY_VALIDATION_FAILED && - num_policy_oids == 0) { - SECOidTag policy = GetFirstCertPolicy(cert_handle); - if (policy != SEC_OID_UNKNOWN) { - DCHECK_EQ(cvin->back().type, cert_pi_end); - cvin->pop_back(); - in_param.type = cert_pi_policyOID; - in_param.value.arraySize = 1; - in_param.value.array.oids = &policy; - cvin->push_back(in_param); - in_param.type = cert_pi_end; - cvin->push_back(in_param); - rv = CERT_PKIXVerifyCert(cert_handle, certificateUsageSSLServer, - &(*cvin)[0], cvout, NULL); - if (rv != SECSuccess) { - // Use the original error code. - PORT_SetError(nss_error); - } - } - } - - return rv; -} - -// Decodes the certificatePolicies extension of the certificate. Returns -// NULL if the certificate doesn't have the extension or the extension can't -// be decoded. The returned value must be freed with a -// CERT_DestroyCertificatePoliciesExtension call. -CERTCertificatePolicies* DecodeCertPolicies( - X509Certificate::OSCertHandle cert_handle) { - SECItem policy_ext; - SECStatus rv = CERT_FindCertExtension(cert_handle, - SEC_OID_X509_CERTIFICATE_POLICIES, - &policy_ext); - if (rv != SECSuccess) - return NULL; - CERTCertificatePolicies* policies = - CERT_DecodeCertificatePoliciesExtension(&policy_ext); - SECITEM_FreeItem(&policy_ext, PR_FALSE); - return policies; -} - -// Returns the OID tag for the first certificate policy in the certificate's -// certificatePolicies extension. Returns SEC_OID_UNKNOWN if the certificate -// has no certificate policy. -SECOidTag GetFirstCertPolicy(X509Certificate::OSCertHandle cert_handle) { - CERTCertificatePolicies* policies = DecodeCertPolicies(cert_handle); - if (!policies) - return SEC_OID_UNKNOWN; - ScopedCERTCertificatePolicies scoped_policies(policies); - CERTPolicyInfo* policy_info = policies->policyInfos[0]; - if (!policy_info) - return SEC_OID_UNKNOWN; - if (policy_info->oid != SEC_OID_UNKNOWN) - return policy_info->oid; - - // The certificate policy is unknown to NSS. We need to create a dynamic - // OID tag for the policy. - SECOidData od; - od.oid.len = policy_info->policyID.len; - od.oid.data = policy_info->policyID.data; - od.offset = SEC_OID_UNKNOWN; - // NSS doesn't allow us to pass an empty description, so I use a hardcoded, - // default description here. The description doesn't need to be unique for - // each OID. - od.desc = "a certificate policy"; - od.mechanism = CKM_INVALID_MECHANISM; - od.supportedExtension = INVALID_CERT_EXTENSION; - return SECOID_AddEntry(&od); -} - -bool CheckCertPolicies(X509Certificate::OSCertHandle cert_handle, - SECOidTag ev_policy_tag) { - CERTCertificatePolicies* policies = DecodeCertPolicies(cert_handle); - if (!policies) { - LOG(ERROR) << "Cert has no policies extension or extension couldn't be " - "decoded."; - return false; - } - ScopedCERTCertificatePolicies scoped_policies(policies); - CERTPolicyInfo** policy_infos = policies->policyInfos; - while (*policy_infos != NULL) { - CERTPolicyInfo* policy_info = *policy_infos++; - SECOidTag oid_tag = policy_info->oid; - if (oid_tag == SEC_OID_UNKNOWN) - continue; - if (oid_tag == ev_policy_tag) - return true; - } - return false; -} - SECStatus PR_CALLBACK CollectCertsCallback(void* arg, SECItem** certs, int num_certs) { X509Certificate::OSCertHandles* results = @@ -687,26 +108,6 @@ CollectCertsCallback(void* arg, SECItem** certs, int num_certs) { return SECSuccess; } -SHA1Fingerprint CertPublicKeyHash(CERTCertificate* cert) { - SHA1Fingerprint hash; - SECStatus rv = HASH_HashBuf(HASH_AlgSHA1, hash.data, - cert->derPublicKey.data, cert->derPublicKey.len); - DCHECK_EQ(rv, SECSuccess); - return hash; -} - -void AppendPublicKeyHashes(CERTCertList* cert_list, - CERTCertificate* root_cert, - std::vector<SHA1Fingerprint>* hashes) { - for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list); - !CERT_LIST_END(node, cert_list); - node = CERT_LIST_NEXT(node)) { - hashes->push_back(CertPublicKeyHash(node->cert)); - } - if (root_cert) - hashes->push_back(CertPublicKeyHash(root_cert)); -} - } // namespace void X509Certificate::Initialize() { @@ -877,160 +278,10 @@ void X509Certificate::GetSubjectAltName( PORT_FreeArena(arena, PR_FALSE); } -int X509Certificate::VerifyInternal(const std::string& hostname, - int flags, - CRLSet* crl_set, - CertVerifyResult* verify_result) const { - // Make sure that the hostname matches with the common name of the cert. - SECStatus status = CERT_VerifyCertName(cert_handle_, hostname.c_str()); - if (status != SECSuccess) - verify_result->cert_status |= CERT_STATUS_COMMON_NAME_INVALID; - - // Make sure that the cert is valid now. - SECCertTimeValidity validity = CERT_CheckCertValidTimes( - cert_handle_, PR_Now(), PR_TRUE); - if (validity != secCertTimeValid) - verify_result->cert_status |= CERT_STATUS_DATE_INVALID; - - CERTValOutParam cvout[3]; - int cvout_index = 0; - cvout[cvout_index].type = cert_po_certList; - cvout[cvout_index].value.pointer.chain = NULL; - int cvout_cert_list_index = cvout_index; - cvout_index++; - cvout[cvout_index].type = cert_po_trustAnchor; - cvout[cvout_index].value.pointer.cert = NULL; - int cvout_trust_anchor_index = cvout_index; - cvout_index++; - cvout[cvout_index].type = cert_po_end; - ScopedCERTValOutParam scoped_cvout(cvout); - - bool cert_io_enabled = flags & VERIFY_CERT_IO_ENABLED; - bool check_revocation = (flags & VERIFY_REV_CHECKING_ENABLED) && - cert_io_enabled; - if (check_revocation) { - verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED; - } - - status = PKIXVerifyCert( - cert_handle_, check_revocation, cert_io_enabled, NULL, 0, cvout); - - if (crl_set) { - CRLSetResult crl_set_result = CheckRevocationWithCRLSet( - cvout[cvout_cert_list_index].value.pointer.chain, - cvout[cvout_trust_anchor_index].value.pointer.cert, - crl_set); - if (crl_set_result == kCRLSetRevoked) { - PORT_SetError(SEC_ERROR_REVOKED_CERTIFICATE); - status = SECFailure; - } - } - - if (status != SECSuccess) { - int err = PORT_GetError(); - LOG(ERROR) << "CERT_PKIXVerifyCert for " << hostname - << " failed err=" << err; - // CERT_PKIXVerifyCert rerports the wrong error code for - // expired certificates (NSS bug 491174) - if (err == SEC_ERROR_CERT_NOT_VALID && - (verify_result->cert_status & CERT_STATUS_DATE_INVALID)) - err = SEC_ERROR_EXPIRED_CERTIFICATE; - CertStatus cert_status = MapCertErrorToCertStatus(err); - if (cert_status) { - verify_result->cert_status |= cert_status; - return MapCertStatusToNetError(verify_result->cert_status); - } - // |err| is not a certificate error. - return MapSecurityError(err); - } - - GetCertChainInfo(cvout[cvout_cert_list_index].value.pointer.chain, - cvout[cvout_trust_anchor_index].value.pointer.cert, - verify_result); - if (IsCertStatusError(verify_result->cert_status)) - return MapCertStatusToNetError(verify_result->cert_status); - - AppendPublicKeyHashes(cvout[cvout_cert_list_index].value.pointer.chain, - cvout[cvout_trust_anchor_index].value.pointer.cert, - &verify_result->public_key_hashes); - - verify_result->is_issued_by_known_root = - IsKnownRoot(cvout[cvout_trust_anchor_index].value.pointer.cert); - - if ((flags & VERIFY_EV_CERT) && VerifyEV(flags, crl_set)) - verify_result->cert_status |= CERT_STATUS_IS_EV; - - return OK; -} - bool X509Certificate::VerifyNameMatch(const std::string& hostname) const { return CERT_VerifyCertName(cert_handle_, hostname.c_str()) == SECSuccess; } -// Studied Mozilla's code (esp. security/manager/ssl/src/nsIdentityChecking.cpp -// and nsNSSCertHelper.cpp) to learn how to verify EV certificate. -// TODO(wtc): A possible optimization is that we get the trust anchor from -// the first PKIXVerifyCert call. We look up the EV policy for the trust -// anchor. If the trust anchor has no EV policy, we know the cert isn't EV. -// Otherwise, we pass just that EV policy (as opposed to all the EV policies) -// to the second PKIXVerifyCert call. -bool X509Certificate::VerifyEV(int flags, CRLSet* crl_set) const { - EVRootCAMetadata* metadata = EVRootCAMetadata::GetInstance(); - - CERTValOutParam cvout[3]; - int cvout_index = 0; - cvout[cvout_index].type = cert_po_certList; - cvout[cvout_index].value.pointer.chain = NULL; - int cvout_cert_list_index = cvout_index; - cvout_index++; - cvout[cvout_index].type = cert_po_trustAnchor; - cvout[cvout_index].value.pointer.cert = NULL; - int cvout_trust_anchor_index = cvout_index; - cvout_index++; - cvout[cvout_index].type = cert_po_end; - ScopedCERTValOutParam scoped_cvout(cvout); - - SECStatus status = PKIXVerifyCert(cert_handle_, - flags & VERIFY_REV_CHECKING_ENABLED, - flags & VERIFY_CERT_IO_ENABLED, - metadata->GetPolicyOIDs(), - metadata->NumPolicyOIDs(), - cvout); - if (status != SECSuccess) - return false; - - CERTCertificate* root_ca = - cvout[cvout_trust_anchor_index].value.pointer.cert; - if (root_ca == NULL) - return false; - - // This second PKIXVerifyCert call could have found a different certification - // path and one or more of the certificates on this new path, that weren't on - // the old path, might have been revoked. - if (crl_set) { - CRLSetResult crl_set_result = CheckRevocationWithCRLSet( - cvout[cvout_cert_list_index].value.pointer.chain, - cvout[cvout_trust_anchor_index].value.pointer.cert, - crl_set); - if (crl_set_result == kCRLSetRevoked) - return false; - } - - SHA1Fingerprint fingerprint = - X509Certificate::CalculateFingerprint(root_ca); - std::vector<SECOidTag> ev_policy_tags; - if (!metadata->GetPolicyOIDsForCA(fingerprint, &ev_policy_tags)) - return false; - DCHECK(!ev_policy_tags.empty()); - - for (std::vector<SECOidTag>::const_iterator - i = ev_policy_tags.begin(); i != ev_policy_tags.end(); ++i) { - if (CheckCertPolicies(cert_handle_, *i)) - return true; - } - return false; -} - // static bool X509Certificate::GetDEREncoded(X509Certificate::OSCertHandle cert_handle, std::string* encoded) { diff --git a/net/base/x509_certificate_openssl.cc b/net/base/x509_certificate_openssl.cc index 6eef06e..754626f 100644 --- a/net/base/x509_certificate_openssl.cc +++ b/net/base/x509_certificate_openssl.cc @@ -19,9 +19,6 @@ #include "base/string_number_conversions.h" #include "base/string_util.h" #include "crypto/openssl_util.h" -#include "net/base/asn1_util.h" -#include "net/base/cert_status_flags.h" -#include "net/base/cert_verify_result.h" #include "net/base/net_errors.h" #include "net/base/x509_util_openssl.h" @@ -139,77 +136,6 @@ void ParseSubjectAltName(X509Certificate::OSCertHandle cert, } } -// Maps X509_STORE_CTX_get_error() return values to our cert status flags. -CertStatus MapCertErrorToCertStatus(int err) { - switch (err) { - case X509_V_ERR_SUBJECT_ISSUER_MISMATCH: - return CERT_STATUS_COMMON_NAME_INVALID; - case X509_V_ERR_CERT_NOT_YET_VALID: - case X509_V_ERR_CERT_HAS_EXPIRED: - case X509_V_ERR_CRL_NOT_YET_VALID: - case X509_V_ERR_CRL_HAS_EXPIRED: - case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: - case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: - case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: - case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: - return CERT_STATUS_DATE_INVALID; - case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: - case X509_V_ERR_UNABLE_TO_GET_CRL: - case X509_V_ERR_INVALID_CA: - case X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER: - case X509_V_ERR_INVALID_NON_CA: - case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: - case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: - case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: - return CERT_STATUS_AUTHORITY_INVALID; -#if 0 -// TODO(bulach): what should we map to these status? - return CERT_STATUS_NO_REVOCATION_MECHANISM; - return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION; - return CERT_STATUS_NOT_IN_DNS; -#endif - case X509_V_ERR_CERT_REVOKED: - return CERT_STATUS_REVOKED; - // All these status are mapped to CERT_STATUS_INVALID. - case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: - case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: - case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: - case X509_V_ERR_CERT_SIGNATURE_FAILURE: - case X509_V_ERR_CRL_SIGNATURE_FAILURE: - case X509_V_ERR_OUT_OF_MEM: - case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: - case X509_V_ERR_CERT_CHAIN_TOO_LONG: - case X509_V_ERR_PATH_LENGTH_EXCEEDED: - case X509_V_ERR_INVALID_PURPOSE: - case X509_V_ERR_CERT_UNTRUSTED: - case X509_V_ERR_CERT_REJECTED: - case X509_V_ERR_AKID_SKID_MISMATCH: - case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH: - case X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION: - case X509_V_ERR_KEYUSAGE_NO_CERTSIGN: - case X509_V_ERR_KEYUSAGE_NO_CRL_SIGN: - case X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION: - case X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED: - case X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE: - case X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED: - case X509_V_ERR_INVALID_EXTENSION: - case X509_V_ERR_INVALID_POLICY_EXTENSION: - case X509_V_ERR_NO_EXPLICIT_POLICY: - case X509_V_ERR_UNNESTED_RESOURCE: - case X509_V_ERR_APPLICATION_VERIFICATION: - return CERT_STATUS_INVALID; - default: - NOTREACHED() << "Invalid X509 err " << err; - return CERT_STATUS_INVALID; - } -} - -// sk_X509_free is a function-style macro, so can't be used as a template -// param directly. -void sk_X509_free_fn(STACK_OF(X509)* st) { - sk_X509_free(st); -} - struct DERCache { unsigned char* data; int data_length; @@ -305,104 +231,6 @@ bool GetDERAndCacheIfNeeded(X509Certificate::OSCertHandle cert, return true; } -void GetCertChainInfo(X509_STORE_CTX* store_ctx, - CertVerifyResult* verify_result) { - STACK_OF(X509)* chain = X509_STORE_CTX_get_chain(store_ctx); - X509* verified_cert = NULL; - std::vector<X509*> verified_chain; - for (int i = 0; i < sk_X509_num(chain); ++i) { - X509* cert = sk_X509_value(chain, i); - if (i == 0) { - verified_cert = cert; - } else { - verified_chain.push_back(cert); - } - - // Only check the algorithm status for certificates that are not in the - // trust store. - if (i < store_ctx->last_untrusted) { - int sig_alg = OBJ_obj2nid(cert->sig_alg->algorithm); - if (sig_alg == NID_md2WithRSAEncryption) { - verify_result->has_md2 = true; - if (i != 0) - verify_result->has_md2_ca = true; - } else if (sig_alg == NID_md4WithRSAEncryption) { - verify_result->has_md4 = true; - } else if (sig_alg == NID_md5WithRSAEncryption) { - verify_result->has_md5 = true; - if (i != 0) - verify_result->has_md5_ca = true; - } - } - } - - if (verified_cert) { - verify_result->verified_cert = - X509Certificate::CreateFromHandle(verified_cert, verified_chain); - } -} - -void AppendPublicKeyHashes(X509_STORE_CTX* store_ctx, - std::vector<SHA1Fingerprint>* hashes) { - STACK_OF(X509)* chain = X509_STORE_CTX_get_chain(store_ctx); - for (int i = 0; i < sk_X509_num(chain); ++i) { - X509* cert = sk_X509_value(chain, i); - - DERCache der_cache; - if (!GetDERAndCacheIfNeeded(cert, &der_cache)) - continue; - - base::StringPiece der_bytes(reinterpret_cast<const char*>(der_cache.data), - der_cache.data_length); - base::StringPiece spki_bytes; - if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki_bytes)) - continue; - - SHA1Fingerprint hash; - base::SHA1HashBytes(reinterpret_cast<const uint8*>(spki_bytes.data()), - spki_bytes.size(), hash.data); - hashes->push_back(hash); - } -} - -#if defined(OS_ANDROID) - -// Returns true if we have verification result in |verify_result| from Android -// Trust Manager. Otherwise returns false. -bool VerifyFromAndroidTrustManager(const std::vector<std::string>& cert_bytes, - CertVerifyResult* verify_result) { - // TODO(joth): Fetch the authentication type from SSL rather than hardcode. - // TODO(jnd): Remove unused |hostname| from net::android::VerifyX509CertChain. - bool verified = true; -#if 0 - android::VerifyResult result = - android::VerifyX509CertChain(cert_bytes, hostname, "RSA"); -#else - // TODO(jingzhao): Recover the original implementation once we support JNI. - android::VerifyResult result = android::VERIFY_INVOCATION_ERROR; - NOTIMPLEMENTED(); -#endif - switch (result) { - case android::VERIFY_OK: - break; - case android::VERIFY_BAD_HOSTNAME: - verify_result->cert_status |= CERT_STATUS_COMMON_NAME_INVALID; - break; - case android::VERIFY_NO_TRUSTED_ROOT: - verify_result->cert_status |= CERT_STATUS_AUTHORITY_INVALID; - break; - case android::VERIFY_INVOCATION_ERROR: - verified = false; - break; - default: - verify_result->cert_status |= CERT_STATUS_INVALID; - break; - } - return verified; -} - -#endif - } // namespace // static @@ -554,70 +382,6 @@ X509_STORE* X509Certificate::cert_store() { return X509InitSingleton::GetInstance()->store(); } -int X509Certificate::VerifyInternal(const std::string& hostname, - int flags, - CRLSet* crl_set, - CertVerifyResult* verify_result) const { - if (!VerifyNameMatch(hostname)) - verify_result->cert_status |= CERT_STATUS_COMMON_NAME_INVALID; - - bool verify_attempted = false; - -#if defined(OS_ANDROID) - std::vector<std::string> cert_bytes; - GetChainDEREncodedBytes(&cert_bytes); - - verify_attempted = VerifyFromAndroidTrustManager(cert_bytes, verify_result); -#endif - - if (verify_attempted) { - if (IsCertStatusError(verify_result->cert_status)) - return MapCertStatusToNetError(verify_result->cert_status); - } else { - crypto::ScopedOpenSSL<X509_STORE_CTX, X509_STORE_CTX_free> ctx( - X509_STORE_CTX_new()); - - crypto::ScopedOpenSSL<STACK_OF(X509), sk_X509_free_fn> intermediates( - sk_X509_new_null()); - if (!intermediates.get()) - return ERR_OUT_OF_MEMORY; - - for (OSCertHandles::const_iterator it = intermediate_ca_certs_.begin(); - it != intermediate_ca_certs_.end(); ++it) { - if (!sk_X509_push(intermediates.get(), *it)) - return ERR_OUT_OF_MEMORY; - } - int rv = X509_STORE_CTX_init(ctx.get(), cert_store(), - cert_handle_, intermediates.get()); - CHECK_EQ(1, rv); - - if (X509_verify_cert(ctx.get()) != 1) { - int x509_error = X509_STORE_CTX_get_error(ctx.get()); - CertStatus cert_status = MapCertErrorToCertStatus(x509_error); - LOG(ERROR) << "X509 Verification error " - << X509_verify_cert_error_string(x509_error) - << " : " << x509_error - << " : " << X509_STORE_CTX_get_error_depth(ctx.get()) - << " : " << cert_status; - verify_result->cert_status |= cert_status; - } - - GetCertChainInfo(ctx.get(), verify_result); - if (IsCertStatusError(verify_result->cert_status)) - return MapCertStatusToNetError(verify_result->cert_status); - AppendPublicKeyHashes(ctx.get(), &verify_result->public_key_hashes); - } - - // Currently we only ues OpenSSL's default root CA paths, so treat all - // correctly verified certs as being from a known root. TODO(joth): if the - // motivations described in http://src.chromium.org/viewvc/chrome?view=rev&revision=80778 - // become an issue on OpenSSL builds, we will need to embed a hardcoded list - // of well known root CAs, as per the _mac and _win versions. - verify_result->is_issued_by_known_root = true; - - return OK; -} - // static bool X509Certificate::GetDEREncoded(X509Certificate::OSCertHandle cert_handle, std::string* encoded) { @@ -704,23 +468,4 @@ void X509Certificate::GetPublicKeyInfo(OSCertHandle cert_handle, } } -#if defined(OS_ANDROID) -void X509Certificate::GetChainDEREncodedBytes( - std::vector<std::string>* chain_bytes) const { - OSCertHandles cert_handles(intermediate_ca_certs_); - // Make sure the peer's own cert is the first in the chain, if it's not - // already there. - if (cert_handles.empty() || cert_handles[0] != cert_handle_) - cert_handles.insert(cert_handles.begin(), cert_handle_); - - chain_bytes->reserve(cert_handles.size()); - for (OSCertHandles::const_iterator it = cert_handles.begin(); - it != cert_handles.end(); ++it) { - std::string cert_bytes; - GetDEREncoded(*it, &cert_bytes); - chain_bytes->push_back(cert_bytes); - } -} -#endif - } // namespace net diff --git a/net/base/x509_certificate_unittest.cc b/net/base/x509_certificate_unittest.cc index 228679d..2bea8e5 100644 --- a/net/base/x509_certificate_unittest.cc +++ b/net/base/x509_certificate_unittest.cc @@ -438,41 +438,6 @@ TEST(X509CertificateTest, CAFingerprints) { cert_chain3_ca_fingerprint, 20) == 0); } -TEST(X509CertificateTest, DigiNotarCerts) { - static const char* const kDigiNotarFilenames[] = { - "diginotar_root_ca.pem", - "diginotar_cyber_ca.pem", - "diginotar_services_1024_ca.pem", - "diginotar_pkioverheid.pem", - "diginotar_pkioverheid_g2.pem", - NULL, - }; - - FilePath certs_dir = GetTestCertsDirectory(); - - for (size_t i = 0; kDigiNotarFilenames[i]; i++) { - scoped_refptr<X509Certificate> diginotar_cert = - ImportCertFromFile(certs_dir, kDigiNotarFilenames[i]); - std::string der_bytes; - ASSERT_TRUE(X509Certificate::GetDEREncoded( - diginotar_cert->os_cert_handle(), &der_bytes)); - - base::StringPiece spki; - ASSERT_TRUE(asn1::ExtractSPKIFromDERCert(der_bytes, &spki)); - - std::string spki_sha1 = base::SHA1HashString(spki.as_string()); - - std::vector<SHA1Fingerprint> public_keys; - SHA1Fingerprint fingerprint; - ASSERT_EQ(sizeof(fingerprint.data), spki_sha1.size()); - memcpy(fingerprint.data, spki_sha1.data(), spki_sha1.size()); - public_keys.push_back(fingerprint); - - EXPECT_TRUE(X509Certificate::IsPublicKeyBlacklisted(public_keys)) << - "Public key not blocked for " << kDigiNotarFilenames[i]; - } -} - TEST(X509CertificateTest, ExtractSPKIFromDERCert) { FilePath certs_dir = GetTestCertsDirectory(); scoped_refptr<X509Certificate> cert = diff --git a/net/base/x509_certificate_win.cc b/net/base/x509_certificate_win.cc index a80833f..88f17a7 100644 --- a/net/base/x509_certificate_win.cc +++ b/net/base/x509_certificate_win.cc @@ -6,7 +6,6 @@ #include <blapi.h> // Implement CalculateChainFingerprint() with NSS. -#include "base/lazy_instance.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/pickle.h" @@ -17,15 +16,7 @@ #include "crypto/capi_util.h" #include "crypto/rsa_private_key.h" #include "crypto/scoped_capi_types.h" -#include "crypto/sha2.h" -#include "net/base/asn1_util.h" -#include "net/base/cert_status_flags.h" -#include "net/base/cert_verify_result.h" -#include "net/base/crl_set.h" -#include "net/base/ev_root_ca_metadata.h" #include "net/base/net_errors.h" -#include "net/base/test_root_certs.h" -#include "net/base/x509_certificate_known_roots_win.h" #pragma comment(lib, "crypt32.lib") @@ -35,145 +26,11 @@ namespace net { namespace { -struct FreeChainEngineFunctor { - void operator()(HCERTCHAINENGINE engine) const { - if (engine) - CertFreeCertificateChainEngine(engine); - } -}; - -struct FreeCertContextFunctor { - void operator()(PCCERT_CONTEXT context) const { - if (context) - CertFreeCertificateContext(context); - } -}; - -struct FreeCertChainContextFunctor { - void operator()(PCCERT_CHAIN_CONTEXT chain_context) const { - if (chain_context) - CertFreeCertificateChain(chain_context); - } -}; - -typedef crypto::ScopedCAPIHandle<HCERTCHAINENGINE, FreeChainEngineFunctor> - ScopedHCERTCHAINENGINE; - typedef crypto::ScopedCAPIHandle< HCERTSTORE, crypto::CAPIDestroyerWithFlags<HCERTSTORE, CertCloseStore, 0> > ScopedHCERTSTORE; -typedef scoped_ptr_malloc<const CERT_CONTEXT, - FreeCertContextFunctor> ScopedPCCERT_CONTEXT; - -typedef scoped_ptr_malloc<const CERT_CHAIN_CONTEXT, - FreeCertChainContextFunctor> - ScopedPCCERT_CHAIN_CONTEXT; - -//----------------------------------------------------------------------------- - -// TODO(wtc): This is a copy of the MapSecurityError function in -// ssl_client_socket_win.cc. Another function that maps Windows error codes -// to our network error codes is WinInetUtil::OSErrorToNetError. We should -// eliminate the code duplication. -int MapSecurityError(SECURITY_STATUS err) { - // There are numerous security error codes, but these are the ones we thus - // far find interesting. - switch (err) { - case SEC_E_WRONG_PRINCIPAL: // Schannel - case CERT_E_CN_NO_MATCH: // CryptoAPI - return ERR_CERT_COMMON_NAME_INVALID; - case SEC_E_UNTRUSTED_ROOT: // Schannel - case CERT_E_UNTRUSTEDROOT: // CryptoAPI - return ERR_CERT_AUTHORITY_INVALID; - case SEC_E_CERT_EXPIRED: // Schannel - case CERT_E_EXPIRED: // CryptoAPI - return ERR_CERT_DATE_INVALID; - case CRYPT_E_NO_REVOCATION_CHECK: - return ERR_CERT_NO_REVOCATION_MECHANISM; - case CRYPT_E_REVOCATION_OFFLINE: - return ERR_CERT_UNABLE_TO_CHECK_REVOCATION; - case CRYPT_E_REVOKED: // Schannel and CryptoAPI - return ERR_CERT_REVOKED; - case SEC_E_CERT_UNKNOWN: - case CERT_E_ROLE: - return ERR_CERT_INVALID; - case CERT_E_WRONG_USAGE: - // TODO(wtc): Should we add ERR_CERT_WRONG_USAGE? - return ERR_CERT_INVALID; - // We received an unexpected_message or illegal_parameter alert message - // from the server. - case SEC_E_ILLEGAL_MESSAGE: - return ERR_SSL_PROTOCOL_ERROR; - case SEC_E_ALGORITHM_MISMATCH: - return ERR_SSL_VERSION_OR_CIPHER_MISMATCH; - case SEC_E_INVALID_HANDLE: - return ERR_UNEXPECTED; - case SEC_E_OK: - return OK; - default: - LOG(WARNING) << "Unknown error " << err << " mapped to net::ERR_FAILED"; - return ERR_FAILED; - } -} - -// Map the errors in the chain_context->TrustStatus.dwErrorStatus returned by -// CertGetCertificateChain to our certificate status flags. -int MapCertChainErrorStatusToCertStatus(DWORD error_status) { - CertStatus cert_status = 0; - - // We don't include CERT_TRUST_IS_NOT_TIME_NESTED because it's obsolete and - // we wouldn't consider it an error anyway - const DWORD kDateInvalidErrors = CERT_TRUST_IS_NOT_TIME_VALID | - CERT_TRUST_CTL_IS_NOT_TIME_VALID; - if (error_status & kDateInvalidErrors) - cert_status |= CERT_STATUS_DATE_INVALID; - - const DWORD kAuthorityInvalidErrors = CERT_TRUST_IS_UNTRUSTED_ROOT | - CERT_TRUST_IS_EXPLICIT_DISTRUST | - CERT_TRUST_IS_PARTIAL_CHAIN; - if (error_status & kAuthorityInvalidErrors) - cert_status |= CERT_STATUS_AUTHORITY_INVALID; - - if ((error_status & CERT_TRUST_REVOCATION_STATUS_UNKNOWN) && - !(error_status & CERT_TRUST_IS_OFFLINE_REVOCATION)) - cert_status |= CERT_STATUS_NO_REVOCATION_MECHANISM; - - if (error_status & CERT_TRUST_IS_OFFLINE_REVOCATION) - cert_status |= CERT_STATUS_UNABLE_TO_CHECK_REVOCATION; - - if (error_status & CERT_TRUST_IS_REVOKED) - cert_status |= CERT_STATUS_REVOKED; - - const DWORD kWrongUsageErrors = CERT_TRUST_IS_NOT_VALID_FOR_USAGE | - CERT_TRUST_CTL_IS_NOT_VALID_FOR_USAGE; - if (error_status & kWrongUsageErrors) { - // TODO(wtc): Should we add CERT_STATUS_WRONG_USAGE? - cert_status |= CERT_STATUS_INVALID; - } - - // The rest of the errors. - const DWORD kCertInvalidErrors = - CERT_TRUST_IS_NOT_SIGNATURE_VALID | - CERT_TRUST_IS_CYCLIC | - CERT_TRUST_INVALID_EXTENSION | - CERT_TRUST_INVALID_POLICY_CONSTRAINTS | - CERT_TRUST_INVALID_BASIC_CONSTRAINTS | - CERT_TRUST_INVALID_NAME_CONSTRAINTS | - CERT_TRUST_CTL_IS_NOT_SIGNATURE_VALID | - CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT | - CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT | - CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT | - CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT | - CERT_TRUST_NO_ISSUANCE_CHAIN_POLICY | - CERT_TRUST_HAS_NOT_SUPPORTED_CRITICAL_EXT; - if (error_status & kCertInvalidErrors) - cert_status |= CERT_STATUS_INVALID; - - return cert_status; -} - void ExplodedTimeToSystemTime(const base::Time::Exploded& exploded, SYSTEMTIME* system_time) { system_time->wYear = exploded.year; @@ -217,189 +74,6 @@ void GetCertSubjectAltName(PCCERT_CONTEXT cert, output->reset(alt_name_info); } -// Returns true if any common name in the certificate's Subject field contains -// a NULL character. -bool CertSubjectCommonNameHasNull(PCCERT_CONTEXT cert) { - CRYPT_DECODE_PARA decode_para; - decode_para.cbSize = sizeof(decode_para); - decode_para.pfnAlloc = crypto::CryptAlloc; - decode_para.pfnFree = crypto::CryptFree; - CERT_NAME_INFO* name_info = NULL; - DWORD name_info_size = 0; - BOOL rv; - rv = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - X509_NAME, - cert->pCertInfo->Subject.pbData, - cert->pCertInfo->Subject.cbData, - CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, - &decode_para, - &name_info, - &name_info_size); - if (rv) { - scoped_ptr_malloc<CERT_NAME_INFO> scoped_name_info(name_info); - - // The Subject field may have multiple common names. According to the - // "PKI Layer Cake" paper, CryptoAPI uses every common name in the - // Subject field, so we inspect every common name. - // - // From RFC 5280: - // X520CommonName ::= CHOICE { - // teletexString TeletexString (SIZE (1..ub-common-name)), - // printableString PrintableString (SIZE (1..ub-common-name)), - // universalString UniversalString (SIZE (1..ub-common-name)), - // utf8String UTF8String (SIZE (1..ub-common-name)), - // bmpString BMPString (SIZE (1..ub-common-name)) } - // - // We also check IA5String and VisibleString. - for (DWORD i = 0; i < name_info->cRDN; ++i) { - PCERT_RDN rdn = &name_info->rgRDN[i]; - for (DWORD j = 0; j < rdn->cRDNAttr; ++j) { - PCERT_RDN_ATTR rdn_attr = &rdn->rgRDNAttr[j]; - if (strcmp(rdn_attr->pszObjId, szOID_COMMON_NAME) == 0) { - switch (rdn_attr->dwValueType) { - // After the CryptoAPI ASN.1 security vulnerabilities described in - // http://www.microsoft.com/technet/security/Bulletin/MS09-056.mspx - // were patched, we get CERT_RDN_ENCODED_BLOB for a common name - // that contains a NULL character. - case CERT_RDN_ENCODED_BLOB: - break; - // Array of 8-bit characters. - case CERT_RDN_PRINTABLE_STRING: - case CERT_RDN_TELETEX_STRING: - case CERT_RDN_IA5_STRING: - case CERT_RDN_VISIBLE_STRING: - for (DWORD k = 0; k < rdn_attr->Value.cbData; ++k) { - if (rdn_attr->Value.pbData[k] == '\0') - return true; - } - break; - // Array of 16-bit characters. - case CERT_RDN_BMP_STRING: - case CERT_RDN_UTF8_STRING: { - DWORD num_wchars = rdn_attr->Value.cbData / 2; - wchar_t* common_name = - reinterpret_cast<wchar_t*>(rdn_attr->Value.pbData); - for (DWORD k = 0; k < num_wchars; ++k) { - if (common_name[k] == L'\0') - return true; - } - break; - } - // Array of ints (32-bit). - case CERT_RDN_UNIVERSAL_STRING: { - DWORD num_ints = rdn_attr->Value.cbData / 4; - int* common_name = - reinterpret_cast<int*>(rdn_attr->Value.pbData); - for (DWORD k = 0; k < num_ints; ++k) { - if (common_name[k] == 0) - return true; - } - break; - } - default: - NOTREACHED(); - break; - } - } - } - } - } - return false; -} - -// Saves some information about the certificate chain |chain_context| in -// |*verify_result|. The caller MUST initialize |*verify_result| before -// calling this function. -void GetCertChainInfo(PCCERT_CHAIN_CONTEXT chain_context, - CertVerifyResult* verify_result) { - if (chain_context->cChain == 0) - return; - - PCERT_SIMPLE_CHAIN first_chain = chain_context->rgpChain[0]; - int num_elements = first_chain->cElement; - PCERT_CHAIN_ELEMENT* element = first_chain->rgpElement; - - PCCERT_CONTEXT verified_cert = NULL; - std::vector<PCCERT_CONTEXT> verified_chain; - - bool has_root_ca = num_elements > 1 && - !(chain_context->TrustStatus.dwErrorStatus & - CERT_TRUST_IS_PARTIAL_CHAIN); - - // Each chain starts with the end entity certificate (i = 0) and ends with - // either the root CA certificate or the last available intermediate. If a - // root CA certificate is present, do not inspect the signature algorithm of - // the root CA certificate because the signature on the trust anchor is not - // important. - if (has_root_ca) { - // If a full chain was constructed, regardless of whether it was trusted, - // don't inspect the root's signature algorithm. - num_elements -= 1; - } - - for (int i = 0; i < num_elements; ++i) { - PCCERT_CONTEXT cert = element[i]->pCertContext; - if (i == 0) { - verified_cert = cert; - } else { - verified_chain.push_back(cert); - } - - const char* algorithm = cert->pCertInfo->SignatureAlgorithm.pszObjId; - if (strcmp(algorithm, szOID_RSA_MD5RSA) == 0) { - // md5WithRSAEncryption: 1.2.840.113549.1.1.4 - verify_result->has_md5 = true; - if (i != 0) - verify_result->has_md5_ca = true; - } else if (strcmp(algorithm, szOID_RSA_MD2RSA) == 0) { - // md2WithRSAEncryption: 1.2.840.113549.1.1.2 - verify_result->has_md2 = true; - if (i != 0) - verify_result->has_md2_ca = true; - } else if (strcmp(algorithm, szOID_RSA_MD4RSA) == 0) { - // md4WithRSAEncryption: 1.2.840.113549.1.1.3 - verify_result->has_md4 = true; - } - } - - if (verified_cert) { - // Add the root certificate, if present, as it was not added above. - if (has_root_ca) - verified_chain.push_back(element[num_elements]->pCertContext); - verify_result->verified_cert = - X509Certificate::CreateFromHandle(verified_cert, verified_chain); - } -} - -// Decodes the cert's certificatePolicies extension into a CERT_POLICIES_INFO -// structure and stores it in *output. -void GetCertPoliciesInfo(PCCERT_CONTEXT cert, - scoped_ptr_malloc<CERT_POLICIES_INFO>* output) { - PCERT_EXTENSION extension = CertFindExtension(szOID_CERT_POLICIES, - cert->pCertInfo->cExtension, - cert->pCertInfo->rgExtension); - if (!extension) - return; - - CRYPT_DECODE_PARA decode_para; - decode_para.cbSize = sizeof(decode_para); - decode_para.pfnAlloc = crypto::CryptAlloc; - decode_para.pfnFree = crypto::CryptFree; - CERT_POLICIES_INFO* policies_info = NULL; - DWORD policies_info_size = 0; - BOOL rv; - rv = CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - szOID_CERT_POLICIES, - extension->Value.pbData, - extension->Value.cbData, - CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, - &decode_para, - &policies_info, - &policies_info_size); - if (rv) - output->reset(policies_info); -} - void AddCertsFromStore(HCERTSTORE store, X509Certificate::OSCertHandles* results) { PCCERT_CONTEXT cert = NULL; @@ -448,93 +122,6 @@ X509Certificate::OSCertHandles ParsePKCS7(const char* data, size_t length) { return results; } -bool CheckRevocationWithCRLSet(PCCERT_CHAIN_CONTEXT chain, - CRLSet* crl_set) { - if (chain->cChain == 0) - return true; - - const PCERT_SIMPLE_CHAIN first_chain = chain->rgpChain[0]; - const PCERT_CHAIN_ELEMENT* element = first_chain->rgpElement; - - const int num_elements = first_chain->cElement; - if (num_elements == 0) - return true; - - // We iterate from the root certificate down to the leaf, keeping track of - // the issuer's SPKI at each step. - std::string issuer_spki_hash; - for (int i = num_elements - 1; i >= 0; i--) { - PCCERT_CONTEXT cert = element[i]->pCertContext; - - base::StringPiece der_bytes( - reinterpret_cast<const char*>(cert->pbCertEncoded), - cert->cbCertEncoded); - - base::StringPiece spki; - if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki)) { - NOTREACHED(); - continue; - } - - const std::string spki_hash = crypto::SHA256HashString(spki); - - const CRYPT_INTEGER_BLOB* serial_blob = &cert->pCertInfo->SerialNumber; - scoped_array<uint8> serial_bytes(new uint8[serial_blob->cbData]); - // The bytes of the serial number are stored little-endian. - for (unsigned j = 0; j < serial_blob->cbData; j++) - serial_bytes[j] = serial_blob->pbData[serial_blob->cbData - j - 1]; - base::StringPiece serial(reinterpret_cast<const char*>(serial_bytes.get()), - serial_blob->cbData); - - CRLSet::Result result = crl_set->CheckSPKI(spki_hash); - - if (result != CRLSet::REVOKED && !issuer_spki_hash.empty()) - result = crl_set->CheckSerial(serial, issuer_spki_hash); - - issuer_spki_hash = spki_hash; - - switch (result) { - case CRLSet::REVOKED: - return false; - case CRLSet::UNKNOWN: - case CRLSet::GOOD: - continue; - default: - NOTREACHED(); - continue; - } - } - - return true; -} - -void AppendPublicKeyHashes(PCCERT_CHAIN_CONTEXT chain, - std::vector<SHA1Fingerprint>* hashes) { - if (chain->cChain == 0) - return; - - PCERT_SIMPLE_CHAIN first_chain = chain->rgpChain[0]; - PCERT_CHAIN_ELEMENT* const element = first_chain->rgpElement; - - const DWORD num_elements = first_chain->cElement; - for (DWORD i = 0; i < num_elements; i++) { - PCCERT_CONTEXT cert = element[i]->pCertContext; - - base::StringPiece der_bytes( - reinterpret_cast<const char*>(cert->pbCertEncoded), - cert->cbCertEncoded); - base::StringPiece spki_bytes; - if (!asn1::ExtractSPKIFromDERCert(der_bytes, &spki_bytes)) - continue; - - SHA1Fingerprint hash; - base::SHA1HashBytes(reinterpret_cast<const uint8*>(spki_bytes.data()), - spki_bytes.size(), hash.data); - hashes->push_back(hash); - } -} - - } // namespace void X509Certificate::Initialize() { @@ -558,22 +145,6 @@ void X509Certificate::Initialize() { reinterpret_cast<char*>(serial_bytes.get()), serial->cbData); } -// IsIssuedByKnownRoot returns true if the given chain is rooted at a root CA -// which we recognise as a standard root. -// static -bool X509Certificate::IsIssuedByKnownRoot(PCCERT_CHAIN_CONTEXT chain_context) { - PCERT_SIMPLE_CHAIN first_chain = chain_context->rgpChain[0]; - int num_elements = first_chain->cElement; - if (num_elements < 1) - return false; - PCERT_CHAIN_ELEMENT* element = first_chain->rgpElement; - PCCERT_CONTEXT cert = element[num_elements - 1]->pCertContext; - - SHA1Fingerprint hash = CalculateFingerprint(cert); - return IsSHA1HashInSortedArray( - hash, &kKnownRootCertSHA1Hashes[0][0], sizeof(kKnownRootCertSHA1Hashes)); -} - // static X509Certificate* X509Certificate::CreateSelfSigned( crypto::RSAPrivateKey* key, @@ -702,227 +273,6 @@ PCCERT_CONTEXT X509Certificate::CreateOSCertChainForCert() const { return primary_cert; } -int X509Certificate::VerifyInternal(const std::string& hostname, - int flags, - CRLSet* crl_set, - CertVerifyResult* verify_result) const { - if (!cert_handle_) - return ERR_UNEXPECTED; - - // Build and validate certificate chain. - CERT_CHAIN_PARA chain_para; - memset(&chain_para, 0, sizeof(chain_para)); - chain_para.cbSize = sizeof(chain_para); - // ExtendedKeyUsage. - // We still need to request szOID_SERVER_GATED_CRYPTO and szOID_SGC_NETSCAPE - // today because some certificate chains need them. IE also requests these - // two usages. - static const LPSTR usage[] = { - szOID_PKIX_KP_SERVER_AUTH, - szOID_SERVER_GATED_CRYPTO, - szOID_SGC_NETSCAPE - }; - chain_para.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; - chain_para.RequestedUsage.Usage.cUsageIdentifier = arraysize(usage); - chain_para.RequestedUsage.Usage.rgpszUsageIdentifier = - const_cast<LPSTR*>(usage); - // We can set CERT_CHAIN_RETURN_LOWER_QUALITY_CONTEXTS to get more chains. - DWORD chain_flags = CERT_CHAIN_CACHE_END_CERT | - CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT; - const bool rev_checking_enabled = flags & VERIFY_REV_CHECKING_ENABLED; - - if (rev_checking_enabled) { - verify_result->cert_status |= CERT_STATUS_REV_CHECKING_ENABLED; - } else { - chain_flags |= CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY; - } - - // Get the certificatePolicies extension of the certificate. - scoped_ptr_malloc<CERT_POLICIES_INFO> policies_info; - LPSTR ev_policy_oid = NULL; - if (flags & VERIFY_EV_CERT) { - GetCertPoliciesInfo(cert_handle_, &policies_info); - if (policies_info.get()) { - EVRootCAMetadata* metadata = EVRootCAMetadata::GetInstance(); - for (DWORD i = 0; i < policies_info->cPolicyInfo; ++i) { - LPSTR policy_oid = policies_info->rgPolicyInfo[i].pszPolicyIdentifier; - if (metadata->IsEVPolicyOID(policy_oid)) { - ev_policy_oid = policy_oid; - chain_para.RequestedIssuancePolicy.dwType = USAGE_MATCH_TYPE_AND; - chain_para.RequestedIssuancePolicy.Usage.cUsageIdentifier = 1; - chain_para.RequestedIssuancePolicy.Usage.rgpszUsageIdentifier = - &ev_policy_oid; - break; - } - } - } - } - - // For non-test scenarios, use the default HCERTCHAINENGINE, NULL, which - // corresponds to HCCE_CURRENT_USER and is is initialized as needed by - // crypt32. However, when testing, it is necessary to create a new - // HCERTCHAINENGINE and use that instead. This is because each - // HCERTCHAINENGINE maintains a cache of information about certificates - // encountered, and each test run may modify the trust status of a - // certificate. - ScopedHCERTCHAINENGINE chain_engine(NULL); - if (TestRootCerts::HasInstance()) - chain_engine.reset(TestRootCerts::GetInstance()->GetChainEngine()); - - ScopedPCCERT_CONTEXT cert_list(CreateOSCertChainForCert()); - PCCERT_CHAIN_CONTEXT chain_context; - // IE passes a non-NULL pTime argument that specifies the current system - // time. IE passes CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT as the - // chain_flags argument. - if (!CertGetCertificateChain( - chain_engine, - cert_list.get(), - NULL, // current system time - cert_list->hCertStore, - &chain_para, - chain_flags, - NULL, // reserved - &chain_context)) { - verify_result->cert_status |= CERT_STATUS_INVALID; - return MapSecurityError(GetLastError()); - } - - if (chain_context->TrustStatus.dwErrorStatus & - CERT_TRUST_IS_NOT_VALID_FOR_USAGE) { - ev_policy_oid = NULL; - chain_para.RequestedIssuancePolicy.Usage.cUsageIdentifier = 0; - chain_para.RequestedIssuancePolicy.Usage.rgpszUsageIdentifier = NULL; - CertFreeCertificateChain(chain_context); - if (!CertGetCertificateChain( - chain_engine, - cert_list.get(), - NULL, // current system time - cert_list->hCertStore, - &chain_para, - chain_flags, - NULL, // reserved - &chain_context)) { - verify_result->cert_status |= CERT_STATUS_INVALID; - return MapSecurityError(GetLastError()); - } - } - - ScopedPCCERT_CHAIN_CONTEXT scoped_chain_context(chain_context); - - GetCertChainInfo(chain_context, verify_result); - verify_result->cert_status |= MapCertChainErrorStatusToCertStatus( - chain_context->TrustStatus.dwErrorStatus); - - // Flag certificates that have a Subject common name with a NULL character. - if (CertSubjectCommonNameHasNull(cert_handle_)) - verify_result->cert_status |= CERT_STATUS_INVALID; - - if (crl_set && !CheckRevocationWithCRLSet(chain_context, crl_set)) - verify_result->cert_status |= CERT_STATUS_REVOKED; - - std::wstring wstr_hostname = ASCIIToWide(hostname); - - SSL_EXTRA_CERT_CHAIN_POLICY_PARA extra_policy_para; - memset(&extra_policy_para, 0, sizeof(extra_policy_para)); - extra_policy_para.cbSize = sizeof(extra_policy_para); - extra_policy_para.dwAuthType = AUTHTYPE_SERVER; - extra_policy_para.fdwChecks = 0; - extra_policy_para.pwszServerName = - const_cast<wchar_t*>(wstr_hostname.c_str()); - - CERT_CHAIN_POLICY_PARA policy_para; - memset(&policy_para, 0, sizeof(policy_para)); - policy_para.cbSize = sizeof(policy_para); - policy_para.dwFlags = 0; - policy_para.pvExtraPolicyPara = &extra_policy_para; - - CERT_CHAIN_POLICY_STATUS policy_status; - memset(&policy_status, 0, sizeof(policy_status)); - policy_status.cbSize = sizeof(policy_status); - - if (!CertVerifyCertificateChainPolicy( - CERT_CHAIN_POLICY_SSL, - chain_context, - &policy_para, - &policy_status)) { - return MapSecurityError(GetLastError()); - } - - if (policy_status.dwError) { - verify_result->cert_status |= MapNetErrorToCertStatus( - MapSecurityError(policy_status.dwError)); - - // CertVerifyCertificateChainPolicy reports only one error (in - // policy_status.dwError) if the certificate has multiple errors. - // CertGetCertificateChain doesn't report certificate name mismatch, so - // CertVerifyCertificateChainPolicy is the only function that can report - // certificate name mismatch. - // - // To prevent a potential certificate name mismatch from being hidden by - // some other certificate error, if we get any other certificate error, - // we call CertVerifyCertificateChainPolicy again, ignoring all other - // certificate errors. Both extra_policy_para.fdwChecks and - // policy_para.dwFlags allow us to ignore certificate errors, so we set - // them both. - if (policy_status.dwError != CERT_E_CN_NO_MATCH) { - const DWORD extra_ignore_flags = - 0x00000080 | // SECURITY_FLAG_IGNORE_REVOCATION - 0x00000100 | // SECURITY_FLAG_IGNORE_UNKNOWN_CA - 0x00002000 | // SECURITY_FLAG_IGNORE_CERT_DATE_INVALID - 0x00000200; // SECURITY_FLAG_IGNORE_WRONG_USAGE - extra_policy_para.fdwChecks = extra_ignore_flags; - const DWORD ignore_flags = - CERT_CHAIN_POLICY_IGNORE_ALL_NOT_TIME_VALID_FLAGS | - CERT_CHAIN_POLICY_IGNORE_INVALID_BASIC_CONSTRAINTS_FLAG | - CERT_CHAIN_POLICY_ALLOW_UNKNOWN_CA_FLAG | - CERT_CHAIN_POLICY_IGNORE_WRONG_USAGE_FLAG | - CERT_CHAIN_POLICY_IGNORE_INVALID_NAME_FLAG | - CERT_CHAIN_POLICY_IGNORE_INVALID_POLICY_FLAG | - CERT_CHAIN_POLICY_IGNORE_ALL_REV_UNKNOWN_FLAGS | - CERT_CHAIN_POLICY_ALLOW_TESTROOT_FLAG | - CERT_CHAIN_POLICY_TRUST_TESTROOT_FLAG | - CERT_CHAIN_POLICY_IGNORE_NOT_SUPPORTED_CRITICAL_EXT_FLAG | - CERT_CHAIN_POLICY_IGNORE_PEER_TRUST_FLAG; - policy_para.dwFlags = ignore_flags; - if (!CertVerifyCertificateChainPolicy( - CERT_CHAIN_POLICY_SSL, - chain_context, - &policy_para, - &policy_status)) { - return MapSecurityError(GetLastError()); - } - if (policy_status.dwError) { - verify_result->cert_status |= MapNetErrorToCertStatus( - MapSecurityError(policy_status.dwError)); - } - } - } - - // TODO(wtc): Suppress CERT_STATUS_NO_REVOCATION_MECHANISM for now to be - // compatible with WinHTTP, which doesn't report this error (bug 3004). - verify_result->cert_status &= ~CERT_STATUS_NO_REVOCATION_MECHANISM; - - if ((flags & VERIFY_REV_CHECKING_ENABLED) == 0) { - // If we didn't do online revocation checking then Windows will report - // CERT_UNABLE_TO_CHECK_REVOCATION unless it had cached OCSP or CRL - // information for every certificate. We only want to put up revoked - // statuses from the offline checks so we squash this error. - verify_result->cert_status &= ~CERT_STATUS_UNABLE_TO_CHECK_REVOCATION; - } - - if (IsCertStatusError(verify_result->cert_status)) - return MapCertStatusToNetError(verify_result->cert_status); - - AppendPublicKeyHashes(chain_context, &verify_result->public_key_hashes); - verify_result->is_issued_by_known_root = IsIssuedByKnownRoot(chain_context); - - if (ev_policy_oid && - CheckEV(chain_context, rev_checking_enabled, ev_policy_oid)) { - verify_result->cert_status |= CERT_STATUS_IS_EV; - } - return OK; -} - // static bool X509Certificate::GetDEREncoded(X509Certificate::OSCertHandle cert_handle, std::string* encoded) { @@ -933,48 +283,6 @@ bool X509Certificate::GetDEREncoded(X509Certificate::OSCertHandle cert_handle, return true; } -// Returns true if the certificate is an extended-validation certificate. -// -// This function checks the certificatePolicies extensions of the -// certificates in the certificate chain according to Section 7 (pp. 11-12) -// of the EV Certificate Guidelines Version 1.0 at -// http://cabforum.org/EV_Certificate_Guidelines.pdf. -bool X509Certificate::CheckEV(PCCERT_CHAIN_CONTEXT chain_context, - bool rev_checking_enabled, - const char* policy_oid) const { - DCHECK_NE(static_cast<DWORD>(0), chain_context->cChain); - // If the cert doesn't match any of the policies, the - // CERT_TRUST_IS_NOT_VALID_FOR_USAGE bit (0x10) in - // chain_context->TrustStatus.dwErrorStatus is set. - DWORD error_status = chain_context->TrustStatus.dwErrorStatus; - - if (!rev_checking_enabled) { - // If online revocation checking is disabled then we will have still - // requested that the revocation cache be checked. However, that will often - // cause the following two error bits to be set. These error bits mean that - // the local OCSP/CRL is stale or missing entries for these certificates. - // Since they are expected, we mask them away. - error_status &= ~(CERT_TRUST_IS_OFFLINE_REVOCATION | - CERT_TRUST_REVOCATION_STATUS_UNKNOWN); - } - if (!chain_context->cChain || error_status != CERT_TRUST_NO_ERROR) - return false; - - // Check the end certificate simple chain (chain_context->rgpChain[0]). - // If the end certificate's certificatePolicies extension contains the - // EV policy OID of the root CA, return true. - PCERT_CHAIN_ELEMENT* element = chain_context->rgpChain[0]->rgpElement; - int num_elements = chain_context->rgpChain[0]->cElement; - if (num_elements < 2) - return false; - - // Look up the EV policy OID of the root CA. - PCCERT_CONTEXT root_cert = element[num_elements - 1]->pCertContext; - SHA1Fingerprint fingerprint = CalculateFingerprint(root_cert); - EVRootCAMetadata* metadata = EVRootCAMetadata::GetInstance(); - return metadata->HasEVPolicyOID(fingerprint, policy_oid); -} - // static bool X509Certificate::IsSameOSCert(X509Certificate::OSCertHandle a, X509Certificate::OSCertHandle b) { @@ -1018,7 +326,6 @@ X509Certificate::OSCertHandles X509Certificate::CreateOSCertHandlesFromBytes( return results; } - // static X509Certificate::OSCertHandle X509Certificate::DupOSCertHandle( OSCertHandle cert_handle) { diff --git a/net/base/x509_util_mac.cc b/net/base/x509_util_mac.cc new file mode 100644 index 0000000..5145467 --- /dev/null +++ b/net/base/x509_util_mac.cc @@ -0,0 +1,215 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/base/x509_util_mac.h" + +#include "base/logging.h" +#include "third_party/apple_apsl/cssmapplePriv.h" + +namespace net { + +namespace x509_util { + +namespace { + +// Creates a SecPolicyRef for the given OID, with optional value. +OSStatus CreatePolicy(const CSSM_OID* policy_oid, + void* option_data, + size_t option_length, + SecPolicyRef* policy) { + SecPolicySearchRef search; + OSStatus err = SecPolicySearchCreate(CSSM_CERT_X_509v3, policy_oid, NULL, + &search); + if (err) + return err; + err = SecPolicySearchCopyNext(search, policy); + CFRelease(search); + if (err) + return err; + + if (option_data) { + CSSM_DATA options_data = { + option_length, + reinterpret_cast<uint8_t*>(option_data) + }; + err = SecPolicySetValue(*policy, &options_data); + if (err) { + CFRelease(*policy); + return err; + } + } + return noErr; +} + +} // namespace + + +OSStatus CreateSSLClientPolicy(SecPolicyRef* policy) { + CSSM_APPLE_TP_SSL_OPTIONS tp_ssl_options; + memset(&tp_ssl_options, 0, sizeof(tp_ssl_options)); + tp_ssl_options.Version = CSSM_APPLE_TP_SSL_OPTS_VERSION; + tp_ssl_options.Flags |= CSSM_APPLE_TP_SSL_CLIENT; + + return CreatePolicy(&CSSMOID_APPLE_TP_SSL, &tp_ssl_options, + sizeof(tp_ssl_options), policy); +} + +OSStatus CreateSSLServerPolicy(const std::string& hostname, + SecPolicyRef* policy) { + CSSM_APPLE_TP_SSL_OPTIONS tp_ssl_options; + memset(&tp_ssl_options, 0, sizeof(tp_ssl_options)); + tp_ssl_options.Version = CSSM_APPLE_TP_SSL_OPTS_VERSION; + if (!hostname.empty()) { + tp_ssl_options.ServerName = hostname.data(); + tp_ssl_options.ServerNameLen = hostname.size(); + } + + return CreatePolicy(&CSSMOID_APPLE_TP_SSL, &tp_ssl_options, + sizeof(tp_ssl_options), policy); +} + +OSStatus CreateBasicX509Policy(SecPolicyRef* policy) { + return CreatePolicy(&CSSMOID_APPLE_X509_BASIC, NULL, 0, policy); +} + +OSStatus CreateRevocationPolicies(bool enable_revocation_checking, + CFMutableArrayRef policies) { + // In order to actually disable revocation checking, the SecTrustRef must + // have at least one revocation policy associated with it. If none are + // present, the Apple TP will add policies according to the system + // preferences, which will enable revocation checking even if the caller + // explicitly disabled it. An OCSP policy is used, rather than a CRL policy, + // because the Apple TP will force an OCSP policy to be present and enabled + // if it believes the certificate may chain to an EV root. By explicitly + // disabling network and OCSP cache access, then even if the Apple TP + // enables OCSP checking, no revocation checking will actually succeed. + CSSM_APPLE_TP_OCSP_OPTIONS tp_ocsp_options; + memset(&tp_ocsp_options, 0, sizeof(tp_ocsp_options)); + tp_ocsp_options.Version = CSSM_APPLE_TP_OCSP_OPTS_VERSION; + + if (enable_revocation_checking) { + // The default for the OCSP policy is to fetch responses via the network, + // unlike the CRL policy default. The policy is further modified to + // prefer OCSP over CRLs, if both are specified on the certificate. This + // is because an OCSP response is both sufficient and typically + // significantly smaller than the CRL counterpart. + tp_ocsp_options.Flags = CSSM_TP_ACTION_OCSP_SUFFICIENT; + } else { + // Effectively disable OCSP checking by making it impossible to get an + // OCSP response. Even if the Apple TP forces OCSP, no checking will + // be able to succeed. If this happens, the Apple TP will report an error + // that OCSP was unavailable, but this will be handled and suppressed in + // X509Certificate::Verify(). + tp_ocsp_options.Flags = CSSM_TP_ACTION_OCSP_DISABLE_NET | + CSSM_TP_ACTION_OCSP_CACHE_READ_DISABLE; + } + + SecPolicyRef ocsp_policy; + OSStatus status = CreatePolicy(&CSSMOID_APPLE_TP_REVOCATION_OCSP, + &tp_ocsp_options, sizeof(tp_ocsp_options), + &ocsp_policy); + if (status) + return status; + CFArrayAppendValue(policies, ocsp_policy); + CFRelease(ocsp_policy); + + if (enable_revocation_checking) { + CSSM_APPLE_TP_CRL_OPTIONS tp_crl_options; + memset(&tp_crl_options, 0, sizeof(tp_crl_options)); + tp_crl_options.Version = CSSM_APPLE_TP_CRL_OPTS_VERSION; + tp_crl_options.CrlFlags = CSSM_TP_ACTION_FETCH_CRL_FROM_NET; + + SecPolicyRef crl_policy; + status = CreatePolicy(&CSSMOID_APPLE_TP_REVOCATION_CRL, &tp_crl_options, + sizeof(tp_crl_options), &crl_policy); + if (status) + return status; + CFArrayAppendValue(policies, crl_policy); + CFRelease(crl_policy); + } + + return status; +} + +CSSMFieldValue::CSSMFieldValue() + : cl_handle_(NULL), + oid_(NULL), + field_(NULL) { +} +CSSMFieldValue::CSSMFieldValue(CSSM_CL_HANDLE cl_handle, + const CSSM_OID* oid, + CSSM_DATA_PTR field) + : cl_handle_(cl_handle), + oid_(const_cast<CSSM_OID_PTR>(oid)), + field_(field) { +} + +CSSMFieldValue::~CSSMFieldValue() { + Reset(NULL, NULL, NULL); +} + +void CSSMFieldValue::Reset(CSSM_CL_HANDLE cl_handle, + CSSM_OID_PTR oid, + CSSM_DATA_PTR field) { + if (cl_handle_ && oid_ && field_) + CSSM_CL_FreeFieldValue(cl_handle_, oid_, field_); + cl_handle_ = cl_handle; + oid_ = oid; + field_ = field; +} + +CSSMCachedCertificate::CSSMCachedCertificate() + : cl_handle_(NULL), + cached_cert_handle_(NULL) { +} +CSSMCachedCertificate::~CSSMCachedCertificate() { + if (cl_handle_ && cached_cert_handle_) + CSSM_CL_CertAbortCache(cl_handle_, cached_cert_handle_); +} + +OSStatus CSSMCachedCertificate::Init(SecCertificateRef os_cert_handle) { + DCHECK(!cl_handle_ && !cached_cert_handle_); + DCHECK(os_cert_handle); + CSSM_DATA cert_data; + OSStatus status = SecCertificateGetData(os_cert_handle, &cert_data); + if (status) + return status; + status = SecCertificateGetCLHandle(os_cert_handle, &cl_handle_); + if (status) { + DCHECK(!cl_handle_); + return status; + } + + status = CSSM_CL_CertCache(cl_handle_, &cert_data, &cached_cert_handle_); + if (status) + DCHECK(!cached_cert_handle_); + return status; +} + +OSStatus CSSMCachedCertificate::GetField(const CSSM_OID* field_oid, + CSSMFieldValue* field) const { + DCHECK(cl_handle_); + DCHECK(cached_cert_handle_); + + CSSM_OID_PTR oid = const_cast<CSSM_OID_PTR>(field_oid); + CSSM_DATA_PTR field_ptr = NULL; + CSSM_HANDLE results_handle = NULL; + uint32 field_value_count = 0; + CSSM_RETURN status = CSSM_CL_CertGetFirstCachedFieldValue( + cl_handle_, cached_cert_handle_, oid, &results_handle, + &field_value_count, &field_ptr); + if (status) + return status; + + // Note: |field_value_count| may be > 1, indicating that more than one + // value is present. This may happen with extensions, but for current + // usages, only the first value is returned. + CSSM_CL_CertAbortQuery(cl_handle_, results_handle); + field->Reset(cl_handle_, oid, field_ptr); + return CSSM_OK; +} + +} // namespace x509_util + +} // namespace net diff --git a/net/base/x509_util_mac.h b/net/base/x509_util_mac.h new file mode 100644 index 0000000..9b629cc --- /dev/null +++ b/net/base/x509_util_mac.h @@ -0,0 +1,131 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_BASE_X509_UTIL_MAC_H_ +#define NET_BASE_X509_UTIL_MAC_H_ + +#include <CoreFoundation/CFArray.h> +#include <Security/Security.h> + +#include <string> + +#include "base/basictypes.h" +#include "net/base/net_export.h" + +namespace net { + +namespace x509_util { + +// Creates a security policy for certificates used as client certificates +// in SSL. +// If a policy is successfully created, it will be stored in +// |*policy| and ownership transferred to the caller. +OSStatus NET_EXPORT CreateSSLClientPolicy(SecPolicyRef* policy); + +// Create an SSL server policy. While certificate name validation will be +// performed by SecTrustEvaluate(), it has the following limitations: +// - Doesn't support IP addresses in dotted-quad literals (127.0.0.1) +// - Doesn't support IPv6 addresses +// - Doesn't support the iPAddress subjectAltName +// Providing the hostname is necessary in order to locate certain user or +// system trust preferences, such as those created by Safari. Preferences +// created by Keychain Access do not share this requirement. +// On success, stores the resultant policy in |*policy| and returns noErr. +OSStatus NET_EXPORT CreateSSLServerPolicy(const std::string& hostname, + SecPolicyRef* policy); + +// Creates a security policy for basic X.509 validation. If the policy is +// successfully created, it will be stored in |*policy| and ownership +// transferred to the caller. +OSStatus NET_EXPORT CreateBasicX509Policy(SecPolicyRef* policy); + +// Creates security policies to control revocation checking (OCSP and CRL). +// If |enable_revocation_checking| is false, the policies returned will be +// explicitly disabled from accessing the network or the cache. This may be +// used to override system settings regarding revocation checking. +// If the policies are successfully created, they will be appended to +// |policies|. +OSStatus NET_EXPORT CreateRevocationPolicies(bool enable_revocation_checking, + CFMutableArrayRef policies); + +// Wrapper for a CSSM_DATA_PTR that was obtained via one of the CSSM field +// accessors (such as CSSM_CL_CertGet[First/Next]Value or +// CSSM_CL_CertGet[First/Next]CachedValue). +class CSSMFieldValue { + public: + CSSMFieldValue(); + CSSMFieldValue(CSSM_CL_HANDLE cl_handle, + const CSSM_OID* oid, + CSSM_DATA_PTR field); + ~CSSMFieldValue(); + + CSSM_OID_PTR oid() const { return oid_; } + CSSM_DATA_PTR field() const { return field_; } + + // Returns the field as if it was an arbitrary type - most commonly, by + // interpreting the field as a specific CSSM/CDSA parsed type, such as + // CSSM_X509_SUBJECT_PUBLIC_KEY_INFO or CSSM_X509_ALGORITHM_IDENTIFIER. + // An added check is applied to ensure that the current field is large + // enough to actually contain the requested type. + template <typename T> const T* GetAs() const { + if (!field_ || field_->Length < sizeof(T)) + return NULL; + return reinterpret_cast<const T*>(field_->Data); + } + + void Reset(CSSM_CL_HANDLE cl_handle, + CSSM_OID_PTR oid, + CSSM_DATA_PTR field); + + private: + CSSM_CL_HANDLE cl_handle_; + CSSM_OID_PTR oid_; + CSSM_DATA_PTR field_; + + DISALLOW_COPY_AND_ASSIGN(CSSMFieldValue); +}; + +// CSSMCachedCertificate is a container class that is used to wrap the +// CSSM_CL_CertCache APIs and provide safe and efficient access to +// certificate fields in their CSSM form. +// +// To provide efficient access to certificate/CRL fields, CSSM provides an +// API/SPI to "cache" a certificate/CRL. The exact meaning of a cached +// certificate is not defined by CSSM, but is documented to generally be some +// intermediate or parsed form of the certificate. In the case of Apple's +// CSSM CL implementation, the intermediate form is the parsed certificate +// stored in an internal format (which happens to be NSS). By caching the +// certificate, callers that wish to access multiple fields (such as subject, +// issuer, and validity dates) do not need to repeatedly parse the entire +// certificate, nor are they forced to convert all fields from their NSS types +// to their CSSM equivalents. This latter point is especially helpful when +// running on OS X 10.5, as it will fail to convert some fields that reference +// unsupported algorithms, such as ECC. +class CSSMCachedCertificate { + public: + CSSMCachedCertificate(); + ~CSSMCachedCertificate(); + + // Initializes the CSSMCachedCertificate by caching the specified + // |os_cert_handle|. On success, returns noErr. + // Note: Once initialized, the cached certificate should only be accessed + // from a single thread. + OSStatus Init(SecCertificateRef os_cert_handle); + + // Fetches the first value for the field associated with |field_oid|. + // If |field_oid| is a valid OID and is present in the current certificate, + // returns CSSM_OK and stores the first value in |field|. If additional + // values are associated with |field_oid|, they are ignored. + OSStatus GetField(const CSSM_OID* field_oid, CSSMFieldValue* field) const; + + private: + CSSM_CL_HANDLE cl_handle_; + CSSM_HANDLE cached_cert_handle_; +}; + +} // namespace x509_util + +} // namespace net + +#endif // NET_BASE_X509_UTIL_MAC_H_ diff --git a/net/net.gyp b/net/net.gyp index d6229a35..4fe5200 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -72,6 +72,14 @@ 'base/cert_verifier.h', 'base/cert_verify_proc.cc', 'base/cert_verify_proc.h', + 'base/cert_verify_proc_mac.cc', + 'base/cert_verify_proc_mac.h', + 'base/cert_verify_proc_nss.cc', + 'base/cert_verify_proc_nss.h', + 'base/cert_verify_proc_openssl.cc', + 'base/cert_verify_proc_openssl.h', + 'base/cert_verify_proc_win.cc', + 'base/cert_verify_proc_win.h', 'base/cert_verify_result.cc', 'base/cert_verify_result.h', 'base/completion_callback.h', @@ -269,6 +277,8 @@ 'base/x509_certificate_openssl.cc', 'base/x509_certificate_win.cc', 'base/x509_util.h', + 'base/x509_util_mac.cc', + 'base/x509_util_mac.h', 'base/x509_util_nss.cc', 'base/x509_util_nss.h', 'base/x509_util_openssl.cc', @@ -821,6 +831,8 @@ ['use_openssl==1', { 'sources!': [ 'base/cert_database_nss.cc', + 'base/cert_verify_proc_nss.cc', + 'base/cert_verify_proc_nss.h', 'base/crypto_module_nss.cc', 'base/dnssec_keyset.cc', 'base/dnssec_keyset.h', @@ -852,6 +864,8 @@ { # else !use_openssl: remove the unneeded files 'sources!': [ 'base/cert_database_openssl.cc', + 'base/cert_verify_proc_openssl.cc', + 'base/cert_verify_proc_openssl.h', 'base/crypto_module_openssl.cc', 'base/keygen_handler_openssl.cc', 'base/openssl_memory_private_key_store.cc', @@ -927,6 +941,12 @@ '../build/linux/system.gyp:gdk', ], }], + [ 'use_nss != 1', { + 'sources!': [ + 'base/cert_verify_proc_nss.cc', + 'base/cert_verify_proc_nss.h', + ], + }], [ 'OS == "win"', { 'sources!': [ 'http/http_auth_handler_ntlm_portable.cc', |