• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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