Hosted on Ubuntu, NGINX, Cloudflare DNS with proxy
OpenIdDict v2.0.1 and _other STS_ are working fine in production.
Dev build works fine.
Being new to OpenIdDict, I'm probably missing something obvious.
Api side:
services
.AddOpenIddict()
.AddValidation(options =>
{
// Note: the validation handler uses OpenID Connect discovery
// to retrieve the issuer signing keys used to validate tokens.
options.SetIssuer(stsOptions.Authority);
options.AddAudiences(CLIENT_ID);
options.UseSystemNetHttp();
options.UseAspNetCore();
});
Everything works using JwtBearer auth instead OpenIdDict
services.AddAuthentication(options =>
.AddJwtBearer(options =>
{
options.Authority = stsOptions.Authority;
options.Audience = CLIENT_ID;
options.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = ECDsaKey,
};
});
Api stack:
System.InvalidOperationException: IDX20803: Unable to obtain configuration from: 'https://***/.well-known/openid-configuration'.
---> OpenIddict.Abstractions.OpenIddictExceptions+GenericException: An error occurred while handling the cryptography response.
Error: server_error
Error description: A JWKS response containing an invalid key was returned.
Error URI:
at OpenIddict.Validation.OpenIddictValidationService.<>c__DisplayClass3_0.<<GetSecurityKeysAsync>g__HandleCryptographyResponseAsync|3>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at OpenIddict.Validation.OpenIddictValidationService.GetSecurityKeysAsync(Uri address, CancellationToken cancellationToken)
at OpenIddict.Validation.OpenIddictValidationService.GetSecurityKeysAsync(Uri address, CancellationToken cancellationToken)
at OpenIddict.Validation.OpenIddictValidationRetriever.Microsoft.IdentityModel.Protocols.IConfigurationRetriever<Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfiguration>.GetConfigur
ationAsync(String address, IDocumentRetriever retriever, CancellationToken cancel)
at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.GetConfigurationAsync(CancellationToken cancel)
--- End of inner exception stack trace ---
at Microsoft.IdentityModel.Protocols.ConfigurationManager`1.GetConfigurationAsync(CancellationToken cancel)
at OpenIddict.Validation.OpenIddictValidationHandlers.ValidateIdentityModelToken.HandleAsync(ProcessAuthenticationContext context)
at OpenIddict.Validation.OpenIddictValidationDispatcher.DispatchAsync[TContext](TContext context)
at OpenIddict.Validation.OpenIddictValidationDispatcher.DispatchAsync[TContext](TContext context)
at OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandler.HandleAuthenticateAsync()
at Microsoft.AspNetCore.Authentication.AuthenticationHandler`1.AuthenticateAsync()
at Microsoft.AspNetCore.Authentication.AuthenticationService.AuthenticateAsync(HttpContext context, String scheme)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Server side:
services.AddOpenIddict()
.AddCore(options => ...
.AddServer(options =>
{
options
.SetTokenEndpointUris("/connect/token")
.SetAuthorizationEndpointUris("/connect/authorize")
.SetLogoutEndpointUris("/connect/logout")
.SetUserinfoEndpointUris("/api/userinfo");
...
.UseAspNetCore()
.EnableAuthorizationEndpointPassthrough()
.EnableVerificationEndpointPassthrough()
.EnableTokenEndpointPassthrough()
.AddSigningKey(ECDsaKey);
.AddValidation(options =>
{
options.UseLocalServer();
options.UseAspNetCore();
});
Server log (Api is calling http:// instead https://):
[16:55:23 Information] Microsoft.AspNetCore.Hosting.Diagnostics
Request starting HTTP/1.1 GET http://***/.well-known/jwks
[16:55:23 Debug] Microsoft.AspNetCore.Routing.Matching.DfaMatcher
No candidates found for the request path '/.well-known/jwks'
[16:55:23 Debug] Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware
Request did not match any endpoints
[16:55:23 Information] OpenIddict.Server.OpenIddictServerDispatcher
The request address matched a server endpoint: Cryptography.
...
[16:55:23 Information] OpenIddict.Server.OpenIddictServerDispatcher
The response was successfully returned as a JSON document: {
"keys":...
}.
...
[16:56:12 Information] Microsoft.AspNetCore.Hosting.Diagnostics
Request starting HTTP/1.1 GET http://***/.well-known/openid-configuration
[16:56:12 Debug] Microsoft.AspNetCore.Routing.Matching.DfaMatcher
No candidates found for the request path '/.well-known/openid-configuration'
[16:56:12 Information] OpenIddict.Server.OpenIddictServerDispatcher
The response was successfully returned as a JSON document: {
"issuer": "https://***/",
...
}.
...
[16:56:14 Information] Microsoft.AspNetCore.Hosting.Diagnostics
Request starting HTTP/1.1 GET http://***/.well-known/jwks
[16:56:14 Debug] Microsoft.AspNetCore.Routing.Matching.DfaMatcher
No candidates found for the request path '/.well-known/jwks'
[16:56:14 Debug] Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware
Request did not match any endpoints
[16:56:14 Information] OpenIddict.Server.OpenIddictServerDispatcher
The request address matched a server endpoint: Cryptography.
Can you share the complete response returned by the /.well-known/jwks endpoint? Without that, it's hard to say what's going on.
Can you share the complete response returned by the /.well-known/jwks endpoint? Without that, it's hard to say what's going on.
Sure.
[16:55:23 Information] OpenIddict.Server.OpenIddictServerDispatcher
The response was successfully returned as a JSON document: {
"keys": [
{
"kid": "BDFOWTSVUCKCTMHFJMBNKNK1V4FQFIHTRUAG1HUU",
"use": "sig",
"kty": "EC",
"alg": "ES256",
"x": "bdFOwTsvuCKcTMHfJMBNknK1v4fQfIhTruag1hUuSPc",
"y": "oe6QQOQbqjUzLHovr2LLxQeBzPZAudObtSKUO8kBKBU"
}
]
}.
As I suspected, the error you see is due to the fact there's no crv node returned by the JWKS endpoint, which seems to indicate it's not one of the supported NIST curves:
How did you create the key?
How did you create the key?
On the fly for this instance.
var bytes = Convert.FromBase64String(...);
var ecdsa = ECDsa.Create();
ecdsa?.ImportECPrivateKey(bytes, out _);
var ECDsaKey = new ECDsaSecurityKey(ecdsa);
Super weird. If you export the parameters using var parameters = ecdsa.ExportParameters(includePrivateParameters: false);, what's the value of parameters.Curve.Oid.FriendlyName and parameters.Curve.Oid.Value?
FriendlyName: nistP256
Value: 1.2.840.10045.3.1.7
Does ECCurve.NamedCurves.nistP256 give you the same values?
It does.
I'm out of ideas. If it gives you exactly the same value, then the string.Equals(parameters.Curve.Oid.FriendlyName, ECCurve.NamedCurves.nistP256.Oid.FriendlyName, StringComparison.Ordinal); check made by OpenIddict should return true and a crv parameter should be returned to indicate it's a P256 curve.
Just to be sure, what's returned by string.Equals(parameters.Curve.Oid.FriendlyName, ECCurve.NamedCurves.nistP256.Oid.FriendlyName, StringComparison.Ordinal); on your Ubuntu machine?
Ubuntu: False
Dev Windows: True
This is insane.
And string.Equals("nistP256", ECCurve.NamedCurves.nistP256.Oid.FriendlyName, StringComparison.Ordinal);?
And string.Equals("nistP256, "nistP256", StringComparison.Ordinal);?
Trueon both cases on both machines.
Heh. Let's try something else:
var result1 = Convert.ToBase64String(Encoding.UTF8.GetBytes(parameters.Curve.Oid.FriendlyName));
var result2 = Convert.ToBase64String(Encoding.UTF8.GetBytes(ECCurve.NamedCurves.nistP256.Oid.FriendlyName));
var result3 = Convert.ToBase64String(Encoding.UTF8.GetBytes(parameters.Curve.Oid.Value));
var result4 = Convert.ToBase64String(Encoding.UTF8.GetBytes(ECCurve.NamedCurves.nistP256.Oid.Value));
Windows:
Result1: bmlzdFAyNTY=
Result2: bmlzdFAyNTY=
Result3: MS4yLjg0MC4xMDA0NS4zLjEuNw==
Result4: MS4yLjg0MC4xMDA0NS4zLjEuNw==
Ubuntu:
Result1: RUNEU0FfUDI1Ng==
Result2: bmlzdFAyNTY=
Result3: MS4yLjg0MC4xMDA0NS4zLjEuNw==
Result4: MS4yLjg0MC4xMDA0NS4zLjEuNw==
You said earlier that FriendlyName was returning nistP256 for your key. Here, it's ECDSA_P256. Does the value change when you restart your app?
Let me reboot server, just to be sure.
Name: ECDSA_P256
Value: 1.2.840.10045.3.1.7
I messed up, was checking values on local machine first time, sorry.
I messed up, was checking values on local machine first time, sorry.
馃槄
So: OpenSSL can return two different friendly names for nistP256. Fun...
I'll update the code to use the OID value.
Thank you.
Should I close it?
Please leave it open, I'll take care of closing it when the bug is fixed in beta6.
Alright, have a nice one.
Thanks, you too!
If you like the project, consider encouraging your employer to sponsor it so I can dedicate more time on it: https://github.com/sponsors/kevinchalet 鉂わ笍
I sure will.
This won't be as easy as expected: on .NET Framework 4.x and .NET Core 2.1, the Oid.Value attached to ECParameters returned by ExportParameters() is typically null (for reasons I ignore), which means we can't remove the FriendlyName check (at least on these platforms). This also explains why I had used FriendlyName instead of Value when adding ECDSA support.
Updated packages were pushed to MyGet.org to fix this issue. Can you please give them a try?
Of course, as soon as I get home.
Everything seems to be fine 馃憤
"keys": [
{
"kid": "BDFOWTSVUCKCTMHFJMBNKNK1V4FQFIHTRUAG1HUU",
"use": "sig",
"kty": "EC",
"alg": "ES256",
"crv": "P-256",
"x": "bdFOwTsvuCKcTMHfJMBNknK1v4fQfIhTruag1hUuSPc",
"y": "oe6QQOQbqjUzLHovr2LLxQeBzPZAudObtSKUO8kBKBU"
}
]
@rvlajcev thanks!