summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorcevans@chromium.org <cevans@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-02 22:35:04 +0000
committercevans@chromium.org <cevans@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-02 22:35:04 +0000
commitc69c3a390eb15107d44fa134044b056f954bb0b2 (patch)
tree418e5c985b04449a6966027939d6c6d1655b03fb
parentc14c5601ef318fc57df652e21537efcd1615f9d3 (diff)
downloadchromium_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.cc302
-rw-r--r--net/base/transport_security_state.h13
-rw-r--r--net/base/transport_security_state_unittest.cc91
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;