dotnet / dotnet/aspnetcore

Kestrel HTTPS: Incorrect behavior when missing access to private key on Windows

Open
#50,400 10 comments 15 reactions 0 assignees View on GitHub
area-networking
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 6h
Merged PRs (30d)
290

Description

### Is there an existing issue for this?

- [X] I have searched the existing issues

### Describe the bug

When using an HTTPS endpoint in Kestrel with a certificate from the Windows `LocalMachine` certificate store, if the ASP.NET Core application is running without administrator privileges, it usually does not have access to the private key of the certificate, only the public information.

If this is the case, Kestrel will start successfully, but any attempt to access the service over HTTPS will result in a "connection closed" error (`ERR_CONNECTION_CLOSED` in Chromium, `PR_END_OF_FILE_ERROR` in Firefox). Kestrel does not log an error, except at the `Debug` log level, while most other certificate issues will cause the application to fail to start entirely.

Aside from the exception logged at the `Debug` level, the only other indication that something is amiss is in the event viewer, where a generic error is logged by Schannel every time Kestrel attempts to load the private key, however no information about the application is present in these errors.

This issue can be very difficult to diagnose on Windows Server when running an ASP.NET Core app as a Windows Service running under a non-admin user.

### Expected Behavior

If possible, the application should fail to start with an exception, as happens with other certificate issues.

Otherwise, the exception thrown internally should be logged at a higher severity and with a clearer message.

### Steps To Reproduce

These steps assume that UAC is enabled on the machine.

1. Open a new PowerShell window **with administrator privileges** and execute the following command to generate a certificate:
```pwsh
New-SelfSignedCertificate -Subject TestCert -DnsName localhost -CertStoreLocation Cert:\LocalMachine\My
```
2. Open any terminal **as a regular user** in an empty directory and create a new ASP.NET Core Web API project:
```cmd
dotnet new webapi
```
3. Edit `appsettings.Development.json` and add the following section:
```json
"Kestrel": {
"Endpoints": {
"Https": {
"Url": "https://localhost:7127",
"Certificate": {
"Store": "My",
"Location": "LocalMachine",
"Subject": "TestCert",
"AllowInvalid": true
}
}
}
}
```
Optionally change the `LogLevel`.
NOTE: The value of `AllowInvalid` has no effect on this issue. I have set it to `true` in this example simply to avoid having to add the certificate to the root store.
4. Run the app with `dotnet run` and attempt to access `https://localhost:7127/swagger` in the browser.
Observe the command line output and Event Viewer.

### Exceptions (if any)

Error in Event Viewer:
```
A fatal error occurred when attempting to access the TLS server credential private key. The error code returned from the cryptographic module is 0x8009030D. The internal error state is 10001.
```

Exception logged by Kestrel:
```
dbug: Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware[1]
Failed to authenticate HTTPS connection.
System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception.
---> System.ComponentModel.Win32Exception (0x8009030D): The credentials supplied to the package were not recognized
at System.Net.SSPIWrapper.AcquireCredentialsHandle(ISSPIInterface secModule, String package, CredentialUse intent, SCH_CREDENTIALS* scc)
at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(CredentialUse credUsage, SCH_CREDENTIALS* secureCredential)
at System.Net.Security.SslStreamPal.AcquireCredentialsHandleSchCredentials(SslAuthenticationOptions authOptions)
at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(SslAuthenticationOptions sslAuthenticationOptions)
--- End of inner exception stack trace ---
at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(SslAuthenticationOptions sslAuthenticationOptions)
at System.Net.Security.SslStream.AcquireCredentialsHandle(SslAuthenticationOptions sslAuthenticationOptions)
at System.Net.Security.SslStream.AcquireServerCredentials(Byte[]& thumbPrint)
at System.Net.Security.SslStream.GenerateToken(ReadOnlySpan`1 inputBuffer, Byte[]& output)
at System.Net.Security.SslStream.NextMessage(ReadOnlySpan`1 incomingBuffer)
at System.Net.Security.SslStream.ProcessBlob(Int32 frameSize)
at System.Net.Security.SslStream.ReceiveBlobAsync[TIOAdapter](CancellationToken cancellationToken)
at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](Boolean receiveFirst, Byte[] reAuthenticationData, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware.OnConnectionAsync(ConnectionContext context)
```

### .NET Version

7.0.400

### Anything else?

For any others encountering this issue, here the correct way to fix the permissions on the private key:
1. Open `certmgr.msc` with admin permissions (start, find `Manage computer certificates`)
2. Locate the certificate that will be used
3. Right-click, All Tasks -> Manage Private Keys...
4. Add permissions for the user that will run the app

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.