Verify JWT ES256 by Apple Notification C# [Sandbox] - c#

I would like just to verify JWT from Apple Pay server notification.
You can see the JWT structure on the screenshot from the jwt.io website.
So, I took the first certificate from the x5c collection in the header, and convert it to the object X509Certificate2, then I get the public key in the ECDsa format and try to verify the token.
Did I implement this correctly in terms of security?
Should I validate a chain of three certificates after verifying the token?
I will be grateful for any information.
private static Dictionary<string, string> GetClaimsByToken(string jwtToken)
{
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.ReadJwtToken(jwtToken);
token.Header.TryGetValue("x5c", out object x5c)
var certeficatesItems = JsonConvert.DeserializeObject<IEnumerable<string>>(x5c.ToString());
ValidateJWS(tokenHandler, jwtToken, certeficatesItems.First());
return token.Claims.ToDictionary(c => c.Type, v => v.Value);
}
private static void ValidateJWS(JwtSecurityTokenHandler tokenHandler, string jwtToken, string publicKey)
{
var certificateBytes = Base64UrlEncoder.DecodeBytes(publicKey);
var certificate = new X509Certificate2(certificateBytes);
var eCDsa = certificate.GetECDsaPublicKey();
TokenValidationParameters tokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuer = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new ECDsaSecurityKey(eCDsa),
};
tokenHandler.ValidateToken(jwtToken, tokenValidationParameters, out var securityToken);
}

I don’t know C# but today I stumble across this problem too.
It seems so after you validate jwt token, you have to validate a chain of certificates. You need to download certificates from Apple(root and intermediate). You can find these certificates on apple site: https://www.apple.com/certificateauthority/.
You need to download them and validate them against the ones found in the x5c header. You can find more info on apple developer forum: https://developer.apple.com/forums/thread/691464

Related

JWT x5c token validation .net core

I would like to validate a JWS token with "alg": "ES256", "x5c" header fields only.
x5c field contain 3 certificates. I'm able to load them but .net return a IDX10503 error telling me kid field is missing.
here is my code :
var handler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler();
var jwtSecurityToken = handler.ReadJwtToken(token);
var x5cList = JsonConvert.DeserializeObject<List<string>>(jwtSecurityToken.Header.X5c);
var certs = ValidateCertificate(x5cList.ToList());
var keys = certs.Select(cert => new X509SecurityKey(cert)).ToList();
SecurityToken validatedToken;
handler.ValidateToken(jwtSecurityToken.RawData, new TokenValidationParameters
{
ValidateLifetime = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
IssuerSigningKeys = keys,
TryAllIssuerSigningKeys = true,
}, out validatedToken);
Anyone know why it's not working ? I also tried to don't load keys by myself thinking that JwtSecurityTokenHandler may be able to do it by it self but it's not working as well.
When I load this token on jwt.io it says it is validated...

Cannot validate signature using System.IdentityModel.Tokens.Jwt library on AAD/Microsoft-Identity id_token

