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

Unified Diff: net/tools/transport_security_state_generator/transport_security_state_generator.cc

Issue 2681733008: Improve error handling of the transport security state generator. (Closed)
Patch Set: 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 side-by-side diff with in-line comments
Download patch
Index: net/tools/transport_security_state_generator/transport_security_state_generator.cc
diff --git a/net/tools/transport_security_state_generator/transport_security_state_generator.cc b/net/tools/transport_security_state_generator/transport_security_state_generator.cc
index 8473414876fc2d9cb5e0af18dbc0d26f07ea49cd..321cc2055f47db351782401aefb468bc5fb9d662 100644
--- a/net/tools/transport_security_state_generator/transport_security_state_generator.cc
+++ b/net/tools/transport_security_state_generator/transport_security_state_generator.cc
@@ -37,12 +37,29 @@ using net::transport_security_state::SPKIHash;
namespace {
+using ErrorList = std::vector<std::string>;
Ryan Sleevi 2017/02/14 20:53:17 Why error list instead of VLOG?
martijnc 2017/02/15 22:07:59 With this I can print the errors directly to the c
Ryan Sleevi 2017/02/16 01:50:40 Windows wouldn't require Sawbuck here - VLOG will
martijnc 2017/02/16 18:02:46 I want to avoid having multiple places/files that
+
// Print the command line help.
void PrintHelp() {
std::cout << "transport_security_state_generator <json-file> <pins-file>"
<< " <template-file> <output-file> [-v]" << std::endl;
}
+// Writes all error messages in |errors| to the output stream for errors.
+void PrintErrors(const ErrorList& errors) {
+ for (const auto& error : errors) {
+ std::cerr << error << std::endl;
+ }
+}
+
+// Converts a number to a string.
+// TODO(Martijnc): Remove once std::to_string is allowed.
+std::string ToString(size_t number) {
+ std::stringstream s;
+ s << number;
+ return s.str();
+}
Ryan Sleevi 2017/02/14 20:53:17 This definitely feels like a cheat around the styl
martijnc 2017/02/15 22:07:59 Thanks, fixed.
+
// Parses the |json| string and copies the items under the "entries" key to
// |entries|, the pinsets under the "pinsets" key to |pinsets|, and the domain
// IDs under the "domain_ids" key to |domain_ids|.
@@ -52,33 +69,37 @@ void PrintHelp() {
bool ParseJSON(const std::string& json,
TransportSecurityStateEntries* entries,
Pinsets* pinsets,
- DomainIDList* domain_ids) {
+ DomainIDList* domain_ids,
+ ErrorList* errors) {
+ bool success = true;
std::unique_ptr<base::Value> value = base::JSONReader::Read(json);
base::DictionaryValue* dict_value = nullptr;
if (!value.get() || !value->GetAsDictionary(&dict_value)) {
- std::cerr << "Could not parse the input JSON" << std::endl;
+ errors->push_back("Could not parse the input JSON file");
return false;
}
const base::ListValue* preload_entries = nullptr;
if (!dict_value->GetList("entries", &preload_entries)) {
- std::cerr << "Could not parse the entries in the input JSON" << std::endl;
+ errors->push_back("Could not parse the entries in the input JSON");
return false;
}
for (size_t i = 0; i < preload_entries->GetSize(); ++i) {
const base::DictionaryValue* parsed = nullptr;
if (!preload_entries->GetDictionary(i, &parsed)) {
- std::cerr << "Could not parse entry " << i << std::endl;
- return false;
+ errors->push_back("Could not parse entry " + ToString(i) +
+ " in the input JSON");
+ success = false;
Ryan Sleevi 2017/02/14 20:53:17 Why do you keep processing when success is false?
martijnc 2017/02/15 22:07:59 I wanted to print a list of all non-fatal errors r
}
std::unique_ptr<TransportSecurityStateEntry> entry(
new TransportSecurityStateEntry());
if (!parsed->GetString("name", &entry->hostname)) {
- std::cerr << "Could not extract the name for entry " << i << std::endl;
- return false;
+ errors->push_back("Could not extract the hostname for entry " +
+ ToString(i) + " from the input JSON");
+ success = false;
Ryan Sleevi 2017/02/14 20:53:17 Why do you keep processing when success is false?
martijnc 2017/02/15 22:07:59 Removed (as above).
}
parsed->GetBoolean("include_subdomains", &entry->include_subdomains);
@@ -101,21 +122,23 @@ bool ParseJSON(const std::string& json,
const base::ListValue* pinsets_list = nullptr;
if (!dict_value->GetList("pinsets", &pinsets_list)) {
- std::cerr << "Could not parse the pinsets in the input JSON" << std::endl;
+ errors->push_back("Could not parse the pinsets in the input JSON");
return false;
}
for (size_t i = 0; i < pinsets_list->GetSize(); ++i) {
const base::DictionaryValue* parsed = nullptr;
if (!pinsets_list->GetDictionary(i, &parsed)) {
- std::cerr << "Could not parse pinset " << i << std::endl;
- return false;
+ errors->push_back("Could not parse pinset " + ToString(i) +
+ " in the input JSON");
+ success = false;
}
std::string name;
if (!parsed->GetString("name", &name)) {
- std::cerr << "Could not extract the name for pinset " << i << std::endl;
- return false;
+ errors->push_back("Could not extract the name for pinset " + ToString(i) +
+ " from the input JSON");
+ success = false;
}
std::string report_uri;
@@ -149,7 +172,7 @@ bool ParseJSON(const std::string& json,
// https://crbug.com/661206.
const base::ListValue* domain_ids_list = nullptr;
if (!dict_value->GetList("domain_ids", &domain_ids_list)) {
- std::cerr << "Failed parsing JSON (domain_ids)" << std::endl;
+ errors->push_back("Could not parse the domain IDs in the input JSON");
return false;
}
@@ -159,7 +182,7 @@ bool ParseJSON(const std::string& json,
domain_ids->push_back(domain);
}
- return true;
+ return success;
}
bool IsImportantWordInCertificateName(base::StringPiece name) {
@@ -189,11 +212,14 @@ std::string FilterName(base::StringPiece name) {
// Returns true if |pin_name| is a reasonable match for the certificate name
// |name|.
-bool MatchCertificateName(base::StringPiece name, base::StringPiece pin_name) {
+bool MatchCertificateName(base::StringPiece name,
+ base::StringPiece pin_name,
+ ErrorList* errors) {
std::vector<base::StringPiece> words = base::SplitStringPiece(
name, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
if (words.empty()) {
- std::cerr << "no words in certificate name" << std::endl;
+ errors->push_back("No words in certificate name for pin (" +
+ pin_name.as_string() + ")");
return false;
}
base::StringPiece first_word = words[0];
@@ -217,7 +243,8 @@ bool MatchCertificateName(base::StringPiece name, base::StringPiece pin_name) {
}
if (first_word.empty()) {
- std::cerr << "first word of certificate name is empty" << std::endl;
+ errors->push_back("First word of certificate name (" + name.as_string() +
+ ") is empty");
return false;
}
@@ -225,10 +252,9 @@ bool MatchCertificateName(base::StringPiece name, base::StringPiece pin_name) {
first_word = filtered_word;
if (!base::EqualsCaseInsensitiveASCII(pin_name.substr(0, first_word.size()),
first_word)) {
- std::cerr << "the first word of the certificate name ("
- << first_word.as_string()
- << ") isn't a prefix of the variable name ("
- << pin_name.as_string() << ")" << std::endl;
+ errors->push_back(
+ "The first word of the certificate name (" + first_word.as_string() +
+ ") isn't a prefix of the variable name (" + pin_name.as_string() + ")");
return false;
}
@@ -240,21 +266,26 @@ bool MatchCertificateName(base::StringPiece name, base::StringPiece pin_name) {
size_t pos = pin_name.find(class_name);
if (pos == std::string::npos) {
- std::cerr << "class specification doesn't appear in the variable name"
- << std::endl;
+ errors->push_back(
+ "Certficate class specification doesn't appear in the variable "
+ "name (" +
+ pin_name.as_string() + ")");
return false;
}
} else if (word.size() == 1 && word[0] >= '0' && word[0] <= '9') {
size_t pos = pin_name.find(word);
if (pos == std::string::npos) {
- std::cerr << "number doesn't appear in the variable name" << std::endl;
+ errors->push_back(
+ "Number doesn't appear in the certificate variable name (" +
+ pin_name.as_string() + ")");
return false;
}
} else if (IsImportantWordInCertificateName(word)) {
size_t pos = pin_name.find(word);
if (pos == std::string::npos) {
- std::cerr << word.as_string() << " doesn't appear in the variable name"
- << std::endl;
+ errors->push_back(word.as_string() +
+ " doesn't appear in the certificate variable name (" +
+ pin_name.as_string() + ")");
return false;
}
}
@@ -300,7 +331,9 @@ enum class CertificateParserState {
//
// More info on the format can be found in
// net/http/transport_security_state_static.pins
-bool ParseCertificatesFile(const std::string& certs_input, Pinsets* pinsets) {
+bool ParseCertificatesFile(const std::string& certs_input,
+ Pinsets* pinsets,
+ ErrorList* errors) {
std::istringstream input_stream(certs_input);
std::string line;
CertificateParserState current_state = CertificateParserState::PRE_NAME;
@@ -324,7 +357,7 @@ bool ParseCertificatesFile(const std::string& certs_input, Pinsets* pinsets) {
switch (current_state) {
case CertificateParserState::PRE_NAME:
if (!IsValidName(line)) {
- std::cerr << "Invalid name in certificates file: " << line;
+ errors->push_back("Invalid name in pins file: " + line);
return false;
}
name = line;
@@ -333,8 +366,7 @@ bool ParseCertificatesFile(const std::string& certs_input, Pinsets* pinsets) {
case CertificateParserState::POST_NAME:
if (base::StartsWith(line, kStartOfSHA256, compare_mode)) {
if (!hash.FromString(line)) {
- std::cerr << "Invalid hash value in certificate file for " << name
- << std::endl;
+ errors->push_back("Invalid hash value in pins file for " + name);
return false;
}
@@ -347,8 +379,7 @@ bool ParseCertificatesFile(const std::string& certs_input, Pinsets* pinsets) {
buffer = line + '\n';
current_state = CertificateParserState::IN_PUBLIC_KEY;
} else {
- std::cerr << "Invalid value in certificates file for " << name
- << std::endl;
+ errors->push_back("Invalid value in pins file for " + name);
return false;
}
break;
@@ -360,26 +391,24 @@ bool ParseCertificatesFile(const std::string& certs_input, Pinsets* pinsets) {
certificate = GetX509CertificateFromPEM(buffer);
if (!certificate) {
- std::cerr << "Could not parse certificate " << name << std::endl;
+ errors->push_back("Could not parse certificate " + name);
return false;
}
if (!CalculateSPKIHashFromCertificate(certificate.get(), &hash)) {
- std::cerr << "Could not extract SPKI from certificate " << name
- << std::endl;
+ errors->push_back("Could not extract SPKI from certificate " + name);
return false;
}
if (!ExtractSubjectNameFromCertificate(certificate.get(),
&subject_name)) {
- std::cerr << "Could not extract name from certificate " << name
- << std::endl;
+ errors->push_back("Could not extract name from certificate " + name);
return false;
}
- if (!MatchCertificateName(subject_name, name)) {
- std::cerr << name << " is not a reasonable name for " << subject_name
- << std::endl;
+ if (!MatchCertificateName(subject_name, name, errors)) {
+ errors->push_back(name + " is not a reasonable name for " +
+ subject_name);
return false;
}
@@ -393,8 +422,7 @@ bool ParseCertificatesFile(const std::string& certs_input, Pinsets* pinsets) {
}
if (!CalculateSPKIHashFromKey(buffer, &hash)) {
- std::cerr << "Parsing of the public key " << name << " failed"
- << std::endl;
+ errors->push_back("Could not parse the public key for " + name);
return false;
}
@@ -410,14 +438,15 @@ bool ParseCertificatesFile(const std::string& certs_input, Pinsets* pinsets) {
}
// Checks if there are pins with the same name or the same hash.
-bool CheckForDuplicatePins(const Pinsets& pinsets) {
+bool CheckForDuplicatePins(const Pinsets& pinsets, ErrorList* errors) {
+ bool success = true;
std::set<std::string> seen_names;
std::map<std::string, std::string> seen_hashes;
for (const auto& pin : pinsets.spki_hashes()) {
if (seen_names.find(pin.first) != seen_names.cend()) {
- std::cerr << "Duplicate pin name " << pin.first << std::endl;
- return false;
+ errors->push_back("Duplicate pin name " + pin.first + " in pins file");
+ success = false;
}
seen_names.insert(pin.first);
@@ -425,19 +454,20 @@ bool CheckForDuplicatePins(const Pinsets& pinsets) {
std::string(pin.second.data(), pin.second.data() + pin.second.size());
std::map<std::string, std::string>::iterator it = seen_hashes.find(hash);
if (it != seen_hashes.cend()) {
- std::cerr << "Duplicate pin hash for " << pin.first
- << ", already seen as " << it->second << std::endl;
- return false;
+ errors->push_back("Duplicate pin hash for " + pin.first +
+ ", already seen as " + it->second);
+ success = false;
}
seen_hashes.insert(std::pair<std::string, std::string>(hash, pin.first));
}
- return true;
+ return success;
}
// Checks if there are pinsets that reference non-existing pins, if two
// pinsets share the same name, or if there are unused pins.
-bool CheckCertificatesInPinsets(const Pinsets& pinsets) {
+bool CheckCertificatesInPinsets(const Pinsets& pinsets, ErrorList* errors) {
+ bool success = true;
std::set<std::string> pin_names;
for (const auto& pin : pinsets.spki_hashes()) {
pin_names.insert(pin.first);
@@ -447,9 +477,8 @@ bool CheckCertificatesInPinsets(const Pinsets& pinsets) {
std::set<std::string> pinset_names;
for (const auto& pinset : pinsets.pinsets()) {
if (pinset_names.find(pinset.second->name()) != pinset_names.cend()) {
- std::cerr << "Duplicate pinset name " << pinset.second->name()
- << std::endl;
- return false;
+ errors->push_back("Duplicate pinset name " + pinset.second->name());
+ success = false;
}
pinset_names.insert(pinset.second->name());
@@ -467,8 +496,10 @@ bool CheckCertificatesInPinsets(const Pinsets& pinsets) {
for (const auto& pin_name : all_pin_names) {
if (pin_names.find(pin_name) == pin_names.cend()) {
- std::cerr << "Unknown pin: " << pin_name << std::endl;
- return false;
+ errors->push_back("Pinset " + pinset.second->name() +
+ " references pin " + pin_name +
+ " which doesn't exist");
+ success = false;
}
used_pin_names.insert(pin_name);
}
@@ -476,29 +507,33 @@ bool CheckCertificatesInPinsets(const Pinsets& pinsets) {
for (const auto& pin_name : pin_names) {
if (used_pin_names.find(pin_name) == used_pin_names.cend()) {
- std::cerr << "Unused pin: " << pin_name << std::endl;
- return false;
+ errors->push_back("Pin " + pin_name + " is unused.");
+ success = false;
}
}
- return true;
+ return success;
}
// Checks if there are two or more entries for the same hostname.
-bool CheckDuplicateEntries(const TransportSecurityStateEntries& entries) {
+bool CheckDuplicateEntries(const TransportSecurityStateEntries& entries,
+ ErrorList* errors) {
+ bool success = true;
std::set<std::string> seen_entries;
for (const auto& entry : entries) {
if (seen_entries.find(entry->hostname) != seen_entries.cend()) {
- std::cerr << "Duplicate entry for " << entry->hostname << std::endl;
- return false;
+ errors->push_back("Duplicate entry for " + entry->hostname);
+ success = false;
}
seen_entries.insert(entry->hostname);
}
- return true;
+ return success;
}
// Checks for entries which have no effect.
-bool CheckNoopEntries(const TransportSecurityStateEntries& entries) {
+bool CheckNoopEntries(const TransportSecurityStateEntries& entries,
+ ErrorList* errors) {
+ bool success = true;
for (const auto& entry : entries) {
if (!entry->force_https && entry->pinset.empty() && !entry->expect_ct &&
!entry->expect_staple) {
@@ -507,28 +542,28 @@ bool CheckNoopEntries(const TransportSecurityStateEntries& entries) {
continue;
}
- std::cerr
- << "Entry for " + entry->hostname +
- " has no mode, no pins and is not expect-CT or expect-staple"
- << std::endl;
- return false;
+ errors->push_back(
+ "Entry for " + entry->hostname +
+ " has no mode, no pins and is not expect-CT or expect-staple");
+ success = false;
}
}
- return true;
+ return success;
}
// Checks all entries for incorrect usage of the includeSubdomains flags.
-bool CheckSubdomainsFlags(const TransportSecurityStateEntries& entries) {
+bool CheckSubdomainsFlags(const TransportSecurityStateEntries& entries,
+ ErrorList* errors) {
+ bool success = true;
for (const auto& entry : entries) {
if (entry->include_subdomains && entry->hpkp_include_subdomains) {
- std::cerr << "Entry for \"" << entry->hostname
- << "\" sets include_subdomains_for_pinning but also sets "
- "include_subdomains, which implies it"
- << std::endl;
- return false;
+ errors->push_back("Entry for " + entry->hostname +
+ " sets include_subdomains_for_pinning but also sets "
+ "include_subdomains, which implies it");
+ success = false;
}
}
- return true;
+ return success;
}
} // namespace
@@ -554,8 +589,6 @@ int main(int argc, char* argv[]) {
return 1;
}
- bool verbose = command_line.HasSwitch("v");
-
base::FilePath json_filepath = base::FilePath::FromUTF8Unsafe(argv[1]);
if (!base::PathExists(json_filepath)) {
std::cerr << "Input JSON file doesn't exist." << std::endl;
@@ -586,19 +619,21 @@ int main(int argc, char* argv[]) {
Pinsets pinsets;
DomainIDList domain_ids;
- if (!ParseCertificatesFile(certs_input, &pinsets)) {
- std::cerr << "Error while parsing the pins file." << std::endl;
- return 1;
- }
- if (!ParseJSON(json_input, &entries, &pinsets, &domain_ids)) {
- std::cerr << "Error while parsing the JSON file." << std::endl;
+ ErrorList errors;
+ if (!ParseCertificatesFile(certs_input, &pinsets, &errors) ||
+ !ParseJSON(json_input, &entries, &pinsets, &domain_ids, &errors)) {
+ std::cerr << "Error while parsing the input files." << std::endl;
+ PrintErrors(errors);
return 1;
}
- if (!CheckDuplicateEntries(entries) || !CheckNoopEntries(entries) ||
- !CheckSubdomainsFlags(entries) || !CheckForDuplicatePins(pinsets) ||
- !CheckCertificatesInPinsets(pinsets)) {
+ if (!CheckDuplicateEntries(entries, &errors) ||
+ !CheckNoopEntries(entries, &errors) ||
+ !CheckSubdomainsFlags(entries, &errors) ||
+ !CheckForDuplicatePins(pinsets, &errors) ||
+ !CheckCertificatesInPinsets(pinsets, &errors)) {
std::cerr << "Checks failed. Aborting." << std::endl;
+ PrintErrors(errors);
return 1;
}
@@ -615,19 +650,29 @@ int main(int argc, char* argv[]) {
return 1;
}
- std::string result;
+ std::string output;
PreloadedStateGenerator generator;
- result = generator.Generate(preload_template, entries, domain_ids, pinsets,
- verbose);
+ if (!generator.Generate(preload_template, entries, domain_ids, pinsets,
+ &output)) {
+ std::cerr << "Trie generation failed." << std::endl;
+ return 1;
+ }
base::FilePath output_path;
output_path = base::FilePath::FromUTF8Unsafe(argv[4]);
- if (base::WriteFile(output_path, result.c_str(),
- static_cast<uint32_t>(result.size())) <= 0) {
+ if (base::WriteFile(output_path, output.c_str(),
+ static_cast<uint32_t>(output.size())) <= 0) {
std::cerr << "Failed to write output." << std::endl;
return 1;
}
+ if (command_line.HasSwitch("v")) {
+ std::cout << "Wrote trie containing " << entries.size()
+ << " entries, referencing " << pinsets.size() << " pinsets and "
+ << domain_ids.size() << " domain IDs to "
+ << output_path.AsUTF8Unsafe() << std::endl;
+ }
+
return 0;
}

Powered by Google App Engine
This is Rietveld 408576698