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