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 // Tool used to do batch comparisons of cert verification results between
6 // the platform verifier and the builtin verifier. Currently only tested on
7 // Windows.
8 #include <iostream>
9
10 #include "base/at_exit.h"
11 #include "base/command_line.h"
12 #include "base/containers/span.h"
13 #include "base/functional/bind.h"
14 #include "base/functional/callback_helpers.h"
15 #include "base/logging.h"
16 #include "base/message_loop/message_pump_type.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/string_split.h"
19 #include "base/synchronization/waitable_event.h"
20 #include "base/task/thread_pool/thread_pool_instance.h"
21 #include "base/threading/thread.h"
22 #include "build/build_config.h"
23 #include "net/cert/cert_net_fetcher.h"
24 #include "net/cert/cert_verify_proc.h"
25 #include "net/cert/cert_verify_proc_builtin.h"
26 #include "net/cert/crl_set.h"
27 #include "net/cert/internal/system_trust_store.h"
28 #include "net/cert/x509_certificate.h"
29 #include "net/cert_net/cert_net_fetcher_url_request.h"
30 #include "net/tools/cert_verify_tool/cert_verify_tool_util.h"
31 #include "net/tools/cert_verify_tool/dumper.pb.h"
32 #include "net/tools/cert_verify_tool/verify_using_cert_verify_proc.h"
33 #include "net/url_request/url_request_context.h"
34 #include "net/url_request/url_request_context_builder.h"
35 #include "net/url_request/url_request_context_getter.h"
36
37 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
38 #include "net/proxy_resolution/proxy_config.h"
39 #include "net/proxy_resolution/proxy_config_service_fixed.h"
40 #endif
41
42 #if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
43 #include "net/cert/internal/trust_store_chrome.h"
44 #endif
45
46 namespace {
GetUserAgent()47 std::string GetUserAgent() {
48 return "cert_verify_comparison_tool/0.1";
49 }
50
SetUpOnNetworkThread(std::unique_ptr<net::URLRequestContext> * context,scoped_refptr<net::CertNetFetcherURLRequest> * cert_net_fetcher,base::WaitableEvent * initialization_complete_event)51 void SetUpOnNetworkThread(
52 std::unique_ptr<net::URLRequestContext>* context,
53 scoped_refptr<net::CertNetFetcherURLRequest>* cert_net_fetcher,
54 base::WaitableEvent* initialization_complete_event) {
55 net::URLRequestContextBuilder url_request_context_builder;
56 url_request_context_builder.set_user_agent(GetUserAgent());
57 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
58 // On Linux, use a fixed ProxyConfigService, since the default one
59 // depends on glib.
60 //
61 // TODO(akalin): Remove this once http://crbug.com/146421 is fixed.
62 url_request_context_builder.set_proxy_config_service(
63 std::make_unique<net::ProxyConfigServiceFixed>(
64 net::ProxyConfigWithAnnotation()));
65 #endif
66 *context = url_request_context_builder.Build();
67
68 // TODO(mattm): add command line flag to configure using
69 // CertNetFetcher
70 *cert_net_fetcher = base::MakeRefCounted<net::CertNetFetcherURLRequest>();
71 (*cert_net_fetcher)->SetURLRequestContext(context->get());
72 initialization_complete_event->Signal();
73 }
74
ShutdownOnNetworkThread(std::unique_ptr<net::URLRequestContext> * context,scoped_refptr<net::CertNetFetcherURLRequest> * cert_net_fetcher)75 void ShutdownOnNetworkThread(
76 std::unique_ptr<net::URLRequestContext>* context,
77 scoped_refptr<net::CertNetFetcherURLRequest>* cert_net_fetcher) {
78 (*cert_net_fetcher)->Shutdown();
79 cert_net_fetcher->reset();
80 context->reset();
81 }
82
83 // Runs certificate verification using a particular CertVerifyProc.
84 class CertVerifyImpl {
85 public:
CertVerifyImpl(const std::string & name,scoped_refptr<net::CertVerifyProc> proc)86 CertVerifyImpl(const std::string& name,
87 scoped_refptr<net::CertVerifyProc> proc)
88 : name_(name), proc_(std::move(proc)) {}
89
90 virtual ~CertVerifyImpl() = default;
GetName() const91 std::string GetName() const { return name_; }
92
93 // Does certificate verification.
VerifyCert(net::X509Certificate & x509_target_and_intermediates,const std::string & hostname,net::CertVerifyResult * result,int * error)94 bool VerifyCert(net::X509Certificate& x509_target_and_intermediates,
95 const std::string& hostname,
96 net::CertVerifyResult* result,
97 int* error) {
98 if (hostname.empty()) {
99 std::cerr << "ERROR: --hostname is required for " << GetName()
100 << ", skipping\n";
101 return true; // "skipping" is considered a successful return.
102 }
103
104 // TODO(mattm): add command line flags to configure VerifyFlags.
105 int flags = 0;
106
107 // TODO(crbug.com/634484): use a real netlog and print the results?
108 *error = proc_->Verify(&x509_target_and_intermediates, hostname,
109 /*ocsp_response=*/std::string(),
110 /*sct_list=*/std::string(), flags, result,
111 net::NetLogWithSource());
112
113 return *error == net::OK;
114 }
115
116 private:
117 const std::string name_;
118 scoped_refptr<net::CertVerifyProc> proc_;
119 };
120
121 // Creates an subclass of CertVerifyImpl based on its name, or returns nullptr.
CreateCertVerifyImplFromName(base::StringPiece impl_name,scoped_refptr<net::CertNetFetcher> cert_net_fetcher)122 std::unique_ptr<CertVerifyImpl> CreateCertVerifyImplFromName(
123 base::StringPiece impl_name,
124 scoped_refptr<net::CertNetFetcher> cert_net_fetcher) {
125 #if !(BUILDFLAG(IS_FUCHSIA) || BUILDFLAG(IS_LINUX) || \
126 BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(CHROME_ROOT_STORE_ONLY))
127 if (impl_name == "platform") {
128 return std::make_unique<CertVerifyImpl>(
129 "CertVerifyProc (system)",
130 net::CertVerifyProc::CreateSystemVerifyProc(
131 std::move(cert_net_fetcher) net::CRLSet::BuiltinCRLSet()));
132 }
133 #endif
134
135 if (impl_name == "builtin") {
136 #if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED)
137 return std::make_unique<CertVerifyImpl>(
138 "CertVerifyProcBuiltin",
139 net::CreateCertVerifyProcBuiltin(
140 std::move(cert_net_fetcher), net::CRLSet::BuiltinCRLSet(),
141 net::CreateSslSystemTrustStoreChromeRoot(
142 std::make_unique<net::TrustStoreChrome>()),
143 {}));
144 #endif
145 }
146
147 std::cerr << "WARNING: Unrecognized impl: " << impl_name << "\n";
148 return nullptr;
149 }
150
151 const char kUsage[] =
152 " --input=<file>\n"
153 "\n"
154 " <file> is a file containing serialized protos from trawler. Format \n"
155 " of the file is a uint32 size, followed by that many bytes of a\n"
156 " serialized proto message of type \n"
157 " cert_verify_tool::CertChain. The path to the file must not\n"
158 " contain any dot(.) characters."
159 "\n";
160
161 // Stats based on errors reading and parsing the input file.
162 std::map<std::string, int> file_error_stats;
163
164 std::map<std::string, int> chain_processing_stats;
165
166 std::map<std::string, int> ignorable_difference_stats;
167
PrintStats()168 void PrintStats() {
169 std::cout << "\n\nFile processing stats:\n";
170 for (const auto& error_stat : file_error_stats) {
171 std::cout << " " << error_stat.first << ": " << error_stat.second << "\n";
172 }
173
174 std::cout << "\n\nChain processing stats:\n";
175 for (const auto& chain_stat : chain_processing_stats) {
176 std::cout << " " << chain_stat.first << ": " << chain_stat.second << "\n";
177 }
178
179 std::cout << "\n\nIgnorable difference stats:\n";
180 for (const auto& ignorable_diff_stat : ignorable_difference_stats) {
181 std::cout << " " << ignorable_diff_stat.first << ": "
182 << ignorable_diff_stat.second << "\n";
183 }
184 }
185
PrintUsage(const char * argv0)186 void PrintUsage(const char* argv0) {
187 std::cerr << "Usage: " << argv0 << kUsage;
188 }
189
190 // Note: This ignores the result of stapled OCSP (which is the same for both
191 // verifiers) and informational statuses about the certificate algorithms and
192 // the hashes, since they will be the same if the certificate chains are the
193 // same.
CertVerifyResultEqual(const net::CertVerifyResult & a,const net::CertVerifyResult & b)194 bool CertVerifyResultEqual(const net::CertVerifyResult& a,
195 const net::CertVerifyResult& b) {
196 return std::tie(a.cert_status, a.is_issued_by_known_root) ==
197 std::tie(b.cert_status, b.is_issued_by_known_root) &&
198 (!!a.verified_cert == !!b.verified_cert) &&
199 (!a.verified_cert ||
200 a.verified_cert->EqualsIncludingChain(b.verified_cert.get()));
201 }
202
203 // Returns -1 if an error occurred.
RunCert(base::File * input_file,const std::unique_ptr<CertVerifyImpl> & platform_proc,const std::unique_ptr<CertVerifyImpl> & builtin_proc)204 int RunCert(base::File* input_file,
205 const std::unique_ptr<CertVerifyImpl>& platform_proc,
206 const std::unique_ptr<CertVerifyImpl>& builtin_proc) {
207 // read 4 bytes, convert to uint32_t
208 std::vector<char> size_bytes(4);
209 int size_bytes_read = input_file->ReadAtCurrentPos(size_bytes.data(), 4);
210 if (size_bytes_read != 4) {
211 std::cerr << "Couldn't read 4 byte size field, read only "
212 << size_bytes_read << "\n";
213 file_error_stats["size_read_error"]++;
214 return -1;
215 }
216
217 uint32_t proto_size;
218 proto_size = (static_cast<uint8_t>(size_bytes[3]) << 24) +
219 (static_cast<uint8_t>(size_bytes[2]) << 16) +
220 (static_cast<uint8_t>(size_bytes[1]) << 8) +
221 (static_cast<uint8_t>(size_bytes[0]));
222
223 // read proto_size bytes, parse to proto.
224 std::vector<char> proto_bytes(proto_size);
225 int proto_bytes_read =
226 input_file->ReadAtCurrentPos(proto_bytes.data(), proto_size);
227
228 if ((proto_bytes_read - proto_size) != 0) {
229 std::cerr << "Couldn't read expected proto of size " << proto_size
230 << "read only " << proto_bytes_read << "\n";
231 file_error_stats["proto_read_error"]++;
232 return -1;
233 }
234
235 cert_verify_tool::CertChain cert_chain;
236
237 if (!cert_chain.ParseFromArray(proto_bytes.data(), proto_bytes_read)) {
238 std::cerr << "Proto parse error for proto of size " << proto_size << ", "
239 << proto_bytes_read << " proto bytes read total, "
240 << size_bytes_read << " size bytes read\n\n\n";
241 file_error_stats["parse_error"]++;
242 return -1;
243 }
244
245 std::vector<base::StringPiece> der_cert_chain;
246 for (int i = 0; i < cert_chain.der_certs_size(); i++) {
247 der_cert_chain.push_back(cert_chain.der_certs(i));
248 }
249
250 scoped_refptr<net::X509Certificate> x509_target_and_intermediates =
251 net::X509Certificate::CreateFromDERCertChain(der_cert_chain);
252 if (!x509_target_and_intermediates) {
253 std::cerr << "X509Certificate::CreateFromDERCertChain failed for host"
254 << cert_chain.host() << "\n\n\n";
255 file_error_stats["chain_parse_error"]++;
256
257 // We try to continue here; its possible that the cert chain contained
258 // invalid certs for some reason so we don't bail out entirely.
259 return 0;
260 }
261
262 net::CertVerifyResult platform_result;
263 int platform_error;
264 net::CertVerifyResult builtin_result;
265 int builtin_error;
266
267 platform_proc->VerifyCert(*x509_target_and_intermediates, cert_chain.host(),
268 &platform_result, &platform_error);
269 builtin_proc->VerifyCert(*x509_target_and_intermediates, cert_chain.host(),
270 &builtin_result, &builtin_error);
271
272 if (CertVerifyResultEqual(platform_result, builtin_result) &&
273 platform_error == builtin_error) {
274 chain_processing_stats["equal"]++;
275 } else {
276 // Much of the below code was originally lifted from
277 // TrialComparisonCertVerifier::Job::OnTrialJobCompleted as it wasn't
278 // obvious how to easily refactor the code here to prevent copying this
279 // section of code. The TrialComparisonCertVerifier is now gone, but we
280 // retain our ability here to show the differences between a result
281 // returned by the builtin verifier and the native platform verifier.
282 const bool chains_equal =
283 platform_result.verified_cert->EqualsIncludingChain(
284 builtin_result.verified_cert.get());
285
286 // Chains built were different with either builtin being OK or both not OK.
287 // Pass builtin chain to platform, see if platform comes back the same.
288 if (!chains_equal &&
289 (builtin_error == net::OK || platform_error != net::OK)) {
290 net::CertVerifyResult platform_reverification_result;
291 int platform_reverification_error;
292
293 platform_proc->VerifyCert(
294 *builtin_result.verified_cert, cert_chain.host(),
295 &platform_reverification_result, &platform_reverification_error);
296 if (CertVerifyResultEqual(platform_reverification_result,
297 builtin_result) &&
298 platform_reverification_error == builtin_error) {
299 chain_processing_stats["reverify_ignorable"]++;
300 return 0;
301 }
302 }
303
304 chain_processing_stats["different"]++;
305
306 std::cout << "\n *************************** \n\n"
307 << "Host " << cert_chain.host()
308 << " has different verify results!\n";
309
310 std::cout << "\nInput chain: \n "
311 << FingerPrintCryptoBuffer(
312 x509_target_and_intermediates->cert_buffer())
313 << " "
314 << SubjectFromX509Certificate(x509_target_and_intermediates.get())
315 << "\n";
316
317 for (const auto& intermediate :
318 x509_target_and_intermediates->intermediate_buffers()) {
319 std::cout << " " << FingerPrintCryptoBuffer(intermediate.get()) << " "
320 << SubjectFromCryptoBuffer(intermediate.get()) << "\n";
321 }
322
323 std::cout << "\nPlatform: (error = "
324 << net::ErrorToShortString(platform_error) << ")\n";
325 PrintCertVerifyResult(platform_result);
326 std::cout << "\nBuiltin: (error = "
327 << net::ErrorToShortString(builtin_error) << ")\n";
328 PrintCertVerifyResult(builtin_result);
329 }
330
331 return 0;
332 }
333
334 } // namespace
335
main(int argc,char ** argv)336 int main(int argc, char** argv) {
337 base::AtExitManager at_exit_manager;
338 if (!base::CommandLine::Init(argc, argv)) {
339 std::cerr << "ERROR in CommandLine::Init\n";
340 return 1;
341 }
342
343 base::ThreadPoolInstance::CreateAndStartWithDefaultParams(
344 "cert_verify_comparison_tool");
345 base::CommandLine& command_line = *base::CommandLine::ForCurrentProcess();
346 logging::LoggingSettings settings;
347 settings.logging_dest =
348 logging::LOG_TO_SYSTEM_DEBUG_LOG | logging::LOG_TO_STDERR;
349 logging::InitLogging(settings);
350
351 base::CommandLine::StringVector args = command_line.GetArgs();
352 if (command_line.HasSwitch("help")) {
353 PrintUsage(argv[0]);
354 return 1;
355 }
356
357 base::FilePath input_path = command_line.GetSwitchValuePath("input");
358 if (input_path.empty()) {
359 std::cerr << "Error: --input is required\n";
360 return 1;
361 }
362
363 int flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
364 std::unique_ptr<base::File> input_file =
365 std::make_unique<base::File>(input_path, flags);
366
367 if (!input_file->IsValid()) {
368 std::cerr << "Error: --dump file " << input_path.MaybeAsASCII()
369 << " is not valid \n";
370 return 1;
371 }
372
373 // Create a network thread to be used for AIA fetches, and wait for a
374 // CertNetFetcher to be constructed on that thread.
375 base::Thread::Options options(base::MessagePumpType::IO, 0);
376 base::Thread thread("network_thread");
377 CHECK(thread.StartWithOptions(std::move(options)));
378 // Owned by this thread, but initialized, used, and shutdown on the network
379 // thread.
380 std::unique_ptr<net::URLRequestContext> context;
381 scoped_refptr<net::CertNetFetcherURLRequest> cert_net_fetcher;
382 base::WaitableEvent initialization_complete_event(
383 base::WaitableEvent::ResetPolicy::MANUAL,
384 base::WaitableEvent::InitialState::NOT_SIGNALED);
385 thread.task_runner()->PostTask(
386 FROM_HERE,
387 base::BindOnce(&SetUpOnNetworkThread, &context, &cert_net_fetcher,
388 &initialization_complete_event));
389 initialization_complete_event.Wait();
390
391 // Initialize verifiers; platform and builtin.
392 std::vector<std::unique_ptr<CertVerifyImpl>> impls;
393 std::unique_ptr<CertVerifyImpl> platform_proc =
394 CreateCertVerifyImplFromName("platform", cert_net_fetcher);
395 if (!platform_proc) {
396 std::cerr << "Error platform proc not sucessfully created";
397 return 1;
398 }
399 std::unique_ptr<CertVerifyImpl> builtin_proc =
400 CreateCertVerifyImplFromName("builtin", cert_net_fetcher);
401 if (!builtin_proc) {
402 std::cerr << "Error builtin proc not sucessfully created";
403 return 1;
404 }
405
406 // Read file and process cert chains.
407 while (RunCert(input_file.get(), platform_proc, builtin_proc) != -1) {
408 }
409
410 PrintStats();
411
412 // Clean up on the network thread and stop it (which waits for the clean up
413 // task to run).
414 thread.task_runner()->PostTask(
415 FROM_HERE,
416 base::BindOnce(&ShutdownOnNetworkThread, &context, &cert_net_fetcher));
417 thread.Stop();
418 }
419