1 // Copyright 2021 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 <inttypes.h>
6
7 #include <iostream>
8 #include <map>
9 #include <set>
10 #include <string>
11
12 #include "base/at_exit.h"
13 #include "base/base_paths.h"
14 #include "base/command_line.h"
15 #include "base/containers/span.h"
16 #include "base/files/file_path.h"
17 #include "base/files/file_util.h"
18 #include "base/logging.h"
19 #include "base/numerics/safe_conversions.h"
20 #include "base/path_service.h"
21 #include "base/strings/string_number_conversions.h"
22 #include "base/strings/string_piece.h"
23 #include "base/strings/string_util.h"
24 #include "base/strings/stringprintf.h"
25 #include "base/strings/utf_string_conversions.h"
26 #include "build/build_config.h"
27 #include "crypto/openssl_util.h"
28 #include "crypto/sha2.h"
29 #include "net/cert/root_store_proto_full/root_store.pb.h"
30 #include "third_party/abseil-cpp/absl/types/optional.h"
31 #include "third_party/boringssl/src/include/openssl/bio.h"
32 #include "third_party/boringssl/src/include/openssl/err.h"
33 #include "third_party/boringssl/src/include/openssl/pem.h"
34 #include "third_party/protobuf/src/google/protobuf/text_format.h"
35
36 using chrome_root_store::RootStore;
37
38 namespace {
39
40 // Returns a map from hex-encoded SHA-256 hash to DER certificate, or
41 // `absl::nullopt` if not found.
DecodeCerts(base::StringPiece in)42 absl::optional<std::map<std::string, std::string>> DecodeCerts(
43 base::StringPiece in) {
44 // TODO(https://crbug.com/1216547): net/cert/pem.h has a much nicer API, but
45 // it would require some build refactoring to avoid a circular dependency.
46 // This is assuming that the chrome trust store code goes in
47 // net/cert/internal, which it may not.
48 bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(in.data(), in.size()));
49 if (!bio) {
50 return absl::nullopt;
51 }
52 std::map<std::string, std::string> certs;
53 for (;;) {
54 char* name;
55 char* header;
56 unsigned char* data;
57 long len;
58 if (!PEM_read_bio(bio.get(), &name, &header, &data, &len)) {
59 uint32_t err = ERR_get_error();
60 if (ERR_GET_LIB(err) == ERR_LIB_PEM &&
61 ERR_GET_REASON(err) == PEM_R_NO_START_LINE) {
62 // Found the last PEM block.
63 break;
64 }
65 LOG(ERROR) << "Error reading PEM.";
66 return absl::nullopt;
67 }
68 bssl::UniquePtr<char> scoped_name(name);
69 bssl::UniquePtr<char> scoped_header(header);
70 bssl::UniquePtr<unsigned char> scoped_data(data);
71 if (strcmp(name, "CERTIFICATE") != 0) {
72 LOG(ERROR) << "Found PEM block of type " << name
73 << " instead of CERTIFICATE";
74 return absl::nullopt;
75 }
76 std::string sha256_hex =
77 base::ToLowerASCII(base::HexEncode(crypto::SHA256Hash(
78 base::make_span(data, base::checked_cast<size_t>(len)))));
79 certs[sha256_hex] = std::string(data, data + len);
80 }
81 return std::move(certs);
82 }
83
ReadTextRootStore(const base::FilePath & root_store_path,const base::FilePath & certs_path)84 absl::optional<RootStore> ReadTextRootStore(
85 const base::FilePath& root_store_path,
86 const base::FilePath& certs_path) {
87 std::string root_store_text;
88 if (!base::ReadFileToString(base::MakeAbsoluteFilePath(root_store_path),
89 &root_store_text)) {
90 LOG(ERROR) << "Could not read " << root_store_path;
91 return absl::nullopt;
92 }
93
94 RootStore root_store;
95 if (!google::protobuf::TextFormat::ParseFromString(root_store_text,
96 &root_store)) {
97 LOG(ERROR) << "Could not parse " << root_store_path;
98 return absl::nullopt;
99 }
100
101 std::map<std::string, std::string> certs;
102 if (!certs_path.empty()) {
103 std::string certs_data;
104 if (!base::ReadFileToString(base::MakeAbsoluteFilePath(certs_path),
105 &certs_data)) {
106 LOG(ERROR) << "Could not read " << certs_path;
107 return absl::nullopt;
108 }
109 auto certs_opt = DecodeCerts(certs_data);
110 if (!certs_opt) {
111 LOG(ERROR) << "Could not decode " << certs_path;
112 return absl::nullopt;
113 }
114 certs = std::move(*certs_opt);
115 }
116
117 // Replace the filenames with the actual certificate contents.
118 for (auto& anchor : *root_store.mutable_trust_anchors()) {
119 if (anchor.certificate_case() !=
120 chrome_root_store::TrustAnchor::kSha256Hex) {
121 continue;
122 }
123
124 auto iter = certs.find(anchor.sha256_hex());
125 if (iter == certs.end()) {
126 LOG(ERROR) << "Could not find certificate " << anchor.sha256_hex();
127 return absl::nullopt;
128 }
129
130 // Remove the certificate from `certs`. This both checks for duplicate
131 // certificates and allows us to check for unused certificates later.
132 anchor.set_der(std::move(iter->second));
133 certs.erase(iter);
134 }
135
136 if (!certs.empty()) {
137 LOG(ERROR) << "Unused certificate (SHA-256 hash " << certs.begin()->first
138 << ") in " << certs_path;
139 return absl::nullopt;
140 }
141
142 return std::move(root_store);
143 }
144
145 // Returns true if file was correctly written, false otherwise.
WriteRootCppFile(const RootStore & root_store,const base::FilePath cpp_path)146 bool WriteRootCppFile(const RootStore& root_store,
147 const base::FilePath cpp_path) {
148 // Root store should have at least one trust anchors.
149 CHECK_GT(root_store.trust_anchors_size(), 0);
150
151 std::string string_to_write =
152 "// This file is auto-generated, DO NOT EDIT.\n\n";
153
154 for (int i = 0; i < root_store.trust_anchors_size(); i++) {
155 const auto& anchor = root_store.trust_anchors(i);
156 // Every trust anchor at this point should have a DER.
157 CHECK(!anchor.der().empty());
158 std::string der = anchor.der();
159
160 base::StringAppendF(&string_to_write,
161 "constexpr uint8_t kChromeRootCert%d[] = {", i);
162
163 // Convert each character to hex representation, escaped.
164 for (auto c : der) {
165 base::StringAppendF(&string_to_write, "0x%02xu,",
166 static_cast<uint8_t>(c));
167 }
168
169 // End struct
170 string_to_write += "};\n";
171 }
172
173 string_to_write += "constexpr ChromeRootCertInfo kChromeRootCertList[] = {\n";
174
175 for (int i = 0; i < root_store.trust_anchors_size(); i++) {
176 base::StringAppendF(&string_to_write, " {kChromeRootCert%d},\n", i);
177 }
178 string_to_write += "};";
179
180 base::StringAppendF(&string_to_write,
181 "\n\n\nstatic const int64_t kRootStoreVersion = %" PRId64
182 ";\n",
183 root_store.version_major());
184 if (!base::WriteFile(cpp_path, string_to_write)) {
185 return false;
186 }
187 return true;
188 }
189
190 // Returns true if file was correctly written, false otherwise.
WriteEvCppFile(const RootStore & root_store,const base::FilePath cpp_path)191 bool WriteEvCppFile(const RootStore& root_store,
192 const base::FilePath cpp_path) {
193 // There should be at least one EV root.
194 CHECK_GT(root_store.trust_anchors_size(), 0);
195
196 std::string string_to_write =
197 "// This file is auto-generated, DO NOT EDIT.\n\n"
198 "static const EVMetadata kEvRootCaMetadata[] = {\n";
199
200 for (auto& anchor : root_store.trust_anchors()) {
201 // Every trust anchor at this point should have a DER.
202 CHECK(!anchor.der().empty());
203 if (anchor.ev_policy_oids_size() == 0) {
204 // The same input file is used for the Chrome Root Store and EV enabled
205 // certificates. Skip anchors that have no EV policy OIDs when generating
206 // the EV include file.
207 continue;
208 }
209
210 std::string sha256_hash = crypto::SHA256HashString(anchor.der());
211
212 // Begin struct. Assumed type of EVMetadata:
213 //
214 // struct EVMetadata {
215 // static const size_t kMaxOIDsPerCA = 2;
216 // SHA256HashValue fingerprint;
217 // const base::StringPiece policy_oids[kMaxOIDsPerCA];
218 // };
219 string_to_write += " {\n";
220 string_to_write += " {{";
221
222 int wrap_count = 0;
223 for (auto c : sha256_hash) {
224 if (wrap_count != 0) {
225 if (wrap_count % 11 == 0) {
226 string_to_write += ",\n ";
227 } else {
228 string_to_write += ", ";
229 }
230 }
231 base::StringAppendF(&string_to_write, "0x%02x", static_cast<uint8_t>(c));
232 wrap_count++;
233 }
234
235 string_to_write += "}},\n";
236 string_to_write += " {\n";
237
238 // struct expects exactly two policy oids, and we can only support 1 or 2
239 // policy OIDs. These checks will need to change if we ever merge the EV and
240 // Chrome Root Store textprotos.
241 const int kMaxPolicyOids = 2;
242 int oids_size = anchor.ev_policy_oids_size();
243 std::string hexencode_hash =
244 base::HexEncode(sha256_hash.data(), sha256_hash.size());
245 if (oids_size > kMaxPolicyOids) {
246 PLOG(ERROR) << hexencode_hash << " has too many OIDs!";
247 return false;
248 }
249 for (int i = 0; i < kMaxPolicyOids; i++) {
250 std::string oid;
251 if (i < oids_size) {
252 oid = anchor.ev_policy_oids(i);
253 }
254 string_to_write += " \"" + oid + "\",\n";
255 }
256
257 // End struct
258 string_to_write += " },\n";
259 string_to_write += " },\n";
260 }
261 string_to_write += "};\n";
262 if (!base::WriteFile(cpp_path, string_to_write)) {
263 PLOG(ERROR) << "Error writing cpp include file";
264 return false;
265 }
266 return true;
267 }
268
269 } // namespace
270
main(int argc,char ** argv)271 int main(int argc, char** argv) {
272 base::AtExitManager at_exit_manager;
273 base::CommandLine::Init(argc, argv);
274
275 logging::LoggingSettings settings;
276 settings.logging_dest =
277 logging::LOG_TO_SYSTEM_DEBUG_LOG | logging::LOG_TO_STDERR;
278 logging::InitLogging(settings);
279
280 crypto::EnsureOpenSSLInit();
281
282 base::CommandLine& command_line = *base::CommandLine::ForCurrentProcess();
283 base::FilePath proto_path = command_line.GetSwitchValuePath("write-proto");
284 base::FilePath root_store_cpp_path =
285 command_line.GetSwitchValuePath("write-cpp-root-store");
286 base::FilePath ev_roots_cpp_path =
287 command_line.GetSwitchValuePath("write-cpp-ev-roots");
288 base::FilePath root_store_path =
289 command_line.GetSwitchValuePath("root-store");
290 base::FilePath certs_path = command_line.GetSwitchValuePath("certs");
291
292 if ((proto_path.empty() && root_store_cpp_path.empty() &&
293 ev_roots_cpp_path.empty()) ||
294 root_store_path.empty() || command_line.HasSwitch("help")) {
295 std::cerr << "Usage: root_store_tool "
296 << "--root-store=TEXTPROTO_FILE "
297 << "[--certs=CERTS_FILE] "
298 << "[--write-proto=PROTO_FILE] "
299 << "[--write-cpp-root-store=CPP_FILE] "
300 << "[--write-cpp-ev-roots=CPP_FILE] " << std::endl;
301 return 1;
302 }
303
304 absl::optional<RootStore> root_store =
305 ReadTextRootStore(root_store_path, certs_path);
306 if (!root_store) {
307 return 1;
308 }
309
310 // TODO(https://crbug.com/1216547): Figure out how to use the serialized
311 // proto to support component update.
312 // components/resources/ssl/ssl_error_assistant/push_proto.py
313 // does it through a GCS bucket (I think) so that might be an option.
314 if (!proto_path.empty()) {
315 std::string serialized;
316 if (!root_store->SerializeToString(&serialized)) {
317 LOG(ERROR) << "Error serializing root store proto"
318 << root_store->DebugString();
319 return 1;
320 }
321 if (!base::WriteFile(proto_path, serialized)) {
322 PLOG(ERROR) << "Error writing serialized proto root store";
323 return 1;
324 }
325 }
326
327 if (!root_store_cpp_path.empty() &&
328 !WriteRootCppFile(*root_store, root_store_cpp_path)) {
329 PLOG(ERROR) << "Error writing root store C++ include file";
330 return 1;
331 }
332 if (!ev_roots_cpp_path.empty() &&
333 !WriteEvCppFile(*root_store, ev_roots_cpp_path)) {
334 PLOG(ERROR) << "Error writing EV roots C++ include file";
335 return 1;
336 }
337
338 return 0;
339 }
340