I have an web application that is using OpenId Connect. I created a self signed certificate but it is still not signed by a CA.
How can I ignore the signature validation?
This is what I have so far:
SecurityToken validatedToken = null;
var tokenHandler = new JwtSecurityTokenHandler {
Configuration = new SecurityTokenHandlerConfiguration {
CertificateValidator = X509CertificateValidator.None
},
};
TokenValidationParameters validationParams =
new TokenValidationParameters()
{
ValidAudience = ConfigurationManager.AppSettings["Audience"],
ValidIssuer = ConfigurationManager.AppSettings["Issuer"],
AudienceValidator = AudienceValidator,
ValidateAudience = true,
ValidateIssuer = true
};
return tokenHandler.ValidateToken(jwtToken, validationParams, out validatedToken);
It throws the following exception:
IDX10500: Signature validation failed. Unable to resolve
SecurityKeyIdentifier: 'SecurityKeyIdentifier\r\n (\r\n
IsReadOnly = False,\r\n Count = 1,\r\n Clause[0] =
System.IdentityModel.Tokens.NamedKeySecurityKeyIdentifierClause\r\n
)\r\n', \ntoken:
'{\"typ\":\"JWT\",\"alg\":\"RS256\",\"kid\":\"issuer_rsaKey\"}.{\"iss\":...
Don't ignore the signature, this is dangerous!
Even if you use a self-signed certificate, you will be able to use the public key for signature validation.
Since you are using OpenId Connect, you should be able to get the public key for your signing certificate by heading over to /.well-known/jwks.
Then you can setup your validation parameters like this:
var certificate = new X509Certificate2(Convert.FromBase64String(yourPublicKeyGoesHere));
var validationParameters = new TokenValidationParameters {
IssuerSigningTokens = new[] { new X509SecurityToken(certificate) }
};
After that, you can call ValidateToken:
SecurityToken token;
var claimsPrincipal = handler.ValidateToken(encodedToken, validationParameters, out token);
You really want to ignore the signature?
Remember, if you do, how do you know someone didn't tamper with the data inside the token? You could easily decode the base64 url encoded payload and change the subject. And if you rely on that in your application, you'll be in trouble (hint: someone accessing someone else data)
You REALLY, REALLY want to ignore it?
You can use ReadToken and just skip every validation there is:
var badJwt = new JwtSecurityTokenHandler()
.ReadToken(encodedMaliciousToken) as JwtSecurityToken;
DON'T do that though, it's bad practice.
public TokenValidationParameters CreateTokenValidationParameters()
{
var result = new TokenValidationParameters
{
ValidateIssuer = false,
ValidIssuer = ValidIssuer,
ValidateAudience = false,
ValidAudience = ValidAudience,
ValidateIssuerSigningKey = false,
//IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey)),
//comment this and add this line to fool the validation logic
SignatureValidator = delegate(string token, TokenValidationParameters parameters)
{
var jwt = new JwtSecurityToken(token);
return jwt;
},
RequireExpirationTime = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
};
result.RequireSignedTokens = false;
enter code here
return result;
}
Related
We use IdentityServer4 and the generated tokens are accepted by the Token middleware.
But the token validation on jwt.io fails
I've already tried the validation on jwt.io with PEM and JWK. Both fails.
As supposed here: Other question
If I do the same with another IdentiyServer Instance but same configuration (IdentityServer3) It works, but the JWK looks different.
(no x5c / c5t)
Are there other tools to check why the validation is failing?
Can the other JWK format be related to this (x5t / x5c)? And if so, can i Configure IdentyServer to return the 'old format' ?
Any other Ideas?
To check the token without middleware i wrote this small piece of code:
var jwksJson = #"
{
""keys"": [
{
""kty"": ""RSA"",
""use"": ""sig"",
""kid"": ""0313B7152576EF7415003F309C7E7F5EABADD0B7RS256"",
""x5t"": ""AxO3FSV273QVAD8wnH5_Xqut0Lc"",
""e"": ""AQAB"",
""n"": ""3FOm0...J0"",
""x5c"": [
""MI....=""
],
""alg"": ""RS256""
}
]
}
";
var token = "ey...ktYx7QBZw";
var jwks = new JsonWebKeySet(jwksJson);
var jwk = jwks.Keys.First();
var validationParameters = new TokenValidationParameters
{
IssuerSigningKey = jwk,
ValidateLifetime = false,
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidAudiences = new []{"aaaa","bbbb","ccc"}, // Your API Audience, can be disabled via ValidateAudience = false
ValidIssuer = "https://dev.xxxx.xxxx.com" // Your token issuer, can be disabled via ValidateIssuer = false
};
WritePem(jwk);
if (ValidateToken(token, validationParameters))
{
Console.WriteLine("Token Valid");
}
else
{
Console.WriteLine("Token Invalid");
}
static bool ValidateToken(string token, TokenValidationParameters validationParameters)
{
var tokenHandler = new JwtSecurityTokenHandler();
try
{
tokenHandler.ValidateToken(token, validationParameters, out var validatedToken);
return validatedToken != null;
}
catch (Exception e)
{
Console.WriteLine(e);
return false;
}
}
static void WritePem(JsonWebKey jsonWebKey)
{
var key = new RSACryptoServiceProvider();
key.ImportParameters(new RSAParameters
{
Modulus = Base64Url.Decode(jsonWebKey.N),
Exponent = Base64Url.Decode(jsonWebKey.E)
});
var pubkey = Convert.ToBase64String(key.ExportSubjectPublicKeyInfo());
const string pemHeader = "-----BEGIN PUBLIC KEY-----";
const string pemFooter = "-----END PUBLIC KEY-----";
var publicKeyPem = pemHeader + Environment.NewLine + pubkey + Environment.NewLine + pemFooter;
Console.WriteLine(publicKeyPem);
}
I've created a new certificate and it works now. I've no idea why the old certificate did not work.
I'm asking for help to verify the GCP token (x-gcp-marketplace-token).
Basically I would like to implement a token verification explained here.
Expected language C#. preferable to be compatible with .Net Core.
UPDATE
This is what I have so far. It is a POC don't worry about fields naming.
Actually validation fails.
private async Task<OrderViewModel> GoogleResolveOrder(string token)
{
OrderViewModel result = new OrderViewModel { Transaction = new TransactionViewModel() };
//Decode token
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
JwtSecurityToken tokenInfo = handler.ReadJwtToken(token);
if (!String.IsNullOrEmpty(tokenInfo.Header.Kid) && !String.IsNullOrEmpty(tokenInfo.Payload.Sub) && !String.IsNullOrEmpty(tokenInfo.Payload.Iss))
{
string keyId = tokenInfo.Header.Kid;
string keysEndpoint = tokenInfo.Payload.Iss;
string procurementId = tokenInfo.Payload.Sub;
//load gcp certificate
var response = await _client.GetAsync(keysEndpoint);
string googleCretificateInfoResponse = await response.Content.ReadAsStringAsync();
Dictionary<string, string> certifateInfo = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(googleCretificateInfoResponse);
//Get public key from certificate
string certificatePublicKeyText = certifateInfo[keyId];
byte[] certicateStream = Encoding.UTF8.GetBytes(certificatePublicKeyText);
X509Certificate certificate = new X509Certificate(certicateStream);
try
{
// Verify token with public key, leave private key as null
SecurityToken tokenValidated = null;
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidateLifetime = true,
ValidateAudience = true,
ValidAudience = tokenInfo.Payload.Aud[0],
ValidateIssuer = true,
ValidIssuer = "https://www.googleapis.com/robot/v1/metadata/x509/cloud-commerce-partner#system.gserviceaccount.com",
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(certificate.GetPublicKey())
};
ClaimsPrincipal clainsResult = handler.ValidateToken(token, validationParameters, out tokenValidated);
//TODO- IF not exception Token is valid
result.Transaction.MinimunQuantity = 1;
}
catch (Exception ex)
{
result.Transaction.MinimunQuantity = -1;
result.Transaction.FulfillmentStatus = ex.Message;
}
//TODO- Delete
result.Transaction.OrgId = tokenInfo.Payload.Sub;
result.Transaction.PlanName = tokenInfo.Payload.Iss;
result.Transaction.PlanID = tokenInfo.Header.Kid;
result.Transaction.Customer = String.Join(',', tokenInfo.Payload.Aud);
result.Transaction.CustomerEmailId = tokenInfo.Payload.Exp.Value.ToString();
result.Transaction.SubscriptionId = token;
}
return result;
}
I'm still working on this.
Getting this error:
IDX10501: Signature validation failed. Unable to match key: kid: '[PII is hidden. For more details, see aka.ms/IdentityModel/PII.]'. Exceptions caught: '[PII is hidden. For more details, see aka.ms/IdentityModel/PII.]'. token: '[PII is hidden. For more details, see aka.ms/IdentityModel/PII.]'.
I'm using actionable messages (with Outlook web app) to call an Logic App. Therefore I am getting an Bearer token in the request:
"Action-Authorization": "Bearer eyJ0eXAi..."
Callstack:
Outlook web app -> Logic App -> my endpoint hosted in azure
Now I tried to validate the token with jwt.io but getting an Issue that the Signature is invalid.
So I tried to validate it in c# with the JwtSecurityTokenHandler.
I tried to add https://substrate.office.com/sts/ to the issuer list, but it seems like the validation don't even get there.
I'm using the following code to validate the jwt token issued by office.com:
bool IsAuthorized(HttpActionContext actionContext)
{
var valid = base.IsAuthorized(actionContext);
// Custom handle for Bearer token, when invalid from base-class
if (!valid && actionContext.Request.Headers.Authorization.Scheme == "Bearer")
{
var jwt = actionContext.Request.Headers.Authorization.Parameter;
var th = new JwtSecurityTokenHandler();
var sjwt = th.ReadToken(jwt) as JwtSecurityToken;
var validationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = false,
//IssuerSigningToken = sjwt,
ValidateActor = false,
ValidateAudience = false,
ValidateIssuer = true,
ValidateLifetime = true,
ValidIssuers = new[] { "https://substrate.office.com/sts/" },
ValidAudiences = new[] {"https://XXX.logic.azure.com"}
};
SecurityToken validatedToken;
try
{
th.ValidateToken(jwt, validationParameters, out validatedToken);
}
catch (Exception ex)
{
return false;
}
}
return valid;
}
Here is my JWT token:
I am getting the exception:
IDX10500: Signature validation failed. Unable to resolve SecurityKeyIdentifier: 'SecurityKeyIdentifier
(
IsReadOnly = False,
Count = 2,
Clause[0] = X509ThumbprintKeyIdentifierClause(Hash = 0x818...),
Clause[1] = System.IdentityModel.Tokens.NamedKeySecurityKeyIdentifierClause
)
', ...
Even though I set ValidateIssuerSigningKey = false.
Is there a way to accept https://substrate.office.com/sts/ as a valid issuer?
The exception says that the "Signature validation failed".
To resolve this problem we can't just add the wanted valid issuer to ValidIssuers, we need the to verify that the token is issued from the issuer itself.
Especially for this case with office.com being the issuer I found the expected key (JWK - JSON Web Key) here:
https://substrate.office.com/sts/common/discovery/keys
(also https://substrate.office.com/sts/common/.well-known/openid-configuration)
Here is the working code:
bool IsAuthorized(HttpActionContext actionContext)
{
var valid = base.IsAuthorized(actionContext);
// Custom handle for Bearer token, when invalid from base-class
if (!valid && actionContext.Request.Headers.Authorization.Scheme == "Bearer")
{
var jwt = actionContext.Request.Headers.Authorization.Parameter;
var th = new JwtSecurityTokenHandler();
var validationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new JsonWebKey(GetJWK()),
ValidIssuers = new[] { "https://substrate.office.com/sts/" }
};
Microsoft.IdentityModel.Tokens.SecurityToken validatedToken;
try
{
var claims = th.ValidateToken(jwt, validationParameters, out validatedToken);
valid = true;
}
catch (Exception ex)
{
valid = false;
}
}
return valid;
}
// Get the token from configuration
private string GetJWK()
{
return ConfigurationManager.AppSettings["ida:jwks_json"];
}
In the appsettings I put the RSA key from the website for validating the token, it looks like:
{"kty":"RSA","use":"sig","kid":"gY...","x5t":"gY...","n":"2w...","e":"AQAB","x5c":["MII..."]}
I am trying to perform JWT validation for each action excepting the LogIn and Register actions, but I cannot find a way to do it in action filters, as i need the token and it's on this.Request.Headers.
This is the validation method which works fine:
try
{
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
string sToken = token.Substring(7, token.Length - 7);
if (!tokenHandler.CanReadToken(sToken))
{
return false;
}
JwtSecurityToken jwtToken = tokenHandler.ReadToken(sToken) as JwtSecurityToken;
if (jwtToken == null)
{
return false;
}
TokenValidationParameters parameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
RequireExpirationTime = true,
ValidAudience = "http://localhost",
ValidIssuer = "http://localhost",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.Default.GetBytes(StandardValues.SecretKey))
};
SecurityToken securityToken;
ClaimsPrincipal principal = tokenHandler.ValidateToken(sToken, parameters, out securityToken);
if (principal == null)
{
return false;
}
}
catch (Exception ex)
{
return false;
}
return true;
And I have multiple controllers on which I want to perform the validation.
Please let me know how can I perform this validation outside and before the actions, (into an action filter or another way) while sending the token as parameter for validation method.
In our ASP.Net WebApi we use the following for validating our tokens:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = $"https://{Configuration.GetValue<string>("AppServiceNameOutput")}",
ValidAudience = $"https://{Configuration.GetValue<string>("AppServiceNameOutput")}",
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(Configuration.GetValue<string>("SigningKey"))),
};
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var tokenBlackList = context.HttpContext.RequestServices.GetRequiredService<ITokenBlackList>();
var tokenParser = context.HttpContext.RequestServices.GetRequiredService<ITokenParser>();
var bearer = context.HttpContext.Request.Headers["Authorization"];
if (String.IsNullOrEmpty(bearer))
{
bearer = context.Request.Query["access_token"];
}
var token = tokenParser.GetBearerTokenFromAuthHeaderString(bearer);
if (tokenBlackList.TokenIsBlackListed(token).Result)
{
context.Fail("Token has expired");
}
return Task.CompletedTask;
}
};
});
Then on each controller action we specify whether the endpoint should be authorised or not, and which policies are allowed access.
[Authorize(Policy = "ManagerOnly")]
[HttpPost]
public IActionResult Update([FromBody] UpdateAppRequest request)
According this question, I'm using the snippet below, to sign and encrypt the JWToken.
var claims = new Claim[] { new SomeClaimes() };
var scKey = Encoding.UTF8.GetBytes("SOME KEY");
var ecKeyTemp = Encoding.UTF8.GetBytes("SOME OTHER KEY");
byte[] ecKey = new byte[256 / 8];
Array.Copy(ecKeyTemp, ecKey, 256 / 8);
var tokenDescriptor = new SecurityTokenDescriptor {
Subject = new ClaimsIdentity(claims),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(
scKey),
SecurityAlgorithms.HmacSha512),
EncryptingCredentials = new EncryptingCredentials(
new SymmetricSecurityKey(
ecKey),
SecurityAlgorithms.Aes256KW,
SecurityAlgorithms.Aes256CbcHmacSha512),
Issuer = "My Jwt Issuer",
Audience = "My Jwt Audience",
IssuedAt = DateTime.UtcNow,
Expires = DateTime.Now.AddDays(7),
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateJwtSecurityToken(tokenDescriptor);
var jwt = tokenHandler.WriteToken(token);
And here is my service registration:
services
.AddAuthentication(o => {
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(cfg => {
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters {
ValidIssuer = "My Jwt Issuer",
ValidAudience = "My Jwt Audience",
IssuerSigningKey = new SymmetricSecurityKey(SameKeyAsGenerating)),
TokenDecryptionKey = new SymmetricSecurityKey(SameKeyAsGenerating),
ClockSkew = TimeSpan.Zero,
RequireSignedTokens = true,
RequireExpirationTime = true,
SaveSigninToken = true,
ValidateActor = true,
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
ValidateTokenReplay = true,
};
});
The problem is that the authentication layer authenticates both encrypted and not-encrypted token as authorized. I mean when I create a token without the EncryptingCredentials (just a signed token, and not encrypted), the token is still valid and the request is authorized. The question is how to force authentication layer to only accept signed-encrypted tokens and reject just-signed-not-encrypted tokens?
UPDATE: The Solution:
Thanks to sellotape's answer, I implemented this JwtEncryptedSecurityTokenHandler:
public class JwtEncryptedSecurityTokenHandler : JwtSecurityTokenHandler {
[DebuggerStepThrough]
public override ClaimsPrincipal ValidateToken(string token, TokenValidationParameters validationParameters, out SecurityToken validatedToken) {
if (string.IsNullOrWhiteSpace(token))
throw new ArgumentNullException(nameof (token));
if (validationParameters == null)
throw new ArgumentNullException(nameof (validationParameters));
if (token.Length > MaximumTokenSizeInBytes)
throw new ArgumentException(
$"IDX10209: token has length: '{token.Length}' which is larger than the MaximumTokenSizeInBytes: '{MaximumTokenSizeInBytes}'.");
var strArray = token.Split(new[] { '.' }, 6);
if (strArray.Length == 5)
return base.ValidateToken(token, validationParameters, out validatedToken);
throw new SecurityTokenDecryptionFailedException();
}
}
And used the new handler in Startup.ConfigureServices():
.AddJwtBearer(cfg => {
cfg.RequireHttpsMetadata = false;
// other configurations...
cfg.SecurityTokenValidators.Clear();
cfg.SecurityTokenValidators.Add(new JwtEncryptedSecurityTokenHandler());
});
For more explanation, see the accepted answer.
I'm sure there are a few ways to do this but how about:
Implement ISecurityTokenValidator, probably inheriting from SecurityTokenHandler; the required overrides are fairly simple and could largely be copied from JwtSecurityTokenHandler. Override ValidateToken() and throw if the JWT is not encrypted**.
Add the new handler/validator in the AddJwtBearer() configure-options action: cfg.SecurityTokenValidators.Add(new RequireEncryptedTokenHandler());
** If you look at the source for JwtSecurityTokenHandler.ValidateToken() (not sure exactly which version you're using but I think the message is the same), it seems that "is encrypted" is simply decided as "has 5 parts to it", which should be easy to implement (copy/paste) in your new handler.