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/x509_util.h"
17 #include "net/third_party/mozilla_win/cert/win_util.h"
18 #include "third_party/boringssl/src/pki/cert_errors.h"
19 #include "third_party/boringssl/src/pki/parsed_certificate.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 bssl::ParsedCertificate * cert,bssl::ParsedCertificateList * issuers)258 void SyncGetIssuersOf(const bssl::ParsedCertificate* cert,
259 bssl::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 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().AsSpan();
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 // TODO(https://crbug.com/1239270): figure out if this is thread-safe or if
316 // we need locking here
317 while ((cert_from_store = CertFindCertificateInStore(
318 root_cert_store_.get(), X509_ASN_ENCODING, 0,
319 CERT_FIND_SHA1_HASH, &cert_hash_blob, cert_from_store))) {
320 base::span<const uint8_t> cert_from_store_span = base::make_span(
321 cert_from_store->pbCertEncoded, cert_from_store->cbCertEncoded);
322 if (base::ranges::equal(cert_span, cert_from_store_span)) {
323 // If we find at least one version of the cert that is trusted for TLS
324 // Server Auth, we will trust the cert.
325 if (IsCertTrustedForServerAuth(cert_from_store)) {
326 if (base::FeatureList::IsEnabled(
327 features::kTrustStoreTrustedLeafSupport)) {
328 // Certificates in the Roots store may be used as either trust
329 // anchors or trusted leafs (if self-signed).
330 return bssl::CertificateTrust::ForTrustAnchorOrLeaf()
331 .WithEnforceAnchorExpiry()
332 .WithEnforceAnchorConstraints(
333 IsLocalAnchorConstraintsEnforcementEnabled())
334 .WithRequireLeafSelfSigned();
335 } else {
336 return bssl::CertificateTrust::ForTrustAnchor()
337 .WithEnforceAnchorExpiry()
338 .WithEnforceAnchorConstraints(
339 IsLocalAnchorConstraintsEnforcementEnabled());
340 }
341 }
342 }
343 }
344
345 if (base::FeatureList::IsEnabled(features::kTrustStoreTrustedLeafSupport)) {
346 while ((cert_from_store = CertFindCertificateInStore(
347 trusted_people_cert_store_.get(), X509_ASN_ENCODING, 0,
348 CERT_FIND_SHA1_HASH, &cert_hash_blob, cert_from_store))) {
349 base::span<const uint8_t> cert_from_store_span = base::make_span(
350 cert_from_store->pbCertEncoded, cert_from_store->cbCertEncoded);
351 if (base::ranges::equal(cert_span, cert_from_store_span)) {
352 // If we find at least one version of the cert that is trusted for TLS
353 // Server Auth, we will trust the cert.
354 if (IsCertTrustedForServerAuth(cert_from_store)) {
355 // Certificates in the Trusted People store may be trusted leafs (if
356 // self-signed).
357 return bssl::CertificateTrust::ForTrustedLeaf()
358 .WithRequireLeafSelfSigned();
359 }
360 }
361 }
362 }
363
364 // If we fall through here, we've either
365 //
366 // (a) found the cert but it is not usable for server auth. Treat this as
367 // Unspecified trust. Originally this was treated as Distrusted, but
368 // this is inconsistent with how the Windows verifier works, which is to
369 // union all of the EKU usages for all instances of the cert, whereas
370 // sending back Distrusted would not do that.
371 //
372 // or
373 //
374 // (b) Haven't found the cert. Tell everyone Unspecified.
375 return bssl::CertificateTrust::ForUnspecified();
376 }
377
378 private:
379 // Cert Collection containing all user-added trust anchors.
380 crypto::ScopedHCERTSTORE root_cert_store_;
381
382 // Cert Collection containing all user-added intermediates.
383 crypto::ScopedHCERTSTORE intermediate_cert_store_;
384
385 // Cert Collection for searching via SyncGetIssuersOf()
386 crypto::ScopedHCERTSTORE all_certs_store_;
387
388 // Cert Collection containing all user-added trust leafs.
389 crypto::ScopedHCERTSTORE trusted_people_cert_store_;
390
391 // Cert Collection for all disallowed certs.
392 crypto::ScopedHCERTSTORE disallowed_cert_store_;
393 };
394
395 // TODO(https://crbug.com/1239268): support CTLs.
396 TrustStoreWin::TrustStoreWin() = default;
397
InitializeStores()398 void TrustStoreWin::InitializeStores() {
399 // Don't need return value
400 MaybeInitializeAndGetImpl();
401 }
402
MaybeInitializeAndGetImpl()403 TrustStoreWin::Impl* TrustStoreWin::MaybeInitializeAndGetImpl() {
404 base::AutoLock lock(init_lock_);
405 if (!impl_) {
406 impl_ = std::make_unique<TrustStoreWin::Impl>();
407 }
408 return impl_.get();
409 }
410
CreateForTesting(CertStores stores)411 std::unique_ptr<TrustStoreWin> TrustStoreWin::CreateForTesting(
412 CertStores stores) {
413 return base::WrapUnique(new TrustStoreWin(
414 std::make_unique<TrustStoreWin::Impl>(std::move(stores))));
415 }
416
TrustStoreWin(std::unique_ptr<Impl> impl)417 TrustStoreWin::TrustStoreWin(std::unique_ptr<Impl> impl)
418 : impl_(std::move(impl)) {}
419
420 TrustStoreWin::~TrustStoreWin() = default;
421
SyncGetIssuersOf(const bssl::ParsedCertificate * cert,bssl::ParsedCertificateList * issuers)422 void TrustStoreWin::SyncGetIssuersOf(const bssl::ParsedCertificate* cert,
423 bssl::ParsedCertificateList* issuers) {
424 MaybeInitializeAndGetImpl()->SyncGetIssuersOf(cert, issuers);
425 }
426
427 // As documented in IsCertTrustedForServerAuth(), on Windows, the
428 // set of extended key usages present in a certificate can be further
429 // scoped down by user setting; effectively, disabling a given EKU for
430 // a given intermediate or root.
431 //
432 // Windows uses this during path building when filtering the EKUs; if it
433 // encounters this property, it uses the combined EKUs to determine
434 // whether to continue path building, but doesn't treat the certificate
435 // as affirmatively revoked/distrusted.
436 //
437 // This behaviour is replicated here by returning Unspecified trust if
438 // we find instances of the cert that do not have the correct EKUs set
439 // for TLS Server Auth. This allows path building to continue and allows
440 // us to later trust the cert if it is present in Chrome Root Store.
441 //
442 // Windows does have some idiosyncrasies here, which result in the
443 // following treatment:
444 //
445 // - If a certificate is in the Disallowed store, it is distrusted for
446 // all purposes regardless of any EKUs that are set.
447 // - If a certificate is in the ROOT store, and usable for TLS Server Auth,
448 // then it's trusted.
449 // - If a certificate is in the root store, and lacks the EKU, then continue
450 // path building, but don't treat it as trusted (aka Unspecified).
451 // - If we can't find the cert anywhere, then continue path
452 // building, but don't treat it as trusted (aka Unspecified).
453 //
454 // If a certificate is found multiple times in the ROOT store, it is trusted
455 // for TLS server auth if any instance of the certificate found
456 // is usable for TLS server auth.
GetTrust(const bssl::ParsedCertificate * cert)457 bssl::CertificateTrust TrustStoreWin::GetTrust(
458 const bssl::ParsedCertificate* cert) {
459 return MaybeInitializeAndGetImpl()->GetTrust(cert);
460 }
461
462 } // namespace net
463