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