Skip to content

Commit 63ac54d

Browse files
committed
Added troubleshooting section for X.509 on Windows web servers
1 parent f9c26e4 commit 63ac54d

File tree

1 file changed

+59
-1
lines changed

1 file changed

+59
-1
lines changed

src/content/docs/identityserver/troubleshooting.md

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,4 +267,62 @@ When dealing with external authentication, you may want to set `MapInboundClaims
267267

268268
### Implement `OnTicketReceived` To Reduce Cookie Size
269269

270-
When dealing with external authentication, you may want to implement `OnTicketReceived` to reduce the size of the cookie. This is a callback that is invoked after the external authentication process is complete. You can use this callback to remove any claims that are not needed by your solution.
270+
When dealing with external authentication, you may want to implement `OnTicketReceived` to reduce the size of the cookie. This is a callback that is invoked after the external authentication process is complete. You can use this callback to remove any claims that are not needed by your solution.
271+
272+
## X.509 Certificates
273+
274+
When your IdentityServer setup is hosted in a Windows environment, there's a high possibility that private key material
275+
is being stored or read from a user profile location. On Azure however, App Services are typically configured not to load
276+
a user profile because this brings overhead and is often not needed. This can result in runtime errors when IdentityServer
277+
attempts to generate or load key material:
278+
279+
```text
280+
System.Security.Cryptography.CryptographicException: Access denied.
281+
at System.Security.Cryptography.X509Certificates.X509CertificateLoader.ImportPfx(ReadOnlySpan`1 data, ReadOnlySpan`1 password, X509KeyStorageFlags keyStorageFlags)
282+
at System.Security.Cryptography.X509Certificates.X509CertificateLoader.LoadPkcs12NoLimits(ReadOnlyMemory`1 data, ReadOnlySpan`1 password, X509KeyStorageFlags keyStorageFlags, Pkcs12Return& earlyReturn)
283+
at System.Security.Cryptography.X509Certificates.X509CertificateLoader.LoadPkcs12(ReadOnlyMemory`1 data, ReadOnlySpan`1 password, X509KeyStorageFlags keyStorageFlags, Pkcs12LoaderLimits loaderLimits)
284+
at System.Security.Cryptography.X509Certificates.X509CertificateLoader.LoadPkcs12Pal(ReadOnlySpan`1 data, ReadOnlySpan`1 password, X509KeyStorageFlags keyStorageFlags, Pkcs12LoaderLimits loaderLimits)
285+
at System.Security.Cryptography.X509Certificates.CertificatePal.FromBlobOrFile(ReadOnlySpan`1 rawData, String fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
286+
at System.Security.Cryptography.X509Certificates.X509Certificate..ctor(Byte[] rawData, String password, X509KeyStorageFlags keyStorageFlags)
287+
at System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(Byte[] rawData, String password, X509KeyStorageFlags keyStorageFlags)
288+
at Duende.IdentityServer.Services.KeyManagement.X509KeyContainer.ToSecurityKey() in /_/identity-server/src/IdentityServer/Services/Default/KeyManagement/X509KeyContainer.cs:line 108
289+
at Duende.IdentityServer.Services.KeyManagement.AutomaticKeyManagerKeyStore.<>c.<GetValidationKeysAsync>b__5_0(KeyContainer x) in /_/identity-server/src/IdentityServer/Services/Default/KeyManagement/AutomaticKeyManagerKeyStore.cs:line 106
290+
at System.Linq.Enumerable.ArraySelectIterator`2.Fill(ReadOnlySpan`1 source, Span`1 destination, Func`2 func)
291+
at System.Linq.Enumerable.ArraySelectIterator`2.ToArray()
292+
at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
293+
at Duende.IdentityServer.Services.KeyManagement.AutomaticKeyManagerKeyStore.GetValidationKeysAsync() in /_/identity-server/src/IdentityServer/Services/Default/KeyManagement/AutomaticKeyManagerKeyStore.cs:line 106
294+
at Duende.IdentityServer.Services.DefaultKeyMaterialService.GetValidationKeysAsync() in /_/identity-server/src/IdentityServer/Services/Default/DefaultKeyMaterialService.cs:line 112
295+
at Duende.IdentityServer.ResponseHandling.DiscoveryResponseGenerator.CreateDiscoveryDocumentAsync(String baseUrl, String issuerUri) in /_/identity-server/src/IdentityServer/ResponseHandling/Default/DiscoveryResponseGenerator.cs:line 110
296+
at Duende.IdentityServer.Endpoints.DiscoveryEndpoint.ProcessAsync(HttpContext context) in /_/identity-server/src/IdentityServer/Endpoints/DiscoveryEndpoint.cs:line 82
297+
at Duende.IdentityServer.Hosting.IdentityServerMiddleware.Invoke(HttpContext context, IdentityServerOptions options, IEndpointRouter router, IUserSession userSession, IEventService events, IIssuerNameService issuerNameService, ISessionCoordinationService sessionCoordinationService) in /_/identity-server/src/IdentityServer/Hosting/IdentityServerMiddleware.cs:line 109
298+
```
299+
300+
To fix this issue on Azure hosted web applications, add the following environment variable to the App Service:
301+
```text
302+
WEBSITE_LOAD_USER_PROFILE=1
303+
```
304+
305+
When saving this environment variable, your App Service will restart and Kudu (the engine behind git deployments in Azure App Service)
306+
will load the user profile when running your web application.
307+
For more information about this and other Kudu configurable settings, see https://github.com/projectkudu/kudu/wiki/Configurable-settings.
308+
309+
If you're hosting the web application using IIS on Windows, you'll need to configure the application pool to load the user profile. See https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/advanced?view=aspnetcore-9.0#data-protection
310+
for more information on how to configure the application pool.
311+
312+
### Why does a web application need to load a user profile to work with X.509 certificates?
313+
314+
The `X509Certificate2` class in .NET stores the private key part of a certificate somewhere else depending on the use of `X509KeyStorageFlags`:
315+
* `X509KeyStorageFlags.MachineKeySet` stores the private key in a `Keys` registry subfolder of the certificate store.
316+
* `X509KeyStorageFlags.UserKeySet` stores the private key in the current user's roaming profile folder, e.g. `%AppData%\Microsoft\SystemCertificates\My\Keys`.
317+
318+
When loading a certificate containing both a public and private key in .NET, the private key may also end up in different locations:
319+
* Machine keys end up in the `%ProgramData%\Microsoft\Crypto\RSA\MachineKeys` folder.
320+
* User keys are stored in the current user's roaming profile folder but this time in a different location: `%AppData%\Microsoft\Crypto\RSA`
321+
322+
If you don't explicitly use the `X509KeyStorageFlags.MachineKeySet` flag value, the default behavior is to use `X509KeyStorageFlags.DefaultKeySet`.
323+
According to the [.NET documentation][1], this means: _The default key set is used. **The user key set is usually the default**_.
324+
325+
When an application runs without an active user profile, any private key material stored in a user profile can't be accessed.
326+
Even loading a certificate can fail, since the load operation could attempt to store the private key material in the user profile.
327+
328+
[1]: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509keystorageflags

0 commit comments

Comments
 (0)