• 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 "net/cert/internal/trust_store_win.h"
6 
7 #include "base/hash/sha1.h"
8 #include "base/location.h"
9 #include "base/logging.h"
10 #include "base/memory/ptr_util.h"
11 #include "base/ranges/algorithm.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/threading/scoped_blocking_call.h"
14 #include "net/base/features.h"
15 #include "net/cert/internal/trust_store_features.h"
16 #include "net/cert/pki/cert_errors.h"
17 #include "net/cert/pki/parsed_certificate.h"
18 #include "net/cert/x509_util.h"
19 #include "net/third_party/mozilla_win/cert/win_util.h"
20 
21 namespace net {
22 
23 namespace {
24 
25 // Returns true if the cert can be used for server authentication, based on
26 // certificate properties.
27 //
28 // While there are a variety of certificate properties that can affect how
29 // trust is computed, the main property is CERT_ENHKEY_USAGE_PROP_ID, which
30 // is intersected with the certificate's EKU extension (if present).
31 // The intersection is documented in the Remarks section of
32 // CertGetEnhancedKeyUsage, and is as follows:
33 // - No EKU property, and no EKU extension = Trusted for all purpose
34 // - Either an EKU property, or EKU extension, but not both = Trusted only
35 //   for the listed purposes
36 // - Both an EKU property and an EKU extension = Trusted for the set
37 //   intersection of the listed purposes
38 // CertGetEnhancedKeyUsage handles this logic, and if an empty set is
39 // returned, the distinction between the first and third case can be
40 // determined by GetLastError() returning CRYPT_E_NOT_FOUND.
41 //
42 // See:
43 // https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certgetenhancedkeyusage
44 //
45 // If we run into any errors reading the certificate properties, we fail
46 // closed.
IsCertTrustedForServerAuth(PCCERT_CONTEXT cert)47 bool IsCertTrustedForServerAuth(PCCERT_CONTEXT cert) {
48   DWORD usage_size = 0;
49 
50   if (!CertGetEnhancedKeyUsage(cert, 0, nullptr, &usage_size)) {
51     return false;
52   }
53 
54   std::vector<BYTE> usage_bytes(usage_size);
55   CERT_ENHKEY_USAGE* usage =
56       reinterpret_cast<CERT_ENHKEY_USAGE*>(usage_bytes.data());
57   if (!CertGetEnhancedKeyUsage(cert, 0, usage, &usage_size)) {
58     return false;
59   }
60 
61   if (usage->cUsageIdentifier == 0) {
62     // check GetLastError
63     HRESULT error_code = GetLastError();
64 
65     switch (error_code) {
66       case CRYPT_E_NOT_FOUND:
67         return true;
68       case S_OK:
69         return false;
70       default:
71         return false;
72     }
73   }
74   for (DWORD i = 0; i < usage->cUsageIdentifier; i++) {
75     base::StringPiece eku = base::StringPiece(usage->rgpszUsageIdentifier[i]);
76     if ((eku == szOID_PKIX_KP_SERVER_AUTH) ||
77         (eku == szOID_ANY_ENHANCED_KEY_USAGE)) {
78       return true;
79     }
80   }
81   return false;
82 }
83 
84 }  // namespace
85 
86 TrustStoreWin::CertStores::CertStores() = default;
87 TrustStoreWin::CertStores::~CertStores() = default;
88 TrustStoreWin::CertStores::CertStores(CertStores&& other) = default;
89 TrustStoreWin::CertStores& TrustStoreWin::CertStores::operator=(
90     CertStores&& other) = default;
91 
92 // static
93 TrustStoreWin::CertStores
CreateInMemoryStoresForTesting()94 TrustStoreWin::CertStores::CreateInMemoryStoresForTesting() {
95   TrustStoreWin::CertStores stores;
96   stores.roots = crypto::ScopedHCERTSTORE(CertOpenStore(
97       CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, NULL, 0, nullptr));
98   stores.intermediates = crypto::ScopedHCERTSTORE(CertOpenStore(
99       CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, NULL, 0, nullptr));
100   stores.trusted_people = crypto::ScopedHCERTSTORE(CertOpenStore(
101       CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, NULL, 0, nullptr));
102   stores.disallowed = crypto::ScopedHCERTSTORE(CertOpenStore(
103       CERT_STORE_PROV_MEMORY, X509_ASN_ENCODING, NULL, 0, nullptr));
104   stores.InitializeAllCertsStore();
105   return stores;
106 }
107 
108 TrustStoreWin::CertStores
CreateNullStoresForTesting()109 TrustStoreWin::CertStores::CreateNullStoresForTesting() {
110   return TrustStoreWin::CertStores();
111 }
112 
113 // static
CreateWithCollections()114 TrustStoreWin::CertStores TrustStoreWin::CertStores::CreateWithCollections() {
115   TrustStoreWin::CertStores stores;
116   stores.roots = crypto::ScopedHCERTSTORE(
117       CertOpenStore(CERT_STORE_PROV_COLLECTION, 0, NULL, 0, nullptr));
118   stores.intermediates = crypto::ScopedHCERTSTORE(
119       CertOpenStore(CERT_STORE_PROV_COLLECTION, 0, NULL, 0, nullptr));
120   stores.trusted_people = crypto::ScopedHCERTSTORE(
121       CertOpenStore(CERT_STORE_PROV_COLLECTION, 0, NULL, 0, nullptr));
122   stores.disallowed = crypto::ScopedHCERTSTORE(
123       CertOpenStore(CERT_STORE_PROV_COLLECTION, 0, NULL, 0, nullptr));
124   stores.InitializeAllCertsStore();
125   return stores;
126 }
127 
InitializeAllCertsStore()128 void TrustStoreWin::CertStores::InitializeAllCertsStore() {
129   all = crypto::ScopedHCERTSTORE(
130       CertOpenStore(CERT_STORE_PROV_COLLECTION, 0, NULL, 0, nullptr));
131   if (is_null()) {
132     return;
133   }
134   // Add intermediate and root cert stores to the all_cert_store collection so
135   // SyncGetIssuersOf will find them. disallowed_cert_store is not added
136   // because the certs are distrusted; making them non-findable in
137   // SyncGetIssuersOf helps us fail path-building faster.
138   // `trusted_people` is not added because it can only contain end-entity
139   // certs, so checking it for issuers during path building is not necessary.
140   if (!CertAddStoreToCollection(all.get(), intermediates.get(),
141                                 /*dwUpdateFlags=*/0, /*dwPriority=*/0)) {
142     return;
143   }
144   if (!CertAddStoreToCollection(all.get(), roots.get(),
145                                 /*dwUpdateFlags=*/0, /*dwPriority=*/0)) {
146     return;
147   }
148 }
149 
150 class TrustStoreWin::Impl {
151  public:
152   // Creates a TrustStoreWin.
Impl()153   Impl() {
154     base::ScopedBlockingCall scoped_blocking_call(
155         FROM_HERE, base::BlockingType::MAY_BLOCK);
156 
157     CertStores stores = CertStores::CreateWithCollections();
158     if (stores.is_null()) {
159       // If there was an error initializing the cert store collections, give
160       // up. The Impl object will still be created but any calls to its public
161       // methods will return no results.
162       return;
163     }
164 
165     // Grab the user-added roots.
166     GatherEnterpriseCertsForLocation(stores.roots.get(),
167                                      CERT_SYSTEM_STORE_LOCAL_MACHINE, L"ROOT");
168     GatherEnterpriseCertsForLocation(
169         stores.roots.get(), CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY,
170         L"ROOT");
171     GatherEnterpriseCertsForLocation(stores.roots.get(),
172                                      CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE,
173                                      L"ROOT");
174     GatherEnterpriseCertsForLocation(stores.roots.get(),
175                                      CERT_SYSTEM_STORE_CURRENT_USER, L"ROOT");
176     GatherEnterpriseCertsForLocation(
177         stores.roots.get(), CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY,
178         L"ROOT");
179 
180     // Grab the user-added intermediates.
181     GatherEnterpriseCertsForLocation(stores.intermediates.get(),
182                                      CERT_SYSTEM_STORE_LOCAL_MACHINE, L"CA");
183     GatherEnterpriseCertsForLocation(
184         stores.intermediates.get(),
185         CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY, L"CA");
186     GatherEnterpriseCertsForLocation(stores.intermediates.get(),
187                                      CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE,
188                                      L"CA");
189     GatherEnterpriseCertsForLocation(stores.intermediates.get(),
190                                      CERT_SYSTEM_STORE_CURRENT_USER, L"CA");
191     GatherEnterpriseCertsForLocation(
192         stores.intermediates.get(), CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY,
193         L"CA");
194 
195     // Grab the user-added trusted server certs. Trusted end-entity certs are
196     // only allowed for server auth in the "local machine" store, but not in the
197     // "current user" store.
198     GatherEnterpriseCertsForLocation(stores.trusted_people.get(),
199                                      CERT_SYSTEM_STORE_LOCAL_MACHINE,
200                                      L"TrustedPeople");
201     GatherEnterpriseCertsForLocation(
202         stores.trusted_people.get(),
203         CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY, L"TrustedPeople");
204     GatherEnterpriseCertsForLocation(stores.trusted_people.get(),
205                                      CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE,
206                                      L"TrustedPeople");
207 
208     // Grab the user-added disallowed certs.
209     GatherEnterpriseCertsForLocation(stores.disallowed.get(),
210                                      CERT_SYSTEM_STORE_LOCAL_MACHINE,
211                                      L"Disallowed");
212     GatherEnterpriseCertsForLocation(
213         stores.disallowed.get(), CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY,
214         L"Disallowed");
215     GatherEnterpriseCertsForLocation(stores.disallowed.get(),
216                                      CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE,
217                                      L"Disallowed");
218     GatherEnterpriseCertsForLocation(
219         stores.disallowed.get(), CERT_SYSTEM_STORE_CURRENT_USER, L"Disallowed");
220     GatherEnterpriseCertsForLocation(
221         stores.disallowed.get(), CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY,
222         L"Disallowed");
223 
224     // Auto-sync all of the cert stores to get updates to the cert store.
225     // Auto-syncing on all_certs_store seems to work to resync the nested
226     // stores, although the docs at
227     // https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certcontrolstore
228     // are somewhat unclear. If and when root store changes are linked to
229     // clearing various caches, this should be replaced with
230     // CERT_STORE_CTRL_NOTIFY_CHANGE and CERT_STORE_CTRL_RESYNC.
231     if (!CertControlStore(stores.all.get(), 0, CERT_STORE_CTRL_AUTO_RESYNC,
232                           0) ||
233         !CertControlStore(stores.trusted_people.get(), 0,
234                           CERT_STORE_CTRL_AUTO_RESYNC, 0) ||
235         !CertControlStore(stores.disallowed.get(), 0,
236                           CERT_STORE_CTRL_AUTO_RESYNC, 0)) {
237       PLOG(ERROR) << "Error enabling CERT_STORE_CTRL_AUTO_RESYNC";
238     }
239 
240     root_cert_store_ = std::move(stores.roots);
241     intermediate_cert_store_ = std::move(stores.intermediates);
242     trusted_people_cert_store_ = std::move(stores.trusted_people);
243     disallowed_cert_store_ = std::move(stores.disallowed);
244     all_certs_store_ = std::move(stores.all);
245   }
246 
Impl(CertStores stores)247   Impl(CertStores stores)
248       : root_cert_store_(std::move(stores.roots)),
249         intermediate_cert_store_(std::move(stores.intermediates)),
250         all_certs_store_(std::move(stores.all)),
251         trusted_people_cert_store_(std::move(stores.trusted_people)),
252         disallowed_cert_store_(std::move(stores.disallowed)) {}
253 
254   ~Impl() = default;
255   Impl(const Impl& other) = delete;
256   Impl& operator=(const Impl& other) = delete;
257 
SyncGetIssuersOf(const ParsedCertificate * cert,ParsedCertificateList * issuers)258   void SyncGetIssuersOf(const ParsedCertificate* cert,
259                         ParsedCertificateList* issuers) {
260     if (!root_cert_store_.get() || !intermediate_cert_store_.get() ||
261         !trusted_people_cert_store_.get() || !all_certs_store_.get() ||
262         !disallowed_cert_store_.get()) {
263       return;
264     }
265     base::span<const uint8_t> issuer_span = cert->issuer_tlv().AsSpan();
266 
267     CERT_NAME_BLOB cert_issuer_blob;
268     cert_issuer_blob.cbData = static_cast<DWORD>(issuer_span.size());
269     cert_issuer_blob.pbData = const_cast<uint8_t*>(issuer_span.data());
270 
271     PCCERT_CONTEXT cert_from_store = nullptr;
272     // TODO(https://crbug.com/1239270): figure out if this is thread-safe or if
273     // we need locking here
274     while ((cert_from_store = CertFindCertificateInStore(
275                 all_certs_store_.get(), X509_ASN_ENCODING, 0,
276                 CERT_FIND_SUBJECT_NAME, &cert_issuer_blob, cert_from_store))) {
277       bssl::UniquePtr<CRYPTO_BUFFER> der_crypto =
278           x509_util::CreateCryptoBuffer(base::make_span(
279               cert_from_store->pbCertEncoded, cert_from_store->cbCertEncoded));
280       CertErrors errors;
281       ParsedCertificate::CreateAndAddToVector(
282           std::move(der_crypto), x509_util::DefaultParseCertificateOptions(),
283           issuers, &errors);
284     }
285   }
286 
GetTrust(const ParsedCertificate * cert,base::SupportsUserData * debug_data)287   CertificateTrust GetTrust(const ParsedCertificate* cert,
288                             base::SupportsUserData* debug_data) {
289     if (!root_cert_store_.get() || !intermediate_cert_store_.get() ||
290         !trusted_people_cert_store_.get() || !all_certs_store_.get() ||
291         !disallowed_cert_store_.get()) {
292       return CertificateTrust::ForUnspecified();
293     }
294 
295     base::span<const uint8_t> cert_span = cert->der_cert().AsSpan();
296     base::SHA1Digest cert_hash = base::SHA1HashSpan(cert_span);
297     CRYPT_HASH_BLOB cert_hash_blob;
298     cert_hash_blob.cbData = static_cast<DWORD>(cert_hash.size());
299     cert_hash_blob.pbData = cert_hash.data();
300 
301     PCCERT_CONTEXT cert_from_store = nullptr;
302 
303     // Check Disallowed store first.
304     while ((cert_from_store = CertFindCertificateInStore(
305                 disallowed_cert_store_.get(), X509_ASN_ENCODING, 0,
306                 CERT_FIND_SHA1_HASH, &cert_hash_blob, cert_from_store))) {
307       base::span<const uint8_t> cert_from_store_span = base::make_span(
308           cert_from_store->pbCertEncoded, cert_from_store->cbCertEncoded);
309       // If a cert is in the windows distruted store, it is considered
310       // distrusted for all purporses. EKU isn't checked. See crbug.com/1355961.
311       if (base::ranges::equal(cert_span, cert_from_store_span)) {
312         return CertificateTrust::ForDistrusted();
313       }
314     }
315 
316     // TODO(https://crbug.com/1239270): figure out if this is thread-safe or if
317     // we need locking here
318     while ((cert_from_store = CertFindCertificateInStore(
319                 root_cert_store_.get(), X509_ASN_ENCODING, 0,
320                 CERT_FIND_SHA1_HASH, &cert_hash_blob, cert_from_store))) {
321       base::span<const uint8_t> cert_from_store_span = base::make_span(
322           cert_from_store->pbCertEncoded, cert_from_store->cbCertEncoded);
323       if (base::ranges::equal(cert_span, cert_from_store_span)) {
324         // If we find at least one version of the cert that is trusted for TLS
325         // Server Auth, we will trust the cert.
326         if (IsCertTrustedForServerAuth(cert_from_store)) {
327           if (base::FeatureList::IsEnabled(
328                   features::kTrustStoreTrustedLeafSupport)) {
329             // Certificates in the Roots store may be used as either trust
330             // anchors or trusted leafs (if self-signed).
331             return CertificateTrust::ForTrustAnchorOrLeaf()
332                 .WithEnforceAnchorExpiry()
333                 .WithEnforceAnchorConstraints(
334                     IsLocalAnchorConstraintsEnforcementEnabled())
335                 .WithRequireLeafSelfSigned();
336           } else {
337             return CertificateTrust::ForTrustAnchor()
338                 .WithEnforceAnchorExpiry()
339                 .WithEnforceAnchorConstraints(
340                     IsLocalAnchorConstraintsEnforcementEnabled());
341           }
342         }
343       }
344     }
345 
346     if (base::FeatureList::IsEnabled(features::kTrustStoreTrustedLeafSupport)) {
347       while ((cert_from_store = CertFindCertificateInStore(
348                   trusted_people_cert_store_.get(), X509_ASN_ENCODING, 0,
349                   CERT_FIND_SHA1_HASH, &cert_hash_blob, cert_from_store))) {
350         base::span<const uint8_t> cert_from_store_span = base::make_span(
351             cert_from_store->pbCertEncoded, cert_from_store->cbCertEncoded);
352         if (base::ranges::equal(cert_span, cert_from_store_span)) {
353           // If we find at least one version of the cert that is trusted for TLS
354           // Server Auth, we will trust the cert.
355           if (IsCertTrustedForServerAuth(cert_from_store)) {
356             // Certificates in the Trusted People store may be trusted leafs (if
357             // self-signed).
358             return CertificateTrust::ForTrustedLeaf()
359                 .WithRequireLeafSelfSigned();
360           }
361         }
362       }
363     }
364 
365     // If we fall through here, we've either
366     //
367     // (a) found the cert but it is not usable for server auth. Treat this as
368     //     Unspecified trust. Originally this was treated as Distrusted, but
369     //     this is inconsistent with how the Windows verifier works, which is to
370     //     union all of the EKU usages for all instances of the cert, whereas
371     //     sending back Distrusted would not do that.
372     //
373     // or
374     //
375     // (b) Haven't found the cert. Tell everyone Unspecified.
376     return CertificateTrust::ForUnspecified();
377   }
378 
379  private:
380   // Cert Collection containing all user-added trust anchors.
381   crypto::ScopedHCERTSTORE root_cert_store_;
382 
383   // Cert Collection containing all user-added intermediates.
384   crypto::ScopedHCERTSTORE intermediate_cert_store_;
385 
386   // Cert Collection for searching via SyncGetIssuersOf()
387   crypto::ScopedHCERTSTORE all_certs_store_;
388 
389   // Cert Collection containing all user-added trust leafs.
390   crypto::ScopedHCERTSTORE trusted_people_cert_store_;
391 
392   // Cert Collection for all disallowed certs.
393   crypto::ScopedHCERTSTORE disallowed_cert_store_;
394 };
395 
396 // TODO(https://crbug.com/1239268): support CTLs.
397 TrustStoreWin::TrustStoreWin() = default;
398 
InitializeStores()399 void TrustStoreWin::InitializeStores() {
400   // Don't need return value
401   MaybeInitializeAndGetImpl();
402 }
403 
MaybeInitializeAndGetImpl()404 TrustStoreWin::Impl* TrustStoreWin::MaybeInitializeAndGetImpl() {
405   base::AutoLock lock(init_lock_);
406   if (!impl_) {
407     impl_ = std::make_unique<TrustStoreWin::Impl>();
408   }
409   return impl_.get();
410 }
411 
CreateForTesting(CertStores stores)412 std::unique_ptr<TrustStoreWin> TrustStoreWin::CreateForTesting(
413     CertStores stores) {
414   return base::WrapUnique(new TrustStoreWin(
415       std::make_unique<TrustStoreWin::Impl>(std::move(stores))));
416 }
417 
TrustStoreWin(std::unique_ptr<Impl> impl)418 TrustStoreWin::TrustStoreWin(std::unique_ptr<Impl> impl)
419     : impl_(std::move(impl)) {}
420 
421 TrustStoreWin::~TrustStoreWin() = default;
422 
SyncGetIssuersOf(const ParsedCertificate * cert,ParsedCertificateList * issuers)423 void TrustStoreWin::SyncGetIssuersOf(const ParsedCertificate* cert,
424                                      ParsedCertificateList* issuers) {
425   MaybeInitializeAndGetImpl()->SyncGetIssuersOf(cert, issuers);
426 }
427 
428 // As documented in IsCertTrustedForServerAuth(), on Windows, the
429 // set of extended key usages present in a certificate can be further
430 // scoped down by user setting; effectively, disabling a given EKU for
431 // a given intermediate or root.
432 //
433 // Windows uses this during path building when filtering the EKUs; if it
434 // encounters this property, it uses the combined EKUs to determine
435 // whether to continue path building, but doesn't treat the certificate
436 // as affirmatively revoked/distrusted.
437 //
438 // This behaviour is replicated here by returning Unspecified trust if
439 // we find instances of the cert that do not have the correct EKUs set
440 // for TLS Server Auth. This allows path building to continue and allows
441 // us to later trust the cert if it is present in Chrome Root Store.
442 //
443 // Windows does have some idiosyncrasies here, which result in the
444 // following treatment:
445 //
446 //   - If a certificate is in the Disallowed store, it is distrusted for
447 //     all purposes regardless of any EKUs that are set.
448 //   - If a certificate is in the ROOT store, and usable for TLS Server Auth,
449 //     then it's trusted.
450 //   - If a certificate is in the root store, and lacks the EKU, then continue
451 //     path building, but don't treat it as trusted (aka Unspecified).
452 //   - If we can't find the cert anywhere, then continue path
453 //     building, but don't treat it as trusted (aka Unspecified).
454 //
455 // If a certificate is found multiple times in the ROOT store, it is trusted
456 // for TLS server auth if any instance of the certificate found
457 // is usable for TLS server auth.
GetTrust(const ParsedCertificate * cert,base::SupportsUserData * debug_data)458 CertificateTrust TrustStoreWin::GetTrust(const ParsedCertificate* cert,
459                                          base::SupportsUserData* debug_data) {
460   return MaybeInitializeAndGetImpl()->GetTrust(cert, debug_data);
461 }
462 
463 }  // namespace net
464