Windows tlsCertificateSelector subject selector picks first matching cert only and can fail on expired duplicate-subject cert

XMLWordPrintableJSON

    • Server Security
    • Fully Compatible
    • ALL
    • Hide

      1. On Windows, create or import two certificates into the Windows `My` certificate store with the same subject.
      2. Ensure the first certificate returned by `CertFindCertificateInStore(... CERT_FIND_SUBJECT_STR ..., NULL)` is expired.
      3. Ensure another certificate with the same subject is valid and has an accessible private key.
      4. Configure MongoDB with a subject selector, for example:

      ```yaml
      net:
        tls:
          mode: requireTLS
          certificateSelector: "subject=<shared subject>"

      Start mongod.

       

      Actual result:
      MongoDB selects the first matching subject certificate, then fails startup with:

       

      The provided SSL certificate is expired or not yet valid.

      Show
      1. On Windows, create or import two certificates into the Windows `My` certificate store with the same subject. 2. Ensure the first certificate returned by `CertFindCertificateInStore(... CERT_FIND_SUBJECT_STR ..., NULL)` is expired. 3. Ensure another certificate with the same subject is valid and has an accessible private key. 4. Configure MongoDB with a subject selector, for example: ```yaml net:   tls:     mode: requireTLS     certificateSelector: "subject=<shared subject>" Start mongod .   Actual result: MongoDB selects the first matching subject certificate, then fails startup with:   The provided SSL certificate is expired or not yet valid.
    • Server Security 2026-07-03
    • None
    • None
    • None
    • None
    • None
    • None
    • None

      1. Windows `tlsCertificateSelector: subject=...` selects first matching cert only
        1. Summary

      On Windows, MongoDB's TLS certificate selector can select the wrong certificate when
      `tlsCertificateSelector` uses `subject=...` and the Windows `My` certificate store contains multiple
      certificates with the same subject.

      The current implementation calls `CertFindCertificateInStore()` once with
      `pPrevCertContext = NULL`, accepts the first returned matching certificate, and only later validates
      whether that certificate is expired or not yet valid.

      If the first matching certificate is expired, MongoDB fatals during startup, even if another
      matching certificate with the same subject is valid and has an accessible private key.

        1. Affected Area
      • MongoDB server TLS certificate selection
      • Windows only
      • Windows certificate store / Schannel path
      • `tlsCertificateSelector: subject=...`

      Observed in `mongodb/mongo` `master` at:

      ```text
      aee5a98a
      ```

        1. Current Behavior

      With a config like:

      ```yaml
      net:
        tls:
          mode: requireTLS
          certificateSelector: "subject=example.domain.local"
      ```

      MongoDB selects the first certificate returned by Windows CryptoAPI for that subject.

      If that first matching certificate is expired, MongoDB fails startup with:

      ```text
      The provided SSL certificate is expired or not yet valid.
      ```

      MongoDB does not continue searching for another certificate with the same subject.

        1. Expected Behavior

      When selecting by `subject=...` on Windows, MongoDB should search all matching certificates and
      choose a usable certificate.

      A usable certificate should at least:

      • Match the requested subject
      • Be currently valid according to `NotBefore` / `NotAfter`
      • Have an accessible private key

      If multiple matching certificates exist, an expired matching certificate should not prevent MongoDB
      from selecting a later valid matching certificate.

        1. Current Code Path

      File:

      ```text
      src/mongo/util/net/ssl_manager_windows.cpp
      ```

      Function:

      ```cpp
      StatusWith<UniqueCertificate> loadCertificateSelectorFromStore(
          SSLParams::CertificateSelector selector, DWORD storeType, std::string_view storeName)
      ```

      Current subject-selector branch:

      ```cpp
      if (!selector.subject.empty()) {
          std::wstring wstr = toNativeString(selector.subject.c_str());

          PCCERT_CONTEXT cert = CertFindCertificateInStore(storeHolder,
                                                           X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
                                                           0,
                                                           CERT_FIND_SUBJECT_STR,
                                                           wstr.c_str(),
                                                           NULL);
          if (!cert)

      {         auto ec = lastSystemError();         return Status(             ErrorCodes::InvalidSSLConfiguration,             str::stream()                 << "CertFindCertificateInStore failed to find cert with subject name '"                 << selector.subject.c_str() << "' in 'My' store in '" << storeName                 << "': " << errorMessage(ec));     }

          return UniqueCertificate(cert);
      }
      ```

      The important part is the final argument:

      ```cpp
      NULL
      ```

      This means MongoDB asks CryptoAPI for the first matching certificate in the store. MongoDB then
      immediately accepts that certificate:

      ```cpp
      return UniqueCertificate(cert);
      ```

      There is no loop over additional matching certificates.

        1. Where Startup Fails

      Later, the selected certificate is validated in:

      ```cpp
      Status SSLManagerWindows::_validateCertificate(PCCERT_CONTEXT cert,
                                                     SSLX509Name* subjectName,
                                                     Date_t* serverCertificateExpirationDate)
      ```

      Relevant code:

      ```cpp
      if ((FiletimeToULL(cert->pCertInfo->NotBefore) > currentTimeLong) ||
          (currentTimeLong > FiletimeToULL(cert->pCertInfo->NotAfter)))

      {     LOGV2_FATAL_NOTRACE(50755, "The provided SSL certificate is expired or not yet valid."); }

      ```

      So the current behavior is:

      ```text
      1. Find first cert matching subject.
      2. Return it immediately.
      3. Later validate it.
      4. If it is expired, fatal startup.
      5. Never try the next matching subject cert.
      ```

        1. Why This Is Windows-Specific

      This path is specific to:

      ```text
      src/mongo/util/net/ssl_manager_windows.cpp
      ```

      The Windows certificate selector uses CryptoAPI / Schannel and searches the Windows `My`
      certificate store. Linux/OpenSSL deployments generally use PEM files directly, so this specific
      duplicate-subject Windows store behavior does not occur in the same way.

        1. Proof of Behavior

      I reproduced the behavior with a small Windows CryptoAPI harness using the same API pattern MongoDB
      uses:

      ```cpp
      CertFindCertificateInStore(
          store,
          X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
          0,
          CERT_FIND_SUBJECT_STR,
          subject,
          NULL);
      ```

      Test setup:

      ```text

      • Create two certificates with the same subject.
      • Certificate 1: expired.
      • Certificate 2: valid.
      • Search by subject using CertFindCertificateInStore.
        ```

      Current MongoDB-shaped behavior:

      ```text
      Current MongoDB shape, one CertFind call:
        selected: CN=MongoSelectorHarness-... | NotBefore=2026-06-05 | NotAfter=2026-06-15 | expired
      ```

      Fixed loop behavior:

      ```text
      Fixed shape, repeated CertFind calls with previous context:
        candidate 1: CN=MongoSelectorHarness-... | NotAfter=2026-06-15 | expired -> skip
        candidate 2: CN=MongoSelectorHarness-... | NotAfter=2026-07-25 | valid -> select
        result: CN=MongoSelectorHarness-... | valid
      ```

      This demonstrates that the current one-call selector can return the expired certificate, while using
      the documented `pPrevCertContext` iteration pattern allows MongoDB to find the later valid
      certificate.

        1. Proposed Fix

      Change the `subject=...` branch in `loadCertificateSelectorFromStore()` to loop over all matching
      certificates.

      Instead of:

      ```cpp
      PCCERT_CONTEXT cert = CertFindCertificateInStore(..., wstr.c_str(), NULL);
      return UniqueCertificate(cert);
      ```

      Use the previous certificate context to continue the search:

      ```cpp
      PCCERT_CONTEXT previous = nullptr;

      while (true) {
          PCCERT_CONTEXT cert = CertFindCertificateInStore(
              storeHolder,
              X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
              0,
              CERT_FIND_SUBJECT_STR,
              wstr.c_str(),
              previous);

          previous = cert;

          if (!cert)

      {         break;     }

          if (cert is expired or not yet valid)

      {         continue;     }

          if (cert does not have an accessible private key) {         continue;     }

          return UniqueCertificate(CertDuplicateCertificateContext(cert));
      }
      ```

      The existing private-key validation currently happens after selection in:

      ```cpp
      loadAndValidateCertificateSelector()
      ```

      That logic could be extracted into a helper and reused while iterating subject candidates.

        1. Important Design Point

      For `thumbprint=...`, the current behavior should probably remain strict.

      If the user selects an exact thumbprint, MongoDB should select exactly that certificate and fail if
      that exact certificate is expired or unusable.

      The issue is specifically with `subject=...`, because subject is not unique.

        1. Operational Impact

      This affects Windows deployments using certificate selectors such as:

      ```yaml
      net:
        tls:
          mode: requireTLS
          certificateSelector: "subject=example.domain.local"
      ```

      A common operational scenario is certificate renewal where the old expired certificate remains in
      `LocalMachine\My` or `CurrentUser\My` alongside the renewed certificate. If both certificates share
      the same subject, MongoDB may select the expired certificate first and fail startup.

        1. Workarounds

      Use `thumbprint=...` instead of `subject=...`, pointing at the renewed valid certificate:

      ```yaml
      net:
        tls:
          certificateSelector: "thumbprint=<valid_certificate_thumbprint>"
      ```

      Alternatively, remove the expired duplicate-subject certificate from the Windows `My` certificate
      store.

        1. Suggested Fix Summary

      `loadCertificateSelectorFromStore()` should not treat the first `CERT_FIND_SUBJECT_STR` match as
      authoritative.

      It should iterate all matching subject certificates with:

      ```cpp
      CertFindCertificateInStore(..., previousContext)
      ```

      Then it should skip expired, not-yet-valid, or otherwise unusable candidates and return the first
      usable candidate.

            Assignee:
            Chye Lin Chee
            Reporter:
            p. nielsen (EXT)
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated:
              Resolved: