Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(79)

Side by Side Diff: net/base/transport_security_state.cc

Issue 10825211: Implement SHA-256 fingerprint support (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "net/base/transport_security_state.h" 5 #include "net/base/transport_security_state.h"
6 6
7 #if defined(USE_OPENSSL) 7 #if defined(USE_OPENSSL)
8 #include <openssl/ecdsa.h> 8 #include <openssl/ecdsa.h>
9 #include <openssl/ssl.h> 9 #include <openssl/ssl.h>
10 #else // !defined(USE_OPENSSL) 10 #else // !defined(USE_OPENSSL)
(...skipping 15 matching lines...) Expand all
26 #include "base/string_number_conversions.h" 26 #include "base/string_number_conversions.h"
27 #include "base/string_tokenizer.h" 27 #include "base/string_tokenizer.h"
28 #include "base/string_util.h" 28 #include "base/string_util.h"
29 #include "base/time.h" 29 #include "base/time.h"
30 #include "base/utf_string_conversions.h" 30 #include "base/utf_string_conversions.h"
31 #include "base/values.h" 31 #include "base/values.h"
32 #include "crypto/sha2.h" 32 #include "crypto/sha2.h"
33 #include "googleurl/src/gurl.h" 33 #include "googleurl/src/gurl.h"
34 #include "net/base/dns_util.h" 34 #include "net/base/dns_util.h"
35 #include "net/base/ssl_info.h" 35 #include "net/base/ssl_info.h"
36 #include "net/base/x509_cert_types.h"
36 #include "net/base/x509_certificate.h" 37 #include "net/base/x509_certificate.h"
37 #include "net/http/http_util.h" 38 #include "net/http/http_util.h"
38 39
39 #if defined(USE_OPENSSL) 40 #if defined(USE_OPENSSL)
40 #include "crypto/openssl_util.h" 41 #include "crypto/openssl_util.h"
41 #endif 42 #endif
42 43
43 namespace net { 44 namespace net {
44 45
45 const long int TransportSecurityState::kMaxHSTSAgeSecs = 86400 * 365; // 1 year 46 const long int TransportSecurityState::kMaxHSTSAgeSecs = 86400 * 365; // 1 year
(...skipping 165 matching lines...) Expand 10 before | Expand all | Expand 10 after
211 StringPair pair; 212 StringPair pair;
212 size_t point = source.find(delimiter); 213 size_t point = source.find(delimiter);
213 214
214 pair.first = source.substr(0, point); 215 pair.first = source.substr(0, point);
215 if (std::string::npos != point) 216 if (std::string::npos != point)
216 pair.second = source.substr(point + 1); 217 pair.second = source.substr(point + 1);
217 218
218 return pair; 219 return pair;
219 } 220 }
220 221
221 // TODO(palmer): Support both sha256 and sha1. This will require additional
222 // infrastructure code changes and can come in a later patch.
223 //
224 // static 222 // static
225 bool TransportSecurityState::ParsePin(const std::string& value, 223 bool TransportSecurityState::ParsePin(const std::string& value,
226 SHA1Fingerprint* out) { 224 HashValue* out) {
227 StringPair slash = Split(Strip(value), '/'); 225 StringPair slash = Split(Strip(value), '/');
228 if (slash.first != "sha1") 226
227 if (slash.first == "sha1")
228 out->tag = HASH_VALUE_SHA1;
229 else if (slash.first == "sha256")
230 out->tag = HASH_VALUE_SHA256;
231 else
229 return false; 232 return false;
230 233
231 std::string decoded; 234 std::string decoded;
232 if (!base::Base64Decode(slash.second, &decoded) || 235 if (!base::Base64Decode(slash.second, &decoded) ||
233 decoded.size() != arraysize(out->data)) { 236 decoded.size() != out->size()) {
234 return false; 237 return false;
235 } 238 }
236 239
237 memcpy(out->data, decoded.data(), arraysize(out->data)); 240 memcpy(out->data(), decoded.data(), out->size());
238 return true; 241 return true;
239 } 242 }
240 243
241 static bool ParseAndAppendPin(const std::string& value, 244 static bool ParseAndAppendPin(const std::string& value,
242 FingerprintVector* fingerprints) { 245 HashValueVector* fingerprints) {
243 // The base64'd fingerprint MUST be a quoted-string. 20 bytes base64'd is 28 246 // The base64'd fingerprint MUST be a quoted-string. 20 bytes base64'd is 28
244 // characters; 32 bytes base64'd is 44 characters. TODO(palmer): Support 247 // characters; 32 bytes base64'd is 44 characters.
245 // SHA256.
246 size_t size = value.size(); 248 size_t size = value.size();
247 if (size != 30 || value[0] != '"' || value[size - 1] != '"') 249 if ((size != 30 && size != 46) || value[0] != '"' || value[size - 1] != '"')
248 return false; 250 return false;
249 251
250 std::string unquoted = HttpUtil::Unquote(value); 252 std::string unquoted = HttpUtil::Unquote(value);
251 std::string decoded; 253 std::string decoded;
252 SHA1Fingerprint fp; 254 HashValue fp;
253 255
254 if (!base::Base64Decode(unquoted, &decoded) || 256 // This code has to assume that 32 bytes is SHA-256 and 20 bytes is SHA-1.
255 decoded.size() != arraysize(fp.data)) { 257 // Currently, those are the only two possibilities, so the assumption is
258 // valid.
259 if (!base::Base64Decode(unquoted, &decoded))
256 return false; 260 return false;
257 }
258 261
259 memcpy(fp.data, decoded.data(), arraysize(fp.data)); 262 if (decoded.size() == 20)
263 fp.tag = HASH_VALUE_SHA1;
264 else if (decoded.size() == 32)
265 fp.tag = HASH_VALUE_SHA256;
266 else
267 return false;
268
269 memcpy(fp.data(), decoded.data(), fp.size());
260 fingerprints->push_back(fp); 270 fingerprints->push_back(fp);
261 return true; 271 return true;
262 } 272 }
263 273
264 struct FingerprintsEqualPredicate { 274 struct HashValuesEqualPredicate {
265 explicit FingerprintsEqualPredicate(const SHA1Fingerprint& fingerprint) : 275 explicit HashValuesEqualPredicate(const HashValue& fingerprint) :
266 fingerprint_(fingerprint) {} 276 fingerprint_(fingerprint) {}
267 277
268 bool operator()(const SHA1Fingerprint& other) const { 278 bool operator()(const HashValue& other) const {
269 return fingerprint_.Equals(other); 279 return fingerprint_.Equals(other);
270 } 280 }
271 281
272 const SHA1Fingerprint& fingerprint_; 282 const HashValue& fingerprint_;
273 }; 283 };
274 284
275 // Returns true iff there is an item in |pins| which is not present in 285 // Returns true iff there is an item in |pins| which is not present in
276 // |from_cert_chain|. Such an SPKI hash is called a "backup pin". 286 // |from_cert_chain|. Such an SPKI hash is called a "backup pin".
277 static bool IsBackupPinPresent(const FingerprintVector& pins, 287 static bool IsBackupPinPresent(const HashValueVector& pins,
278 const FingerprintVector& from_cert_chain) { 288 const HashValueVector& from_cert_chain) {
279 for (FingerprintVector::const_iterator 289 for (HashValueVector::const_iterator
280 i = pins.begin(); i != pins.end(); ++i) { 290 i = pins.begin(); i != pins.end(); ++i) {
281 FingerprintVector::const_iterator j = 291 HashValueVector::const_iterator j =
282 std::find_if(from_cert_chain.begin(), from_cert_chain.end(), 292 std::find_if(from_cert_chain.begin(), from_cert_chain.end(),
283 FingerprintsEqualPredicate(*i)); 293 HashValuesEqualPredicate(*i));
284 if (j == from_cert_chain.end()) 294 if (j == from_cert_chain.end())
285 return true; 295 return true;
286 } 296 }
287 297
288 return false; 298 return false;
289 } 299 }
290 300
291 static bool HashesIntersect(const FingerprintVector& a, 301 static bool HashesIntersect(const HashValueVector& a,
292 const FingerprintVector& b) { 302 const HashValueVector& b) {
293 for (FingerprintVector::const_iterator 303 for (HashValueVector::const_iterator
294 i = a.begin(); i != a.end(); ++i) { 304 i = a.begin(); i != a.end(); ++i) {
295 FingerprintVector::const_iterator j = 305 HashValueVector::const_iterator j =
296 std::find_if(b.begin(), b.end(), FingerprintsEqualPredicate(*i)); 306 std::find_if(b.begin(), b.end(), HashValuesEqualPredicate(*i));
297 if (j != b.end()) 307 if (j != b.end())
298 return true; 308 return true;
299 } 309 }
300 310
301 return false; 311 return false;
302 } 312 }
303 313
304 // Returns true iff |pins| contains both a live and a backup pin. A live pin 314 // Returns true iff |pins| contains both a live and a backup pin. A live pin
305 // is a pin whose SPKI is present in the certificate chain in |ssl_info|. A 315 // is a pin whose SPKI is present in the certificate chain in |ssl_info|. A
306 // backup pin is a pin intended for disaster recovery, not day-to-day use, and 316 // backup pin is a pin intended for disaster recovery, not day-to-day use, and
307 // thus must be absent from the certificate chain. The Public-Key-Pins header 317 // thus must be absent from the certificate chain. The Public-Key-Pins header
308 // specification requires both. 318 // specification requires both.
309 static bool IsPinListValid(const FingerprintVector& pins, 319 static bool IsPinListValid(const HashValueVector& pins,
310 const SSLInfo& ssl_info) { 320 const SSLInfo& ssl_info) {
321 // Fast fail: 1 live + 1 backup = at least 2 pins. (Check for actual
322 // liveness and backupness below.)
311 if (pins.size() < 2) 323 if (pins.size() < 2)
312 return false; 324 return false;
313 325
314 const FingerprintVector& from_cert_chain = ssl_info.public_key_hashes; 326 // Site operators might pin a key using either the SHA-1 or SHA-256 hash
315 if (from_cert_chain.empty()) 327 // of the SPKI. So check for success using either hash function.
328 //
329 // TODO(palmer): Make this generic so that it works regardless of what
330 // HashValueTags are defined in the future.
331 const HashValueVector& from_cert_chain_sha1 =
332 ssl_info.public_key_hashes[HASH_VALUE_SHA1];
333 const HashValueVector& from_cert_chain_sha256 =
334 ssl_info.public_key_hashes[HASH_VALUE_SHA256];
335
336 if (from_cert_chain_sha1.empty() && from_cert_chain_sha256.empty())
316 return false; 337 return false;
317 338
318 return IsBackupPinPresent(pins, from_cert_chain) && 339 return (IsBackupPinPresent(pins, from_cert_chain_sha1) ||
319 HashesIntersect(pins, from_cert_chain); 340 IsBackupPinPresent(pins, from_cert_chain_sha256)) &&
341 (HashesIntersect(pins, from_cert_chain_sha1) ||
342 HashesIntersect(pins, from_cert_chain_sha256));
320 } 343 }
321 344
322 // "Public-Key-Pins" ":" 345 // "Public-Key-Pins" ":"
323 // "max-age" "=" delta-seconds ";" 346 // "max-age" "=" delta-seconds ";"
324 // "pin-" algo "=" base64 [ ";" ... ] 347 // "pin-" algo "=" base64 [ ";" ... ]
325 bool TransportSecurityState::DomainState::ParsePinsHeader( 348 bool TransportSecurityState::DomainState::ParsePinsHeader(
326 const base::Time& now, 349 const base::Time& now,
327 const std::string& value, 350 const std::string& value,
328 const SSLInfo& ssl_info) { 351 const SSLInfo& ssl_info) {
329 bool parsed_max_age = false; 352 bool parsed_max_age = false;
330 int max_age_candidate = 0; 353 int max_age_candidate = 0;
331 FingerprintVector pins; 354 HashValueVector pins;
332 355
333 std::string source = value; 356 std::string source = value;
334 357
335 while (!source.empty()) { 358 while (!source.empty()) {
336 StringPair semicolon = Split(source, ';'); 359 StringPair semicolon = Split(source, ';');
337 semicolon.first = Strip(semicolon.first); 360 semicolon.first = Strip(semicolon.first);
338 semicolon.second = Strip(semicolon.second); 361 semicolon.second = Strip(semicolon.second);
339 StringPair equals = Split(semicolon.first, '='); 362 StringPair equals = Split(semicolon.first, '=');
340 equals.first = Strip(equals.first); 363 equals.first = Strip(equals.first);
341 equals.second = Strip(equals.second); 364 equals.second = Strip(equals.second);
342 365
343 if (LowerCaseEqualsASCII(equals.first, "max-age")) { 366 if (LowerCaseEqualsASCII(equals.first, "max-age")) {
344 if (equals.second.empty() || 367 if (equals.second.empty() ||
345 !MaxAgeToInt(equals.second.begin(), equals.second.end(), 368 !MaxAgeToInt(equals.second.begin(), equals.second.end(),
346 &max_age_candidate)) { 369 &max_age_candidate)) {
347 return false; 370 return false;
348 } 371 }
349 if (max_age_candidate > kMaxHSTSAgeSecs) 372 if (max_age_candidate > kMaxHSTSAgeSecs)
350 max_age_candidate = kMaxHSTSAgeSecs; 373 max_age_candidate = kMaxHSTSAgeSecs;
351 parsed_max_age = true; 374 parsed_max_age = true;
352 } else if (LowerCaseEqualsASCII(equals.first, "pin-sha1")) { 375 } else if (LowerCaseEqualsASCII(equals.first, "pin-sha1") ||
376 LowerCaseEqualsASCII(equals.first, "pin-sha256")) {
353 if (!ParseAndAppendPin(equals.second, &pins)) 377 if (!ParseAndAppendPin(equals.second, &pins))
354 return false; 378 return false;
355 } else if (LowerCaseEqualsASCII(equals.first, "pin-sha256")) {
356 // TODO(palmer)
357 } else { 379 } else {
358 // Silently ignore unknown directives for forward compatibility. 380 // Silently ignore unknown directives for forward compatibility.
359 } 381 }
360 382
361 source = semicolon.second; 383 source = semicolon.second;
362 } 384 }
363 385
364 if (!parsed_max_age || !IsPinListValid(pins, ssl_info)) 386 if (!parsed_max_age || !IsPinListValid(pins, ssl_info))
365 return false; 387 return false;
366 388
367 dynamic_spki_hashes_expiry = 389 dynamic_spki_hashes_expiry =
368 now + base::TimeDelta::FromSeconds(max_age_candidate); 390 now + base::TimeDelta::FromSeconds(max_age_candidate);
369 391
370 dynamic_spki_hashes.clear(); 392 dynamic_spki_hashes.clear();
371 if (max_age_candidate > 0) { 393 if (max_age_candidate > 0) {
372 for (FingerprintVector::const_iterator i = pins.begin(); 394 for (HashValueVector::const_iterator i = pins.begin();
373 i != pins.end(); ++i) { 395 i != pins.end(); ++i) {
374 dynamic_spki_hashes.push_back(*i); 396 dynamic_spki_hashes.push_back(*i);
375 } 397 }
376 } 398 }
377 399
378 return true; 400 return true;
379 } 401 }
380 402
381 // "Strict-Transport-Security" ":" 403 // "Strict-Transport-Security" ":"
382 // "max-age" "=" delta-seconds [ ";" "includeSubDomains" ] 404 // "max-age" "=" delta-seconds [ ";" "includeSubDomains" ]
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after
469 include_subdomains = true; 491 include_subdomains = true;
470 upgrade_mode = MODE_FORCE_HTTPS; 492 upgrade_mode = MODE_FORCE_HTTPS;
471 return true; 493 return true;
472 default: 494 default:
473 NOTREACHED(); 495 NOTREACHED();
474 return false; 496 return false;
475 } 497 }
476 } 498 }
477 499
478 static bool AddHash(const std::string& type_and_base64, 500 static bool AddHash(const std::string& type_and_base64,
479 FingerprintVector* out) { 501 HashValueVector* out) {
480 SHA1Fingerprint hash; 502 HashValue hash;
481 503
482 if (!TransportSecurityState::ParsePin(type_and_base64, &hash)) 504 if (!TransportSecurityState::ParsePin(type_and_base64, &hash))
483 return false; 505 return false;
484 506
485 out->push_back(hash); 507 out->push_back(hash);
486 return true; 508 return true;
487 } 509 }
488 510
489 TransportSecurityState::~TransportSecurityState() {} 511 TransportSecurityState::~TransportSecurityState() {}
490 512
(...skipping 245 matching lines...) Expand 10 before | Expand all | Expand 10 after
736 const std::string& hashed_host, const DomainState& state) { 758 const std::string& hashed_host, const DomainState& state) {
737 enabled_hosts_[hashed_host] = state; 759 enabled_hosts_[hashed_host] = state;
738 } 760 }
739 761
740 void TransportSecurityState::AddOrUpdateForcedHosts( 762 void TransportSecurityState::AddOrUpdateForcedHosts(
741 const std::string& hashed_host, const DomainState& state) { 763 const std::string& hashed_host, const DomainState& state) {
742 forced_hosts_[hashed_host] = state; 764 forced_hosts_[hashed_host] = state;
743 } 765 }
744 766
745 static std::string HashesToBase64String( 767 static std::string HashesToBase64String(
746 const FingerprintVector& hashes) { 768 const HashValueVector& hashes) {
747 std::vector<std::string> hashes_strs; 769 std::vector<std::string> hashes_strs;
748 for (FingerprintVector::const_iterator 770 for (HashValueVector::const_iterator
749 i = hashes.begin(); i != hashes.end(); i++) { 771 i = hashes.begin(); i != hashes.end(); i++) {
750 std::string s; 772 std::string s;
751 const std::string hash_str(reinterpret_cast<const char*>(i->data), 773 const std::string hash_str(reinterpret_cast<const char*>(i->data()),
752 sizeof(i->data)); 774 i->size());
753 base::Base64Encode(hash_str, &s); 775 base::Base64Encode(hash_str, &s);
754 hashes_strs.push_back(s); 776 hashes_strs.push_back(s);
755 } 777 }
756 778
757 return JoinString(hashes_strs, ','); 779 return JoinString(hashes_strs, ',');
758 } 780 }
759 781
760 TransportSecurityState::DomainState::DomainState() 782 TransportSecurityState::DomainState::DomainState()
761 : upgrade_mode(MODE_FORCE_HTTPS), 783 : upgrade_mode(MODE_FORCE_HTTPS),
762 created(base::Time::Now()), 784 created(base::Time::Now()),
763 include_subdomains(false) { 785 include_subdomains(false) {
764 } 786 }
765 787
766 TransportSecurityState::DomainState::~DomainState() { 788 TransportSecurityState::DomainState::~DomainState() {
767 } 789 }
768 790
769 bool TransportSecurityState::DomainState::IsChainOfPublicKeysPermitted( 791 bool TransportSecurityState::DomainState::IsChainOfPublicKeysPermitted(
770 const FingerprintVector& hashes) const { 792 const std::vector<HashValueVector>& hashes) const {
771 if (HashesIntersect(bad_static_spki_hashes, hashes)) { 793 // Validate that hashes is not empty. By the time this code is called (in
772 LOG(ERROR) << "Rejecting public key chain for domain " << domain 794 // production), that should never happen, but it's good to be defensive.
773 << ". Validated chain: " << HashesToBase64String(hashes) 795 // And, hashes *can* be empty in some test scenarios.
774 << ", matches one or more bad hashes: " 796 bool empty = true;
775 << HashesToBase64String(bad_static_spki_hashes); 797 for (size_t i = 0; i < hashes.size(); ++i) {
798 if (hashes[i].size()) {
799 empty = false;
800 break;
801 }
802 }
803 if (empty) {
804 LOG(ERROR) << "Rejecting empty certificate chain for public key pinned "
805 "domain " << domain;
776 return false; 806 return false;
777 } 807 }
778 808
779 if (!(dynamic_spki_hashes.empty() && static_spki_hashes.empty()) && 809 for (size_t i = 0; i < hashes.size(); ++i) {
780 !HashesIntersect(dynamic_spki_hashes, hashes) && 810 if (HashesIntersect(bad_static_spki_hashes, hashes[i])) {
781 !HashesIntersect(static_spki_hashes, hashes)) { 811 LOG(ERROR) << "Rejecting public key chain for domain " << domain
782 LOG(ERROR) << "Rejecting public key chain for domain " << domain 812 << ". Validated chain: " << HashesToBase64String(hashes[i])
783 << ". Validated chain: " << HashesToBase64String(hashes) 813 << ", matches one or more bad hashes: "
784 << ", expected: " << HashesToBase64String(dynamic_spki_hashes) 814 << HashesToBase64String(bad_static_spki_hashes);
785 << " or: " << HashesToBase64String(static_spki_hashes); 815 return false;
816 }
786 817
787 return false; 818 if (!(dynamic_spki_hashes.empty() && static_spki_hashes.empty()) &&
819 hashes[i].size() > 0 &&
820 !HashesIntersect(dynamic_spki_hashes, hashes[i]) &&
821 !HashesIntersect(static_spki_hashes, hashes[i])) {
822 LOG(ERROR) << "Rejecting public key chain for domain " << domain
823 << ". Validated chain: " << HashesToBase64String(hashes[i])
824 << ", expected: " << HashesToBase64String(dynamic_spki_hashes)
825 << " or: " << HashesToBase64String(static_spki_hashes);
826
827 return false;
828 }
788 } 829 }
789 830
790 return true; 831 return true;
791 } 832 }
792 833
793 bool TransportSecurityState::DomainState::ShouldRedirectHTTPToHTTPS() const { 834 bool TransportSecurityState::DomainState::ShouldRedirectHTTPToHTTPS() const {
794 return upgrade_mode == MODE_FORCE_HTTPS; 835 return upgrade_mode == MODE_FORCE_HTTPS;
795 } 836 }
796 837
797 bool TransportSecurityState::DomainState::Equals( 838 bool TransportSecurityState::DomainState::Equals(
798 const DomainState& other) const { 839 const DomainState& other) const {
799 // TODO(palmer): Implement this 840 // TODO(palmer): Implement this
800 (void) other; 841 (void) other;
801 return true; 842 return true;
802 } 843 }
803 844
804 bool TransportSecurityState::DomainState::HasPins() const { 845 bool TransportSecurityState::DomainState::HasPins() const {
805 return static_spki_hashes.size() > 0 || 846 return static_spki_hashes.size() > 0 ||
806 bad_static_spki_hashes.size() > 0 || 847 bad_static_spki_hashes.size() > 0 ||
807 dynamic_spki_hashes.size() > 0; 848 dynamic_spki_hashes.size() > 0;
808 } 849 }
809 850
810 } // namespace 851 } // namespace
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698