| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2016 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/domain_security_preload_generator/preloaded_state_generator.
h" | |
| 6 | |
| 7 #include <iostream> | |
| 8 | |
| 9 #include <string> | |
| 10 | |
| 11 #include "base/strings/string_util.h" | |
| 12 #include "base/strings/stringprintf.h" | |
| 13 #include "net/tools/domain_security_preload_generator/cert_util.h" | |
| 14 #include "net/tools/domain_security_preload_generator/huffman/huffman_frequency_
tracker.h" | |
| 15 #include "net/tools/domain_security_preload_generator/spki_hash.h" | |
| 16 | |
| 17 namespace net { | |
| 18 | |
| 19 namespace transport_security_state { | |
| 20 | |
| 21 namespace { | |
| 22 | |
| 23 static const char kNewLine[] = "\n"; | |
| 24 static const char kIndent[] = " "; | |
| 25 | |
| 26 std::string FormatSPKIName(const std::string& name) { | |
| 27 return "kSPKIHash_" + name; | |
| 28 } | |
| 29 | |
| 30 std::string FormatAcceptedKeyName(const std::string& name) { | |
| 31 return "k" + name + "AcceptableCerts"; | |
| 32 } | |
| 33 | |
| 34 std::string FormatRejectedKeyName(const std::string& name) { | |
| 35 return "k" + name + "RejectedCerts"; | |
| 36 } | |
| 37 | |
| 38 std::string FormatReportURIName(const std::string& name) { | |
| 39 return "k" + name + "ReportURI"; | |
| 40 } | |
| 41 | |
| 42 // Replaces the first occurrence of "[[" + name + "]]" in |*tpl| with | |
| 43 // |value|. | |
| 44 bool ReplaceTag(const std::string& name, | |
| 45 const std::string& value, | |
| 46 std::string* tpl) { | |
| 47 std::string tag = "[[" + name + "]]"; | |
| 48 | |
| 49 size_t start_pos = tpl->find(tag); | |
| 50 if (start_pos == std::string::npos) { | |
| 51 return false; | |
| 52 } | |
| 53 | |
| 54 tpl->replace(start_pos, tag.length(), value); | |
| 55 return true; | |
| 56 } | |
| 57 | |
| 58 // Formats the bytes in |bytes| as an C++ array initializer and returns the | |
| 59 // resulting string. | |
| 60 std::string FormatVectorAsArray(const std::vector<uint8_t>& bytes) { | |
| 61 std::string output = "{"; | |
| 62 output.append(kNewLine); | |
| 63 output.append(kIndent); | |
| 64 output.append(kIndent); | |
| 65 | |
| 66 size_t bytes_on_current_line = 0; | |
| 67 | |
| 68 for (size_t i = 0; i < bytes.size(); ++i) { | |
| 69 base::StringAppendF(&output, "0x%02x,", bytes[i]); | |
| 70 | |
| 71 bytes_on_current_line++; | |
| 72 if (bytes_on_current_line >= 12 && i + 1 < bytes.size()) { | |
| 73 output.append(kNewLine); | |
| 74 output.append(kIndent); | |
| 75 output.append(kIndent); | |
| 76 | |
| 77 bytes_on_current_line = 0; | |
| 78 } else if (i + 1 < bytes.size()) { | |
| 79 output.append(" "); | |
| 80 } | |
| 81 } | |
| 82 | |
| 83 output.append(kNewLine); | |
| 84 output.append("}"); | |
| 85 | |
| 86 return output; | |
| 87 } | |
| 88 | |
| 89 std::string WritePinsetList(const std::string& name, | |
| 90 const std::vector<std::string>& pins) { | |
| 91 std::string output = "static const char* const " + name + "[] = {"; | |
| 92 output.append(kNewLine); | |
| 93 | |
| 94 for (const auto& pin_name : pins) { | |
| 95 output.append(kIndent); | |
| 96 output.append(kIndent); | |
| 97 output.append(FormatSPKIName(pin_name)); | |
| 98 output.append(","); | |
| 99 output.append(kNewLine); | |
| 100 } | |
| 101 | |
| 102 output.append(kIndent); | |
| 103 output.append(kIndent); | |
| 104 output.append("NULL,"); | |
| 105 output.append(kNewLine); | |
| 106 output.append("};"); | |
| 107 | |
| 108 return output; | |
| 109 } | |
| 110 | |
| 111 HuffmanRepresentationTable ApproximateHuffman( | |
| 112 const DomainSecurityEntries& entries) { | |
| 113 HuffmanFrequencyTracker tracker; | |
| 114 for (const auto& entry : entries) { | |
| 115 for (const auto& c : entry->hostname) { | |
| 116 tracker.RecordUsage(c); | |
| 117 } | |
| 118 | |
| 119 tracker.RecordUsage(TrieWriter::kTerminalValue); | |
| 120 tracker.RecordUsage(TrieWriter::kEndOfTableValue); | |
| 121 } | |
| 122 | |
| 123 return tracker.ToTable(); | |
| 124 } | |
| 125 | |
| 126 } // namespace | |
| 127 | |
| 128 PreloadedStateGenerator::PreloadedStateGenerator() {} | |
| 129 | |
| 130 PreloadedStateGenerator::~PreloadedStateGenerator() {} | |
| 131 | |
| 132 std::string PreloadedStateGenerator::Generate( | |
| 133 const std::string& preload_template, | |
| 134 const DomainSecurityEntries& entries, | |
| 135 const DomainIDList& domain_ids, | |
| 136 const Pinsets& pinsets, | |
| 137 bool verbose) { | |
| 138 std::string output = preload_template; | |
| 139 | |
| 140 NameIDMap domain_ids_map; | |
| 141 ProcessDomainIds(domain_ids, &domain_ids_map, &output); | |
| 142 | |
| 143 ProcessSPKIHashes(pinsets, &output); | |
| 144 | |
| 145 NameIDMap expect_ct_report_uri_map; | |
| 146 ProcessExpectCTURIs(entries, &expect_ct_report_uri_map, &output); | |
| 147 | |
| 148 NameIDMap expect_staple_report_uri_map; | |
| 149 ProcessExpectStapleURIs(entries, &expect_staple_report_uri_map, &output); | |
| 150 | |
| 151 NameIDMap pinsets_map; | |
| 152 ProcessPinsets(pinsets, &pinsets_map, &output); | |
| 153 | |
| 154 // The trie generation process is ran twice, the first time using an | |
| 155 // approximate Huffman table. During this first run, the correct character | |
| 156 // frequencies are collected which are then used to calculate the most space | |
| 157 // efficient Huffman table for the given inputs. This table is used for the | |
| 158 // second run. | |
| 159 HuffmanRepresentationTable table = ApproximateHuffman(entries); | |
| 160 HuffmanFrequencyTracker tracker; | |
| 161 TrieWriter writer(table, domain_ids_map, expect_ct_report_uri_map, | |
| 162 expect_staple_report_uri_map, pinsets_map, &tracker); | |
| 163 writer.WriteEntries(entries); | |
| 164 uint32_t initial_length = writer.position(); | |
| 165 | |
| 166 HuffmanRepresentationTable optimal_table = tracker.ToTable(); | |
| 167 TrieWriter new_writer(optimal_table, domain_ids_map, expect_ct_report_uri_map, | |
| 168 expect_staple_report_uri_map, pinsets_map, nullptr); | |
| 169 | |
| 170 uint32_t root_position = new_writer.WriteEntries(entries); | |
| 171 uint32_t new_length = new_writer.position(); | |
| 172 | |
| 173 std::vector<uint8_t> huffman_tree = tracker.ToVector(); | |
| 174 | |
| 175 new_writer.Flush(); | |
| 176 | |
| 177 ReplaceTag("HUFFMAN_TREE", FormatVectorAsArray(huffman_tree), &output); | |
| 178 ReplaceTag("HSTS_TRIE", FormatVectorAsArray(new_writer.bytes()), &output); | |
| 179 | |
| 180 ReplaceTag("HSTS_TRIE_BITS", std::to_string(new_length), &output); | |
| 181 ReplaceTag("HSTS_TRIE_ROOT", std::to_string(root_position), &output); | |
| 182 | |
| 183 if (verbose) { | |
| 184 std::cout << "Saved " << std::to_string(initial_length - new_length) | |
| 185 << " bits by using accurate Huffman counts." << std::endl; | |
| 186 std::cout << "Bit length " << std::to_string(new_length) << std::endl; | |
| 187 std::cout << "Root position " << std::to_string(root_position) << std::endl; | |
| 188 } | |
| 189 | |
| 190 return output; | |
| 191 } | |
| 192 | |
| 193 void PreloadedStateGenerator::ProcessDomainIds(const DomainIDList& domain_ids, | |
| 194 NameIDMap* map, | |
| 195 std::string* tpl) { | |
| 196 std::string output = "{"; | |
| 197 output.append(kNewLine); | |
| 198 | |
| 199 for (size_t i = 0; i < domain_ids.size(); ++i) { | |
| 200 const std::string& current = domain_ids.at(i); | |
| 201 output.append(kIndent); | |
| 202 output.append("DOMAIN_" + current + ","); | |
| 203 output.append(kNewLine); | |
| 204 | |
| 205 map->insert(NameIDPair(current, static_cast<uint32_t>(i))); | |
| 206 } | |
| 207 | |
| 208 output.append(kIndent); | |
| 209 output.append("// Boundary value for UMA_HISTOGRAM_ENUMERATION."); | |
| 210 output.append(kNewLine); | |
| 211 output.append(kIndent); | |
| 212 output.append("DOMAIN_NUM_EVENTS,"); | |
| 213 output.append(kNewLine); | |
| 214 output.append("}"); | |
| 215 | |
| 216 ReplaceTag("DOMAIN_IDS", output, tpl); | |
| 217 } | |
| 218 | |
| 219 void PreloadedStateGenerator::ProcessSPKIHashes(const Pinsets& pinset, | |
| 220 std::string* tpl) { | |
| 221 std::string output; | |
| 222 | |
| 223 const SPKIHashMap& hashes = pinset.spki_hashes(); | |
| 224 for (const auto& current : hashes) { | |
| 225 const std::string& name = current.first; | |
| 226 const SPKIHash& hash = current.second; | |
| 227 | |
| 228 output.append("static const char " + FormatSPKIName(name) + "[] ="); | |
| 229 output.append(kNewLine); | |
| 230 | |
| 231 for (size_t i = 0; i < hash.size() / 16; ++i) { | |
| 232 output.append(kIndent); | |
| 233 output.append(kIndent); | |
| 234 output.append("\""); | |
| 235 | |
| 236 for (size_t j = i * 16; j < ((i + 1) * 16); ++j) { | |
| 237 base::StringAppendF(&output, "\\x%02x", hash.data()[j]); | |
| 238 } | |
| 239 | |
| 240 output.append("\""); | |
| 241 if (i + 1 == hash.size() / 16) { | |
| 242 output.append(";"); | |
| 243 } | |
| 244 output.append(kNewLine); | |
| 245 } | |
| 246 | |
| 247 output.append(kNewLine); | |
| 248 } | |
| 249 | |
| 250 base::TrimString(output, kNewLine, &output); | |
| 251 ReplaceTag("SPKI_HASHES", output, tpl); | |
| 252 } | |
| 253 | |
| 254 void PreloadedStateGenerator::ProcessExpectCTURIs( | |
| 255 const DomainSecurityEntries& entries, | |
| 256 NameIDMap* expect_ct_report_uri_map, | |
| 257 std::string* tpl) { | |
| 258 std::string output = "{"; | |
| 259 output.append(kNewLine); | |
| 260 | |
| 261 for (const auto& entry : entries) { | |
| 262 const std::string& url = entry->expect_ct_report_uri; | |
| 263 if (entry->expect_ct && url.size() && | |
| 264 expect_ct_report_uri_map->find(url) == | |
| 265 expect_ct_report_uri_map->cend()) { | |
| 266 output.append(kIndent); | |
| 267 output.append(kIndent); | |
| 268 output.append("\"" + entry->expect_ct_report_uri + "\","); | |
| 269 output.append(kNewLine); | |
| 270 | |
| 271 expect_ct_report_uri_map->insert( | |
| 272 NameIDPair(entry->expect_ct_report_uri, | |
| 273 static_cast<uint32_t>(expect_ct_report_uri_map->size()))); | |
| 274 } | |
| 275 } | |
| 276 | |
| 277 output.append("}"); | |
| 278 ReplaceTag("EXPECT_CT_REPORT_URIS", output, tpl); | |
| 279 } | |
| 280 | |
| 281 void PreloadedStateGenerator::ProcessExpectStapleURIs( | |
| 282 const DomainSecurityEntries& entries, | |
| 283 NameIDMap* expect_staple_report_uri_map, | |
| 284 std::string* tpl) { | |
| 285 std::string output = "{"; | |
| 286 output.append(kNewLine); | |
| 287 | |
| 288 for (const auto& entry : entries) { | |
| 289 const std::string& url = entry->expect_staple_report_uri; | |
| 290 if (entry->expect_staple && url.size() && | |
| 291 expect_staple_report_uri_map->find(url) == | |
| 292 expect_staple_report_uri_map->cend()) { | |
| 293 output.append(kIndent); | |
| 294 output.append(kIndent); | |
| 295 output.append("\"" + entry->expect_staple_report_uri + "\","); | |
| 296 output.append(kNewLine); | |
| 297 | |
| 298 expect_staple_report_uri_map->insert(NameIDPair( | |
| 299 entry->expect_staple_report_uri, | |
| 300 static_cast<uint32_t>(expect_staple_report_uri_map->size()))); | |
| 301 } | |
| 302 } | |
| 303 | |
| 304 output.append("}"); | |
| 305 ReplaceTag("EXPECT_STAPLE_REPORT_URIS", output, tpl); | |
| 306 } | |
| 307 | |
| 308 void PreloadedStateGenerator::ProcessPinsets(const Pinsets& pinset, | |
| 309 NameIDMap* pinset_map, | |
| 310 std::string* tpl) { | |
| 311 std::string certs_output; | |
| 312 std::string pinsets_output = "{"; | |
| 313 pinsets_output.append(kNewLine); | |
| 314 | |
| 315 const PinsetMap& pinsets = pinset.pinsets(); | |
| 316 for (const auto& current : pinsets) { | |
| 317 const std::unique_ptr<Pinset>& pinset = current.second; | |
| 318 std::string uppercased_name = pinset->name(); | |
| 319 uppercased_name[0] = base::ToUpperASCII(uppercased_name[0]); | |
| 320 | |
| 321 const std::string& accepted_pins_names = | |
| 322 FormatAcceptedKeyName(uppercased_name); | |
| 323 certs_output.append( | |
| 324 WritePinsetList(accepted_pins_names, pinset->static_spki_hashes())); | |
| 325 certs_output.append(kNewLine); | |
| 326 | |
| 327 std::string rejected_pins_names = "kNoRejectedPublicKeys"; | |
| 328 if (pinset->bad_static_spki_hashes().size()) { | |
| 329 rejected_pins_names = FormatRejectedKeyName(uppercased_name); | |
| 330 certs_output.append(WritePinsetList(rejected_pins_names, | |
| 331 pinset->bad_static_spki_hashes())); | |
| 332 certs_output.append(kNewLine); | |
| 333 } | |
| 334 | |
| 335 std::string report_uri = "kNoReportURI"; | |
| 336 if (pinset->report_uri().size()) { | |
| 337 report_uri = FormatReportURIName(uppercased_name); | |
| 338 certs_output.append("static const char " + report_uri + "[] = "); | |
| 339 certs_output.append("\""); | |
| 340 certs_output.append(pinset->report_uri()); | |
| 341 certs_output.append("\";"); | |
| 342 certs_output.append(kNewLine); | |
| 343 } | |
| 344 certs_output.append(kNewLine); | |
| 345 | |
| 346 pinsets_output.append(kIndent); | |
| 347 pinsets_output.append(kIndent); | |
| 348 pinsets_output.append("{" + accepted_pins_names + ", " + | |
| 349 rejected_pins_names + ", " + report_uri + "},"); | |
| 350 pinsets_output.append(kNewLine); | |
| 351 | |
| 352 pinset_map->insert( | |
| 353 NameIDPair(pinset->name(), static_cast<uint32_t>(pinset_map->size()))); | |
| 354 } | |
| 355 | |
| 356 pinsets_output.append("}"); | |
| 357 | |
| 358 base::TrimString(certs_output, kNewLine, &certs_output); | |
| 359 | |
| 360 ReplaceTag("ACCEPTABLE_CERTS", certs_output, tpl); | |
| 361 ReplaceTag("PINSETS", pinsets_output, tpl); | |
| 362 } | |
| 363 | |
| 364 } // namespace transport_security_state | |
| 365 | |
| 366 } // namespace net | |
| OLD | NEW |