I am using a WebApp in Azure with Azure Active Directory authentication turned on and and am pulling the id_token from the /.auth/me page here:
From my understanding when I paste this into JWT.io it validates the signature by looking up the OpenId Configuration at https://login.microsoftonline.com/{tenantId}/v2.0/.well-known/openid-configuration
and then uses the jwks_uri (https://login.microsoftonline.com/{tenantId}/discovery/v2.0/keys) to find the public signing key (x5c Array) based on the kid from the JWT header here:
However when I try to replicate this scenario in C# I am unable to validate the signature using the System.IdentityModel.Tokens.Jwt library.
Here are a few of my attempts and results:
ATTEMPT
var idToken = "{id_token}";
var signingKey = "{x5c[0]}";
SecurityToken validatedToken;
ClaimsPrincipal claimsPrincipal;
var handler = new JwtSecurityTokenHandler();
var symmetricSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("-----BEGIN CERTIFICATE-----\n" + signingKey + "\n-----END CERTIFICATE-----"));
var tokenValidationParameters = new TokenValidationParameters()
{
IssuerSigningKey = symmetricSecurityKey,
ValidateIssuerSigningKey = true,
ValidateLifetime = false,
ValidateAudience = false,
ValidateIssuer = false
};
try
{
claimsPrincipal = handler.ValidateToken(idToken, tokenValidationParameters, out validatedToken);
Console.WriteLine($"{claimsPrincipal.Identity.Name}");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
RESULT
IDX10501: Signature validation failed. Unable to match key: kid:
'System.String'. Exceptions caught: 'System.Text.StringBuilder'.
token: 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken'.
ATTEMPT
var kid = "l3sQ-50cCH4xBVZLHTGwnSR7680";
var idToken = "{id_token}";
var signingKey = "{x5c[0]}";
SecurityToken validatedToken;
ClaimsPrincipal claimsPrincipal;
var handler = new JwtSecurityTokenHandler();
var symmetricSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("-----BEGIN CERTIFICATE-----\n" + signingKey + "\n-----END CERTIFICATE-----")) { KeyId = kid, };
var tokenValidationParameters = new TokenValidationParameters()
{
IssuerSigningKey = symmetricSecurityKey,
ValidateIssuerSigningKey = true,
ValidateLifetime = false,
ValidateAudience = false,
ValidateIssuer = false
};
try
{
claimsPrincipal = handler.ValidateToken(idToken, tokenValidationParameters, out validatedToken);
Console.WriteLine($"{claimsPrincipal.Identity.Name}");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
RESULT
IDX10511: Signature validation failed. Keys tried:
'System.Text.StringBuilder'. kid: 'System.String'. Exceptions
caught: 'System.Text.StringBuilder'. token:
'System.IdentityModel.Tokens.Jwt.JwtSecurityToken'.
I also tried both attempts without the "-----BEGIN CERTIFICATE-----\n" and "\n-----END CERTIFICATE-----" additions to the signing key and had the same results as well as trying with ASCII encoding instead of UTF8.
==== UPDATE ====
I was able to use the Microsoft.IdentityModel.Protocols and Microsoft.IdentityModel.Protocols.OpenIdConnect libraries to validate the token using the following code to create the signing keys:
var issuer = jwtToken.Issuer;
var audiences = jwtToken.Audiences;
var issuerOpenIdUri = $"{issuer}/.well-known/openid-configuration";
string stsDiscoveryEndpoint = issuerOpenIdUri;
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;
...and then use the OpenIdConnectConfiguration to assign the keys to the TokenValidatetionParameters:
var tokenValidationParameters = new TokenValidationParameters()
{
ValidAudiences = audiences,
ValidIssuer = issuer,
IssuerSigningKeys = config.SigningKeys,
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
ValidateAudience = true,
ValidateIssuer = true
};
However I am still curious why my original attempts would not work. I would like to understand a little better what is happening under the hood.
Microsoft.IdentityModel.Tokens.X509SecurityKey uses the certificate thumbprint in base 16 in its KeyId. Azure Active Directory KeyId uses the certificate thumbprint in base 64.
To use same key ID format as that used by Azure Active directory ,It will be possible (Just the way you did) with Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever and by using signing keys this way
myTokenValidationParameters.IssuerSigningKeys = myOpenIdConnectionConfiguration.SigningKeys;
The error may also occur when “aud” claim doesn’t match the audience which is nothing but the client Id and can be mitigated by giving proper scopes in AAD and in code with proper audience and scope and by granting consent.

How to secure Audience and Issure in JWT Token as they are validated at time authorization yet available in claims which are prone to be hacked easily

I'm trying to issue the authentication token using JWT in .net core application. I have read the documentation available at https://www.rfc-editor.org/rfc/rfc7519
I am signing my token either with HmacSha256Signature or with certificate. both Audience and Issuers are also being provided at time of generating the token and I'm validating it at time of authorization. As per are https://www.rfc-editor.org/rfc/rfc7519, both of these are added in the token as claim which makes them readable and available to hackers to play with; as they can easily be decrypted, read and retrieved from the token and if it is so then:
how it is secured then? Some one may generate a false token using the same audience and issuer value which was used in code. Authentication will approve it as its a simple text comparison values would be same?
One thing, which I have done to secure it is; I have fetched the URI from httpcontext and used it as audience and time of generating the token and comparing it at time of authentication with the static expected value and its working fine.
but I'm worried about:
What step we may take to ensure that our token is secured and can not be hacked or cypher?
Is signing the token make it much secure to keep this information publicly accessible?
Can JWT automatically identify its target audience and secure it?
Can we use encryption to secure it and decrypt the information at time of authentication?
Any other advice to make it secure?
Token Generator:
public string GenerateSecurityToken(string email)
{
var tokenHandler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Email, email)
}),
Issuer = _jwtSettings.Value.Issuer, // static text from config
Audience = _jwtSettings.Value.Audience, // base url from http context
Expires = DateTime.UtcNow.AddMinutes(_jwtSettings.Value.ExpirationInMinutes),
SigningCredentials = this.getSigningCredentials(),
IssuedAt = DateTime.Now
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var strToken = tokenHandler.WriteToken(token);
return strToken;
}
Authentication:
private static TokenValidationParameters GetX509BasedParameters(IConfiguration config)
{
var issuer = config.GetSection("JwtConfig").GetSection("issuer").Value;
var audience = config.GetSection("JwtConfig").GetSection("audience").Value;
var pfxFilename = config.GetSection("JwtConfig").GetSection("securityFile").Value;
X509Certificate2 x509 = new X509Certificate2(pfxFilename, "");
X509SecurityKey x509Key = new X509SecurityKey(x509);
return new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidIssuer = issuer,
ValidAudience = audience,
ValidateIssuerSigningKey = true,
RequireExpirationTime = true,
IssuerSigningKey = x509Key,
};
}

ASP.NET Core: validating Auth0's JWT token

I'm using Auth0 and parsing its idToken server-side like this:
var tokenHandler = new JwtSecurityTokenHandler();
var jwtToken = tokenHandler.ReadJwtToken(idToken); // idToken comes from client using auth0.js
var sub = jwtToken.Claims.First(claim => claim.Type == "sub").Value;
The above code works well and I'm able to parse the idToken successfully, but I'd like to validate the idToken before trusting it, so I've tried this:
string clientSecret = "{client_secret}"; // comes from Auth0 application's client secret
var validations = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "some value", // used "iss" from here: https://jwt.io/
ValidAudience = "some value", // used "aud" from here: https://jwt.io/
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(clientSecret)),
};
var principal = tokenHandler.ValidateToken(idToken, validations, out var validatedToken);
When trying to validate the token, it results in this exception:
Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException
HResult=0x80131500
Message=IDX10501: Signature validation failed. Unable to match key:
kid: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'
I grabbed the issuer and audience values by parsing one of the tokens here: https://jwt.io/. The client secret is my application's client secret at Auth0.
How can I validate Auth0's idToken using JwtSecurityTokenHandler?
To manually validate Auth0's JWT token, you need these 2 Nuget packages:
System.IdentityModel.Tokens.Jwt
Microsoft.IdentityModel.Protocols.OpenIdConnect
Then get these values from Auth0's application settings
string auth0Domain = ""; // Note: if your Domain is foo.auth0.com, this needs to be https://foo.auth0.com/
string auth0ClientId = "";
Validate Auth0's token as follows:
IConfigurationManager<OpenIdConnectConfiguration> configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>($"{auth0Domain}.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration openIdConfig = await configurationManager.GetConfigurationAsync(CancellationToken.None);
var validations = new TokenValidationParameters
{
ValidIssuer = auth0Domain,
ValidAudiences = new[] { auth0ClientId },
IssuerSigningKeys = openIdConfig.SigningKeys
};
var user = tokenHandler.ValidateToken(idToken, validations, out var validatedToken);
With the validated token, you can extract useful info like this:
var securityToken = (JwtSecurityToken)validatedToken;
var userId = user.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value;
Source: Manually validating a JWT using .NET
Credit: #Nkosi

.NET Core IssuerSigningKey from file for JWT Bearer Authentication

I am struggling with the implementation (or the understanding) of signing keys for JWT Bearer Token authentication. And I hope somebody can help me or explain me what I am missunderstanding.
The last few weeks I crawled tons of tutorials and managed to get a custom Auth-Controller running which issues my tokens and managed to set up the JWT bearer authentication to validate the tokens in the header.
It works.
My problem is that all examples and tutorials either generate random or inmemory (issuer) signing keys or use hardcoded "password" strings or take them from some config file (look for "password" in the code samples).
What I mean for validation setup (in the StartUp.cs):
//using hardcoded "password"
SecurityKey key = new SymmetricSecurityKey(System.Text.Encoding.ASCII.GetBytes("password"));
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "MyIssuer",
ValidateAudience = true,
ValidAudience = "MyAudience",
ValidateLifetime = true,
IssuerSigningKey = key
}
});
In the AuthController creating the token:
//using hardcoded password
var signingKey = new SymmetricSecurityKey(System.Text.Encoding.ASCII.GetBytes("password"));
SigningCredentials credentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);
var jwt = new JwtSecurityToken // Create the JWT and write it to a string
(
issuer: _jwtTokenSettings.Issuer,
audience: _jwtTokenSettings.Audience,
claims: claims,
notBefore: now,
expires: now.AddSeconds(_jwtTokenSettings.LifetimeInSeconds),
signingCredentials: credentials
);
In this question they used:
RSAParameters keyParams = RSAKeyUtils.GetRandomKey();
My (current) assumptions was that in production you should not use hardcoded strings or strings from config files for the token signing keys. But instead use some certificate files??? Am I wrong?
So I tried to replace the strings with a certificate which works in the auth controller:
//using a certificate file
X509Certificate2 cert = new X509Certificate2("MySelfSignedCertificate.pfx", "password");
X509SecurityKey key = new X509SecurityKey(cert);
SigningCredentials credentials = new SigningCredentials(key, "RS256");
var jwt = new JwtSecurityToken // Create the JWT and write it to a string
(
issuer: _jwtTokenSettings.Issuer,
audience: _jwtTokenSettings.Audience,
claims: claims,
notBefore: now,
expires: now.AddSeconds(_jwtTokenSettings.LifetimeInSeconds),
signingCredentials: credentials
);
But there seems no way to get the validation using a certificate.
X509Certificate2 cert = new X509Certificate2("MySelfSignedCertificate.pfx", "password");
SecurityKey key == // ??? how to get the security key from file (not necessarily pfx)
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "MyIssuer",
ValidateAudience = true,
ValidAudience = "MyAudience",
ValidateLifetime = true,
IssuerSigningKey = key
}
});
Am I wrong that I should use certificates for the signing keys?
How else would I change the signing keys in production when the auth controller is on a different server than the consuming/secured web api (may one time, not now)?
It seems I also miss the point (required steps) to get the answer of this question working.
Now I got it running I am still missing the point if it should be like that?
Noteworthy: File not found (after deployment to server)
For all those using this and it works when started from Visual Studio but after deployment to a server / azure it says "File not found":
read and upvote this question: X509 certificate not loading private key file on server
Noteworthy 2: one actually doesn't need it for token based authentication
The public key does not need to be on the API side. It will be retrieved via the discovery endpoint of the authentication server automatically.
Oh dear, that simple:
SecurityKey key = new X509SecurityKey(cert);
Or as complete sample from above:
X509Certificate2 cert = new X509Certificate2("MySelfSignedCertificate.pfx", "password");
SecurityKey key = new X509SecurityKey(cert); //well, seems to be that simple
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "MyIssuer",
ValidateAudience = true,
ValidAudience = "MyAudience",
ValidateLifetime = true,
IssuerSigningKey = key
}
});
A very important point, if you are using certificate files, that while the server requires the file with the private key, the client should only use the public key.
You do not want to be giving your private key file to anyone; they only ever need the public key.
// On client
var publicCert = new X509Certificate2("MySelfSignedCertificate.cer");
var publicKey = new X509SecurityKey(publicCert);
...
IssuerSigningKey = publicKey
The simplest way to convert the PFX (private) to CER (public) may be to import into the Windows certificate manager, then export with the public key only.
From the command line, you can create also use PowerShell 5 (not yet in PowerShell 6):
Get-PfxCertificate -FilePath MySelfSignedCertificate.pfx | Export-Certificate -FilePath MySelfSignedCertificate.cer
Alternatively, you can install and use OpenSSL to convert it from the command line.
Note 1: As you found, once you set the Authority, the auto-discovery may be able to find the public key from the server.
Note 2: Rather than store the certificate in a file, you can also store it in the Windows certificate store, and reference it by thumbprint (both PFX and CER files can be imported).

Categories

Resources