diff options
-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 |