diff options
author | agl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-09-20 19:39:06 +0000 |
---|---|---|
committer | agl@chromium.org <agl@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-09-20 19:39:06 +0000 |
commit | ae780c8ff8cf7a6ee227beb8e7c6837f4933b02c (patch) | |
tree | b452938a0277732cd8b3c55a191458ce2baaf3d1 /net | |
parent | fe2255a16ad2c2ffb6390c1ec9d6b6bc0ae9a708 (diff) | |
download | chromium_src-ae780c8ff8cf7a6ee227beb8e7c6837f4933b02c.zip chromium_src-ae780c8ff8cf7a6ee227beb8e7c6837f4933b02c.tar.gz chromium_src-ae780c8ff8cf7a6ee227beb8e7c6837f4933b02c.tar.bz2 |
net: support side-pinning of public keys.
Side-pinning allows a site to pin to a public key that is both offline and not
a CA public key (without owning an intermediate CA themselves).
We do this by supporting a superfluous certificate in the chain which contains
a P256 public key and ECDSA signature over the leaf SPKI.
BUG=none
TEST=net_unittests
Review URL: http://codereview.chromium.org/7951005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@101993 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/base/transport_security_state.cc | 229 | ||||
-rw-r--r-- | net/base/transport_security_state.h | 9 | ||||
-rw-r--r-- | net/base/transport_security_state_unittest.cc | 106 | ||||
-rw-r--r-- | net/socket/ssl_client_socket_nss.cc | 57 | ||||
-rw-r--r-- | net/socket/ssl_client_socket_nss.h | 1 |
5 files changed, 402 insertions, 0 deletions
diff --git a/net/base/transport_security_state.cc b/net/base/transport_security_state.cc index 2a7fafa..601ab35 100644 --- a/net/base/transport_security_state.cc +++ b/net/base/transport_security_state.cc @@ -4,6 +4,21 @@ #include "net/base/transport_security_state.h" +#include <nspr.h> + +#include <cryptohi.h> +#include <hasht.h> +#include <keyhi.h> +#include <pk11pub.h> + +// NSS leaks #defines from its headers which will upset base/sha1.h. +#if defined(SHA1_LENGTH) +#undef SHA1_LENGTH +#endif +#if defined(SHA256_LENGTH) +#undef SHA256_LENGTH +#endif + #include "base/base64.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" @@ -275,6 +290,220 @@ bool TransportSecurityState::ParseHeader(const std::string& value, } } +// Side pinning and superfluous certificates: +// +// In SSLClientSocketNSS::DoVerifyCertComplete we look for certificates with a +// Subject of CN=meta. When we find one we'll currently try and parse side +// pinned key from it. +// +// A side pin is a key which can be pinned to, but also can be kept offline and +// still held by the site owner. The CN=meta certificate is just a backwards +// compatiable method of carrying a lump of bytes to the client. (We could use +// a TLS extension just as well, but it's a lot easier for admins to add extra +// certificates to the chain.) + +// A TagMap represents the simple key-value structure that we use. Keys are +// 32-bit ints. Values are byte strings. +typedef std::map<uint32, base::StringPiece> TagMap; + +// ParseTags parses a list of key-value pairs from |in| to |out| and advances +// |in| past the data. The key-value pair data is: +// u16le num_tags +// u32le tag[num_tags] +// u16le lengths[num_tags] +// ...data... +static bool ParseTags(base::StringPiece* in, TagMap *out) { + // Many part of Chrome already assume little-endian. This is just to help + // anyone who should try to port it in the future. +#if defined(__BYTE_ORDER) + // Linux check + COMPILE_ASSERT(__BYTE_ORDER == __LITTLE_ENDIAN, assumes_little_endian); +#elif defined(__BIG_ENDIAN__) + // Mac check + #error assumes little endian +#endif + + if (in->size() < sizeof(uint16)) + return false; + + uint16 num_tags_16; + memcpy(&num_tags_16, in->data(), sizeof(uint16)); + in->remove_prefix(sizeof(uint16)); + unsigned num_tags = num_tags_16; + + if (in->size() < 6 * num_tags) + return false; + + const uint32* tags = reinterpret_cast<const uint32*>(in->data()); + const uint16* lens = reinterpret_cast<const uint16*>( + in->data() + 4*num_tags); + in->remove_prefix(6*num_tags); + + uint32 prev_tag = 0; + for (unsigned i = 0; i < num_tags; i++) { + size_t len = lens[i]; + uint32 tag = tags[i]; + + if (in->size() < len) + return false; + // tags must be in ascending order. + if (i > 0 && prev_tag >= tag) + return false; + (*out)[tag] = base::StringPiece(in->data(), len); + in->remove_prefix(len); + prev_tag = tag; + } + + return true; +} + +// GetTag extracts the data associated with |tag| in |tags|. +static bool GetTag(uint32 tag, const TagMap& tags, base::StringPiece* out) { + TagMap::const_iterator i = tags.find(tag); + if (i == tags.end()) + return false; + + *out = i->second; + return true; +} + +// kP256SubjectPublicKeyInfoPrefix can be prepended onto a P256 elliptic curve +// point in X9.62 format in order to make a valid SubjectPublicKeyInfo. The +// ASN.1 interpretation of these bytes is: +// +// 0:d=0 hl=2 l= 89 cons: SEQUENCE +// 2:d=1 hl=2 l= 19 cons: SEQUENCE +// 4:d=2 hl=2 l= 7 prim: OBJECT :id-ecPublicKey +// 13:d=2 hl=2 l= 8 prim: OBJECT :prime256v1 +// 23:d=1 hl=2 l= 66 prim: BIT STRING +static const uint8 kP256SubjectPublicKeyInfoPrefix[] = { + 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, + 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, + 0x42, 0x00, +}; + +// DecodeX962P256PublicKey parses an uncompressed, X9.62 format, P256 elliptic +// curve point from |pubkey_bytes| and returns it as a SECKEYPublicKey. +static SECKEYPublicKey* DecodeX962P256PublicKey( + const base::StringPiece& pubkey_bytes) { + // The public key is an X9.62 encoded P256 point. + if (pubkey_bytes.size() != 1 + 2*32) + return NULL; + + std::string pubkey_spki( + reinterpret_cast<const char*>(kP256SubjectPublicKeyInfoPrefix), + sizeof(kP256SubjectPublicKeyInfoPrefix)); + pubkey_spki += pubkey_bytes.as_string(); + + SECItem der; + memset(&der, 0, sizeof(der)); + der.data = reinterpret_cast<uint8*>(const_cast<char*>(pubkey_spki.data())); + der.len = pubkey_spki.size(); + + CERTSubjectPublicKeyInfo* spki = SECKEY_DecodeDERSubjectPublicKeyInfo(&der); + if (!spki) + return NULL; + SECKEYPublicKey* public_key = SECKEY_ExtractPublicKey(spki); + SECKEY_DestroySubjectPublicKeyInfo(spki); + + return public_key; +} + +// These are the tag values that we use. Tags are little-endian on the wire and +// these values correspond to the ASCII of the name. +static const uint32 kTagALGO = 0x4f474c41; +static const uint32 kTagP256 = 0x36353250; +static const uint32 kTagPUBK = 0x4b425550; +static const uint32 kTagSIG = 0x474953; +static const uint32 kTagSPIN = 0x4e495053; + +// static +bool TransportSecurityState::ParseSidePin( + const base::StringPiece& leaf_spki, + const base::StringPiece& in_side_info, + std::vector<SHA1Fingerprint> *out_pub_key_hash) { + base::StringPiece side_info(in_side_info); + + TagMap outer; + if (!ParseTags(&side_info, &outer)) + return false; + // trailing data is not allowed + if (side_info.size()) + return false; + + base::StringPiece side_pin_bytes; + if (!GetTag(kTagSPIN, outer, &side_pin_bytes)) + return false; + + bool have_parsed_a_key = false; + uint8 leaf_spki_hash[crypto::SHA256_LENGTH]; + bool have_leaf_spki_hash = false; + + while (side_pin_bytes.size() > 0) { + TagMap side_pin; + if (!ParseTags(&side_pin_bytes, &side_pin)) + return false; + + base::StringPiece algo, pubkey, sig; + if (!GetTag(kTagALGO, side_pin, &algo) || + !GetTag(kTagPUBK, side_pin, &pubkey) || + !GetTag(kTagSIG, side_pin, &sig)) { + return false; + } + + if (algo.size() != sizeof(kTagP256) || + 0 != memcmp(algo.data(), &kTagP256, sizeof(kTagP256))) { + // We don't support anything but P256 at the moment. + continue; + } + + if (!have_leaf_spki_hash) { + crypto::SHA256HashString( + leaf_spki.as_string(), leaf_spki_hash, sizeof(leaf_spki_hash)); + have_leaf_spki_hash = true; + } + SECItem hashitem; + memset(&hashitem, 0, sizeof(hashitem)); + hashitem.data = leaf_spki_hash; + hashitem.len = sizeof(leaf_spki_hash); + + SECKEYPublicKey* secpubkey = DecodeX962P256PublicKey(pubkey); + if (!secpubkey) + continue; + + SECItem sigitem; + memset(&sigitem, 0, sizeof(sigitem)); + sigitem.data = reinterpret_cast<uint8*>(const_cast<char*>(sig.data())); + sigitem.len = sig.size(); + + // |decoded_sigitem| is newly allocated, as is the data that it points to. + SECItem* decoded_sigitem = DSAU_DecodeDerSigToLen( + &sigitem, SECKEY_SignatureLen(secpubkey)); + + if (!decoded_sigitem) { + SECKEY_DestroyPublicKey(secpubkey); + continue; + } + + SECStatus rv = PK11_Verify(secpubkey, decoded_sigitem, &hashitem, NULL); + SECKEY_DestroyPublicKey(secpubkey); + SECITEM_FreeItem(decoded_sigitem, PR_TRUE); + + if (rv == SECSuccess) { + SHA1Fingerprint fpr; + base::SHA1HashBytes( + reinterpret_cast<const uint8*>(pubkey.data()), + pubkey.size(), + fpr.data); + out_pub_key_hash->push_back(fpr); + have_parsed_a_key = true; + } + } + + return have_parsed_a_key; +} + void TransportSecurityState::SetDelegate( TransportSecurityState::Delegate* delegate) { delegate_ = delegate; diff --git a/net/base/transport_security_state.h b/net/base/transport_security_state.h index 513ae8b..73d7bde 100644 --- a/net/base/transport_security_state.h +++ b/net/base/transport_security_state.h @@ -111,6 +111,15 @@ class NET_EXPORT TransportSecurityState : int* max_age, bool* include_subdomains); + // ParseSidePin attempts to parse a side pin from |side_info| which signs the + // SubjectPublicKeyInfo in |leaf_spki|. A side pin is a way for a site to + // sign their public key with a key that is offline but still controlled by + // them. If successful, the hash of the public key used to sign |leaf_spki| + // is put into |out_pub_key_hash|. + static bool ParseSidePin(const base::StringPiece& leaf_spki, + const base::StringPiece& side_info, + std::vector<SHA1Fingerprint> *out_pub_key_hash); + class Delegate { public: // This function may not block and may be called with internal locks held. diff --git a/net/base/transport_security_state_unittest.cc b/net/base/transport_security_state_unittest.cc index 1068e05..263e29a 100644 --- a/net/base/transport_security_state_unittest.cc +++ b/net/base/transport_security_state_unittest.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/string_piece.h" +#include "crypto/nss_util.h" #include "net/base/transport_security_state.h" #include "testing/gtest/include/gtest/gtest.h" @@ -898,4 +900,108 @@ TEST_F(TransportSecurityStateTest, OverrideBuiltins) { EXPECT_TRUE(state->IsEnabledForHost(&domain_state, "www.google.com", true)); } +static const uint8 kSidePinLeafSPKI[] = { + 0x30, 0x5c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x01, 0x01, 0x05, 0x00, 0x03, 0x4b, 0x00, 0x30, 0x48, 0x02, 0x41, 0x00, 0xe4, + 0x1d, 0xcc, 0xf2, 0x92, 0xe7, 0x7a, 0xc6, 0x36, 0xf7, 0x1a, 0x62, 0x31, 0x7d, + 0x37, 0xea, 0x0d, 0xa2, 0xa8, 0x12, 0x2b, 0xc2, 0x1c, 0x82, 0x3e, 0xa5, 0x70, + 0x4a, 0x83, 0x5d, 0x9b, 0x84, 0x82, 0x70, 0xa4, 0x88, 0x98, 0x98, 0x41, 0x29, + 0x31, 0xcb, 0x6e, 0x2a, 0x54, 0x65, 0x14, 0x60, 0xcc, 0x00, 0xe8, 0x10, 0x30, + 0x0a, 0x4a, 0xd1, 0xa7, 0x52, 0xfe, 0x2d, 0x31, 0x2a, 0x1d, 0x0d, 0x02, 0x03, + 0x01, 0x00, 0x01, +}; + +static const uint8 kSidePinInfo[] = { + 0x01, 0x00, 0x53, 0x50, 0x49, 0x4e, 0xa0, 0x00, 0x03, 0x00, 0x53, 0x49, 0x47, + 0x00, 0x50, 0x55, 0x42, 0x4b, 0x41, 0x4c, 0x47, 0x4f, 0x47, 0x00, 0x41, 0x00, + 0x04, 0x00, 0x30, 0x45, 0x02, 0x21, 0x00, 0xfb, 0x26, 0xd5, 0xe8, 0x76, 0x35, + 0x96, 0x6d, 0x91, 0x9b, 0x5b, 0x27, 0xe6, 0x09, 0x1c, 0x7b, 0x6c, 0xcd, 0xc8, + 0x10, 0x25, 0x95, 0xc0, 0xa5, 0xf6, 0x6c, 0x6f, 0xfb, 0x59, 0x1e, 0x2d, 0xf4, + 0x02, 0x20, 0x33, 0x0a, 0xf8, 0x8b, 0x3e, 0xc4, 0xca, 0x75, 0x28, 0xdf, 0x5f, + 0xab, 0xe4, 0x46, 0xa0, 0xdd, 0x2d, 0xe5, 0xad, 0xc3, 0x81, 0x44, 0x70, 0xb2, + 0x10, 0x87, 0xe8, 0xc3, 0xd6, 0x6e, 0x12, 0x5d, 0x04, 0x67, 0x0b, 0x7d, 0xf2, + 0x99, 0x75, 0x57, 0x99, 0x3a, 0x98, 0xf8, 0xe4, 0xdf, 0x79, 0xdf, 0x8e, 0x02, + 0x2c, 0xbe, 0xd8, 0xfd, 0x75, 0x80, 0x18, 0xb1, 0x6f, 0x43, 0xd9, 0x8a, 0x79, + 0xc3, 0x6e, 0x18, 0xdf, 0x79, 0xc0, 0x59, 0xab, 0xd6, 0x77, 0x37, 0x6a, 0x94, + 0x5a, 0x7e, 0xfb, 0xa9, 0xc5, 0x54, 0x14, 0x3a, 0x7b, 0x97, 0x17, 0x2a, 0xb6, + 0x1e, 0x59, 0x4f, 0x2f, 0xb1, 0x15, 0x1a, 0x34, 0x50, 0x32, 0x35, 0x36, +}; + +static const uint8 kSidePinExpectedHash[20] = { + 0xb5, 0x91, 0x66, 0x47, 0x43, 0x16, 0x62, 0x86, 0xd4, 0x1e, 0x5d, 0x36, 0xe1, + 0xc4, 0x09, 0x3d, 0x2d, 0x1d, 0xea, 0x1e, +}; + +TEST_F(TransportSecurityStateTest, ParseSidePins) { + crypto::EnsureNSSInit(); + + base::StringPiece leaf_spki(reinterpret_cast<const char*>(kSidePinLeafSPKI), + sizeof(kSidePinLeafSPKI)); + base::StringPiece side_info(reinterpret_cast<const char*>(kSidePinInfo), + sizeof(kSidePinInfo)); + + std::vector<SHA1Fingerprint> pub_key_hashes; + EXPECT_TRUE(TransportSecurityState::ParseSidePin( + leaf_spki, side_info, &pub_key_hashes)); + ASSERT_EQ(1u, pub_key_hashes.size()); + EXPECT_TRUE(0 == memcmp(pub_key_hashes[0].data, kSidePinExpectedHash, + sizeof(kSidePinExpectedHash))); +} + +TEST_F(TransportSecurityStateTest, ParseSidePinsFailsWithBadData) { + crypto::EnsureNSSInit(); + + uint8 leaf_spki_copy[sizeof(kSidePinLeafSPKI)]; + memcpy(leaf_spki_copy, kSidePinLeafSPKI, sizeof(leaf_spki_copy)); + + uint8 side_info_copy[sizeof(kSidePinInfo)]; + memcpy(side_info_copy, kSidePinInfo, sizeof(kSidePinInfo)); + + base::StringPiece leaf_spki(reinterpret_cast<const char*>(leaf_spki_copy), + sizeof(leaf_spki_copy)); + base::StringPiece side_info(reinterpret_cast<const char*>(side_info_copy), + sizeof(side_info_copy)); + std::vector<SHA1Fingerprint> pub_key_hashes; + + // Tweak |leaf_spki| and expect a failure. + leaf_spki_copy[10] ^= 1; + EXPECT_FALSE(TransportSecurityState::ParseSidePin( + leaf_spki, side_info, &pub_key_hashes)); + ASSERT_EQ(0u, pub_key_hashes.size()); + + // Undo the change to |leaf_spki| and tweak |side_info|. + leaf_spki_copy[10] ^= 1; + side_info_copy[30] ^= 1; + EXPECT_FALSE(TransportSecurityState::ParseSidePin( + leaf_spki, side_info, &pub_key_hashes)); + ASSERT_EQ(0u, pub_key_hashes.size()); +} + +TEST_F(TransportSecurityStateTest, DISABLED_ParseSidePinsFuzz) { + // Disabled because it's too slow for normal tests. Run manually when + // changing the underlying code. + + crypto::EnsureNSSInit(); + + base::StringPiece leaf_spki(reinterpret_cast<const char*>(kSidePinLeafSPKI), + sizeof(kSidePinLeafSPKI)); + uint8 side_info_copy[sizeof(kSidePinInfo)]; + base::StringPiece side_info(reinterpret_cast<const char*>(side_info_copy), + sizeof(side_info_copy)); + std::vector<SHA1Fingerprint> pub_key_hashes; + static const size_t bit_length = sizeof(kSidePinInfo) * 8; + + for (size_t bit_to_flip = 0; bit_to_flip < bit_length; bit_to_flip++) { + memcpy(side_info_copy, kSidePinInfo, sizeof(kSidePinInfo)); + + size_t byte = bit_to_flip >> 3; + size_t bit = bit_to_flip & 7; + side_info_copy[byte] ^= (1 << bit); + + EXPECT_FALSE(TransportSecurityState::ParseSidePin( + leaf_spki, side_info, &pub_key_hashes)); + ASSERT_EQ(0u, pub_key_hashes.size()); + } +} + } // namespace net diff --git a/net/socket/ssl_client_socket_nss.cc b/net/socket/ssl_client_socket_nss.cc index 186cc86..879689a 100644 --- a/net/socket/ssl_client_socket_nss.cc +++ b/net/socket/ssl_client_socket_nss.cc @@ -76,12 +76,14 @@ #include "crypto/rsa_private_key.h" #include "crypto/scoped_nss_types.h" #include "net/base/address_list.h" +#include "net/base/asn1_util.h" #include "net/base/cert_status_flags.h" #include "net/base/cert_verifier.h" #include "net/base/connection_type_histograms.h" #include "net/base/dns_util.h" #include "net/base/dnsrr_resolver.h" #include "net/base/dnssec_chain_verifier.h" +#include "net/base/transport_security_state.h" #include "net/base/io_buffer.h" #include "net/base/net_errors.h" #include "net/base/net_log.h" @@ -488,6 +490,11 @@ void SSLClientSocketNSS::GetSSLInfo(SSLInfo* ssl_info) { ssl_info->cert = server_cert_; ssl_info->connection_status = ssl_connection_status_; ssl_info->public_key_hashes = server_cert_verify_result_->public_key_hashes; + for (std::vector<SHA1Fingerprint>::const_iterator + i = side_pinned_public_keys_.begin(); + i != side_pinned_public_keys_.end(); i++) { + ssl_info->public_key_hashes.push_back(*i); + } ssl_info->is_issued_by_known_root = server_cert_verify_result_->is_issued_by_known_root; @@ -1713,6 +1720,56 @@ int SSLClientSocketNSS::DoVerifyCertComplete(int result) { UMA_HISTOGRAM_TIMES("Net.SSLCertVerificationTimeError", verify_time); } + PeerCertificateChain chain(nss_fd_); + for (unsigned i = 1; i < chain.size(); i++) { + if (strcmp(chain[i]->subjectName, "CN=meta") != 0) + continue; + + base::StringPiece leaf_der( + reinterpret_cast<char*>(server_cert_nss_->derCert.data), + server_cert_nss_->derCert.len); + base::StringPiece leaf_spki; + if (!asn1::ExtractSPKIFromDERCert(leaf_der, &leaf_spki)) + break; + + static SECOidTag side_data_tag; + static bool side_data_tag_valid; + if (!side_data_tag_valid) { + // It's harmless if multiple threads enter this block concurrently. + static const uint8 kSideDataOID[] = + // 1.3.6.1.4.1.11129.2.1.4 + // (iso.org.dod.internet.private.enterprises.google.googleSecurity. + // certificateExtensions.sideData) + {0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x01, 0x05}; + SECOidData oid_data; + memset(&oid_data, 0, sizeof(oid_data)); + oid_data.oid.data = const_cast<uint8*>(kSideDataOID); + oid_data.oid.len = sizeof(kSideDataOID); + oid_data.desc = "Certificate side data"; + oid_data.supportedExtension = SUPPORTED_CERT_EXTENSION; + side_data_tag = SECOID_AddEntry(&oid_data); + DCHECK_NE(SEC_OID_UNKNOWN, side_data_tag); + side_data_tag_valid = true; + } + + SECItem side_data_item; + SECStatus rv = CERT_FindCertExtension(chain[i], + side_data_tag, &side_data_item); + if (rv != SECSuccess) + continue; + + base::StringPiece side_data( + reinterpret_cast<char*>(side_data_item.data), + side_data_item.len); + + if (!TransportSecurityState::ParseSidePin( + leaf_spki, side_data, &side_pinned_public_keys_)) { + LOG(WARNING) << "Side pinning data failed to parse: " + << host_and_port_.host(); + } + break; + } + // We used to remember the intermediate CA certs in the NSS database // persistently. However, NSS opens a connection to the SQLite database // during NSS initialization and doesn't close the connection until NSS diff --git a/net/socket/ssl_client_socket_nss.h b/net/socket/ssl_client_socket_nss.h index 88e8fde..0d0b342 100644 --- a/net/socket/ssl_client_socket_nss.h +++ b/net/socket/ssl_client_socket_nss.h @@ -219,6 +219,7 @@ class SSLClientSocketNSS : public SSLClientSocket { // we used an SSLHostInfo's verification. const CertVerifyResult* server_cert_verify_result_; CertVerifyResult local_server_cert_verify_result_; + std::vector<SHA1Fingerprint> side_pinned_public_keys_; int ssl_connection_status_; // Stores client authentication information between ClientAuthHandler and |