diff options
Diffstat (limited to 'net/base')
-rw-r--r-- | net/base/cert_verify_result.cc | 3 | ||||
-rw-r--r-- | net/base/cert_verify_result.h | 19 | ||||
-rw-r--r-- | net/base/x509_certificate.cc | 1 | ||||
-rw-r--r-- | net/base/x509_certificate_mac.cc | 17 | ||||
-rw-r--r-- | net/base/x509_certificate_nss.cc | 20 | ||||
-rw-r--r-- | net/base/x509_certificate_openssl.cc | 13 | ||||
-rw-r--r-- | net/base/x509_certificate_unittest.cc | 142 | ||||
-rw-r--r-- | net/base/x509_certificate_win.cc | 20 |
8 files changed, 229 insertions, 6 deletions
diff --git a/net/base/cert_verify_result.cc b/net/base/cert_verify_result.cc index 54e15f5..d51566b 100644 --- a/net/base/cert_verify_result.cc +++ b/net/base/cert_verify_result.cc @@ -4,6 +4,8 @@ #include "net/base/cert_verify_result.h" +#include "net/base/x509_certificate.h" + namespace net { CertVerifyResult::CertVerifyResult() { @@ -14,6 +16,7 @@ CertVerifyResult::~CertVerifyResult() { } void CertVerifyResult::Reset() { + verified_cert = NULL; cert_status = 0; has_md5 = false; has_md2 = false; diff --git a/net/base/cert_verify_result.h b/net/base/cert_verify_result.h index e4fb84a..2e4c250 100644 --- a/net/base/cert_verify_result.h +++ b/net/base/cert_verify_result.h @@ -9,12 +9,14 @@ #include <vector> #include "net/base/net_api.h" +#include "base/memory/ref_counted.h" #include "net/base/x509_cert_types.h" namespace net { -// The result of certificate verification. Eventually this may contain the -// certificate chain that was constructed during certificate verification. +class X509Certificate; + +// The result of certificate verification. class NET_API CertVerifyResult { public: CertVerifyResult(); @@ -22,7 +24,18 @@ class NET_API CertVerifyResult { void Reset(); - // Bitmask of CERT_STATUS_* from net/base/cert_status_flags.h + // The certificate and chain that was constructed during verification. + // Note that the though the verified certificate will match the originally + // supplied certificate, the intermediate certificates stored within may + // be substantially different. In the event of a verification failure, this + // will contain the chain as supplied by the server. This may be NULL if + // running within the sandbox. + scoped_refptr<X509Certificate> verified_cert; + + // Bitmask of CERT_STATUS_* from net/base/cert_status_flags.h. Note that + // these status flags apply to the certificate chain returned in + // |verified_cert|, rather than the originally supplied certificate + // chain. int cert_status; // Properties of the certificate chain. diff --git a/net/base/x509_certificate.cc b/net/base/x509_certificate.cc index 9fbf702..0c52de7 100644 --- a/net/base/x509_certificate.cc +++ b/net/base/x509_certificate.cc @@ -592,6 +592,7 @@ bool X509Certificate::VerifyHostname( int X509Certificate::Verify(const std::string& hostname, int flags, CertVerifyResult* verify_result) const { verify_result->Reset(); + verify_result->verified_cert = const_cast<X509Certificate*>(this); if (IsBlacklisted()) { verify_result->cert_status |= CERT_STATUS_REVOKED; diff --git a/net/base/x509_certificate_mac.cc b/net/base/x509_certificate_mac.cc index 88ff95f..ef69748 100644 --- a/net/base/x509_certificate_mac.cc +++ b/net/base/x509_certificate_mac.cc @@ -832,6 +832,23 @@ int X509Certificate::VerifyInternal(const std::string& hostname, return NetErrorFromOSStatus(status); ScopedCFTypeRef<CFArrayRef> scoped_completed_chain(completed_chain); + SecCertificateRef verified_cert = NULL; + std::vector<SecCertificateRef> verified_chain; + for (CFIndex i = 0, count = CFArrayGetCount(completed_chain); + i < count; ++i) { + SecCertificateRef chain_cert = reinterpret_cast<SecCertificateRef>( + const_cast<void*>(CFArrayGetValueAtIndex(completed_chain, i))); + if (i == 0) { + verified_cert = chain_cert; + } else { + verified_chain.push_back(chain_cert); + } + } + if (verified_cert) { + verify_result->verified_cert = CreateFromHandle(verified_cert, + verified_chain); + } + // Evaluate the results OSStatus cssm_result; switch (trust_result) { diff --git a/net/base/x509_certificate_nss.cc b/net/base/x509_certificate_nss.cc index 0162663..7224020 100644 --- a/net/base/x509_certificate_nss.cc +++ b/net/base/x509_certificate_nss.cc @@ -168,19 +168,27 @@ int MapCertErrorToCertStatus(int err) { // 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 and cert_list doesn't -// contain the root CA certificate. +// 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++) { + node = CERT_LIST_NEXT(node), ++i) { + if (i == 0) { + verified_cert = node->cert; + } else { + verified_chain.push_back(node->cert); + } SECAlgorithmID& signature = node->cert->signature; SECOidTag oid_tag = SECOID_FindOIDTag(&signature.algorithm); switch (oid_tag) { @@ -201,6 +209,11 @@ void GetCertChainInfo(CERTCertList* cert_list, 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 @@ -811,6 +824,7 @@ int X509Certificate::VerifyInternal(const std::string& hostname, } 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); diff --git a/net/base/x509_certificate_openssl.cc b/net/base/x509_certificate_openssl.cc index d3cad61..5416a76 100644 --- a/net/base/x509_certificate_openssl.cc +++ b/net/base/x509_certificate_openssl.cc @@ -462,8 +462,16 @@ int X509Certificate::VerifyInternal(const std::string& hostname, return MapCertStatusToNetError(verify_result->cert_status); STACK_OF(X509)* chain = X509_STORE_CTX_get_chain(ctx.get()); + 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(verified_cert); + } + DERCache der_cache; if (!GetDERAndCacheIfNeeded(cert, &der_cache)) continue; @@ -480,6 +488,11 @@ int X509Certificate::VerifyInternal(const std::string& hostname, verify_result->public_key_hashes.push_back(hash); } + if (verified_cert) { + verify_result->verified_cert = CreateFromHandle(verified_cert, + verified_chain); + } + // 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 diff --git a/net/base/x509_certificate_unittest.cc b/net/base/x509_certificate_unittest.cc index 7adad4f..32417ac 100644 --- a/net/base/x509_certificate_unittest.cc +++ b/net/base/x509_certificate_unittest.cc @@ -820,6 +820,148 @@ TEST(X509CertificateTest, IntermediateCertificates) { X509Certificate::FreeOSCertHandle(google_handle); } +// Basic test for returning the chain in CertVerifyResult. Note that the +// returned chain may just be a reflection of the originally supplied chain; +// that is, if any errors occur, the default chain returned is an exact copy +// of the certificate to be verified. The remaining VerifyReturn* tests are +// used to ensure that the actual, verified chain is being returned by +// Verify(). +TEST(X509CertificateTest, VerifyReturnChainBasic) { + FilePath certs_dir = GetTestCertsDirectory(); + CertificateList certs = CreateCertificateListFromFile( + certs_dir, "x509_verify_results.chain.pem", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(3U, certs.size()); + + X509Certificate::OSCertHandles intermediates; + intermediates.push_back(certs[1]->os_cert_handle()); + intermediates.push_back(certs[2]->os_cert_handle()); + + TestRootCerts::GetInstance()->Add(certs[2]); + + scoped_refptr<X509Certificate> google_full_chain = + X509Certificate::CreateFromHandle(certs[0]->os_cert_handle(), + intermediates); + ASSERT_NE(static_cast<X509Certificate*>(NULL), google_full_chain); + ASSERT_EQ(2U, google_full_chain->GetIntermediateCertificates().size()); + + CertVerifyResult verify_result; + EXPECT_EQ(static_cast<X509Certificate*>(NULL), verify_result.verified_cert); + int error = google_full_chain->Verify("127.0.0.1", 0, &verify_result); + EXPECT_EQ(OK, error); + ASSERT_NE(static_cast<X509Certificate*>(NULL), verify_result.verified_cert); + + EXPECT_NE(google_full_chain, verify_result.verified_cert); + EXPECT_TRUE(X509Certificate::IsSameOSCert( + google_full_chain->os_cert_handle(), + verify_result.verified_cert->os_cert_handle())); + const X509Certificate::OSCertHandles& return_intermediates = + verify_result.verified_cert->GetIntermediateCertificates(); + ASSERT_EQ(2U, return_intermediates.size()); + EXPECT_TRUE(X509Certificate::IsSameOSCert(return_intermediates[0], + certs[1]->os_cert_handle())); + EXPECT_TRUE(X509Certificate::IsSameOSCert(return_intermediates[1], + certs[2]->os_cert_handle())); + + TestRootCerts::GetInstance()->Clear(); +} + +// Test that the certificate returned in CertVerifyResult is able to reorder +// certificates that are not ordered from end-entity to root. While this is +// a protocol violation if sent during a TLS handshake, if multiple sources +// of intermediate certificates are combined, it's possible that order may +// not be maintained. +TEST(X509CertificateTest, VerifyReturnChainProperlyOrdered) { + FilePath certs_dir = GetTestCertsDirectory(); + CertificateList certs = CreateCertificateListFromFile( + certs_dir, "x509_verify_results.chain.pem", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(3U, certs.size()); + + // Construct the chain out of order. + X509Certificate::OSCertHandles intermediates; + intermediates.push_back(certs[2]->os_cert_handle()); + intermediates.push_back(certs[1]->os_cert_handle()); + + TestRootCerts::GetInstance()->Add(certs[2]); + + scoped_refptr<X509Certificate> google_full_chain = + X509Certificate::CreateFromHandle(certs[0]->os_cert_handle(), + intermediates); + ASSERT_NE(static_cast<X509Certificate*>(NULL), google_full_chain); + ASSERT_EQ(2U, google_full_chain->GetIntermediateCertificates().size()); + + CertVerifyResult verify_result; + EXPECT_EQ(static_cast<X509Certificate*>(NULL), verify_result.verified_cert); + int error = google_full_chain->Verify("127.0.0.1", 0, &verify_result); + EXPECT_EQ(OK, error); + ASSERT_NE(static_cast<X509Certificate*>(NULL), verify_result.verified_cert); + + EXPECT_NE(google_full_chain, verify_result.verified_cert); + EXPECT_TRUE(X509Certificate::IsSameOSCert( + google_full_chain->os_cert_handle(), + verify_result.verified_cert->os_cert_handle())); + const X509Certificate::OSCertHandles& return_intermediates = + verify_result.verified_cert->GetIntermediateCertificates(); + ASSERT_EQ(2U, return_intermediates.size()); + EXPECT_TRUE(X509Certificate::IsSameOSCert(return_intermediates[0], + certs[1]->os_cert_handle())); + EXPECT_TRUE(X509Certificate::IsSameOSCert(return_intermediates[1], + certs[2]->os_cert_handle())); + + TestRootCerts::GetInstance()->Clear(); +} + +// Test that Verify() filters out certificates which are not related to +// or part of the certificate chain being verified. +TEST(X509CertificateTest, VerifyReturnChainFiltersUnrelatedCerts) { + FilePath certs_dir = GetTestCertsDirectory(); + CertificateList certs = CreateCertificateListFromFile( + certs_dir, "x509_verify_results.chain.pem", + X509Certificate::FORMAT_AUTO); + ASSERT_EQ(3U, certs.size()); + TestRootCerts::GetInstance()->Add(certs[2]); + + scoped_refptr<X509Certificate> unrelated_dod_certificate = + ImportCertFromFile(certs_dir, "dod_ca_17_cert.der"); + scoped_refptr<X509Certificate> unrelated_dod_certificate2 = + ImportCertFromFile(certs_dir, "dod_root_ca_2_cert.der"); + ASSERT_NE(static_cast<X509Certificate*>(NULL), unrelated_dod_certificate); + ASSERT_NE(static_cast<X509Certificate*>(NULL), unrelated_dod_certificate2); + + // Interject unrelated certificates into the list of intermediates. + X509Certificate::OSCertHandles intermediates; + intermediates.push_back(unrelated_dod_certificate->os_cert_handle()); + intermediates.push_back(certs[1]->os_cert_handle()); + intermediates.push_back(unrelated_dod_certificate2->os_cert_handle()); + intermediates.push_back(certs[2]->os_cert_handle()); + + scoped_refptr<X509Certificate> google_full_chain = + X509Certificate::CreateFromHandle(certs[0]->os_cert_handle(), + intermediates); + ASSERT_NE(static_cast<X509Certificate*>(NULL), google_full_chain); + ASSERT_EQ(4U, google_full_chain->GetIntermediateCertificates().size()); + + CertVerifyResult verify_result; + EXPECT_EQ(static_cast<X509Certificate*>(NULL), verify_result.verified_cert); + int error = google_full_chain->Verify("127.0.0.1", 0, &verify_result); + EXPECT_EQ(OK, error); + ASSERT_NE(static_cast<X509Certificate*>(NULL), verify_result.verified_cert); + + EXPECT_NE(google_full_chain, verify_result.verified_cert); + EXPECT_TRUE(X509Certificate::IsSameOSCert( + google_full_chain->os_cert_handle(), + verify_result.verified_cert->os_cert_handle())); + const X509Certificate::OSCertHandles& return_intermediates = + verify_result.verified_cert->GetIntermediateCertificates(); + ASSERT_EQ(2U, return_intermediates.size()); + EXPECT_TRUE(X509Certificate::IsSameOSCert(return_intermediates[0], + certs[1]->os_cert_handle())); + EXPECT_TRUE(X509Certificate::IsSameOSCert(return_intermediates[1], + certs[2]->os_cert_handle())); + TestRootCerts::GetInstance()->Clear(); +} + #if defined(OS_MACOSX) TEST(X509CertificateTest, IsIssuedBy) { FilePath certs_dir = GetTestCertsDirectory(); diff --git a/net/base/x509_certificate_win.cc b/net/base/x509_certificate_win.cc index 306bd28..5dfc285 100644 --- a/net/base/x509_certificate_win.cc +++ b/net/base/x509_certificate_win.cc @@ -296,16 +296,28 @@ bool CertSubjectCommonNameHasNull(PCCERT_CONTEXT cert) { // 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; + // Each chain starts with the end entity certificate (i = 0) and ends with // the root CA certificate (i = num_elements - 1). Do not inspect the // signature algorithm of the root CA certificate because the signature on // the trust anchor is not important. for (int i = 0; i < num_elements - 1; ++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 @@ -322,6 +334,14 @@ void GetCertChainInfo(PCCERT_CHAIN_CONTEXT chain_context, verify_result->has_md4 = true; } } + + if (verified_cert) { + // Add the root certificate, if present, as it was not added above. + if (num_elements > 1) + verified_chain.push_back(element[num_elements - 1]->pCertContext); + verify_result->verified_cert = + X509Certificate::CreateFromHandle(verified_cert, verified_chain); + } } // Decodes the cert's certificatePolicies extension into a CERT_POLICIES_INFO |