| OLD | NEW |
| 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 "base/base64.h" | 5 #include "base/base64.h" |
| 6 #include "base/basictypes.h" | 6 #include "base/basictypes.h" |
| 7 #include "base/strings/string_number_conversions.h" | 7 #include "base/strings/string_number_conversions.h" |
| 8 #include "base/strings/string_piece.h" | 8 #include "base/strings/string_piece.h" |
| 9 #include "base/strings/string_tokenizer.h" | 9 #include "base/strings/string_tokenizer.h" |
| 10 #include "base/strings/string_util.h" | 10 #include "base/strings/string_util.h" |
| 11 #include "net/http/http_security_headers.h" | 11 #include "net/http/http_security_headers.h" |
| 12 #include "net/http/http_util.h" | 12 #include "net/http/http_util.h" |
| 13 #include "url/gurl.h" | 13 #include "url/gurl.h" |
| 14 | 14 |
| 15 namespace net { | 15 namespace net { |
| 16 | 16 |
| 17 namespace { | 17 namespace { |
| 18 | 18 |
| 19 enum MaxAgeParsing { REQUIRE_MAX_AGE, DO_NOT_REQUIRE_MAX_AGE }; |
| 20 |
| 19 static_assert(kMaxHSTSAgeSecs <= kuint32max, "kMaxHSTSAgeSecs too large"); | 21 static_assert(kMaxHSTSAgeSecs <= kuint32max, "kMaxHSTSAgeSecs too large"); |
| 20 | 22 |
| 21 // MaxAgeToInt converts a string representation of a "whole number" of | 23 // MaxAgeToInt converts a string representation of a "whole number" of |
| 22 // seconds into a uint32. The string may contain an arbitrarily large number, | 24 // seconds into a uint32. The string may contain an arbitrarily large number, |
| 23 // which will be clipped to kMaxHSTSAgeSecs and which is guaranteed to fit | 25 // which will be clipped to kMaxHSTSAgeSecs and which is guaranteed to fit |
| 24 // within a 32-bit unsigned integer. False is returned on any parse error. | 26 // within a 32-bit unsigned integer. False is returned on any parse error. |
| 25 bool MaxAgeToInt(std::string::const_iterator begin, | 27 bool MaxAgeToInt(std::string::const_iterator begin, |
| 26 std::string::const_iterator end, | 28 std::string::const_iterator end, |
| 27 uint32* result) { | 29 uint32* result) { |
| 28 const base::StringPiece s(begin, end); | 30 const base::StringPiece s(begin, end); |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 111 | 113 |
| 112 HashValue hash(tag); | 114 HashValue hash(tag); |
| 113 if (decoded.size() != hash.size()) | 115 if (decoded.size() != hash.size()) |
| 114 return false; | 116 return false; |
| 115 | 117 |
| 116 memcpy(hash.data(), decoded.data(), hash.size()); | 118 memcpy(hash.data(), decoded.data(), hash.size()); |
| 117 hashes->push_back(hash); | 119 hashes->push_back(hash); |
| 118 return true; | 120 return true; |
| 119 } | 121 } |
| 120 | 122 |
| 123 bool ParseHPKPHeaderImpl(const std::string& value, |
| 124 MaxAgeParsing max_age_status, |
| 125 base::TimeDelta* max_age, |
| 126 bool* include_subdomains, |
| 127 HashValueVector* hashes, |
| 128 GURL* report_uri) { |
| 129 bool parsed_max_age = false; |
| 130 bool include_subdomains_candidate = false; |
| 131 uint32 max_age_candidate = 0; |
| 132 GURL parsed_report_uri; |
| 133 HashValueVector pins; |
| 134 bool require_max_age = max_age_status == REQUIRE_MAX_AGE; |
| 135 |
| 136 HttpUtil::NameValuePairsIterator name_value_pairs( |
| 137 value.begin(), value.end(), ';', |
| 138 HttpUtil::NameValuePairsIterator::VALUES_OPTIONAL); |
| 139 |
| 140 while (name_value_pairs.GetNext()) { |
| 141 if (base::LowerCaseEqualsASCII( |
| 142 base::StringPiece(name_value_pairs.name_begin(), |
| 143 name_value_pairs.name_end()), |
| 144 "max-age")) { |
| 145 if (!MaxAgeToInt(name_value_pairs.value_begin(), |
| 146 name_value_pairs.value_end(), &max_age_candidate)) { |
| 147 return false; |
| 148 } |
| 149 parsed_max_age = true; |
| 150 } else if (base::LowerCaseEqualsASCII( |
| 151 base::StringPiece(name_value_pairs.name_begin(), |
| 152 name_value_pairs.name_end()), |
| 153 "pin-sha1")) { |
| 154 // Pins are always quoted. |
| 155 if (!name_value_pairs.value_is_quoted() || |
| 156 !ParseAndAppendPin(name_value_pairs.value_begin(), |
| 157 name_value_pairs.value_end(), HASH_VALUE_SHA1, |
| 158 &pins)) { |
| 159 return false; |
| 160 } |
| 161 } else if (base::LowerCaseEqualsASCII( |
| 162 base::StringPiece(name_value_pairs.name_begin(), |
| 163 name_value_pairs.name_end()), |
| 164 "pin-sha256")) { |
| 165 // Pins are always quoted. |
| 166 if (!name_value_pairs.value_is_quoted() || |
| 167 !ParseAndAppendPin(name_value_pairs.value_begin(), |
| 168 name_value_pairs.value_end(), HASH_VALUE_SHA256, |
| 169 &pins)) { |
| 170 return false; |
| 171 } |
| 172 } else if (base::LowerCaseEqualsASCII( |
| 173 base::StringPiece(name_value_pairs.name_begin(), |
| 174 name_value_pairs.name_end()), |
| 175 "includesubdomains")) { |
| 176 include_subdomains_candidate = true; |
| 177 } else if (base::LowerCaseEqualsASCII( |
| 178 base::StringPiece(name_value_pairs.name_begin(), |
| 179 name_value_pairs.name_end()), |
| 180 "report-uri")) { |
| 181 // report-uris are always quoted. |
| 182 if (!name_value_pairs.value_is_quoted()) |
| 183 return false; |
| 184 |
| 185 parsed_report_uri = GURL(name_value_pairs.value()); |
| 186 if (parsed_report_uri.is_empty() || !parsed_report_uri.is_valid()) |
| 187 return false; |
| 188 } else { |
| 189 // Silently ignore unknown directives for forward compatibility. |
| 190 } |
| 191 } |
| 192 |
| 193 if (!name_value_pairs.valid()) |
| 194 return false; |
| 195 |
| 196 if (!parsed_max_age && require_max_age) |
| 197 return false; |
| 198 |
| 199 *max_age = base::TimeDelta::FromSeconds(max_age_candidate); |
| 200 *include_subdomains = include_subdomains_candidate; |
| 201 hashes->swap(pins); |
| 202 *report_uri = parsed_report_uri; |
| 203 |
| 204 return true; |
| 205 } |
| 206 |
| 121 } // namespace | 207 } // namespace |
| 122 | 208 |
| 123 // Parse the Strict-Transport-Security header, as currently defined in | 209 // Parse the Strict-Transport-Security header, as currently defined in |
| 124 // http://tools.ietf.org/html/draft-ietf-websec-strict-transport-sec-14: | 210 // http://tools.ietf.org/html/draft-ietf-websec-strict-transport-sec-14: |
| 125 // | 211 // |
| 126 // Strict-Transport-Security = "Strict-Transport-Security" ":" | 212 // Strict-Transport-Security = "Strict-Transport-Security" ":" |
| 127 // [ directive ] *( ";" [ directive ] ) | 213 // [ directive ] *( ";" [ directive ] ) |
| 128 // | 214 // |
| 129 // directive = directive-name [ "=" directive-value ] | 215 // directive = directive-name [ "=" directive-value ] |
| 130 // directive-name = token | 216 // directive-name = token |
| (...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 245 case START: | 331 case START: |
| 246 case AFTER_MAX_AGE_LABEL: | 332 case AFTER_MAX_AGE_LABEL: |
| 247 case AFTER_MAX_AGE_EQUALS: | 333 case AFTER_MAX_AGE_EQUALS: |
| 248 return false; | 334 return false; |
| 249 default: | 335 default: |
| 250 NOTREACHED(); | 336 NOTREACHED(); |
| 251 return false; | 337 return false; |
| 252 } | 338 } |
| 253 } | 339 } |
| 254 | 340 |
| 255 // "Public-Key-Pins[-Report-Only]" ":" | 341 // "Public-Key-Pins" ":" |
| 256 // "max-age" "=" delta-seconds ";" | 342 // "max-age" "=" delta-seconds ";" |
| 257 // "pin-" algo "=" base64 [ ";" ... ] | 343 // "pin-" algo "=" base64 [ ";" ... ] |
| 258 // [ ";" "includeSubdomains" ] | 344 // [ ";" "includeSubdomains" ] |
| 259 // [ ";" "report-uri" "=" uri-reference ] | 345 // [ ";" "report-uri" "=" uri-reference ] |
| 260 bool ParseHPKPHeader(const std::string& value, | 346 bool ParseHPKPHeader(const std::string& value, |
| 261 const HashValueVector& chain_hashes, | 347 const HashValueVector& chain_hashes, |
| 262 base::TimeDelta* max_age, | 348 base::TimeDelta* max_age, |
| 263 bool* include_subdomains, | 349 bool* include_subdomains, |
| 264 HashValueVector* hashes, | 350 HashValueVector* hashes, |
| 265 GURL* report_uri) { | 351 GURL* report_uri) { |
| 266 bool parsed_max_age = false; | 352 base::TimeDelta candidate_max_age; |
| 267 bool include_subdomains_candidate = false; | 353 bool candidate_include_subdomains; |
| 268 uint32 max_age_candidate = 0; | 354 HashValueVector candidate_hashes; |
| 269 GURL parsed_report_uri; | 355 GURL candidate_report_uri; |
| 270 HashValueVector pins; | |
| 271 | 356 |
| 272 HttpUtil::NameValuePairsIterator name_value_pairs( | 357 if (!ParseHPKPHeaderImpl(value, REQUIRE_MAX_AGE, &candidate_max_age, |
| 273 value.begin(), value.end(), ';', | 358 &candidate_include_subdomains, &candidate_hashes, |
| 274 HttpUtil::NameValuePairsIterator::VALUES_OPTIONAL); | 359 &candidate_report_uri)) { |
| 275 | 360 return false; |
| 276 while (name_value_pairs.GetNext()) { | |
| 277 if (base::LowerCaseEqualsASCII( | |
| 278 base::StringPiece(name_value_pairs.name_begin(), | |
| 279 name_value_pairs.name_end()), | |
| 280 "max-age")) { | |
| 281 if (!MaxAgeToInt(name_value_pairs.value_begin(), | |
| 282 name_value_pairs.value_end(), &max_age_candidate)) { | |
| 283 return false; | |
| 284 } | |
| 285 parsed_max_age = true; | |
| 286 } else if (base::LowerCaseEqualsASCII( | |
| 287 base::StringPiece(name_value_pairs.name_begin(), | |
| 288 name_value_pairs.name_end()), | |
| 289 "pin-sha1")) { | |
| 290 // Pins are always quoted. | |
| 291 if (!name_value_pairs.value_is_quoted() || | |
| 292 !ParseAndAppendPin(name_value_pairs.value_begin(), | |
| 293 name_value_pairs.value_end(), HASH_VALUE_SHA1, | |
| 294 &pins)) { | |
| 295 return false; | |
| 296 } | |
| 297 } else if (base::LowerCaseEqualsASCII( | |
| 298 base::StringPiece(name_value_pairs.name_begin(), | |
| 299 name_value_pairs.name_end()), | |
| 300 "pin-sha256")) { | |
| 301 // Pins are always quoted. | |
| 302 if (!name_value_pairs.value_is_quoted() || | |
| 303 !ParseAndAppendPin(name_value_pairs.value_begin(), | |
| 304 name_value_pairs.value_end(), HASH_VALUE_SHA256, | |
| 305 &pins)) { | |
| 306 return false; | |
| 307 } | |
| 308 } else if (base::LowerCaseEqualsASCII( | |
| 309 base::StringPiece(name_value_pairs.name_begin(), | |
| 310 name_value_pairs.name_end()), | |
| 311 "includesubdomains")) { | |
| 312 include_subdomains_candidate = true; | |
| 313 } else if (base::LowerCaseEqualsASCII( | |
| 314 base::StringPiece(name_value_pairs.name_begin(), | |
| 315 name_value_pairs.name_end()), | |
| 316 "report-uri")) { | |
| 317 // report-uris are always quoted. | |
| 318 if (!name_value_pairs.value_is_quoted()) | |
| 319 return false; | |
| 320 | |
| 321 parsed_report_uri = GURL(name_value_pairs.value()); | |
| 322 if (parsed_report_uri.is_empty() || !parsed_report_uri.is_valid()) | |
| 323 return false; | |
| 324 } else { | |
| 325 // Silently ignore unknown directives for forward compatibility. | |
| 326 } | |
| 327 } | 361 } |
| 328 | 362 |
| 329 if (!name_value_pairs.valid()) | 363 if (!IsPinListValid(candidate_hashes, chain_hashes)) |
| 330 return false; | 364 return false; |
| 331 | 365 |
| 332 if (!parsed_max_age) | 366 *max_age = candidate_max_age; |
| 333 return false; | 367 *include_subdomains = candidate_include_subdomains; |
| 334 | 368 hashes->swap(candidate_hashes); |
| 335 if (!IsPinListValid(pins, chain_hashes)) | 369 *report_uri = candidate_report_uri; |
| 336 return false; | |
| 337 | |
| 338 *max_age = base::TimeDelta::FromSeconds(max_age_candidate); | |
| 339 *include_subdomains = include_subdomains_candidate; | |
| 340 hashes->swap(pins); | |
| 341 *report_uri = parsed_report_uri; | |
| 342 | |
| 343 return true; | 370 return true; |
| 344 } | 371 } |
| 345 | 372 |
| 373 // "Public-Key-Pins-Report-Only" ":" |
| 374 // [ "max-age" "=" delta-seconds ";" ] |
| 375 // "pin-" algo "=" base64 [ ";" ... ] |
| 376 // [ ";" "includeSubdomains" ] |
| 377 // [ ";" "report-uri" "=" uri-reference ] |
| 378 bool ParseHPKPReportOnlyHeader(const std::string& value, |
| 379 bool* include_subdomains, |
| 380 HashValueVector* hashes, |
| 381 GURL* report_uri) { |
| 382 // max-age is irrelevant for Report-Only headers. |
| 383 base::TimeDelta unused_max_age; |
| 384 return ParseHPKPHeaderImpl(value, DO_NOT_REQUIRE_MAX_AGE, &unused_max_age, |
| 385 include_subdomains, hashes, report_uri); |
| 386 } |
| 387 |
| 346 } // namespace net | 388 } // namespace net |
| OLD | NEW |