diff options
author | cevans@chromium.org <cevans@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-02 22:35:04 +0000 |
---|---|---|
committer | cevans@chromium.org <cevans@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-02 22:35:04 +0000 |
commit | c69c3a390eb15107d44fa134044b056f954bb0b2 (patch) | |
tree | 418e5c985b04449a6966027939d6c6d1655b03fb | |
parent | c14c5601ef318fc57df652e21537efcd1615f9d3 (diff) | |
download | chromium_src-c69c3a390eb15107d44fa134044b056f954bb0b2.zip chromium_src-c69c3a390eb15107d44fa134044b056f954bb0b2.tar.gz chromium_src-c69c3a390eb15107d44fa134044b056f954bb0b2.tar.bz2 |
Backport HSTS fixes to M16.
BUG=102710,102456
Review URL: http://codereview.chromium.org/8440054
git-svn-id: svn://svn.chromium.org/chrome/branches/912/src@108351 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | net/base/transport_security_state.cc | 302 | ||||
-rw-r--r-- | net/base/transport_security_state.h | 13 | ||||
-rw-r--r-- | net/base/transport_security_state_unittest.cc | 91 |
3 files changed, 288 insertions, 118 deletions
diff --git a/net/base/transport_security_state.cc b/net/base/transport_security_state.cc index b634cfc..22b6f9c 100644 --- a/net/base/transport_security_state.cc +++ b/net/base/transport_security_state.cc @@ -807,12 +807,20 @@ std::string TransportSecurityState::CanonicalizeHost(const std::string& host) { return new_host; } +// PublicKeyPins contains a number of SubjectPublicKeyInfo hashes for a site. +// The validated certificate chain for the site must not include any of +// |excluded_hashes| and must include one or more of |required_hashes|. +struct PublicKeyPins { + const char* const* required_hashes; + const char* const* excluded_hashes; +}; + struct HSTSPreload { uint8 length; bool include_subdomains; char dns_name[30]; bool https_required; - const char* const* required_hashes; + PublicKeyPins pins; }; static bool HasPreload(const struct HSTSPreload* entries, size_t num_entries, @@ -829,14 +837,22 @@ static bool HasPreload(const struct HSTSPreload* entries, size_t num_entries, *ret = true; if (!entries[j].https_required) out->mode = TransportSecurityState::DomainState::MODE_NONE; - if (entries[j].required_hashes) { - const char* const* hash = entries[j].required_hashes; + if (entries[j].pins.required_hashes) { + const char* const* hash = entries[j].pins.required_hashes; while (*hash) { bool ok = AddHash(*hash, &out->public_key_hashes); DCHECK(ok) << " failed to parse " << *hash; hash++; } } + if (entries[j].pins.excluded_hashes) { + const char* const* hash = entries[j].pins.excluded_hashes; + while (*hash) { + bool ok = AddHash(*hash, &out->bad_public_key_hashes); + DCHECK(ok) << " failed to parse " << *hash; + hash++; + } + } } return true; } @@ -844,6 +860,11 @@ static bool HasPreload(const struct HSTSPreload* entries, size_t num_entries, return false; } +// kNoRejectedPublicKeys is a placeholder for when no public keys are rejected. +static const char* const kNoRejectedPublicKeys[] = { + NULL, +}; + // These hashes are base64 encodings of SHA1 hashes for cert public keys. static const char kCertPKHashVerisignClass3[] = "sha1/4n972HfV354KP560yw4uqe/baXc="; @@ -855,6 +876,18 @@ static const char kCertPKHashGoogle2048[] = "sha1/AbkhxY0L343gKf+cki7NVWp+ozk="; static const char kCertPKHashEquifaxSecureCA[] = "sha1/SOZo+SvSspXXR9gjIBBPM5iQn9Q="; +static const char kCertPKHashAetna[] = + "sha1/klKqFN6/gK4wqtlOYDhwJKVDLxo="; +static const char kCertPKHashGeoTrustGlobal[] = + "sha1/wHqYaI2J+6sFZAwRfap9ZbjKzE4="; +static const char kCertPKHashGeoTrustPrimary[] = + "sha1/sBmJ5+/7Sq/LFI9YRjl2IkFQ4bo="; +static const char kCertPKHashIntel[] = + "sha1/DsYq91myCBCQJW/D3f2KZjEwK8U="; +static const char kCertPKHashTrustCenter[] = + "sha1/gzuEEAB/bkqdQS3EIjk2by7lW+k="; +static const char kCertPKHashVodaphone[] = + "sha1/DX/hXFUUNmiZ/EDWIgjvIuvRFRw="; static const char* const kGoogleAcceptableCerts[] = { kCertPKHashVerisignClass3, kCertPKHashVerisignClass3G3, @@ -863,9 +896,22 @@ static const char* const kGoogleAcceptableCerts[] = { kCertPKHashEquifaxSecureCA, NULL, }; +static const char* const kGoogleRejectedCerts[] = { + kCertPKHashAetna, + kCertPKHashGeoTrustGlobal, + kCertPKHashGeoTrustPrimary, + kCertPKHashIntel, + kCertPKHashTrustCenter, + kCertPKHashVodaphone, + NULL, +}; +static const PublicKeyPins kGooglePins = { + kGoogleAcceptableCerts, + kGoogleRejectedCerts, +}; static const char kCertRapidSSL[] = - "sha1/m9lHYJYke9k0GtVZ+bXSQYE8nDI="; + "sha1/o5OZxATDsgmwgcIfIWIneMJ0jkw="; static const char kCertDigiCertEVRoot[] = "sha1/gzF+YoVCU9bXeDGQ7JGQVumRueM="; static const char kCertTor1[] = @@ -882,6 +928,10 @@ static const char* const kTorAcceptableCerts[] = { kCertTor3, NULL, }; +static const PublicKeyPins kTorPins = { + kTorAcceptableCerts, + kNoRejectedPublicKeys, +}; static const char kCertVerisignClass1[] = "sha1/I0PRSKJViZuUfUYaeX7ATP7RcLc="; @@ -909,16 +959,12 @@ static const char kCertVerisignUniversal[] = static const char kCertTwitter1[] = "sha1/Vv7zwhR9TtOIN/29MFI4cgHld40="; -static const char kCertGeoTrustGlobal[] = - "sha1/wHqYaI2J+6sFZAwRfap9ZbjKzE4="; static const char kCertGeoTrustGlobal2[] = "sha1/cTg28gIxU0crbrplRqkQFVggBQk="; static const char kCertGeoTrustUniversal[] = "sha1/h+hbY1PGI6MSjLD/u/VR/lmADiI="; static const char kCertGeoTrustUniversal2[] = "sha1/Xk9ThoXdT57KX9wNRW99UbHcm3s="; -static const char kCertGeoTrustPrimary[] = - "sha1/sBmJ5+/7Sq/LFI9YRjl2IkFQ4bo="; static const char kCertGeoTrustPrimaryG2[] = "sha1/vb6nG6txV/nkddlU0rcngBqCJoI="; static const char kCertGeoTrustPrimaryG3[] = @@ -936,22 +982,34 @@ static const char* const kTwitterComAcceptableCerts[] = { kCertVerisignClass2_G2, kCertVerisignClass3_G5, kCertVerisignUniversal, - kCertGeoTrustGlobal, + kCertPKHashGeoTrustGlobal, kCertGeoTrustGlobal2, kCertGeoTrustUniversal, kCertGeoTrustUniversal2, - kCertGeoTrustPrimary, + kCertPKHashGeoTrustPrimary, kCertGeoTrustPrimaryG2, kCertGeoTrustPrimaryG3, kCertTwitter1, NULL, }; +static const PublicKeyPins kTwitterComPins = { + kTwitterComAcceptableCerts, + kNoRejectedPublicKeys, +}; // kTestAcceptableCerts doesn't actually match any public keys and is used // with "pinningtest.appspot.com", below, to test if pinning is active. static const char* const kTestAcceptableCerts[] = { "sha1/AAAAAAAAAAAAAAAAAAAAAAAAAAA=", }; +static const PublicKeyPins kTestPins = { + kTestAcceptableCerts, + kNoRejectedPublicKeys, +}; + +static const PublicKeyPins kNoPins = { + NULL, NULL, +}; #if defined(OS_CHROMEOS) static const bool kTwitterHSTS = true; @@ -963,134 +1021,133 @@ static const char* const kTestAcceptableCerts[] = { // slightly odd form removes the need for additional relocations records. static const struct HSTSPreload kPreloadedSTS[] = { // (*.)google.com, iff using SSL must use an acceptable certificate. - {12, true, "\006google\003com", false, kGoogleAcceptableCerts }, - {25, true, "\013pinningtest\007appspot\003com", false, - kTestAcceptableCerts }, + {12, true, "\006google\003com", false, kGooglePins }, + {25, true, "\013pinningtest\007appspot\003com", false, kTestPins }, // Now we force HTTPS for subtrees of google.com. - {19, true, "\006health\006google\003com", true, kGoogleAcceptableCerts }, - {21, true, "\010checkout\006google\003com", true, kGoogleAcceptableCerts }, - {19, true, "\006chrome\006google\003com", true, kGoogleAcceptableCerts }, - {17, true, "\004docs\006google\003com", true, kGoogleAcceptableCerts }, - {18, true, "\005sites\006google\003com", true, kGoogleAcceptableCerts }, + {19, true, "\006health\006google\003com", true, kGooglePins }, + {21, true, "\010checkout\006google\003com", true, kGooglePins }, + {19, true, "\006chrome\006google\003com", true, kGooglePins }, + {17, true, "\004docs\006google\003com", true, kGooglePins }, + {18, true, "\005sites\006google\003com", true, kGooglePins }, {25, true, "\014spreadsheets\006google\003com", true, - kGoogleAcceptableCerts }, + kGooglePins }, {22, false, "\011appengine\006google\003com", true, - kGoogleAcceptableCerts }, - {22, true, "\011encrypted\006google\003com", true, kGoogleAcceptableCerts }, - {21, true, "\010accounts\006google\003com", true, kGoogleAcceptableCerts }, - {21, true, "\010profiles\006google\003com", true, kGoogleAcceptableCerts }, - {17, true, "\004mail\006google\003com", true, kGoogleAcceptableCerts }, + kGooglePins }, + {22, true, "\011encrypted\006google\003com", true, kGooglePins }, + {21, true, "\010accounts\006google\003com", true, kGooglePins }, + {21, true, "\010profiles\006google\003com", true, kGooglePins }, + {17, true, "\004mail\006google\003com", true, kGooglePins }, {23, true, "\012talkgadget\006google\003com", true, - kGoogleAcceptableCerts }, - {17, true, "\004talk\006google\003com", true, kGoogleAcceptableCerts }, + kGooglePins }, + {17, true, "\004talk\006google\003com", true, kGooglePins }, {29, true, "\020hostedtalkgadget\006google\003com", true, - kGoogleAcceptableCerts }, - {17, true, "\004plus\006google\003com", true, kGoogleAcceptableCerts }, + kGooglePins }, + {17, true, "\004plus\006google\003com", true, kGooglePins }, // Other Google-related domains that must use HTTPS. - {20, true, "\006market\007android\003com", true, kGoogleAcceptableCerts }, + {20, true, "\006market\007android\003com", true, kGooglePins }, {26, true, "\003ssl\020google-analytics\003com", true, - kGoogleAcceptableCerts }, - {18, true, "\005drive\006google\003com", true, kGoogleAcceptableCerts }, - {16, true, "\012googleplex\003com", true, kGoogleAcceptableCerts }, + kGooglePins }, + {18, true, "\005drive\006google\003com", true, kGooglePins }, + {16, true, "\012googleplex\003com", true, kGooglePins }, // Other Google-related domains that must use an acceptable certificate // iff using SSL. - {11, true, "\005ytimg\003com", false, kGoogleAcceptableCerts }, - {23, true, "\021googleusercontent\003com", false, kGoogleAcceptableCerts }, - {13, true, "\007youtube\003com", false, kGoogleAcceptableCerts }, - {16, true, "\012googleapis\003com", false, kGoogleAcceptableCerts }, - {22, true, "\020googleadservices\003com", false, kGoogleAcceptableCerts }, - {16, true, "\012googlecode\003com", false, kGoogleAcceptableCerts }, - {13, true, "\007appspot\003com", false, kGoogleAcceptableCerts }, - {23, true, "\021googlesyndication\003com", false, kGoogleAcceptableCerts }, - {17, true, "\013doubleclick\003net", false, kGoogleAcceptableCerts }, - {17, true, "\003ssl\007gstatic\003com", false, kGoogleAcceptableCerts }, + {11, true, "\005ytimg\003com", false, kGooglePins }, + {23, true, "\021googleusercontent\003com", false, kGooglePins }, + {13, true, "\007youtube\003com", false, kGooglePins }, + {16, true, "\012googleapis\003com", false, kGooglePins }, + {22, true, "\020googleadservices\003com", false, kGooglePins }, + {16, true, "\012googlecode\003com", false, kGooglePins }, + {13, true, "\007appspot\003com", false, kGooglePins }, + {23, true, "\021googlesyndication\003com", false, kGooglePins }, + {17, true, "\013doubleclick\003net", false, kGooglePins }, + {17, true, "\003ssl\007gstatic\003com", false, kGooglePins }, // Exclude the learn.doubleclick.net subdomain because it uses a different // CA. - {23, true, "\005learn\013doubleclick\003net", false, 0 }, + {23, true, "\005learn\013doubleclick\003net", false, kNoPins }, // Now we force HTTPS for other sites that have requested it. - {16, false, "\003www\006paypal\003com", true, 0 }, - {16, false, "\003www\006elanex\003biz", true, 0 }, - {12, true, "\006jottit\003com", true, 0 }, - {19, true, "\015sunshinepress\003org", true, 0 }, - {21, false, "\003www\013noisebridge\003net", true, 0 }, - {10, false, "\004neg9\003org", true, 0 }, - {12, true, "\006riseup\003net", true, 0 }, - {11, false, "\006factor\002cc", true, 0 }, - {22, false, "\007members\010mayfirst\003org", true, 0 }, - {22, false, "\007support\010mayfirst\003org", true, 0 }, - {17, false, "\002id\010mayfirst\003org", true, 0 }, - {20, false, "\005lists\010mayfirst\003org", true, 0 }, - {19, true, "\015splendidbacon\003com", true, 0 }, - {28, false, "\016aladdinschools\007appspot\003com", true, 0 }, - {14, true, "\011ottospora\002nl", true, 0 }, - {25, false, "\003www\017paycheckrecords\003com", true, 0 }, - {14, false, "\010lastpass\003com", true, 0 }, - {18, false, "\003www\010lastpass\003com", true, 0 }, - {14, true, "\010keyerror\003com", true, 0 }, - {13, false, "\010entropia\002de", true, 0 }, - {17, false, "\003www\010entropia\002de", true, 0 }, - {11, true, "\005romab\003com", true, 0 }, - {16, false, "\012logentries\003com", true, 0 }, - {20, false, "\003www\012logentries\003com", true, 0 }, - {12, true, "\006stripe\003com", true, 0 }, - {27, true, "\025cloudsecurityalliance\003org", true, 0 }, - {15, true, "\005login\004sapo\002pt", true, 0 }, - {19, true, "\015mattmccutchen\003net", true, 0 }, - {11, true, "\006betnet\002fr", true, 0 }, - {13, true, "\010uprotect\002it", true, 0 }, - {14, false, "\010squareup\003com", true, 0 }, - {9, true, "\004cert\002se", true, 0 }, - {11, true, "\006crypto\002is", true, 0 }, - {20, true, "\005simon\007butcher\004name", true, 0 }, - {10, true, "\004linx\003net", true, 0 }, - {13, false, "\007dropcam\003com", true, 0 }, - {17, false, "\003www\007dropcam\003com", true, 0 }, - {30, true, "\010ebanking\014indovinabank\003com\002vn", true, 0 }, - {13, false, "\007epoxate\003com", true, 0 }, - {16, false, "\012torproject\003org", true, kTorAcceptableCerts }, - {21, true, "\004blog\012torproject\003org", true, kTorAcceptableCerts }, - {22, true, "\005check\012torproject\003org", true, kTorAcceptableCerts }, - {20, true, "\003www\012torproject\003org", true, kTorAcceptableCerts }, - {22, true, "\003www\014moneybookers\003com", true, 0 }, - {17, false, "\013ledgerscope\003net", true, 0 }, - {21, false, "\003www\013ledgerscope\003net", true, 0 }, - {10, false, "\004kyps\003net", true, 0 }, - {14, false, "\003www\004kyps\003net", true, 0 }, - {17, true, "\003app\007recurly\003com", true, 0 }, - {17, true, "\003api\007recurly\003com", true, 0 }, - {13, false, "\007greplin\003com", true, 0 }, - {17, false, "\003www\007greplin\003com", true, 0 }, - {27, true, "\006luneta\016nearbuysystems\003com", true, 0 }, - {12, true, "\006ubertt\003org", true, 0 }, - - {13, false, "\007twitter\003com", kTwitterHSTS, kTwitterComAcceptableCerts }, - {17, true, "\003www\007twitter\003com", kTwitterHSTS, kTwitterComAcceptableCerts }, - {17, true, "\003api\007twitter\003com", kTwitterHSTS, kTwitterComAcceptableCerts }, - {19, true, "\005oauth\007twitter\003com", kTwitterHSTS, kTwitterComAcceptableCerts }, - {20, true, "\006mobile\007twitter\003com", kTwitterHSTS, kTwitterComAcceptableCerts }, - {17, true, "\003dev\007twitter\003com", kTwitterHSTS, kTwitterComAcceptableCerts }, - {22, true, "\010business\007twitter\003com", kTwitterHSTS, kTwitterComAcceptableCerts }, + {16, false, "\003www\006paypal\003com", true, kNoPins }, + {16, false, "\003www\006elanex\003biz", true, kNoPins }, + {12, true, "\006jottit\003com", true, kNoPins }, + {19, true, "\015sunshinepress\003org", true, kNoPins }, + {21, false, "\003www\013noisebridge\003net", true, kNoPins }, + {10, false, "\004neg9\003org", true, kNoPins }, + {12, true, "\006riseup\003net", true, kNoPins }, + {11, false, "\006factor\002cc", true, kNoPins }, + {22, false, "\007members\010mayfirst\003org", true, kNoPins }, + {22, false, "\007support\010mayfirst\003org", true, kNoPins }, + {17, false, "\002id\010mayfirst\003org", true, kNoPins }, + {20, false, "\005lists\010mayfirst\003org", true, kNoPins }, + {19, true, "\015splendidbacon\003com", true, kNoPins }, + {28, false, "\016aladdinschools\007appspot\003com", true, kNoPins }, + {14, true, "\011ottospora\002nl", true, kNoPins }, + {25, false, "\003www\017paycheckrecords\003com", true, kNoPins }, + {14, false, "\010lastpass\003com", true, kNoPins }, + {18, false, "\003www\010lastpass\003com", true, kNoPins }, + {14, true, "\010keyerror\003com", true, kNoPins }, + {13, false, "\010entropia\002de", true, kNoPins }, + {17, false, "\003www\010entropia\002de", true, kNoPins }, + {11, true, "\005romab\003com", true, kNoPins }, + {16, false, "\012logentries\003com", true, kNoPins }, + {20, false, "\003www\012logentries\003com", true, kNoPins }, + {12, true, "\006stripe\003com", true, kNoPins }, + {27, true, "\025cloudsecurityalliance\003org", true, kNoPins }, + {15, true, "\005login\004sapo\002pt", true, kNoPins }, + {19, true, "\015mattmccutchen\003net", true, kNoPins }, + {11, true, "\006betnet\002fr", true, kNoPins }, + {13, true, "\010uprotect\002it", true, kNoPins }, + {14, false, "\010squareup\003com", true, kNoPins }, + {9, true, "\004cert\002se", true, kNoPins }, + {11, true, "\006crypto\002is", true, kNoPins }, + {20, true, "\005simon\007butcher\004name", true, kNoPins }, + {10, true, "\004linx\003net", true, kNoPins }, + {13, false, "\007dropcam\003com", true, kNoPins }, + {17, false, "\003www\007dropcam\003com", true, kNoPins }, + {30, true, "\010ebanking\014indovinabank\003com\002vn", true, kNoPins }, + {13, false, "\007epoxate\003com", true, kNoPins }, + {16, false, "\012torproject\003org", true, kTorPins }, + {21, true, "\004blog\012torproject\003org", true, kTorPins }, + {22, true, "\005check\012torproject\003org", true, kTorPins }, + {20, true, "\003www\012torproject\003org", true, kTorPins }, + {22, true, "\003www\014moneybookers\003com", true, kNoPins }, + {17, false, "\013ledgerscope\003net", true, kNoPins }, + {21, false, "\003www\013ledgerscope\003net", true, kNoPins }, + {10, false, "\004kyps\003net", true, kNoPins }, + {14, false, "\003www\004kyps\003net", true, kNoPins }, + {17, true, "\003app\007recurly\003com", true, kNoPins }, + {17, true, "\003api\007recurly\003com", true, kNoPins }, + {13, false, "\007greplin\003com", true, kNoPins }, + {17, false, "\003www\007greplin\003com", true, kNoPins }, + {27, true, "\006luneta\016nearbuysystems\003com", true, kNoPins }, + {12, true, "\006ubertt\003org", true, kNoPins }, + + {13, false, "\007twitter\003com", kTwitterHSTS, kTwitterComPins }, + {17, true, "\003www\007twitter\003com", kTwitterHSTS, kTwitterComPins }, + {17, true, "\003api\007twitter\003com", kTwitterHSTS, kTwitterComPins }, + {19, true, "\005oauth\007twitter\003com", kTwitterHSTS, kTwitterComPins }, + {20, true, "\006mobile\007twitter\003com", kTwitterHSTS, kTwitterComPins }, + {17, true, "\003dev\007twitter\003com", kTwitterHSTS, kTwitterComPins }, + {22, true, "\010business\007twitter\003com", kTwitterHSTS, kTwitterComPins }, #if 0 // Twitter CDN pins disabled in order to track down pinning failures --agl - {22, true, "\010platform\007twitter\003com", false, kTwitterCDNAcceptableCerts }, - {15, true, "\003si0\005twimg\003com", false, kTwitterCDNAcceptableCerts }, - {23, true, "\010twimg0-a\010akamaihd\003net", false, kTwitterCDNAcceptableCerts }, + {22, true, "\010platform\007twitter\003com", false, kTwitterCDNPins }, + {15, true, "\003si0\005twimg\003com", false, kTwitterCDNPins }, + {23, true, "\010twimg0-a\010akamaihd\003net", false, kTwitterCDNPins }, #endif }; static const size_t kNumPreloadedSTS = ARRAYSIZE_UNSAFE(kPreloadedSTS); static const struct HSTSPreload kPreloadedSNISTS[] = { // These SNI-only domains must always use HTTPS. - {11, false, "\005gmail\003com", true, kGoogleAcceptableCerts }, - {16, false, "\012googlemail\003com", true, kGoogleAcceptableCerts }, - {15, false, "\003www\005gmail\003com", true, kGoogleAcceptableCerts }, - {20, false, "\003www\012googlemail\003com", true, kGoogleAcceptableCerts }, + {11, false, "\005gmail\003com", true, kGooglePins }, + {16, false, "\012googlemail\003com", true, kGooglePins }, + {15, false, "\003www\005gmail\003com", true, kGooglePins }, + {20, false, "\003www\012googlemail\003com", true, kGooglePins }, // These SNI-only domains must use an acceptable certificate iff using // HTTPS. - {22, true, "\020google-analytics\003com", false, kGoogleAcceptableCerts }, + {22, true, "\020google-analytics\003com", false, kGooglePins }, // www. requires SNI. - {18, true, "\014googlegroups\003com", false, kGoogleAcceptableCerts }, + {18, true, "\014googlegroups\003com", false, kGooglePins }, }; static const size_t kNumPreloadedSNISTS = ARRAYSIZE_UNSAFE(kPreloadedSNISTS); @@ -1112,7 +1169,7 @@ static bool ScanForHostAndCerts( if (entry.length == canonicalized_host.size() - i && memcmp(entry.dns_name, &canonicalized_host[i], entry.length) == 0) { - hit = entry.required_hashes == certs; + hit = entry.pins.required_hashes == certs; // Return immediately upon exact match: if (i == 0) return hit; @@ -1209,6 +1266,21 @@ TransportSecurityState::DomainState::~DomainState() { bool TransportSecurityState::DomainState::IsChainOfPublicKeysPermitted( const std::vector<net::SHA1Fingerprint>& hashes) { + for (std::vector<net::SHA1Fingerprint>::const_iterator + i = hashes.begin(); i != hashes.end(); ++i) { + for (std::vector<net::SHA1Fingerprint>::const_iterator + j = bad_public_key_hashes.begin(); j != bad_public_key_hashes.end(); + ++j) { + if (i->Equals(*j)) { + LOG(ERROR) << "Rejecting public key chain for domain " << domain + << ". Validated chain: " << HashesToBase64String(hashes) + << ", matches one or more bad hashes: " + << HashesToBase64String(bad_public_key_hashes); + return false; + } + } + } + if (public_key_hashes.empty()) return true; diff --git a/net/base/transport_security_state.h b/net/base/transport_security_state.h index 6832daf..8a2ef36 100644 --- a/net/base/transport_security_state.h +++ b/net/base/transport_security_state.h @@ -56,8 +56,16 @@ class NET_EXPORT TransportSecurityState // IsChainOfPublicKeysPermitted takes a set of public key hashes and // returns true if: - // 1) |public_key_hashes| is empty, i.e. no public keys have been pinned. - // 2) |hashes| and |public_key_hashes| are not disjoint. + // 1) None of the hashes are in |bad_public_key_hashes| AND + // 2) |public_key_hashes| is empty, i.e. no public keys have been pinned. + // OR + // 3) |hashes| and |public_key_hashes| are not disjoint. + // + // |public_key_hashes| is intended to contain a number of trust roots for + // the chain in question, any one of which is sufficient. + // |bad_public_key_hashes| is intended to contain unwanted intermediate CA + // certifciates that those trust roots may have issued but that we don't + // want to trust. bool IsChainOfPublicKeysPermitted( const std::vector<SHA1Fingerprint>& hashes); @@ -66,6 +74,7 @@ class NET_EXPORT TransportSecurityState base::Time expiry; // the absolute time (UTC) when this record expires bool include_subdomains; // subdomains included? std::vector<SHA1Fingerprint> public_key_hashes; // optional; permitted keys + std::vector<SHA1Fingerprint> bad_public_key_hashes; // optional;rejectd keys // The follow members are not valid when stored in |enabled_hosts_|. bool preloaded; // is this a preloaded entry? diff --git a/net/base/transport_security_state_unittest.cc b/net/base/transport_security_state_unittest.cc index d9337a9..7a603be 100644 --- a/net/base/transport_security_state_unittest.cc +++ b/net/base/transport_security_state_unittest.cc @@ -2,8 +2,11 @@ // 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 "net/base/transport_security_state.h" + +#include "base/base64.h" +#include "base/sha1.h" +#include "base/string_piece.h" #include "testing/gtest/include/gtest/gtest.h" #if defined(USE_OPENSSL) @@ -849,6 +852,92 @@ TEST_F(TransportSecurityStateTest, BuiltinCertPins) { #endif } +static bool AddHash(const std::string& type_and_base64, + std::vector<SHA1Fingerprint>* out) { + std::string hash_str; + if (type_and_base64.find("sha1/") == 0 && + base::Base64Decode(type_and_base64.substr(5, type_and_base64.size() - 5), + &hash_str) && + hash_str.size() == base::kSHA1Length) { + SHA1Fingerprint hash; + memcpy(hash.data, hash_str.data(), sizeof(hash.data)); + out->push_back(hash); + return true; + } + return false; +} + +TEST_F(TransportSecurityStateTest, PinValidationWithRejectedCerts) { + // kGoodPath is plus.google.com via Google Internet Authority. + static const char* kGoodPath[] = { + "sha1/4BjDjn8v2lWeUFQnqSs0BgbIcrU=", + "sha1/QMVAHW+MuvCLAO3vse6H0AWzuc0=", + "sha1/SOZo+SvSspXXR9gjIBBPM5iQn9Q=", + NULL, + }; + + // kBadPath is plus.google.com via Trustcenter, which contains a required + // certificate (Equifax root), but also an excluded certificate + // (Trustcenter). + static const char* kBadPath[] = { + "sha1/4BjDjn8v2lWeUFQnqSs0BgbIcrU=", + "sha1/gzuEEAB/bkqdQS3EIjk2by7lW+k=", + "sha1/SOZo+SvSspXXR9gjIBBPM5iQn9Q=", + NULL, + }; + + std::vector<net::SHA1Fingerprint> good_hashes, bad_hashes; + + for (size_t i = 0; kGoodPath[i]; i++) { + EXPECT_TRUE(AddHash(kGoodPath[i], &good_hashes)); + } + for (size_t i = 0; kBadPath[i]; i++) { + EXPECT_TRUE(AddHash(kBadPath[i], &bad_hashes)); + } + + TransportSecurityState state(""); + TransportSecurityState::DomainState domain_state; + EXPECT_TRUE(state.HasPinsForHost(&domain_state, "plus.google.com", true)); + + EXPECT_TRUE(domain_state.IsChainOfPublicKeysPermitted(good_hashes)); + EXPECT_FALSE(domain_state.IsChainOfPublicKeysPermitted(bad_hashes)); +} + +TEST_F(TransportSecurityStateTest, PinValidationWithoutRejectedCerts) { + // kGoodPath is blog.torproject.org. + static const char* kGoodPath[] = { + "sha1/m9lHYJYke9k0GtVZ+bXSQYE8nDI=", + "sha1/o5OZxATDsgmwgcIfIWIneMJ0jkw=", + "sha1/wHqYaI2J+6sFZAwRfap9ZbjKzE4=", + NULL, + }; + + // kBadPath is plus.google.com via Trustcenter, which is utterly wrong for + // torproject.org. + static const char* kBadPath[] = { + "sha1/4BjDjn8v2lWeUFQnqSs0BgbIcrU=", + "sha1/gzuEEAB/bkqdQS3EIjk2by7lW+k=", + "sha1/SOZo+SvSspXXR9gjIBBPM5iQn9Q=", + NULL, + }; + + std::vector<net::SHA1Fingerprint> good_hashes, bad_hashes; + + for (size_t i = 0; kGoodPath[i]; i++) { + EXPECT_TRUE(AddHash(kGoodPath[i], &good_hashes)); + } + for (size_t i = 0; kBadPath[i]; i++) { + EXPECT_TRUE(AddHash(kBadPath[i], &bad_hashes)); + } + + TransportSecurityState state(""); + TransportSecurityState::DomainState domain_state; + EXPECT_TRUE(state.HasPinsForHost(&domain_state, "blog.torproject.org", true)); + + EXPECT_TRUE(domain_state.IsChainOfPublicKeysPermitted(good_hashes)); + EXPECT_FALSE(domain_state.IsChainOfPublicKeysPermitted(bad_hashes)); +} + TEST_F(TransportSecurityStateTest, OptionalHSTSCertPins) { TransportSecurityState state(""); TransportSecurityState::DomainState domain_state; |