OLD | NEW |
(Empty) | |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "net/tools/transport_security_state_generator/input_file_parsers.h" |
| 6 |
| 7 #include <sstream> |
| 8 #include <vector> |
| 9 |
| 10 #include "base/json/json_reader.h" |
| 11 #include "base/strings/string_piece.h" |
| 12 #include "base/strings/string_split.h" |
| 13 #include "base/strings/string_util.h" |
| 14 #include "base/values.h" |
| 15 #include "net/tools/transport_security_state_generator/cert_util.h" |
| 16 #include "net/tools/transport_security_state_generator/pinset.h" |
| 17 #include "net/tools/transport_security_state_generator/pinsets.h" |
| 18 #include "net/tools/transport_security_state_generator/spki_hash.h" |
| 19 #include "third_party/boringssl/src/include/openssl/x509v3.h" |
| 20 |
| 21 namespace net { |
| 22 |
| 23 namespace transport_security_state { |
| 24 |
| 25 namespace { |
| 26 |
| 27 std::string ToString(size_t number) { |
| 28 std::stringstream s; |
| 29 s << number; |
| 30 return s.str(); |
| 31 } |
| 32 |
| 33 bool IsImportantWordInCertificateName(base::StringPiece name) { |
| 34 const char* const important_words[] = {"Universal", "Global", "EV", "G1", |
| 35 "G2", "G3", "G4", "G5"}; |
| 36 for (auto* important_word : important_words) { |
| 37 if (name == important_word) { |
| 38 return true; |
| 39 } |
| 40 } |
| 41 return false; |
| 42 } |
| 43 |
| 44 // Strips all characters not matched by the RegEx [A-Za-z0-9_] from |name| and |
| 45 // returns the result. |
| 46 std::string FilterName(base::StringPiece name) { |
| 47 std::string filtered; |
| 48 for (const char& character : name) { |
| 49 if ((character >= '0' && character <= '9') || |
| 50 (character >= 'a' && character <= 'z') || |
| 51 (character >= 'A' && character <= 'Z') || character == '_') { |
| 52 filtered += character; |
| 53 } |
| 54 } |
| 55 return base::ToLowerASCII(filtered); |
| 56 } |
| 57 |
| 58 // Returns true if |pin_name| is a reasonable match for the certificate name |
| 59 // |name|. |
| 60 bool MatchCertificateName(base::StringPiece name, |
| 61 base::StringPiece pin_name, |
| 62 std::vector<std::string>* errors) { |
| 63 std::vector<base::StringPiece> words = base::SplitStringPiece( |
| 64 name, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
| 65 if (words.empty()) { |
| 66 errors->push_back("No words in certificate name for pin (" + |
| 67 pin_name.as_string() + ")"); |
| 68 return false; |
| 69 } |
| 70 base::StringPiece first_word = words[0]; |
| 71 |
| 72 if (first_word.ends_with(",")) { |
| 73 first_word = first_word.substr(0, first_word.size() - 1); |
| 74 } |
| 75 |
| 76 if (first_word.starts_with("*.")) { |
| 77 first_word = first_word.substr(2, first_word.size() - 2); |
| 78 } |
| 79 |
| 80 size_t pos = first_word.find('.'); |
| 81 if (pos != std::string::npos) { |
| 82 first_word = first_word.substr(0, first_word.size() - pos); |
| 83 } |
| 84 |
| 85 pos = first_word.find('-'); |
| 86 if (pos != std::string::npos) { |
| 87 first_word = first_word.substr(0, first_word.size() - pos); |
| 88 } |
| 89 |
| 90 if (first_word.empty()) { |
| 91 errors->push_back("First word of certificate name (" + name.as_string() + |
| 92 ") is empty"); |
| 93 return false; |
| 94 } |
| 95 |
| 96 std::string filtered_word = FilterName(first_word); |
| 97 first_word = filtered_word; |
| 98 if (!base::EqualsCaseInsensitiveASCII(pin_name.substr(0, first_word.size()), |
| 99 first_word)) { |
| 100 errors->push_back( |
| 101 "The first word of the certificate name (" + first_word.as_string() + |
| 102 ") isn't a prefix of the variable name (" + pin_name.as_string() + ")"); |
| 103 return false; |
| 104 } |
| 105 |
| 106 for (size_t i = 0; i < words.size(); ++i) { |
| 107 const base::StringPiece& word = words[i]; |
| 108 if (word == "Class" && (i + 1) < words.size()) { |
| 109 std::string class_name = word.as_string(); |
| 110 words[i + 1].AppendToString(&class_name); |
| 111 |
| 112 size_t pos = pin_name.find(class_name); |
| 113 if (pos == std::string::npos) { |
| 114 errors->push_back( |
| 115 "Class specification doesn't appear in the variable name (" + |
| 116 pin_name.as_string() + ")"); |
| 117 return false; |
| 118 } |
| 119 } else if (word.size() == 1 && word[0] >= '0' && word[0] <= '9') { |
| 120 size_t pos = pin_name.find(word); |
| 121 if (pos == std::string::npos) { |
| 122 errors->push_back("Number doesn't appear in the variable name (" + |
| 123 pin_name.as_string() + ")"); |
| 124 return false; |
| 125 } |
| 126 } else if (IsImportantWordInCertificateName(word)) { |
| 127 size_t pos = pin_name.find(word); |
| 128 if (pos == std::string::npos) { |
| 129 errors->push_back(word.as_string() + |
| 130 " doesn't appear in the variable name (" + |
| 131 pin_name.as_string() + ")"); |
| 132 return false; |
| 133 } |
| 134 } |
| 135 } |
| 136 |
| 137 return true; |
| 138 } |
| 139 |
| 140 // Returns true iff |candidate| is not empty, the first character is in the |
| 141 // range A-Z, and the remaining characters are in the ranges a-Z, 0-9, or '_'. |
| 142 bool IsValidName(const std::string& candidate) { |
| 143 if (candidate.empty() || candidate[0] < 'A' || candidate[0] > 'Z') { |
| 144 return false; |
| 145 } |
| 146 |
| 147 bool isValid = true; |
| 148 for (const char& character : candidate) { |
| 149 isValid = (character >= '0' && character <= '9') || |
| 150 (character >= 'a' && character <= 'z') || |
| 151 (character >= 'A' && character <= 'Z') || character == '_'; |
| 152 if (!isValid) { |
| 153 return false; |
| 154 } |
| 155 } |
| 156 return true; |
| 157 } |
| 158 |
| 159 static const char kStartOfCert[] = "-----BEGIN CERTIFICATE"; |
| 160 static const char kStartOfPublicKey[] = "-----BEGIN PUBLIC KEY"; |
| 161 static const char kEndOfCert[] = "-----END CERTIFICATE"; |
| 162 static const char kEndOfPublicKey[] = "-----END PUBLIC KEY"; |
| 163 static const char kStartOfSHA256[] = "sha256/"; |
| 164 |
| 165 enum class CertificateParserState { |
| 166 PRE_NAME, |
| 167 POST_NAME, |
| 168 IN_CERTIFICATE, |
| 169 IN_PUBLIC_KEY |
| 170 }; |
| 171 |
| 172 } // namespace |
| 173 |
| 174 bool ParseJSON(const std::string& json, |
| 175 TransportSecurityStateEntries* entries, |
| 176 Pinsets* pinsets, |
| 177 DomainIDList* domain_ids, |
| 178 std::vector<std::string>* errors) { |
| 179 DCHECK(errors); |
| 180 std::unique_ptr<base::Value> value = base::JSONReader::Read(json); |
| 181 base::DictionaryValue* dict_value = nullptr; |
| 182 if (!value.get() || !value->GetAsDictionary(&dict_value)) { |
| 183 errors->push_back("Could not parse the input JSON file"); |
| 184 return false; |
| 185 } |
| 186 |
| 187 const base::ListValue* preload_entries = nullptr; |
| 188 if (!dict_value->GetList("entries", &preload_entries)) { |
| 189 errors->push_back("Could not parse the entries in the input JSON"); |
| 190 return false; |
| 191 } |
| 192 |
| 193 for (size_t i = 0; i < preload_entries->GetSize(); ++i) { |
| 194 const base::DictionaryValue* parsed = nullptr; |
| 195 if (!preload_entries->GetDictionary(i, &parsed)) { |
| 196 errors->push_back("Could not parse entry " + ToString(i) + |
| 197 " in the input JSON"); |
| 198 return false; |
| 199 } |
| 200 |
| 201 std::unique_ptr<TransportSecurityStateEntry> entry( |
| 202 new TransportSecurityStateEntry()); |
| 203 |
| 204 if (!parsed->GetString("name", &entry->hostname)) { |
| 205 errors->push_back("Could not extract the hostname for entry " + |
| 206 ToString(i) + " from the input JSON"); |
| 207 return false; |
| 208 } |
| 209 |
| 210 parsed->GetBoolean("include_subdomains", &entry->include_subdomains); |
| 211 std::string mode; |
| 212 parsed->GetString("mode", &mode); |
| 213 entry->force_https = (mode == "force-https"); |
| 214 parsed->GetBoolean("include_subdomains_for_pinning", |
| 215 &entry->hpkp_include_subdomains); |
| 216 parsed->GetString("pins", &entry->pinset); |
| 217 parsed->GetBoolean("expect_ct", &entry->expect_ct); |
| 218 parsed->GetString("expect_ct_report_uri", &entry->expect_ct_report_uri); |
| 219 parsed->GetBoolean("expect_staple", &entry->expect_staple); |
| 220 parsed->GetBoolean("include_subdomains_for_expect_staple", |
| 221 &entry->expect_staple_include_subdomains); |
| 222 parsed->GetString("expect_staple_report_uri", |
| 223 &entry->expect_staple_report_uri); |
| 224 |
| 225 entries->push_back(std::move(entry)); |
| 226 } |
| 227 |
| 228 const base::ListValue* pinsets_list = nullptr; |
| 229 if (!dict_value->GetList("pinsets", &pinsets_list)) { |
| 230 errors->push_back("Could not parse the pinsets in the input JSON"); |
| 231 return false; |
| 232 } |
| 233 |
| 234 for (size_t i = 0; i < pinsets_list->GetSize(); ++i) { |
| 235 const base::DictionaryValue* parsed = nullptr; |
| 236 if (!pinsets_list->GetDictionary(i, &parsed)) { |
| 237 errors->push_back("Could not parse pinset " + ToString(i) + |
| 238 " in the input JSON"); |
| 239 return false; |
| 240 } |
| 241 |
| 242 std::string name; |
| 243 if (!parsed->GetString("name", &name)) { |
| 244 errors->push_back("Could not extract the name for pinset " + ToString(i) + |
| 245 " from the input JSON"); |
| 246 return false; |
| 247 } |
| 248 |
| 249 std::string report_uri; |
| 250 parsed->GetString("report_uri", &report_uri); |
| 251 |
| 252 std::unique_ptr<Pinset> pinset(new Pinset(name, report_uri)); |
| 253 |
| 254 const base::ListValue* pinset_static_hashes_list = nullptr; |
| 255 if (parsed->GetList("static_spki_hashes", &pinset_static_hashes_list)) { |
| 256 for (size_t i = 0; i < pinset_static_hashes_list->GetSize(); ++i) { |
| 257 std::string hash; |
| 258 pinset_static_hashes_list->GetString(i, &hash); |
| 259 pinset->AddStaticSPKIHash(hash); |
| 260 } |
| 261 } |
| 262 |
| 263 const base::ListValue* pinset_bad_static_hashes_list = nullptr; |
| 264 if (parsed->GetList("bad_static_spki_hashes", |
| 265 &pinset_bad_static_hashes_list)) { |
| 266 for (size_t i = 0; i < pinset_bad_static_hashes_list->GetSize(); ++i) { |
| 267 std::string hash; |
| 268 pinset_bad_static_hashes_list->GetString(i, &hash); |
| 269 pinset->AddBadStaticSPKIHash(hash); |
| 270 } |
| 271 } |
| 272 |
| 273 pinsets->RegisterPinset(std::move(pinset)); |
| 274 } |
| 275 |
| 276 // TODO(Martijnc): Remove the domain IDs from the preload format. |
| 277 // https://crbug.com/661206. |
| 278 const base::ListValue* domain_ids_list = nullptr; |
| 279 if (!dict_value->GetList("domain_ids", &domain_ids_list)) { |
| 280 errors->push_back("Could not parse the domain IDs in the input JSON"); |
| 281 return false; |
| 282 } |
| 283 |
| 284 for (size_t i = 0; i < domain_ids_list->GetSize(); ++i) { |
| 285 std::string domain; |
| 286 domain_ids_list->GetString(i, &domain); |
| 287 domain_ids->push_back(domain); |
| 288 } |
| 289 |
| 290 return true; |
| 291 } |
| 292 |
| 293 bool ParseCertificatesFile(const std::string& certs_input, |
| 294 Pinsets* pinsets, |
| 295 std::vector<std::string>* errors) { |
| 296 std::istringstream input_stream(certs_input); |
| 297 std::string line; |
| 298 CertificateParserState current_state = CertificateParserState::PRE_NAME; |
| 299 |
| 300 const base::CompareCase& compare_mode = base::CompareCase::INSENSITIVE_ASCII; |
| 301 std::string name; |
| 302 std::string buffer; |
| 303 std::string subject_name; |
| 304 bssl::UniquePtr<X509> certificate; |
| 305 SPKIHash hash; |
| 306 |
| 307 for (std::string line; std::getline(input_stream, line);) { |
| 308 if (line[0] == '#') { |
| 309 continue; |
| 310 } |
| 311 |
| 312 if (line.empty() && current_state == CertificateParserState::PRE_NAME) { |
| 313 continue; |
| 314 } |
| 315 |
| 316 switch (current_state) { |
| 317 case CertificateParserState::PRE_NAME: |
| 318 if (!IsValidName(line)) { |
| 319 errors->push_back("Invalid name in pins file: " + line); |
| 320 return false; |
| 321 } |
| 322 name = line; |
| 323 current_state = CertificateParserState::POST_NAME; |
| 324 break; |
| 325 case CertificateParserState::POST_NAME: |
| 326 if (base::StartsWith(line, kStartOfSHA256, compare_mode)) { |
| 327 if (!hash.FromString(line)) { |
| 328 errors->push_back("Invalid hash value in pins file for " + name); |
| 329 return false; |
| 330 } |
| 331 |
| 332 pinsets->RegisterSPKIHash(name, hash); |
| 333 current_state = CertificateParserState::PRE_NAME; |
| 334 } else if (base::StartsWith(line, kStartOfCert, compare_mode)) { |
| 335 buffer = line + '\n'; |
| 336 current_state = CertificateParserState::IN_CERTIFICATE; |
| 337 } else if (base::StartsWith(line, kStartOfPublicKey, compare_mode)) { |
| 338 buffer = line + '\n'; |
| 339 current_state = CertificateParserState::IN_PUBLIC_KEY; |
| 340 } else { |
| 341 errors->push_back("Invalid value in pins file for " + name); |
| 342 return false; |
| 343 } |
| 344 break; |
| 345 case CertificateParserState::IN_CERTIFICATE: |
| 346 buffer += line + '\n'; |
| 347 if (!base::StartsWith(line, kEndOfCert, compare_mode)) { |
| 348 continue; |
| 349 } |
| 350 |
| 351 certificate = GetX509CertificateFromPEM(buffer); |
| 352 if (!certificate) { |
| 353 errors->push_back("Could not parse certificate " + name); |
| 354 return false; |
| 355 } |
| 356 |
| 357 if (!CalculateSPKIHashFromCertificate(certificate.get(), &hash)) { |
| 358 errors->push_back("Could not extract SPKI from certificate " + name); |
| 359 return false; |
| 360 } |
| 361 |
| 362 if (!ExtractSubjectNameFromCertificate(certificate.get(), |
| 363 &subject_name)) { |
| 364 errors->push_back("Could not extract name from certificate " + name); |
| 365 return false; |
| 366 } |
| 367 |
| 368 if (!MatchCertificateName(subject_name, name, errors)) { |
| 369 errors->push_back(name + " is not a reasonable name for " + |
| 370 subject_name); |
| 371 return false; |
| 372 } |
| 373 |
| 374 pinsets->RegisterSPKIHash(name, hash); |
| 375 current_state = CertificateParserState::PRE_NAME; |
| 376 break; |
| 377 case CertificateParserState::IN_PUBLIC_KEY: |
| 378 buffer += line + '\n'; |
| 379 if (!base::StartsWith(line, kEndOfPublicKey, compare_mode)) { |
| 380 continue; |
| 381 } |
| 382 |
| 383 if (!CalculateSPKIHashFromKey(buffer, &hash)) { |
| 384 errors->push_back("Parsing of the public key " + name + " failed"); |
| 385 return false; |
| 386 } |
| 387 |
| 388 pinsets->RegisterSPKIHash(name, hash); |
| 389 current_state = CertificateParserState::PRE_NAME; |
| 390 break; |
| 391 default: |
| 392 DCHECK(false) << "Unknown parser state"; |
| 393 } |
| 394 } |
| 395 |
| 396 return true; |
| 397 } |
| 398 |
| 399 } // namespace transport_security_state |
| 400 |
| 401 } // namespace net |
OLD | NEW |