• 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 // 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