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

Side by Side Diff: net/tools/transport_security_state_generator/input_file_parsers.cc

Issue 2660793002: Add transport security state generator tests. (Closed)
Patch Set: export method for tests Created 3 years, 10 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
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698