-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Tidy up some irregularities in certificate reloading #46819
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7abc89c
a89fe8d
9438e04
631512c
612904d
0ecec6d
7e5e531
7d142a2
3c6f814
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -151,14 +151,22 @@ public class KestrelServerOptions | |
| private Action<HttpsConnectionAdapterOptions> HttpsDefaults { get; set; } = _ => { }; | ||
|
|
||
| /// <summary> | ||
| /// The default server certificate for https endpoints. This is applied lazily after HttpsDefaults and user options. | ||
| /// The development server certificate for https endpoints. This is applied lazily after HttpsDefaults and user options. | ||
| /// </summary> | ||
| internal X509Certificate2? DefaultCertificate { get; set; } | ||
| /// <remarks> | ||
| /// Getter exposed for testing. | ||
| /// </remarks> | ||
| internal X509Certificate2? DevelopmentCertificate { get; private set; } | ||
|
|
||
| /// <summary> | ||
| /// Allow tests to explicitly set the default certificate. | ||
| /// </summary> | ||
| internal X509Certificate2? TestOverrideDefaultCertificate { get; set; } | ||
|
||
|
|
||
| /// <summary> | ||
| /// Has the default dev certificate load been attempted? | ||
| /// </summary> | ||
| internal bool IsDevCertLoaded { get; set; } | ||
| internal bool IsDevelopmentCertificateLoaded { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Internal AppContext switch to toggle the WebTransport and HTTP/3 datagrams experiemental features. | ||
|
|
@@ -227,16 +235,34 @@ internal void ApplyHttpsDefaults(HttpsConnectionAdapterOptions httpsOptions) | |
| HttpsDefaults(httpsOptions); | ||
| } | ||
|
|
||
| internal void ApplyDefaultCert(HttpsConnectionAdapterOptions httpsOptions) | ||
| internal void ApplyDefaultCertificate(HttpsConnectionAdapterOptions httpsOptions) | ||
| { | ||
| if (httpsOptions.ServerCertificate != null || httpsOptions.ServerCertificateSelector != null) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| EnsureDefaultCert(); | ||
| if (TestOverrideDefaultCertificate is X509Certificate2 certificateFromTest) | ||
| { | ||
| httpsOptions.ServerCertificate = certificateFromTest; | ||
| return; | ||
| } | ||
|
|
||
| if (ConfigurationLoader?.DefaultCertificate is X509Certificate2 certificateFromLoader) | ||
| { | ||
| httpsOptions.ServerCertificate = certificateFromLoader; | ||
| return; | ||
| } | ||
|
|
||
| httpsOptions.ServerCertificate = DefaultCertificate; | ||
| if (!IsDevelopmentCertificateLoaded) | ||
| { | ||
| IsDevelopmentCertificateLoaded = true; | ||
| Debug.Assert(DevelopmentCertificate is null); | ||
| var logger = ApplicationServices!.GetRequiredService<ILogger<KestrelServer>>(); | ||
| DevelopmentCertificate = GetDevelopmentCertificateFromStore(logger); | ||
| } | ||
|
|
||
| httpsOptions.ServerCertificate = DevelopmentCertificate; | ||
| } | ||
|
|
||
| internal void Serialize(Utf8JsonWriter writer) | ||
|
|
@@ -253,8 +279,8 @@ internal void Serialize(Utf8JsonWriter writer) | |
| writer.WritePropertyName(nameof(AllowResponseHeaderCompression)); | ||
| writer.WriteBooleanValue(AllowResponseHeaderCompression); | ||
|
|
||
| writer.WritePropertyName(nameof(IsDevCertLoaded)); | ||
| writer.WriteBooleanValue(IsDevCertLoaded); | ||
| writer.WritePropertyName(nameof(IsDevelopmentCertificateLoaded)); | ||
| writer.WriteBooleanValue(IsDevelopmentCertificateLoaded); | ||
|
|
||
| writer.WriteString(nameof(RequestHeaderEncodingSelector), RequestHeaderEncodingSelector == DefaultHeaderEncodingSelector ? "default" : "configured"); | ||
| writer.WriteString(nameof(ResponseHeaderEncodingSelector), ResponseHeaderEncodingSelector == DefaultHeaderEncodingSelector ? "default" : "configured"); | ||
|
|
@@ -280,46 +306,44 @@ internal void Serialize(Utf8JsonWriter writer) | |
| writer.WriteEndArray(); | ||
| } | ||
|
|
||
| private void EnsureDefaultCert() | ||
| private static X509Certificate2? GetDevelopmentCertificateFromStore(ILogger<KestrelServer> logger) | ||
| { | ||
| if (DefaultCertificate == null && !IsDevCertLoaded) | ||
| try | ||
| { | ||
| IsDevCertLoaded = true; // Only try once | ||
| var logger = ApplicationServices!.GetRequiredService<ILogger<KestrelServer>>(); | ||
| try | ||
| var cert = CertificateManager.Instance.ListCertificates(StoreName.My, StoreLocation.CurrentUser, isValid: true, requireExportable: false) | ||
| .FirstOrDefault(); | ||
|
|
||
| if (cert is null) | ||
| { | ||
| DefaultCertificate = CertificateManager.Instance.ListCertificates(StoreName.My, StoreLocation.CurrentUser, isValid: true, requireExportable: false) | ||
| .FirstOrDefault(); | ||
|
|
||
| if (DefaultCertificate != null) | ||
| { | ||
| var status = CertificateManager.Instance.CheckCertificateState(DefaultCertificate, interactive: false); | ||
| if (!status.Success) | ||
| { | ||
| // Display a warning indicating to the user that a prompt might appear and provide instructions on what to do in that | ||
| // case. The underlying implementation of this check is specific to Mac OS and is handled within CheckCertificateState. | ||
| // Kestrel must NEVER cause a UI prompt on a production system. We only attempt this here because Mac OS is not supported | ||
| // in production. | ||
| Debug.Assert(status.FailureMessage != null, "Status with a failure result must have a message."); | ||
| logger.DeveloperCertificateFirstRun(status.FailureMessage); | ||
|
|
||
| // Prevent binding to HTTPS if the certificate is not valid (avoid the prompt) | ||
| DefaultCertificate = null; | ||
| } | ||
| else if (!CertificateManager.Instance.IsTrusted(DefaultCertificate)) | ||
| { | ||
| logger.DeveloperCertificateNotTrusted(); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| logger.UnableToLocateDevelopmentCertificate(); | ||
| } | ||
| logger.UnableToLocateDevelopmentCertificate(); | ||
| return null; | ||
amcasey marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| catch | ||
|
|
||
| var status = CertificateManager.Instance.CheckCertificateState(cert, interactive: false); | ||
| if (!status.Success) | ||
| { | ||
| logger.UnableToLocateDevelopmentCertificate(); | ||
| // Display a warning indicating to the user that a prompt might appear and provide instructions on what to do in that | ||
| // case. The underlying implementation of this check is specific to Mac OS and is handled within CheckCertificateState. | ||
| // Kestrel must NEVER cause a UI prompt on a production system. We only attempt this here because Mac OS is not supported | ||
| // in production. | ||
| Debug.Assert(status.FailureMessage != null, "Status with a failure result must have a message."); | ||
| logger.DeveloperCertificateFirstRun(status.FailureMessage); | ||
|
|
||
| // Prevent binding to HTTPS if the certificate is not valid (avoid the prompt) | ||
| return null; | ||
| } | ||
|
|
||
| if (!CertificateManager.Instance.IsTrusted(cert)) | ||
| { | ||
| logger.DeveloperCertificateNotTrusted(); | ||
| } | ||
|
|
||
| return cert; | ||
| } | ||
| catch | ||
| { | ||
| logger.UnableToLocateDevelopmentCertificate(); | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.