1 // Copyright 2016 The Chromium Authors
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/preloaded_state_generator.h"
6
7 #include <string>
8
9 #include "base/strings/string_number_conversions.h"
10 #include "base/strings/string_util.h"
11 #include "base/strings/stringprintf.h"
12 #include "net/tools/huffman_trie/huffman/huffman_builder.h"
13 #include "net/tools/transport_security_state_generator/cert_util.h"
14 #include "net/tools/transport_security_state_generator/spki_hash.h"
15
16 namespace net::transport_security_state {
17
18 namespace {
19
20 static const char kNewLine[] = "\n";
21 static const char kIndent[] = " ";
22
FormatSPKIName(const std::string & name)23 std::string FormatSPKIName(const std::string& name) {
24 return "kSPKIHash_" + name;
25 }
26
FormatAcceptedKeyName(const std::string & name)27 std::string FormatAcceptedKeyName(const std::string& name) {
28 return "k" + name + "AcceptableCerts";
29 }
30
FormatRejectedKeyName(const std::string & name)31 std::string FormatRejectedKeyName(const std::string& name) {
32 return "k" + name + "RejectedCerts";
33 }
34
FormatReportURIName(const std::string & name)35 std::string FormatReportURIName(const std::string& name) {
36 return "k" + name + "ReportURI";
37 }
38
39 // Replaces the first occurrence of "[[" + name + "]]" in |*tpl| with
40 // |value|.
ReplaceTag(const std::string & name,const std::string & value,std::string * tpl)41 bool ReplaceTag(const std::string& name,
42 const std::string& value,
43 std::string* tpl) {
44 std::string tag = "[[" + name + "]]";
45
46 size_t start_pos = tpl->find(tag);
47 if (start_pos == std::string::npos) {
48 return false;
49 }
50
51 tpl->replace(start_pos, tag.length(), value);
52 return true;
53 }
54
55 // Formats the bytes in |bytes| as an C++ array initializer and returns the
56 // resulting string.
FormatVectorAsArray(const std::vector<uint8_t> & bytes)57 std::string FormatVectorAsArray(const std::vector<uint8_t>& bytes) {
58 std::string output = "{";
59 output.append(kNewLine);
60 output.append(kIndent);
61 output.append(kIndent);
62
63 size_t bytes_on_current_line = 0;
64
65 for (size_t i = 0; i < bytes.size(); ++i) {
66 base::StringAppendF(&output, "0x%02x,", bytes[i]);
67
68 bytes_on_current_line++;
69 if (bytes_on_current_line >= 12 && i + 1 < bytes.size()) {
70 output.append(kNewLine);
71 output.append(kIndent);
72 output.append(kIndent);
73
74 bytes_on_current_line = 0;
75 } else if (i + 1 < bytes.size()) {
76 output.append(" ");
77 }
78 }
79
80 output.append(kNewLine);
81 output.append("}");
82
83 return output;
84 }
85
WritePinsetList(const std::string & name,const std::vector<std::string> & pins)86 std::string WritePinsetList(const std::string& name,
87 const std::vector<std::string>& pins) {
88 std::string output = "static const char* const " + name + "[] = {";
89 output.append(kNewLine);
90
91 for (const auto& pin_name : pins) {
92 output.append(kIndent);
93 output.append(kIndent);
94 output.append(FormatSPKIName(pin_name));
95 output.append(",");
96 output.append(kNewLine);
97 }
98
99 output.append(kIndent);
100 output.append(kIndent);
101 output.append("nullptr,");
102 output.append(kNewLine);
103 output.append("};");
104
105 return output;
106 }
107
ApproximateHuffman(const TransportSecurityStateEntries & entries)108 huffman_trie::HuffmanRepresentationTable ApproximateHuffman(
109 const TransportSecurityStateEntries& entries) {
110 huffman_trie::HuffmanBuilder huffman_builder;
111 for (const auto& entry : entries) {
112 for (const auto& c : entry->hostname) {
113 huffman_builder.RecordUsage(c);
114 }
115
116 huffman_builder.RecordUsage(huffman_trie::kTerminalValue);
117 huffman_builder.RecordUsage(huffman_trie::kEndOfTableValue);
118 }
119
120 return huffman_builder.ToTable();
121 }
122
123 } // namespace
124
125 PreloadedStateGenerator::PreloadedStateGenerator() = default;
126
127 PreloadedStateGenerator::~PreloadedStateGenerator() = default;
128
Generate(const std::string & preload_template,const TransportSecurityStateEntries & entries,const Pinsets & pinsets,const base::Time & timestamp)129 std::string PreloadedStateGenerator::Generate(
130 const std::string& preload_template,
131 const TransportSecurityStateEntries& entries,
132 const Pinsets& pinsets,
133 const base::Time& timestamp) {
134 std::string output = preload_template;
135
136 ProcessSPKIHashes(pinsets, &output);
137
138 NameIDMap pinsets_map;
139 ProcessPinsets(pinsets, &pinsets_map, &output);
140
141 std::vector<std::unique_ptr<TransportSecurityStateTrieEntry>> trie_entries;
142 std::vector<huffman_trie::TrieEntry*> raw_trie_entries;
143 for (const auto& entry : entries) {
144 auto trie_entry = std::make_unique<TransportSecurityStateTrieEntry>(
145 pinsets_map, entry.get());
146 raw_trie_entries.push_back(trie_entry.get());
147 trie_entries.push_back(std::move(trie_entry));
148 }
149
150 // The trie generation process is ran twice, the first time using an
151 // approximate Huffman table. During this first run, the correct character
152 // frequencies are collected which are then used to calculate the most space
153 // efficient Huffman table for the given inputs. This table is used for the
154 // second run.
155 huffman_trie::HuffmanRepresentationTable table = ApproximateHuffman(entries);
156 huffman_trie::HuffmanBuilder huffman_builder;
157 huffman_trie::TrieWriter writer(table, &huffman_builder);
158 uint32_t root_position;
159 if (!writer.WriteEntries(raw_trie_entries, &root_position)) {
160 return std::string();
161 }
162
163 huffman_trie::HuffmanRepresentationTable optimal_table =
164 huffman_builder.ToTable();
165 huffman_trie::TrieWriter new_writer(optimal_table, nullptr);
166
167 if (!new_writer.WriteEntries(raw_trie_entries, &root_position)) {
168 return std::string();
169 }
170
171 uint32_t new_length = new_writer.position();
172 std::vector<uint8_t> huffman_tree = huffman_builder.ToVector();
173 new_writer.Flush();
174
175 ReplaceTag("HUFFMAN_TREE", FormatVectorAsArray(huffman_tree), &output);
176 ReplaceTag("HSTS_TRIE", FormatVectorAsArray(new_writer.bytes()), &output);
177
178 ReplaceTag("HSTS_TRIE_BITS", base::NumberToString(new_length), &output);
179 ReplaceTag("HSTS_TRIE_ROOT", base::NumberToString(root_position), &output);
180
181 ReplaceTag("PINS_LIST_TIMESTAMP", base::NumberToString(timestamp.ToTimeT()),
182 &output);
183
184 return output;
185 }
186
ProcessSPKIHashes(const Pinsets & pinset,std::string * tpl)187 void PreloadedStateGenerator::ProcessSPKIHashes(const Pinsets& pinset,
188 std::string* tpl) {
189 std::string output;
190
191 const SPKIHashMap& hashes = pinset.spki_hashes();
192 for (const auto& current : hashes) {
193 const std::string& name = current.first;
194 const SPKIHash& hash = current.second;
195
196 output.append("static const char " + FormatSPKIName(name) + "[] =");
197 output.append(kNewLine);
198
199 for (size_t i = 0; i < hash.size() / 16; ++i) {
200 output.append(kIndent);
201 output.append(kIndent);
202 output.append("\"");
203
204 for (size_t j = i * 16; j < ((i + 1) * 16); ++j) {
205 base::StringAppendF(&output, "\\x%02x", hash.data()[j]);
206 }
207
208 output.append("\"");
209 if (i + 1 == hash.size() / 16) {
210 output.append(";");
211 }
212 output.append(kNewLine);
213 }
214
215 output.append(kNewLine);
216 }
217
218 base::TrimString(output, kNewLine, &output);
219 ReplaceTag("SPKI_HASHES", output, tpl);
220 }
221
ProcessPinsets(const Pinsets & pinset,NameIDMap * pinset_map,std::string * tpl)222 void PreloadedStateGenerator::ProcessPinsets(const Pinsets& pinset,
223 NameIDMap* pinset_map,
224 std::string* tpl) {
225 std::string certs_output;
226 std::string pinsets_output = "{";
227 pinsets_output.append(kNewLine);
228
229 const PinsetMap& pinsets = pinset.pinsets();
230 for (const auto& current : pinsets) {
231 const std::unique_ptr<Pinset>& pinset_ptr = current.second;
232 std::string uppercased_name = pinset_ptr->name();
233 uppercased_name[0] = base::ToUpperASCII(uppercased_name[0]);
234
235 const std::string& accepted_pins_names =
236 FormatAcceptedKeyName(uppercased_name);
237 certs_output.append(
238 WritePinsetList(accepted_pins_names, pinset_ptr->static_spki_hashes()));
239 certs_output.append(kNewLine);
240
241 std::string rejected_pins_names = "kNoRejectedPublicKeys";
242 if (pinset_ptr->bad_static_spki_hashes().size()) {
243 rejected_pins_names = FormatRejectedKeyName(uppercased_name);
244 certs_output.append(WritePinsetList(
245 rejected_pins_names, pinset_ptr->bad_static_spki_hashes()));
246 certs_output.append(kNewLine);
247 }
248
249 std::string report_uri = "kNoReportURI";
250 if (pinset_ptr->report_uri().size()) {
251 report_uri = FormatReportURIName(uppercased_name);
252 certs_output.append("static const char " + report_uri + "[] = ");
253 certs_output.append("\"");
254 certs_output.append(pinset_ptr->report_uri());
255 certs_output.append("\";");
256 certs_output.append(kNewLine);
257 }
258 certs_output.append(kNewLine);
259
260 pinsets_output.append(kIndent);
261 pinsets_output.append(kIndent);
262 pinsets_output.append("{" + accepted_pins_names + ", " +
263 rejected_pins_names + ", " + report_uri + "},");
264 pinsets_output.append(kNewLine);
265
266 pinset_map->insert(NameIDPair(pinset_ptr->name(),
267 static_cast<uint32_t>(pinset_map->size())));
268 }
269
270 pinsets_output.append("}");
271
272 base::TrimString(certs_output, kNewLine, &certs_output);
273
274 ReplaceTag("ACCEPTABLE_CERTS", certs_output, tpl);
275 ReplaceTag("PINSETS", pinsets_output, tpl);
276 }
277
278 } // namespace net::transport_security_state
279