Skip to content

Load the HTTPS certificate while validating settings instead of at Kestrel bind time - #5891

Open
ramonsmits wants to merge 3 commits into
masterfrom
ramon/validate-https-certificate-earlier
Open

ramonsmits wants to merge 3 commits into
masterfrom
ramon/validate-https-certificate-earlier

Conversation

@ramonsmits

@ramonsmits ramonsmits commented Sep 14, 2026

Copy link
Copy Markdown
Member

Is your improvement related to a problem? Please describe.

AddServiceControlHttps loads the certificate inside the ConfigureHttpsDefaults callback, which Kestrel invokes when binding endpoints — the last step of startup. An unusable certificate therefore fails only after RavenDB, the transport and every hosted service have started and have to be torn down again. The reported error is .NET's own text, which names neither the file nor the setting that supplies the password.

Two further certificate problems were not reported at all or only at bind time:

  • A PFX without a private key loads fine and Kestrel binds it, but every TLS handshake then fails. The only symptom is that clients cannot connect.
  • A certificate whose Extended Key Usage (EKU) extension excludes Server Authentication is rejected by Kestrel, but again only when the endpoint is bound.

Describe the suggested solution

The certificate is now loaded and validated while HttpsSettings is constructed, before the host is built, and AddServiceControlHttps uses the loaded instance. Validation rejects, in order:

  1. A file that cannot be loaded. The message names the file, its size and last-modified time, whether a password was configured (never the password itself, since .NET collapses several unrelated causes into "the password may be incorrect"), and the root cause via GetBaseException(). For an empty file that exposes the underlying EndOfStreamException that the outer CryptographicException hides; for the other cases both messages are identical.
  2. A certificate without a private key.
  3. A certificate with an EKU extension that lacks Server Authentication (OID 1.3.6.1.5.5.7.3.1). This mirrors Kestrel's own rule, including accepting a certificate that has no EKU extension at all, so nothing that binds today is refused.

Every message ends with the same escape hatch: set Https.Enabled to false to start without HTTPS while investigating.

The stale ".pfx or .pem" wording in the property documentation and the required-path message was replaced with PKCS#12 / .pfx, which is what the loader accepts.

Describe alternatives you've considered

Improving only the message and leaving the load where it is: keeps the discarded startup work and the ungraceful teardown that follows a failed Host.StartAsync.

Leaving the EKU rule to Kestrel: it is the last certificate failure that would still surface only at bind time, and the check is a few lines that follow Kestrel's rule exactly.

Additional context

The test fixture used an empty Path.GetTempFileName() as a stand-in certificate, which only worked because nothing opened it; it now generates real self-signed PFX files, optionally with a password or a specific EKU. Tests added for loading, a wrong password, and a client-authentication-only EKU. The private key check has no dedicated test since it only forwards X509Certificate2.HasPrivateKey.

…strel bind time

AddServiceControlHttps loaded the certificate inside the ConfigureHttpsDefaults
callback, which Kestrel invokes when binding endpoints. An unusable certificate
therefore failed only after RavenDB, the transport and every hosted service had
started and had to be torn down again.

ValidateCertificateConfiguration now loads the certificate too, and the failure
names the file, its size and last-modified time, and whether a password was
configured, but never the password itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread src/ServiceControl.Infrastructure/HttpsSettings.cs Outdated
Comment thread src/ServiceControl.Infrastructure/HttpsSettings.cs Outdated
… cause of load failures

Kestrel does not verify the private key when binding, so a certificate-only PFX
started fine and then failed every TLS handshake. The load-failure message now
uses the base exception, which for an empty file exposes the EndOfStreamException
that CryptographicException otherwise hides. The certificate is assigned in the
constructor like the other properties, and the PFX-only wording replaces the
stale ".pfx or .pem" references.
…Authentication

Mirrors the rule Kestrel applies when binding the HTTPS endpoint, so the failure
is reported during settings validation instead of after every hosted service has
started. A certificate without an EKU extension is accepted, as Kestrel does.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants