|
22 | 22 | #include <Security/Security.h>
|
23 | 23 | #endif
|
24 | 24 |
|
| 25 | +#ifdef _WIN32 |
| 26 | +#include <Windows.h> |
| 27 | +#include <wincrypt.h> |
| 28 | +#endif |
| 29 | + |
25 | 30 | namespace node {
|
26 | 31 |
|
27 | 32 | using ncrypto::BignumPointer;
|
@@ -285,13 +290,15 @@ void X509VectorToPEMVector(const std::vector<X509Pointer>& src,
|
285 | 290 | }
|
286 | 291 | }
|
287 | 292 |
|
288 |
| -#ifdef __APPLE__ |
289 |
| -// This code is loosely based on |
| 293 | +// The following code is loosely based on |
290 | 294 | // https://github.com/chromium/chromium/blob/54bd8e3/net/cert/internal/trust_store_mac.cc
|
| 295 | +// and |
| 296 | +// https://github.com/chromium/chromium/blob/0192587/net/cert/internal/trust_store_win.cc |
291 | 297 | // Copyright 2015 The Chromium Authors
|
292 | 298 | // Licensed under a BSD-style license
|
293 | 299 | // See https://chromium.googlesource.com/chromium/src/+/HEAD/LICENSE for
|
294 | 300 | // details.
|
| 301 | +#ifdef __APPLE__ |
295 | 302 | TrustStatus IsTrustDictionaryTrustedForPolicy(CFDictionaryRef trust_dict,
|
296 | 303 | bool is_self_issued) {
|
297 | 304 | // Trust settings may be scoped to a single application
|
@@ -524,11 +531,160 @@ void ReadMacOSKeychainCertificates(
|
524 | 531 | }
|
525 | 532 | #endif // __APPLE__
|
526 | 533 |
|
| 534 | +#ifdef _WIN32 |
| 535 | + |
| 536 | +// Returns true if the cert can be used for server authentication, based on |
| 537 | +// certificate properties. |
| 538 | +// |
| 539 | +// While there are a variety of certificate properties that can affect how |
| 540 | +// trust is computed, the main property is CERT_ENHKEY_USAGE_PROP_ID, which |
| 541 | +// is intersected with the certificate's EKU extension (if present). |
| 542 | +// The intersection is documented in the Remarks section of |
| 543 | +// CertGetEnhancedKeyUsage, and is as follows: |
| 544 | +// - No EKU property, and no EKU extension = Trusted for all purpose |
| 545 | +// - Either an EKU property, or EKU extension, but not both = Trusted only |
| 546 | +// for the listed purposes |
| 547 | +// - Both an EKU property and an EKU extension = Trusted for the set |
| 548 | +// intersection of the listed purposes |
| 549 | +// CertGetEnhancedKeyUsage handles this logic, and if an empty set is |
| 550 | +// returned, the distinction between the first and third case can be |
| 551 | +// determined by GetLastError() returning CRYPT_E_NOT_FOUND. |
| 552 | +// |
| 553 | +// See: |
| 554 | +// https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certgetenhancedkeyusage |
| 555 | +// |
| 556 | +// If we run into any errors reading the certificate properties, we fail |
| 557 | +// closed. |
| 558 | +bool IsCertTrustedForServerAuth(PCCERT_CONTEXT cert) { |
| 559 | + DWORD usage_size = 0; |
| 560 | + |
| 561 | + if (!CertGetEnhancedKeyUsage(cert, 0, nullptr, &usage_size)) { |
| 562 | + return false; |
| 563 | + } |
| 564 | + |
| 565 | + std::vector<BYTE> usage_bytes(usage_size); |
| 566 | + CERT_ENHKEY_USAGE* usage = |
| 567 | + reinterpret_cast<CERT_ENHKEY_USAGE*>(usage_bytes.data()); |
| 568 | + if (!CertGetEnhancedKeyUsage(cert, 0, usage, &usage_size)) { |
| 569 | + return false; |
| 570 | + } |
| 571 | + |
| 572 | + if (usage->cUsageIdentifier == 0) { |
| 573 | + // check GetLastError |
| 574 | + HRESULT error_code = GetLastError(); |
| 575 | + |
| 576 | + switch (error_code) { |
| 577 | + case CRYPT_E_NOT_FOUND: |
| 578 | + return true; |
| 579 | + case S_OK: |
| 580 | + return false; |
| 581 | + default: |
| 582 | + return false; |
| 583 | + } |
| 584 | + } |
| 585 | + |
| 586 | + // SAFETY: `usage->rgpszUsageIdentifier` is an array of LPSTR (pointer to null |
| 587 | + // terminated string) of length `usage->cUsageIdentifier`. |
| 588 | + for (DWORD i = 0; i < usage->cUsageIdentifier; ++i) { |
| 589 | + std::string_view eku(usage->rgpszUsageIdentifier[i]); |
| 590 | + if ((eku == szOID_PKIX_KP_SERVER_AUTH) || |
| 591 | + (eku == szOID_ANY_ENHANCED_KEY_USAGE)) { |
| 592 | + return true; |
| 593 | + } |
| 594 | + } |
| 595 | + |
| 596 | + return false; |
| 597 | +} |
| 598 | + |
| 599 | +void GatherCertsForLocation(std::vector<X509Pointer>* vector, |
| 600 | + DWORD location, |
| 601 | + LPCWSTR store_name) { |
| 602 | + if (!(location == CERT_SYSTEM_STORE_LOCAL_MACHINE || |
| 603 | + location == CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY || |
| 604 | + location == CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE || |
| 605 | + location == CERT_SYSTEM_STORE_CURRENT_USER || |
| 606 | + location == CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY)) { |
| 607 | + return; |
| 608 | + } |
| 609 | + |
| 610 | + DWORD flags = |
| 611 | + location | CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG; |
| 612 | + |
| 613 | + HCERTSTORE opened_store( |
| 614 | + CertOpenStore(CERT_STORE_PROV_SYSTEM, |
| 615 | + 0, |
| 616 | + // The Windows API only accepts NULL for hCryptProv. |
| 617 | + NULL, /* NOLINT (readability/null_usage) */ |
| 618 | + flags, |
| 619 | + store_name)); |
| 620 | + if (!opened_store) { |
| 621 | + return; |
| 622 | + } |
| 623 | + |
| 624 | + auto cleanup = OnScopeLeave( |
| 625 | + [opened_store]() { CHECK_EQ(CertCloseStore(opened_store, 0), TRUE); }); |
| 626 | + |
| 627 | + PCCERT_CONTEXT cert_from_store = nullptr; |
| 628 | + while ((cert_from_store = CertEnumCertificatesInStore( |
| 629 | + opened_store, cert_from_store)) != nullptr) { |
| 630 | + if (!IsCertTrustedForServerAuth(cert_from_store)) { |
| 631 | + continue; |
| 632 | + } |
| 633 | + const unsigned char* cert_data = |
| 634 | + reinterpret_cast<const unsigned char*>(cert_from_store->pbCertEncoded); |
| 635 | + const size_t cert_size = cert_from_store->cbCertEncoded; |
| 636 | + |
| 637 | + vector->emplace_back(d2i_X509(nullptr, &cert_data, cert_size)); |
| 638 | + } |
| 639 | +} |
| 640 | + |
| 641 | +void ReadWindowsCertificates( |
| 642 | + std::vector<std::string>* system_root_certificates) { |
| 643 | + std::vector<X509Pointer> system_root_certificates_X509; |
| 644 | + // TODO(joyeecheung): match Chromium's policy, collect more certificates |
| 645 | + // from user-added CAs and support disallowed (revoked) certificates. |
| 646 | + |
| 647 | + // Grab the user-added roots. |
| 648 | + GatherCertsForLocation( |
| 649 | + &system_root_certificates_X509, CERT_SYSTEM_STORE_LOCAL_MACHINE, L"ROOT"); |
| 650 | + GatherCertsForLocation(&system_root_certificates_X509, |
| 651 | + CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY, |
| 652 | + L"ROOT"); |
| 653 | + GatherCertsForLocation(&system_root_certificates_X509, |
| 654 | + CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE, |
| 655 | + L"ROOT"); |
| 656 | + GatherCertsForLocation( |
| 657 | + &system_root_certificates_X509, CERT_SYSTEM_STORE_CURRENT_USER, L"ROOT"); |
| 658 | + GatherCertsForLocation(&system_root_certificates_X509, |
| 659 | + CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY, |
| 660 | + L"ROOT"); |
| 661 | + |
| 662 | + // Grab the user-added trusted server certs. Trusted end-entity certs are |
| 663 | + // only allowed for server auth in the "local machine" store, but not in the |
| 664 | + // "current user" store. |
| 665 | + GatherCertsForLocation(&system_root_certificates_X509, |
| 666 | + CERT_SYSTEM_STORE_LOCAL_MACHINE, |
| 667 | + L"TrustedPeople"); |
| 668 | + GatherCertsForLocation(&system_root_certificates_X509, |
| 669 | + CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY, |
| 670 | + L"TrustedPeople"); |
| 671 | + GatherCertsForLocation(&system_root_certificates_X509, |
| 672 | + CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE, |
| 673 | + L"TrustedPeople"); |
| 674 | + |
| 675 | + X509VectorToPEMVector(system_root_certificates_X509, |
| 676 | + system_root_certificates); |
| 677 | +} |
| 678 | +#endif |
| 679 | + |
527 | 680 | void ReadSystemStoreCertificates(
|
528 | 681 | std::vector<std::string>* system_root_certificates) {
|
529 | 682 | #ifdef __APPLE__
|
530 | 683 | ReadMacOSKeychainCertificates(system_root_certificates);
|
531 | 684 | #endif
|
| 685 | +#ifdef _WIN32 |
| 686 | + ReadWindowsCertificates(system_root_certificates); |
| 687 | +#endif |
532 | 688 | }
|
533 | 689 |
|
534 | 690 | std::vector<std::string> getCombinedRootCertificates() {
|
|
0 commit comments