blob: 0070b6e00860e97d3c4e98a51129027406e39c02 [file] [log] [blame]
// Copyright (c) 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/tools/transport_security_state_generator/preloaded_state_generator.h"
#include <string>
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "net/tools/huffman_trie/huffman/huffman_builder.h"
#include "net/tools/transport_security_state_generator/cert_util.h"
#include "net/tools/transport_security_state_generator/spki_hash.h"
namespace net {
namespace transport_security_state {
namespace {
static const char kNewLine[] = "\n";
static const char kIndent[] = " ";
std::string FormatSPKIName(const std::string& name) {
return "kSPKIHash_" + name;
}
std::string FormatAcceptedKeyName(const std::string& name) {
return "k" + name + "AcceptableCerts";
}
std::string FormatRejectedKeyName(const std::string& name) {
return "k" + name + "RejectedCerts";
}
std::string FormatReportURIName(const std::string& name) {
return "k" + name + "ReportURI";
}
// Replaces the first occurrence of "[[" + name + "]]" in |*tpl| with
// |value|.
bool ReplaceTag(const std::string& name,
const std::string& value,
std::string* tpl) {
std::string tag = "[[" + name + "]]";
size_t start_pos = tpl->find(tag);
if (start_pos == std::string::npos) {
return false;
}
tpl->replace(start_pos, tag.length(), value);
return true;
}
// Formats the bytes in |bytes| as an C++ array initializer and returns the
// resulting string.
std::string FormatVectorAsArray(const std::vector<uint8_t>& bytes) {
std::string output = "{";
output.append(kNewLine);
output.append(kIndent);
output.append(kIndent);
size_t bytes_on_current_line = 0;
for (size_t i = 0; i < bytes.size(); ++i) {
base::StringAppendF(&output, "0x%02x,", bytes[i]);
bytes_on_current_line++;
if (bytes_on_current_line >= 12 && i + 1 < bytes.size()) {
output.append(kNewLine);
output.append(kIndent);
output.append(kIndent);
bytes_on_current_line = 0;
} else if (i + 1 < bytes.size()) {
output.append(" ");
}
}
output.append(kNewLine);
output.append("}");
return output;
}
std::string WritePinsetList(const std::string& name,
const std::vector<std::string>& pins) {
std::string output = "static const char* const " + name + "[] = {";
output.append(kNewLine);
for (const auto& pin_name : pins) {
output.append(kIndent);
output.append(kIndent);
output.append(FormatSPKIName(pin_name));
output.append(",");
output.append(kNewLine);
}
output.append(kIndent);
output.append(kIndent);
output.append("nullptr,");
output.append(kNewLine);
output.append("};");
return output;
}
huffman_trie::HuffmanRepresentationTable ApproximateHuffman(
const TransportSecurityStateEntries& entries) {
huffman_trie::HuffmanBuilder huffman_builder;
for (const auto& entry : entries) {
for (const auto& c : entry->hostname) {
huffman_builder.RecordUsage(c);
}
huffman_builder.RecordUsage(huffman_trie::kTerminalValue);
huffman_builder.RecordUsage(huffman_trie::kEndOfTableValue);
}
return huffman_builder.ToTable();
}
} // namespace
PreloadedStateGenerator::PreloadedStateGenerator() = default;
PreloadedStateGenerator::~PreloadedStateGenerator() = default;
std::string PreloadedStateGenerator::Generate(
const std::string& preload_template,
const TransportSecurityStateEntries& entries,
const Pinsets& pinsets) {
std::string output = preload_template;
ProcessSPKIHashes(pinsets, &output);
NameIDMap expect_ct_report_uri_map;
ProcessExpectCTURIs(entries, &expect_ct_report_uri_map, &output);
NameIDMap pinsets_map;
ProcessPinsets(pinsets, &pinsets_map, &output);
std::vector<std::unique_ptr<TransportSecurityStateTrieEntry>> trie_entries;
std::vector<huffman_trie::TrieEntry*> raw_trie_entries;
for (const auto& entry : entries) {
std::unique_ptr<TransportSecurityStateTrieEntry> trie_entry(
new TransportSecurityStateTrieEntry(expect_ct_report_uri_map,
pinsets_map, entry.get()));
raw_trie_entries.push_back(trie_entry.get());
trie_entries.push_back(std::move(trie_entry));
}
// The trie generation process is ran twice, the first time using an
// approximate Huffman table. During this first run, the correct character
// frequencies are collected which are then used to calculate the most space
// efficient Huffman table for the given inputs. This table is used for the
// second run.
huffman_trie::HuffmanRepresentationTable table = ApproximateHuffman(entries);
huffman_trie::HuffmanBuilder huffman_builder;
huffman_trie::TrieWriter writer(table, &huffman_builder);
uint32_t root_position;
if (!writer.WriteEntries(raw_trie_entries, &root_position)) {
return std::string();
}
huffman_trie::HuffmanRepresentationTable optimal_table =
huffman_builder.ToTable();
huffman_trie::TrieWriter new_writer(optimal_table, nullptr);
if (!new_writer.WriteEntries(raw_trie_entries, &root_position)) {
return std::string();
}
uint32_t new_length = new_writer.position();
std::vector<uint8_t> huffman_tree = huffman_builder.ToVector();
new_writer.Flush();
ReplaceTag("HUFFMAN_TREE", FormatVectorAsArray(huffman_tree), &output);
ReplaceTag("HSTS_TRIE", FormatVectorAsArray(new_writer.bytes()), &output);
ReplaceTag("HSTS_TRIE_BITS", base::NumberToString(new_length), &output);
ReplaceTag("HSTS_TRIE_ROOT", base::NumberToString(root_position), &output);
return output;
}
void PreloadedStateGenerator::ProcessSPKIHashes(const Pinsets& pinset,
std::string* tpl) {
std::string output;
const SPKIHashMap& hashes = pinset.spki_hashes();
for (const auto& current : hashes) {
const std::string& name = current.first;
const SPKIHash& hash = current.second;
output.append("static const char " + FormatSPKIName(name) + "[] =");
output.append(kNewLine);
for (size_t i = 0; i < hash.size() / 16; ++i) {
output.append(kIndent);
output.append(kIndent);
output.append("\"");
for (size_t j = i * 16; j < ((i + 1) * 16); ++j) {
base::StringAppendF(&output, "\\x%02x", hash.data()[j]);
}
output.append("\"");
if (i + 1 == hash.size() / 16) {
output.append(";");
}
output.append(kNewLine);
}
output.append(kNewLine);
}
base::TrimString(output, kNewLine, &output);
ReplaceTag("SPKI_HASHES", output, tpl);
}
void PreloadedStateGenerator::ProcessExpectCTURIs(
const TransportSecurityStateEntries& entries,
NameIDMap* expect_ct_report_uri_map,
std::string* tpl) {
std::string output = "{";
output.append(kNewLine);
for (const auto& entry : entries) {
const std::string& url = entry->expect_ct_report_uri;
if (entry->expect_ct && url.size() &&
expect_ct_report_uri_map->find(url) ==
expect_ct_report_uri_map->cend()) {
output.append(kIndent);
output.append(kIndent);
output.append("\"" + entry->expect_ct_report_uri + "\",");
output.append(kNewLine);
expect_ct_report_uri_map->insert(
NameIDPair(entry->expect_ct_report_uri,
static_cast<uint32_t>(expect_ct_report_uri_map->size())));
}
}
output.append(kIndent);
output.append(kIndent);
output.append("nullptr,");
output.append(kNewLine);
output.append("}");
ReplaceTag("EXPECT_CT_REPORT_URIS", output, tpl);
}
void PreloadedStateGenerator::ProcessPinsets(const Pinsets& pinset,
NameIDMap* pinset_map,
std::string* tpl) {
std::string certs_output;
std::string pinsets_output = "{";
pinsets_output.append(kNewLine);
const PinsetMap& pinsets = pinset.pinsets();
for (const auto& current : pinsets) {
const std::unique_ptr<Pinset>& pinset = current.second;
std::string uppercased_name = pinset->name();
uppercased_name[0] = base::ToUpperASCII(uppercased_name[0]);
const std::string& accepted_pins_names =
FormatAcceptedKeyName(uppercased_name);
certs_output.append(
WritePinsetList(accepted_pins_names, pinset->static_spki_hashes()));
certs_output.append(kNewLine);
std::string rejected_pins_names = "kNoRejectedPublicKeys";
if (pinset->bad_static_spki_hashes().size()) {
rejected_pins_names = FormatRejectedKeyName(uppercased_name);
certs_output.append(WritePinsetList(rejected_pins_names,
pinset->bad_static_spki_hashes()));
certs_output.append(kNewLine);
}
std::string report_uri = "kNoReportURI";
if (pinset->report_uri().size()) {
report_uri = FormatReportURIName(uppercased_name);
certs_output.append("static const char " + report_uri + "[] = ");
certs_output.append("\"");
certs_output.append(pinset->report_uri());
certs_output.append("\";");
certs_output.append(kNewLine);
}
certs_output.append(kNewLine);
pinsets_output.append(kIndent);
pinsets_output.append(kIndent);
pinsets_output.append("{" + accepted_pins_names + ", " +
rejected_pins_names + ", " + report_uri + "},");
pinsets_output.append(kNewLine);
pinset_map->insert(
NameIDPair(pinset->name(), static_cast<uint32_t>(pinset_map->size())));
}
pinsets_output.append("}");
base::TrimString(certs_output, kNewLine, &certs_output);
ReplaceTag("ACCEPTABLE_CERTS", certs_output, tpl);
ReplaceTag("PINSETS", pinsets_output, tpl);
}
} // namespace transport_security_state
} // namespace net