summaryrefslogtreecommitdiffstats
path: root/net/base
diff options
context:
space:
mode:
authoragl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-12 16:26:26 +0000
committeragl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-04-12 16:26:26 +0000
commit2a89c4c490dc98307f502d698d7d738371a1b5c6 (patch)
tree865a7fd7300fa5b7eadba519c36357f576f8baa7 /net/base
parent60f36f9c86ca3e2203eadaf0db2dc4f3d7382c6b (diff)
downloadchromium_src-2a89c4c490dc98307f502d698d7d738371a1b5c6.zip
chromium_src-2a89c4c490dc98307f502d698d7d738371a1b5c6.tar.gz
chromium_src-2a89c4c490dc98307f502d698d7d738371a1b5c6.tar.bz2
net: add support for checking if a known public key is in a chain.
This is intended for use with future support for HSTS or CAA pinning of certificate chains. Since we build certificate chains from a pool of certificates, and certificates get reissued with updated expiries etc, only the public keys in the chain will always match. Thus we match on SHA1 hashes of SubjectPublicKeyInfos. BUG=none TEST=net_unittests Review URL: http://codereview.chromium.org/6821010 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@81259 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/base')
-rw-r--r--net/base/asn1_util.cc127
-rw-r--r--net/base/asn1_util.h55
-rw-r--r--net/base/cert_verify_result.h10
-rw-r--r--net/base/x509_certificate.h1
-rw-r--r--net/base/x509_certificate_mac.cc24
-rw-r--r--net/base/x509_certificate_nss.cc23
-rw-r--r--net/base/x509_certificate_unittest.cc68
-rw-r--r--net/base/x509_certificate_win.cc28
8 files changed, 335 insertions, 1 deletions
diff --git a/net/base/asn1_util.cc b/net/base/asn1_util.cc
new file mode 100644
index 0000000..701f53f
--- /dev/null
+++ b/net/base/asn1_util.cc
@@ -0,0 +1,127 @@
+// 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.
+
+#include "net/base/asn1_util.h"
+
+namespace net {
+
+namespace asn1 {
+
+bool ParseElement(base::StringPiece* in,
+ unsigned tag_value,
+ base::StringPiece* out,
+ unsigned *out_header_len) {
+ const uint8* data = reinterpret_cast<const uint8*>(in->data());
+
+ if (in->size() < 2)
+ return false;
+
+ if (static_cast<unsigned char>(data[0]) != tag_value)
+ return false;
+
+ size_t len = 0;
+ if ((data[1] & 0x80) == 0) {
+ // short form length
+ if (out_header_len)
+ *out_header_len = 2;
+ len = static_cast<size_t>(data[1]) + 2;
+ } else {
+ // long form length
+ const unsigned num_bytes = data[1] & 0x7f;
+ if (num_bytes == 0 || num_bytes > 2)
+ return false;
+ if (in->size() < 2 + num_bytes)
+ return false;
+ len = data[2];
+ if (num_bytes == 2) {
+ if (len == 0) {
+ // the length encoding must be minimal.
+ return false;
+ }
+ len <<= 8;
+ len += data[3];
+ }
+ if (len < 128) {
+ // the length should have been encoded in short form. This distinguishes
+ // DER from BER encoding.
+ return false;
+ }
+ if (out_header_len)
+ *out_header_len = 2 + num_bytes;
+ len += 2 + num_bytes;
+ }
+
+ if (in->size() < len)
+ return false;
+ if (out)
+ *out = base::StringPiece(in->data(), len);
+ in->remove_prefix(len);
+ return true;
+}
+
+bool GetElement(base::StringPiece* in,
+ unsigned tag_value,
+ base::StringPiece* out) {
+ unsigned header_len;
+ if (!ParseElement(in, tag_value, out, &header_len))
+ return false;
+ if (out)
+ out->remove_prefix(header_len);
+ return true;
+}
+
+bool ExtractSPKIFromDERCert(base::StringPiece cert,
+ base::StringPiece* spki_out) {
+ // From RFC 5280, section 4.1
+ // Certificate ::= SEQUENCE {
+ // tbsCertificate TBSCertificate,
+ // signatureAlgorithm AlgorithmIdentifier,
+ // signatureValue BIT STRING }
+
+ // TBSCertificate ::= SEQUENCE {
+ // version [0] EXPLICIT Version DEFAULT v1,
+ // serialNumber CertificateSerialNumber,
+ // signature AlgorithmIdentifier,
+ // issuer Name,
+ // validity Validity,
+ // subject Name,
+ // subjectPublicKeyInfo SubjectPublicKeyInfo,
+
+ base::StringPiece certificate;
+ if (!asn1::GetElement(&cert, asn1::kSEQUENCE, &certificate))
+ return false;
+
+ base::StringPiece tbs_certificate;
+ if (!asn1::GetElement(&certificate, asn1::kSEQUENCE, &tbs_certificate))
+ return false;
+
+ // The version is optional, so a failure to parse it is fine.
+ asn1::GetElement(&tbs_certificate,
+ asn1::kCompound | asn1::kContextSpecific | 0,
+ NULL);
+
+ // serialNumber
+ if (!asn1::GetElement(&tbs_certificate, asn1::kINTEGER, NULL))
+ return false;
+ // signature
+ if (!asn1::GetElement(&tbs_certificate, asn1::kSEQUENCE, NULL))
+ return false;
+ // issuer
+ if (!asn1::GetElement(&tbs_certificate, asn1::kSEQUENCE, NULL))
+ return false;
+ // validity
+ if (!asn1::GetElement(&tbs_certificate, asn1::kSEQUENCE, NULL))
+ return false;
+ // subject
+ if (!asn1::GetElement(&tbs_certificate, asn1::kSEQUENCE, NULL))
+ return false;
+ // subjectPublicKeyInfo
+ if (!asn1::ParseElement(&tbs_certificate, asn1::kSEQUENCE, spki_out, NULL))
+ return false;
+ return true;
+}
+
+} // namespace asn1
+
+} // namespace net
diff --git a/net/base/asn1_util.h b/net/base/asn1_util.h
new file mode 100644
index 0000000..507e79c
--- /dev/null
+++ b/net/base/asn1_util.h
@@ -0,0 +1,55 @@
+// 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.
+
+#ifndef NET_BASE_ASN1_UTIL_H_
+#define NET_BASE_ASN1_UTIL_H_
+#pragma once
+
+#include "base/string_piece.h"
+
+namespace net {
+
+namespace asn1 {
+
+// These are the DER encodings of the tag byte for ASN.1 objects.
+static const unsigned kINTEGER = 0x02;
+static const unsigned kOID = 0x06;
+static const unsigned kSEQUENCE = 0x30;
+
+// These are flags that can be ORed with the above tag numbers.
+static const unsigned kContextSpecific = 0x80;
+static const unsigned kCompound = 0x20;
+
+// ParseElement parses a DER encoded ASN1 element from |in|, requiring that
+// it have the given |tag_value|. It returns true on success. The following
+// limitations are imposed:
+// 1) tag numbers > 31 are not permitted.
+// 2) lengths > 65535 are not permitted.
+// On successful return:
+// |in| is advanced over the element
+// |out| contains the element, including the tag and length bytes.
+// |out_header_len| contains the length of the tag and length bytes in |out|.
+bool ParseElement(base::StringPiece* in,
+ unsigned tag_value,
+ base::StringPiece* out,
+ unsigned *out_header_len);
+
+// GetElement performs the same actions as ParseElement, except that the header
+// bytes are not included in the output.
+bool GetElement(base::StringPiece* in,
+ unsigned tag_value,
+ base::StringPiece* out);
+
+
+// ExtractSPKIFromDERCert parses the DER encoded certificate in |cert| and
+// extracts the bytes of the SubjectPublicKeyInfo. On successful return,
+// |spki_out| is set to contain the SPKI, pointing into |cert|.
+bool ExtractSPKIFromDERCert(base::StringPiece cert,
+ base::StringPiece* spki_out);
+
+} // namespace asn1
+
+} // namespace net
+
+#endif // NET_BASE_ASN1_UTIL_H_
diff --git a/net/base/cert_verify_result.h b/net/base/cert_verify_result.h
index 2939c3a..96b50e2 100644
--- a/net/base/cert_verify_result.h
+++ b/net/base/cert_verify_result.h
@@ -6,6 +6,10 @@
#define NET_BASE_CERT_VERIFY_RESULT_H_
#pragma once
+#include <vector>
+
+#include "net/base/x509_cert_types.h"
+
namespace net {
// The result of certificate verification. Eventually this may contain the
@@ -22,6 +26,7 @@ class CertVerifyResult {
has_md5_ca = false;
has_md2_ca = false;
is_issued_by_known_root = false;
+ public_key_hashes.clear();
}
// Bitmask of CERT_STATUS_* from net/base/cert_status_flags.h
@@ -34,6 +39,11 @@ class CertVerifyResult {
bool has_md5_ca;
bool has_md2_ca;
+ // If the certificate was successfully verified then this contains the SHA1
+ // fingerprints of the SubjectPublicKeyInfos of the chain. The fingerprint
+ // from the leaf certificate will be the first element of the vector.
+ std::vector<SHA1Fingerprint> public_key_hashes;
+
// 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
diff --git a/net/base/x509_certificate.h b/net/base/x509_certificate.h
index 986f867..a6896e9 100644
--- a/net/base/x509_certificate.h
+++ b/net/base/x509_certificate.h
@@ -37,6 +37,7 @@ struct CERTCertificateStr;
class Pickle;
namespace base {
+class StringPiece;
class RSAPrivateKey;
} // namespace base
diff --git a/net/base/x509_certificate_mac.cc b/net/base/x509_certificate_mac.cc
index fd3f665..3156e56 100644
--- a/net/base/x509_certificate_mac.cc
+++ b/net/base/x509_certificate_mac.cc
@@ -20,6 +20,7 @@
#include "base/pickle.h"
#include "base/sha1.h"
#include "base/sys_string_conversions.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"
@@ -495,6 +496,28 @@ private:
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);
+ }
+}
+
} // namespace
void X509Certificate::Initialize() {
@@ -924,6 +947,7 @@ int X509Certificate::Verify(const std::string& hostname, int flags,
}
}
+ AppendPublicKeyHashes(completed_chain, &verify_result->public_key_hashes);
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 a14690a..4b39f9e 100644
--- a/net/base/x509_certificate_nss.cc
+++ b/net/base/x509_certificate_nss.cc
@@ -611,6 +611,25 @@ 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));
+ }
+ hashes->push_back(CertPublicKeyHash(root_cert));
+}
+
} // namespace
void X509Certificate::Initialize() {
@@ -823,6 +842,10 @@ int X509Certificate::Verify(const std::string& hostname,
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);
diff --git a/net/base/x509_certificate_unittest.cc b/net/base/x509_certificate_unittest.cc
index c7dd8fb..47a931a 100644
--- a/net/base/x509_certificate_unittest.cc
+++ b/net/base/x509_certificate_unittest.cc
@@ -7,7 +7,9 @@
#include "base/file_util.h"
#include "base/path_service.h"
#include "base/pickle.h"
+#include "base/sha1.h"
#include "base/string_split.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"
@@ -503,13 +505,77 @@ TEST(X509CertificateTest, TestKnownRoot) {
int flags = 0;
CertVerifyResult verify_result;
// This is going to blow up in Feb 2012. Sorry! Disable and file a bug
- // against agl.
+ // against agl. Also see PublicKeyHashes in this file.
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);
}
+// This is the SHA1 hash of the SubjectPublicKeyInfo of nist.der.
+static const char nistSPKIHash[] =
+ "\x15\x60\xde\x65\x4e\x03\x9f\xd0\x08\x82"
+ "\xa9\x6a\xc4\x65\x8e\x6f\x92\x06\x84\x35";
+
+TEST(X509CertificateTest, ExtractSPKIFromDERCert) {
+ FilePath certs_dir = GetTestCertsDirectory();
+ scoped_refptr<X509Certificate> cert =
+ ImportCertFromFile(certs_dir, "nist.der");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), cert);
+
+ std::string derBytes;
+ EXPECT_TRUE(cert->GetDEREncoded(&derBytes));
+
+ base::StringPiece spkiBytes;
+ EXPECT_TRUE(asn1::ExtractSPKIFromDERCert(derBytes, &spkiBytes));
+
+ uint8 hash[base::SHA1_LENGTH];
+ base::SHA1HashBytes(reinterpret_cast<const uint8*>(spkiBytes.data()),
+ spkiBytes.size(), hash);
+
+ EXPECT_TRUE(0 == memcmp(hash, nistSPKIHash, sizeof(hash)));
+}
+
+TEST(X509CertificateTest, PublicKeyHashes) {
+ FilePath certs_dir = GetTestCertsDirectory();
+ // This is going to blow up in Feb 2012. Sorry! Disable and file a bug
+ // against agl. Also see TestKnownRoot in this file.
+ 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);
+
+ TestRootCerts::GetInstance()->Add(intermediate_cert.get());
+
+ 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;
+
+ int error = cert_chain->Verify("www.nist.gov", flags, &verify_result);
+ EXPECT_EQ(OK, error);
+ EXPECT_EQ(0, verify_result.cert_status);
+ ASSERT_LE(2u, verify_result.public_key_hashes.size());
+ EXPECT_TRUE(0 == memcmp(verify_result.public_key_hashes[0].data,
+ nistSPKIHash, base::SHA1_LENGTH));
+ EXPECT_TRUE(0 == memcmp(verify_result.public_key_hashes[1].data,
+ "\x83\x24\x42\x23\xd6\xcb\xf0\xa2\x6f\xc7"
+ "\xde\x27\xce\xbc\xa4\xbd\xa3\x26\x12\xad",
+ base::SHA1_LENGTH));
+
+ TestRootCerts::GetInstance()->Clear();
+}
+
// 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 3fb9a5c..f42fdc4 100644
--- a/net/base/x509_certificate_win.cc
+++ b/net/base/x509_certificate_win.cc
@@ -13,6 +13,7 @@
#include "base/string_tokenizer.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
+#include "net/base/asn1_util.h"
#include "net/base/cert_status_flags.h"
#include "net/base/cert_verify_result.h"
#include "net/base/ev_root_ca_metadata.h"
@@ -464,6 +465,32 @@ X509Certificate::OSCertHandles ParsePKCS7(const char* data, size_t length) {
return results;
}
+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() {
@@ -885,6 +912,7 @@ int X509Certificate::Verify(const std::string& hostname,
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, ev_policy_oid))