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