diff options
author | agl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-07 15:06:46 +0000 |
---|---|---|
committer | agl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-07 15:06:46 +0000 |
commit | 81502db60102ce82450113c755cba4987bd311ab (patch) | |
tree | d30fde473581469ea239230b1f821c6b2d9e4c78 | |
parent | fbb1bffa8523f50389815d80038949ce7af6c0c6 (diff) | |
download | chromium_src-81502db60102ce82450113c755cba4987bd311ab.zip chromium_src-81502db60102ce82450113c755cba4987bd311ab.tar.gz chromium_src-81502db60102ce82450113c755cba4987bd311ab.tar.bz2 |
net: add ability to distinguish user-added root CAs.
We have several places where a need to distinguish `real' root CAs from
user-added root CAs will be useful:
1) Monoscope wants to inspect correctly signed, but unknown certificates, but
doesn't want to deal with proxy MITM certificates.
2) HSTS is likely to add a method for pinning to a certificate, but we don't
want to break every proxy MITM with it.
This change adds several lists of known, `real' roots. These lists present an
ongoing maintainance issue. However, in the event that the lists are incomplete
in the future, we fail open. This is because roots not in these lists are
treated as user-added and user-added roots have more authority than `real'
roots.
In some sense, this is a problem because it might be a security issue that new
roots are given too much authority. On the other hand, we're not breaking
things when we're behind on updating the lists so the maintainance issue isn't
too pressing.
BUG=none
TEST=none
Review URL: http://codereview.chromium.org/6793041
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@80778 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | net/base/cert_verify_result.h | 9 | ||||
-rw-r--r-- | net/base/x509_certificate.cc | 19 | ||||
-rw-r--r-- | net/base/x509_certificate.h | 12 | ||||
-rw-r--r-- | net/base/x509_certificate_mac.cc | 20 | ||||
-rw-r--r-- | net/base/x509_certificate_nss.cc | 20 | ||||
-rw-r--r-- | net/base/x509_certificate_unittest.cc | 29 | ||||
-rw-r--r-- | net/base/x509_certificate_win.cc | 22 | ||||
-rw-r--r-- | net/data/ssl/certificates/nist_intermediate.der | bin | 0 -> 1520 bytes |
8 files changed, 126 insertions, 5 deletions
diff --git a/net/base/cert_verify_result.h b/net/base/cert_verify_result.h index faac17a..2939c3a 100644 --- a/net/base/cert_verify_result.h +++ b/net/base/cert_verify_result.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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 @@ class CertVerifyResult { has_md4 = false; has_md5_ca = false; has_md2_ca = false; + is_issued_by_known_root = false; } // Bitmask of CERT_STATUS_* from net/base/cert_status_flags.h @@ -32,6 +33,12 @@ class CertVerifyResult { bool has_md4; bool has_md5_ca; bool has_md2_ca; + + // is_issued_by_known_root is true if we recognise the root CA as a standard + // root. If it isn't then it's probably the case that this certificate was + // generated by a MITM proxy whose root has been installed locally. This is + // meaningless if the certificate was not trusted. + bool is_issued_by_known_root; }; } // namespace net diff --git a/net/base/x509_certificate.cc b/net/base/x509_certificate.cc index 089bf096..91ed346 100644 --- a/net/base/x509_certificate.cc +++ b/net/base/x509_certificate.cc @@ -4,6 +4,8 @@ #include "net/base/x509_certificate.h" +#include <stdlib.h> + #include <map> #include <string> #include <vector> @@ -12,6 +14,7 @@ #include "base/logging.h" #include "base/memory/singleton.h" #include "base/metrics/histogram.h" +#include "base/sha1.h" #include "base/string_piece.h" #include "base/string_util.h" #include "base/time.h" @@ -113,6 +116,12 @@ X509Certificate* X509CertificateCache::Find( return pos->second; }; +// CompareSHA1Hashes is a helper function for using bsearch() with an array of +// SHA1 hashes. +static int CompareSHA1Hashes(const void* a, const void* b) { + return memcmp(a, b, base::SHA1_LENGTH); +} + } // namespace bool X509Certificate::LessThan::operator()(X509Certificate* lhs, @@ -528,4 +537,14 @@ bool X509Certificate::IsBlacklisted() const { return false; } +// static +bool X509Certificate::IsSHA1HashInSortedArray(const SHA1Fingerprint& hash, + const uint8* array, + size_t array_byte_len) { + DCHECK_EQ(0u, array_byte_len % base::SHA1_LENGTH); + const unsigned arraylen = array_byte_len / base::SHA1_LENGTH; + return NULL != bsearch(hash.data, array, arraylen, base::SHA1_LENGTH, + CompareSHA1Hashes); +} + } // namespace net diff --git a/net/base/x509_certificate.h b/net/base/x509_certificate.h index 52bb9f1..986f867 100644 --- a/net/base/x509_certificate.h +++ b/net/base/x509_certificate.h @@ -167,7 +167,7 @@ class X509Certificate : public base::RefCountedThreadSafe<X509Certificate> { // An example: // CN=Michael Wong,O=FooBar Corporation,DC=foobar,DC=com // - // SECURUITY WARNING + // SECURITY WARNING // // Using self-signed certificates has the following security risks: // 1. Encryption without authentication and thus vulnerable to @@ -344,6 +344,10 @@ class X509Certificate : public base::RefCountedThreadSafe<X509Certificate> { #if defined(OS_WIN) bool CheckEV(PCCERT_CHAIN_CONTEXT chain_context, const char* policy_oid) const; + static bool IsIssuedByKnownRoot(PCCERT_CHAIN_CONTEXT chain_context); +#endif +#if defined(OS_MACOSX) + static bool IsIssuedByKnownRoot(CFArrayRef chain); #endif bool VerifyEV() const; @@ -378,6 +382,12 @@ class X509Certificate : public base::RefCountedThreadSafe<X509Certificate> { // IsBlacklisted returns true if this certificate is explicitly blacklisted. bool IsBlacklisted() const; + // 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); + // The subject of the certificate. CertPrincipal subject_; diff --git a/net/base/x509_certificate_mac.cc b/net/base/x509_certificate_mac.cc index 4cecb50..fd3f665 100644 --- a/net/base/x509_certificate_mac.cc +++ b/net/base/x509_certificate_mac.cc @@ -14,15 +14,17 @@ #include "base/crypto/rsa_private_key.h" #include "base/lazy_instance.h" #include "base/logging.h" +#include "base/mac/scoped_cftyperef.h" #include "base/memory/singleton.h" #include "base/nss_util.h" #include "base/pickle.h" -#include "base/mac/scoped_cftyperef.h" +#include "base/sha1.h" #include "base/sys_string_conversions.h" #include "net/base/cert_status_flags.h" #include "net/base/cert_verify_result.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/nss/mozilla/security/nss/lib/certdb/cert.h" using base::mac::ScopedCFTypeRef; @@ -514,6 +516,20 @@ void X509Certificate::Initialize() { serial_number_ = GetCertSerialNumber(cert_handle_); } +// 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::CreateFromPickle(const Pickle& pickle, void** pickle_iter) { @@ -908,6 +924,8 @@ int X509Certificate::Verify(const std::string& hostname, int flags, } } + verify_result->is_issued_by_known_root = IsIssuedByKnownRoot(completed_chain); + return OK; } diff --git a/net/base/x509_certificate_nss.cc b/net/base/x509_certificate_nss.cc index d71381a..a14690a 100644 --- a/net/base/x509_certificate_nss.cc +++ b/net/base/x509_certificate_nss.cc @@ -203,6 +203,18 @@ void GetCertChainInfo(CERTCertList* cert_list, } } +// 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->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"); +} + typedef char* (*CERTGetNameFunc)(CERTName* name); void ParsePrincipal(CERTName* name, @@ -769,11 +781,14 @@ int X509Certificate::Verify(const std::string& hostname, CERTValOutParam cvout[3]; int cvout_index = 0; - // We don't need the trust anchor for the first PKIXVerifyCert call. 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); @@ -808,6 +823,9 @@ int X509Certificate::Verify(const std::string& hostname, if (IsCertStatusError(verify_result->cert_status)) return MapCertStatusToNetError(verify_result->cert_status); + verify_result->is_issued_by_known_root = + IsKnownRoot(cvout[cvout_trust_anchor_index].value.pointer.cert); + if ((flags & VERIFY_EV_CERT) && VerifyEV()) verify_result->cert_status |= CERT_STATUS_IS_EV; return OK; diff --git a/net/base/x509_certificate_unittest.cc b/net/base/x509_certificate_unittest.cc index 53a131f..c7dd8fb 100644 --- a/net/base/x509_certificate_unittest.cc +++ b/net/base/x509_certificate_unittest.cc @@ -481,6 +481,35 @@ TEST(X509CertificateTest, IntermediateCARequireExplicitPolicy) { root_certs->Clear(); } +TEST(X509CertificateTest, TestKnownRoot) { + FilePath certs_dir = GetTestCertsDirectory(); + scoped_refptr<X509Certificate> cert = + ImportCertFromFile(certs_dir, "nist.der"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), cert); + + // This intermediate is only needed for old Linux machines. Modern NSS + // includes it as a root already. + scoped_refptr<X509Certificate> intermediate_cert = + ImportCertFromFile(certs_dir, "nist_intermediate.der"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert); + + X509Certificate::OSCertHandles intermediates; + intermediates.push_back(intermediate_cert->os_cert_handle()); + scoped_refptr<X509Certificate> cert_chain = + X509Certificate::CreateFromHandle(cert->os_cert_handle(), + X509Certificate::SOURCE_FROM_NETWORK, + intermediates); + + int flags = 0; + CertVerifyResult verify_result; + // This is going to blow up in Feb 2012. Sorry! Disable and file a bug + // against agl. + int error = cert_chain->Verify("www.nist.gov", flags, &verify_result); + EXPECT_EQ(OK, error); + EXPECT_EQ(0, verify_result.cert_status); + EXPECT_TRUE(verify_result.is_issued_by_known_root); +} + // A regression test for http://crbug.com/70293. // The Key Usage extension in this RSA SSL server certificate does not have // the keyEncipherment bit. diff --git a/net/base/x509_certificate_win.cc b/net/base/x509_certificate_win.cc index a9d6606..3fb9a5c 100644 --- a/net/base/x509_certificate_win.cc +++ b/net/base/x509_certificate_win.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -9,6 +9,7 @@ #include "base/lazy_instance.h" #include "base/logging.h" #include "base/pickle.h" +#include "base/sha1.h" #include "base/string_tokenizer.h" #include "base/string_util.h" #include "base/utf_string_conversions.h" @@ -18,6 +19,7 @@ #include "net/base/net_errors.h" #include "net/base/scoped_cert_chain_context.h" #include "net/base/test_root_certs.h" +#include "net/base/x509_certificate_known_roots_win.h" #pragma comment(lib, "crypt32.lib") @@ -504,6 +506,22 @@ void X509Certificate::Initialize() { serial_number_ = serial_number_.substr(1, serial_number_.size() - 1); } +// 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::CreateFromPickle(const Pickle& pickle, void** pickle_iter) { @@ -867,6 +885,8 @@ int X509Certificate::Verify(const std::string& hostname, if (IsCertStatusError(verify_result->cert_status)) return MapCertStatusToNetError(verify_result->cert_status); + verify_result->is_issued_by_known_root = IsIssuedByKnownRoot(chain_context); + if (ev_policy_oid && CheckEV(chain_context, ev_policy_oid)) verify_result->cert_status |= CERT_STATUS_IS_EV; return OK; diff --git a/net/data/ssl/certificates/nist_intermediate.der b/net/data/ssl/certificates/nist_intermediate.der Binary files differnew file mode 100644 index 0000000..55923a0 --- /dev/null +++ b/net/data/ssl/certificates/nist_intermediate.der |