public string DecodeAndValidateIdToken(string idToken, string clientId, string issuer, string audience) { ILog logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); logger.Debug("DecodeAndValidateIdToken"); OidcGetKeys oidcGetKeys = new OidcGetKeys(); oidcGetKeys.keys = new List <Key>(); IRestResponse <OidcGetKeys> response = null; //var clientId = appSettings["oidc.spintnative.clientId"]; string secretKeyn = null; string secretKeye = null; string jsonPayload = null; //find key reference in JWT string[] parts = idToken.Split('.'); string header = parts[0]; string payload = parts[1]; byte[] crypto = Base64UrlDecode(parts[2]); System.IdentityModel.Tokens.SecurityToken validatedToken; //decode JWT header and payload JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler(); //SecurityToken tokenReceived = tokenHandler.ReadToken(idToken); JwtSecurityToken tokenReceived2 = new JwtSecurityToken(idToken); //decode JWT payload string decodePayload = Encoding.UTF8.GetString(Base64UrlDecode(payload)); JObject payloadData = JObject.Parse(decodePayload); //decode JWT header string decodeHeader = Encoding.UTF8.GetString(Base64UrlDecode(header)); JObject headerData = JObject.Parse(decodeHeader); //deserialize header to find key id of id token OidcHeader oidcHeader = new OidcHeader(); oidcHeader = Newtonsoft.Json.JsonConvert.DeserializeObject <OidcHeader>(decodeHeader); //computer hash from JWT header and payload SHA256 sha256 = SHA256.Create(); byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(header + '.' + payload)); try { //get keys from okta var client = new RestClient(appSettings["oidc.AuthServer"] + "/v1/keys"); var request = new RestRequest(Method.GET); // request.AddHeader("cache-control", "no-cache"); request.AddHeader("Accept", "application/json"); request.AddHeader("Content-Type", "application/json"); request.AddQueryParameter("client_id", clientId); response = client.Execute <OidcGetKeys>(request); //loop through returned keys, copy match on kid foreach (var item in response.Data.keys) { if (oidcHeader.kid == item.kid) { secretKeyn = item.n; secretKeye = item.e; } } } catch (Exception) { logger.Error("falied to access keys from Authorization Server"); return("Failed calling keys endpoint"); } try { //incorporate public received from keys endpoint RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); rsa.ImportParameters( new RSAParameters() { Modulus = FromBase64Url(secretKeyn), Exponent = FromBase64Url(secretKeye) }); //verify JWT signature versus computed RSAPKCS1SignatureDeformatter rsaDeformatter = new RSAPKCS1SignatureDeformatter(rsa); rsaDeformatter.SetHashAlgorithm("SHA256"); bool rspRsa = rsaDeformatter.VerifySignature(hash, crypto); if (rspRsa) { logger.Debug("Signature validation successful"); } else { logger.Error("Signature validation failed"); return("Failure; Signature validation"); } } catch (Exception) { logger.Error("Signature validation failed"); return("Failure; Signature validation"); } //verify remainder of JWT token var tokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters() { //ValidAudiences = new string[] //{ // clientId //}, //ValidIssuers = new string[] //{ // issuer //}, ValidAudience = audience, ValidIssuer = issuer, //IssuerSigningToken = new System.ServiceModel.Security.Tokens.BinarySecretSecurityToken(Convert.FromBase64String(secret)), //IssuerSigningKey = new X509SecurityKey(cert), RequireExpirationTime = true, ValidateLifetime = true, ValidateAudience = false, ValidateIssuer = true, ValidateIssuerSigningKey = false, RequireSignedTokens = false, CertificateValidator = System.IdentityModel.Selectors.X509CertificateValidator.None }; try { // if token is valid, it will output the validated token that contains the JWT information // strip off signature from token string token1 = idToken.Substring(0, idToken.LastIndexOf('.') + 1); // Convert Base64 encoded token to Base64Url encoding string token2 = token1.Replace('+', '-').Replace('/', '_').Replace("=", ""); System.Security.Claims.ClaimsPrincipal principal = tokenHandler.ValidateToken(token2, tokenValidationParameters, out validatedToken); } catch (Exception ex) { logger.Error("Failed to validate token"); return("Failure; Validating token"); } return(payloadData.ToString()); }
public APIGatewayCustomAuthorizerResponse FunctionHandler(APIGatewayCustomAuthorizerRequest apigAuthRequest, ILambdaContext context) { string auth0Domain = context.ClientContext.Environment["Auth0Domain"]; string auth0Audience = context.ClientContext.Environment["Auth0Audience"]; IConfigurationManager <OpenIdConnectConfiguration> configurationManager = new ConfigurationManager <OpenIdConnectConfiguration>($"{auth0Domain}.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever()); OpenIdConnectConfiguration openIdConfig = AsyncHelper.RunSync(async() => await configurationManager.GetConfigurationAsync(CancellationToken.None)); var tokenValidationParameters = new TokenValidationParameters { ValidIssuer = auth0Domain, ValidAudiences = new[] { auth0Audience }, IssuerSigningKeys = openIdConfig.SigningKeys }; JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler(); bool authorized = false; Claim subject = null; string[] scopes = null; if (!string.IsNullOrWhiteSpace(apigAuthRequest.AuthorizationToken)) { try { var user = handler.ValidateToken(apigAuthRequest.AuthorizationToken, tokenValidationParameters, out _); var audience = user.Claims.FirstOrDefault(c => c.Type == "aud"); var issuer = user.Claims.FirstOrDefault(c => c.Type == "iss"); subject = user.Claims.FirstOrDefault(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"); if (user.Claims.Any(c => c.Type == "scope")) { scopes = user.Claims.First(c => c.Type == "scope").Value.Split(" ").Where(x => x.Contains(":")).ToArray(); } if (audience != null && issuer != null) { authorized = issuer.Value == auth0Domain && audience.Value == auth0Audience; // Ensure that the claim value matches the assertion } } catch (Exception ex) { LambdaLogger.Log($"Error occurred validating token: {ex.Message}"); } } APIGatewayCustomAuthorizerPolicy policy = new APIGatewayCustomAuthorizerPolicy { Version = "2012-10-17", Statement = new List <APIGatewayCustomAuthorizerPolicy.IAMPolicyStatement>() }; policy.Statement.Add(new APIGatewayCustomAuthorizerPolicy.IAMPolicyStatement { Action = new HashSet <string>(new string[] { "execute-api:Invoke" }), Effect = authorized ? "Allow" : "Deny", Resource = new HashSet <string>(new string[] { apigAuthRequest.MethodArn }) }); APIGatewayCustomAuthorizerContextOutput contextOutput = new APIGatewayCustomAuthorizerContextOutput(); contextOutput["User"] = authorized ? subject.Value : null; contextOutput["Path"] = apigAuthRequest.MethodArn; var permissions = scopes ?? new string[] {}; contextOutput["Permissions"] = string.Join(",", permissions); return(new APIGatewayCustomAuthorizerResponse { PrincipalID = authorized ? subject.Value : null, Context = contextOutput, PolicyDocument = policy }); }
public async Task <LinkState> GetCurrentUserLinkStateAsync(string payload) { var jwt = OwnIdSerializer.Deserialize <JwtContainer>(payload)?.Jwt; if (string.IsNullOrEmpty(jwt)) { throw new Exception("No JWT was found in HttpRequest"); } var tokenHandler = new JwtSecurityTokenHandler(); if (!tokenHandler.CanReadToken(jwt)) { throw new Exception("Invalid jwt"); } var rsaSecurityKey = await _restApiClient.GetPublicKey(); try { tokenHandler.ValidateToken(jwt, new TokenValidationParameters { IssuerSigningKey = rsaSecurityKey, RequireSignedTokens = true, RequireExpirationTime = true, ValidateIssuerSigningKey = true, ValidateLifetime = true, ValidateAudience = false, ValidateIssuer = false // TODO: add issuer to token for validation }, out _); } catch (SecurityTokenValidationException ex) { throw new Exception($"Token failed validation: {ex.Message}"); } catch (ArgumentException ex) { throw new Exception($"Token was invalid: {ex.Message}"); } var token = tokenHandler.ReadJwtToken(jwt); if (!token.Payload.ContainsKey(ApiKeyPayloadKey) || (string)token.Payload[ApiKeyPayloadKey] != _configuration.ApiKey) { throw new Exception("Jwt was created for different apiKey"); } var did = token.Subject; var accountInfo = await _restApiClient.SearchAsync <AccountInfoResponse <TProfile> >(GigyaFields.UID, did); if (!accountInfo.IsSuccess) { if (accountInfo.ErrorCode != 0) { throw new Exception(accountInfo.GetFailureMessage()); } throw new Exception($"Can't find user with did = {did}"); } return(new LinkState(did, (uint)(accountInfo.First.Data?.OwnId?.Connections?.Count ?? 0))); }
private async Task <TokenValidationResult> ValidateJwtAsync(string jwt, string audience, IEnumerable <X509Certificate2> signingCertificates, bool validateLifetime = true) { var handler = new JwtSecurityTokenHandler(); // todo #if NET451 JwtSecurityTokenHandler.InboundClaimTypeMap.Clear(); #elif DOTNET5_4 handler.InboundClaimTypeMap.Clear(); #endif //{ // Configuration = // new SecurityTokenHandlerConfiguration // { // CertificateValidationMode = X509CertificateValidationMode.None, // CertificateValidator = X509CertificateValidator.None // } //}; var keys = (from c in signingCertificates select new X509SecurityKey(c)).ToList(); var parameters = new TokenValidationParameters { ValidIssuer = _context.GetIssuerUri(), IssuerSigningKeys = keys, ValidateLifetime = validateLifetime, ValidAudience = audience }; try { SecurityToken jwtToken; var id = handler.ValidateToken(jwt, parameters, out jwtToken); // if access token contains an ID, log it var jwtId = id.FindFirst(JwtClaimTypes.JwtId); if (jwtId != null) { _log.JwtId = jwtId.Value; } // load the client that belongs to the client_id claim Client client = null; var clientId = id.FindFirst(JwtClaimTypes.ClientId); if (clientId != null) { client = await _clients.FindClientByIdAsync(clientId.Value); if (client == null) { throw new InvalidOperationException("Client does not exist anymore."); } } return(new TokenValidationResult { IsError = false, Claims = id.Claims, Client = client, Jwt = jwt }); } catch (Exception ex) { _logger.LogError("JWT token validation error", ex); return(Invalid(OidcConstants.ProtectedResourceErrors.InvalidToken)); } }
/// <summary> /// OAuth 2.0 validation /// </summary> /// <returns></returns> private JwtSecurityToken ValidateOAuth20() { // OAuth 2 Specs for Platform Originating Messages // https://www.imsglobal.org/spec/security/v1p0/#platform-originating-messages // Get the JWT Token from the id_token form field. // We could detect the LTI version by checking which OAuth Form Fields are present. // Presence of id_token indicates OAuth 2. Once the token is unpacked, we can check the LTI Version Claim for the exact LTI version string ltiLaunchJwtToken = _formData["id_token"]; if (ltiLaunchJwtToken == null) { return(null); } JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler(); TokenValidationParameters validationParameters = new TokenValidationParameters { // OAuth 2.0 Required Validations (https://www.imsglobal.org/spec/security/v1p0/#message-security-and-message-signing) // 5.1.3 Authentication Response Validation // 5.1.3.1 IssuerSigningKeys = OutOfBandData.GetPlatformSigningKeys(), ValidateIssuerSigningKey = true, RequireSignedTokens = true, // 5.1.3.2 ValidIssuer = OutOfBandData.PlatforIssuerId, ValidateIssuer = true, // 5.1.3.3 ValidAudience = OutOfBandData.ToolClientId, ValidateAudience = true, // 5.1.3.4, 5.1.3.5 related to multiple audiences - skip for now // 5.1.3.7, 5.1.3.8 ValidateLifetime = true, RequireExpirationTime = true, // 5.1.3.9 TokenReplayCache = _replayCache, ValidateTokenReplay = true, ClockSkew = new TimeSpan(0, 0, 15) }; // Validate token. ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(ltiLaunchJwtToken, validationParameters, out SecurityToken validatedToken); // Set the ClaimsPrincipal on the current thread. Thread.CurrentPrincipal = claimsPrincipal; // Set the ClaimsPrincipal on HttpContext.Current if the app is running in web hosted environment. if (HttpContext.Current != null) { HttpContext.Current.User = claimsPrincipal; } // If the token is scoped, verify that required permission is set in the scope claim. // See https://www.imsglobal.org/spec/security/v1p0/#access-token-management // 7.1 Access Token Management if (ClaimsPrincipal.Current.FindFirst(scopeClaimType) != null && ClaimsPrincipal.Current.FindFirst(scopeClaimType).Value != "user_impersonation") { return(null); } // now we have validated token and can use the claims containing usable LTI Launch parameters var launchToken = (JwtSecurityToken)validatedToken; return(launchToken); }
public ClaimsPrincipal ValidateToken(string token) { return(mJwtSecurityTokenHandler.ValidateToken(token, mTokenValidation, out var securityToken)); }
static async Task Main() { // Flip to true for ADAL logging - // it's pretty verbose thus pretty handy. LoggerCallbackHandler.UseDefaultLogging = false; AuthenticationContext context = new AuthenticationContext(authority); DeviceCodeResult codeResult = await context.AcquireDeviceCodeAsync(resourceId, clientId); Console.BackgroundColor = ConsoleColor.DarkCyan; Console.WriteLine("\nUsing OAuth 2.0 Device Flow. You need to sign in."); Console.WriteLine(codeResult.Message + "\n"); Console.ResetColor(); AuthenticationResult result = await context.AcquireTokenByDeviceCodeAsync(codeResult); // Get the JWT bearer token from the authorization header // Use https://jwt.ms to decode string accessToken = result.AccessToken; string accessTokenType = result.AccessTokenType; Console.WriteLine("Azure AD returned this JWT:"); Console.BackgroundColor = ConsoleColor.Red; Console.WriteLine($"\n{accessTokenType} {accessToken}\n"); Console.ResetColor(); // Get tenant information to validate incoming JWT tokens string stsDiscoveryEndpoint = $"{authority}/.well-known/openid-configuration"; OpenIdConnectConfigurationRetriever configRetriever = new OpenIdConnectConfigurationRetriever(); ConfigurationManager <OpenIdConnectConfiguration> configManager = new ConfigurationManager <OpenIdConnectConfiguration>(stsDiscoveryEndpoint, configRetriever); OpenIdConnectConfiguration config = await configManager.GetConfigurationAsync(); TokenValidationParameters validationParameters = new TokenValidationParameters { ValidIssuer = config.Issuer, // gets pulled from https://login.microsoftonline.com/swearjarbank.onmicrosoft.com/.well-known/openid-configuration ValidateIssuer = true, // compares token issuer claim with https://login.microsoftonline.com/swearjarbank.onmicrosoft.com/.well-known/openid-configuration ValidAudience = resourceId, // or ValidAudiences = new [] { resourceId, "https://2ndresource" }, ValidateAudience = true, // compares aud claim to ValidAudience ValidateIssuerSigningKey = true, IssuerSigningKeys = config.SigningKeys, RequireExpirationTime = true, RequireSignedTokens = true }; // VALIDATE TOKEN // https://stackoverflow.com/a/39870281/4148708 // "There are two steps to verity the token. // First, verify the signature of the token to ensure the token // was issued by Azure Active Directory. Second verify the claims // in the token based on the business logic." SecurityToken validatedToken = new JwtSecurityToken(); JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler(); ClaimsPrincipal validationResult = null; try { validationResult = tokenHandler.ValidateToken(accessToken, validationParameters, out validatedToken); } catch (Exception ex) { Console.BackgroundColor = ConsoleColor.DarkRed; Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("TOKEN DID NOT PASS VALIDATION LOGIC."); Console.WriteLine(ex.Message); Console.ResetColor(); Console.ReadKey(); Environment.Exit(-1); } Console.BackgroundColor = ConsoleColor.DarkGreen; Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("TOKEN IS VALID."); Console.ResetColor(); Console.WriteLine($"Issuer: {validatedToken.Issuer}\n" + $"ValidFrom: {validatedToken.ValidFrom}\n" + $"ValidTo: {validatedToken.ValidTo}\n" + $"Public Signing Key: {validatedToken.SigningKey.KeyId}\n\n" + "Claims:"); IEnumerator claims = validationResult.Claims.GetEnumerator(); while (claims.MoveNext()) { Console.WriteLine($" {claims.Current}"); } Console.ReadKey(); }
public async Task JwtAccessTokenIssuer_SignsAccessToken() { // Arrange var expectedDateTime = new DateTimeOffset(2000, 01, 01, 0, 0, 0, TimeSpan.FromHours(1)); var now = DateTimeOffset.UtcNow; var expires = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, TimeSpan.Zero); var timeManager = GetTimeManager(expectedDateTime, expires, expectedDateTime); var options = GetOptions(); var handler = new JwtSecurityTokenHandler(); var tokenValidationParameters = new TokenValidationParameters { IssuerSigningKey = options.Value.SigningKeys[0].Key, ValidAudiences = new[] { "resourceId" }, ValidIssuers = new[] { options.Value.Issuer } }; var issuer = new JwtAccessTokenIssuer( GetClaimsManager(timeManager), GetSigningPolicy(options, timeManager), new JwtSecurityTokenHandler(), options); var context = GetTokenGenerationContext( new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, "user") })), new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(IdentityServiceClaimTypes.ClientId, "clientId") }))); context.InitializeForToken(TokenTypes.AccessToken); // Act await issuer.IssueAccessTokenAsync(context); // Assert Assert.NotNull(context.AccessToken); Assert.NotNull(context.AccessToken.SerializedValue); SecurityToken validatedToken; Assert.NotNull(handler.ValidateToken(context.AccessToken.SerializedValue, tokenValidationParameters, out validatedToken)); Assert.NotNull(validatedToken); var jwtToken = Assert.IsType <JwtSecurityToken>(validatedToken); var accessToken = Assert.IsType <AccessToken>(context.AccessToken.Token); Assert.Equal("http://www.example.com/issuer", jwtToken.Issuer); var tokenAudience = Assert.Single(jwtToken.Audiences); Assert.Equal("resourceId", tokenAudience); var tokenAuthorizedParty = Assert.Single(jwtToken.Claims, c => c.Type.Equals("azp")).Value; Assert.Equal("clientId", tokenAuthorizedParty); Assert.Equal("user", jwtToken.Subject); Assert.Equal(expires, jwtToken.ValidTo); Assert.Equal(expectedDateTime.UtcDateTime, jwtToken.ValidFrom); var tokenScopes = jwtToken.Claims .Where(c => c.Type == IdentityServiceClaimTypes.Scope) .Select(c => c.Value).OrderBy(c => c) .ToArray(); Assert.Equal(new[] { "all" }, tokenScopes); }
private static JwtSecurityToken GetAndValidateJwt(string rawToken, bool checkExpiry) { JwtSecurityToken jwt; try { jwt = new JwtSecurityToken(rawToken); } catch (Exception ex) { Logger.Error("Unable to construct JWT object from authorization value. " + ex.Message); return(null); } if (checkExpiry) { var now = DateTime.UtcNow; if (now < jwt.ValidFrom || now > jwt.ValidTo) { if (Logger.IsDebugEnabled) { Logger.Debug("Token is expired"); } return(null); } } var portalSettings = PortalController.Instance.GetCurrentPortalSettings(); var azureB2cConfig = new AzureConfig(AzureConfig.ServiceName, portalSettings.PortalId); if (portalSettings == null) { if (Logger.IsDebugEnabled) { Logger.Debug("Unable to retrieve portal settings"); } return(null); } if (!azureB2cConfig.Enabled || !azureB2cConfig.JwtAuthEnabled) { if (Logger.IsDebugEnabled) { Logger.Debug($"Azure B2C JWT auth is not enabled for portal {portalSettings.PortalId}"); } return(null); } var _config = GetConfig(portalSettings.PortalId, azureB2cConfig); var validAudiences = azureB2cConfig.JwtAudiences.Split(',').Select(x => x.Trim()).Where(x => !string.IsNullOrEmpty(x)).ToArray(); if (validAudiences.Length == 0) { validAudiences = new[] { azureB2cConfig.APIKey }; } try { // Validate token. var _tokenValidator = new JwtSecurityTokenHandler(); var validationParameters = new TokenValidationParameters { // App Id URI and AppId of this service application are both valid audiences. ValidAudiences = validAudiences, // Support Azure AD V1 and V2 endpoints. ValidIssuers = new[] { _config.Issuer, $"{_config.Issuer}v2.0/" }, IssuerSigningKeys = _config.SigningKeys }; var claimsPrincipal = _tokenValidator.ValidateToken(rawToken, validationParameters, out SecurityToken _); } catch (Exception ex) { if (Logger.IsDebugEnabled) { Logger.Debug($"Error validating token: {ex}"); } return(null); } return(jwt); }
public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken) { var principal = _securityTokenHandler.ValidateToken(securityToken, validationParameters, out validatedToken); return(principal); }
public override void Verify() { // 1. Verify that attStmt is valid CBOR conforming to the syntax defined above and perform // CBOR decoding on it to extract the contained fields // (handled in base class) if ((CBORType.TextString != attStmt["ver"].Type) || (0 == attStmt["ver"].AsString().Length)) { throw new Fido2VerificationException("Invalid version in SafetyNet data"); } // 2. Verify that response is a valid SafetyNet response of version ver var ver = attStmt["ver"].AsString(); if ((CBORType.ByteString != attStmt["response"].Type) || (0 == attStmt["response"].GetByteString().Length)) { throw new Fido2VerificationException("Invalid response in SafetyNet data"); } var response = attStmt["response"].GetByteString(); var responseJWT = Encoding.UTF8.GetString(response); if (string.IsNullOrWhiteSpace(responseJWT)) { throw new Fido2VerificationException("SafetyNet response null or whitespace"); } var jwtParts = responseJWT.Split('.'); if (jwtParts.Length != 3) { throw new Fido2VerificationException("SafetyNet response JWT does not have the 3 expected components"); } var jwtHeaderString = jwtParts.First(); var jwtHeaderJSON = JObject.Parse(Encoding.UTF8.GetString(Base64Url.Decode(jwtHeaderString))); var x5cArray = jwtHeaderJSON["x5c"] as JArray; if (x5cArray == null) { throw new Fido2VerificationException("SafetyNet response JWT header missing x5c"); } var x5cStrings = x5cArray.Values <string>().ToList(); if (x5cStrings.Count == 0) { throw new Fido2VerificationException("No keys were present in the TOC header in SafetyNet response JWT"); } var certs = new List <X509Certificate2>(); var keys = new List <SecurityKey>(); foreach (var certString in x5cStrings) { var cert = GetX509Certificate(certString); certs.Add(cert); var ecdsaPublicKey = cert.GetECDsaPublicKey(); if (ecdsaPublicKey != null) { keys.Add(new ECDsaSecurityKey(ecdsaPublicKey)); } var rsaPublicKey = cert.GetRSAPublicKey(); if (rsaPublicKey != null) { keys.Add(new RsaSecurityKey(rsaPublicKey)); } } var validationParameters = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = false, ValidateIssuerSigningKey = true, IssuerSigningKeys = keys }; var tokenHandler = new JwtSecurityTokenHandler(); SecurityToken validatedToken = null; try { tokenHandler.ValidateToken( responseJWT, validationParameters, out validatedToken); } catch (SecurityTokenException ex) { throw new Fido2VerificationException("SafetyNet response security token validation failed", ex); } var nonce = ""; bool?ctsProfileMatch = null; var timestampMs = DateTimeHelper.UnixEpoch; var jwtToken = validatedToken as JwtSecurityToken; foreach (var claim in jwtToken.Claims) { if (("nonce" == claim.Type) && ("http://www.w3.org/2001/XMLSchema#string" == claim.ValueType) && (0 != claim.Value.Length)) { nonce = claim.Value; } if (("ctsProfileMatch" == claim.Type) && ("http://www.w3.org/2001/XMLSchema#boolean" == claim.ValueType)) { ctsProfileMatch = bool.Parse(claim.Value); } if (("timestampMs" == claim.Type) && ("http://www.w3.org/2001/XMLSchema#integer64" == claim.ValueType)) { timestampMs = DateTimeHelper.UnixEpoch.AddMilliseconds(double.Parse(claim.Value)); } } var notAfter = DateTime.UtcNow.AddMilliseconds(_driftTolerance); var notBefore = DateTime.UtcNow.AddMinutes(-1).AddMilliseconds(-(_driftTolerance)); if ((notAfter < timestampMs) || ((notBefore) > timestampMs)) { throw new Fido2VerificationException(string.Format("SafetyNet timestampMs must be present and between one minute ago and now, got: {0}", timestampMs.ToString())); } // 3. Verify that the nonce in the response is identical to the SHA-256 hash of the concatenation of authenticatorData and clientDataHash if ("" == nonce) { throw new Fido2VerificationException("Nonce value not found in SafetyNet attestation"); } byte[] nonceHash = null; try { nonceHash = Convert.FromBase64String(nonce); } catch (Exception ex) { throw new Fido2VerificationException("Nonce value not base64string in SafetyNet attestation", ex); } using (var hasher = CryptoUtils.GetHasher(HashAlgorithmName.SHA256)) { var dataHash = hasher.ComputeHash(Data); if (false == dataHash.SequenceEqual(nonceHash)) { throw new Fido2VerificationException( string.Format( "SafetyNet response nonce / hash value mismatch, nonce {0}, hash {1}", BitConverter.ToString(nonceHash).Replace("-", ""), BitConverter.ToString(dataHash).Replace("-", "") ) ); } } // 4. Let attestationCert be the attestation certificate var subject = certs[0].GetNameInfo(X509NameType.DnsName, false); // 5. Verify that the attestation certificate is issued to the hostname "attest.android.com" if (false == ("attest.android.com").Equals(subject)) { throw new Fido2VerificationException(string.Format("SafetyNet attestation cert DnsName invalid, want {0}, got {1}", "attest.android.com", subject)); } // 6. Verify that the ctsProfileMatch attribute in the payload of response is true if (null == ctsProfileMatch) { throw new Fido2VerificationException("SafetyNet response ctsProfileMatch missing"); } if (true != ctsProfileMatch) { throw new Fido2VerificationException("SafetyNet response ctsProfileMatch false"); } }
public IActionResult VerifyToken([FromHeader(Name = HEADER_AUTH)] string token) { IActionResult response = Unauthorized(); var headerKeys = string.Empty; foreach (var key in Request.Headers.Keys) { headerKeys += key.ToString() + ", "; } headerKeys = headerKeys.Substring(0, headerKeys.Length - 2); if (Request.Headers.Keys.Contains(HEADER_AUTH)) { var reqHeader = Request.Headers.FirstOrDefault(h => h.Key == HEADER_AUTH); if (reqHeader.Value != string.Empty && reqHeader.Value != "null") { try { if (ModelState.IsValid) // ModelState อาจจะไม่จำเป็นต้องใช้ หรือใช้ไม่ได้กับ API { try { var handler = new JwtSecurityTokenHandler(); var jwtSecToken = handler.ReadToken(token) as JwtSecurityToken; string[] jwtArray = token.Split('.'); var jwtHeader = JwtHeader.Base64UrlDeserialize(jwtArray[0]); var jwtPayload = JwtPayload.Base64UrlDeserialize(jwtArray[1]); Jwt.Algorithm jwtAlg; if (jwtHeader.Alg == "HS256") { jwtAlg = Jwt.Algorithm.HS256; } else if (jwtHeader.Alg == "RS256") { jwtAlg = Jwt.Algorithm.RS256; } else if (jwtHeader.Alg == "ES256") { jwtAlg = Jwt.Algorithm.ES256; } else { jwtAlg = Jwt.Algorithm.HS256; } var tokenHandler = new JwtSecurityTokenHandler(); try { var claimPrincipal = tokenHandler.ValidateToken(token, new TokenValidationParameters { ValidIssuer = _config["Jwt:Issuer"], ValidAudience = _config["Jwt:Audience"], ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ClockSkew = TimeSpan.Zero, IssuerSigningKey = Jwt.GetSecurityKey(jwtAlg, _config, _azObj) }, out var parsedToken); var isAuthen = claimPrincipal.Identity.IsAuthenticated; if (isAuthen) { var result = "valid"; return(Ok(new { result, token })); } else { response = CustomHttpResponse.Error(HttpStatusCode.Unauthorized, Errors.Invalid_AccessToken, "Unauthorized, AccessToken (" + token + ") is invalid (Can Not Authenticated)."); } } catch (Exception ex) { response = CustomHttpResponse.Error(HttpStatusCode.Unauthorized, Errors.Invalid_AccessToken, "Unauthorized, AccessToken (" + token + ") is invalid (>> " + ex.Message + ")."); } } catch (Exception ex) { response = CustomHttpResponse.Error(HttpStatusCode.Unauthorized, Errors.Invalid_AccessToken, "Unauthorized, AccessToken (" + token + ") is invalid (> " + ex.Message + ")."); } } else // ModelState อาจจะไม่จำเป็นต้องใช้ หรือใช้ไม่ได้กับ API { response = CustomHttpResponse.Error(HttpStatusCode.Unauthorized, Errors.ExceptionalOccured, "Unauthorized, Input Model ('" + headerKeys + "') is invalid."); } } catch (Exception ex) { response = CustomHttpResponse.Error(HttpStatusCode.Unauthorized, Errors.ExceptionalOccured, "Unauthorized, Exception occurred (" + ex.Message + " - " + ex.Source + " - " + ex.StackTrace + " - " + ex.InnerException + " - " + ex.HelpLink + ")."); } } else { response = CustomHttpResponse.Error(HttpStatusCode.Unauthorized, Errors.Invalid_AccessToken, "Unauthorized, AccessToken is empty"); } } else { response = CustomHttpResponse.Error(HttpStatusCode.Unauthorized, Errors.ExceptionalOccured, "Unauthorized, Input Model is invalid (There is no Auth-Jwt)."); } return(response); }
/// <summary> /// DeserializeAndValidateBlob method implementation /// </summary> protected async Task <MetadataBLOBPayload> DeserializeAndValidateBlob(string rawBLOBJwt) { if (string.IsNullOrWhiteSpace(rawBLOBJwt)) { throw new ArgumentNullException(nameof(rawBLOBJwt)); } var jwtParts = rawBLOBJwt.Split('.'); if (jwtParts.Length != 3) { throw new ArgumentException("The JWT does not have the 3 expected components"); } var blobHeader = jwtParts.First(); var tokenHeader = JObject.Parse(System.Text.Encoding.UTF8.GetString(Base64Url.Decode(blobHeader))); var blobAlg = tokenHeader["alg"]?.Value <string>(); if (blobAlg == null) { throw new ArgumentNullException("No alg value was present in the BLOB header."); } var x5cArray = tokenHeader["x5c"] as JArray; if (x5cArray == null) { throw new ArgumentException("No x5c array was present in the BLOB header."); } var rootCert = GetX509Certificate(ROOT_CERT); var blobCertStrings = x5cArray.Values <string>().ToList(); var blobCertificates = new List <X509Certificate2>(); var blobPublicKeys = new List <SecurityKey>(); foreach (var certString in blobCertStrings) { var cert = GetX509Certificate(certString); blobCertificates.Add(cert); var ecdsaPublicKey = cert.GetECDsaPublicKey(); if (ecdsaPublicKey != null) { blobPublicKeys.Add(new ECDsaSecurityKey(ecdsaPublicKey)); } var rsa = cert.GetRSAPublicKey(); if (rsa != null) { blobPublicKeys.Add(new RsaSecurityKey(rsa)); } } var certChain = new X509Chain(); certChain.ChainPolicy.ExtraStore.Add(rootCert); certChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; var validationParameters = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = false, ValidateIssuerSigningKey = true, IssuerSigningKeys = blobPublicKeys, }; var tokenHandler = new JwtSecurityTokenHandler() { // 250k isn't enough bytes for conformance test tool // https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/1097 MaximumTokenSizeInBytes = rawBLOBJwt.Length }; tokenHandler.ValidateToken( rawBLOBJwt, validationParameters, out var validatedToken); if (blobCertificates.Count > 1) { certChain.ChainPolicy.ExtraStore.AddRange(blobCertificates.Skip(1).ToArray()); } var certChainIsValid = certChain.Build(blobCertificates.First()); // if the root is trusted in the context we are running in, valid should be true here if (!certChainIsValid) { foreach (var element in certChain.ChainElements) { if (element.Certificate.Issuer != element.Certificate.Subject) { var cdp = CryptoUtils.CDPFromCertificateExts(element.Certificate.Extensions); var crlFile = await DownloadDataAsync(cdp); if (true == CryptoUtils.IsCertInCRL(crlFile, element.Certificate)) { throw new VerificationException(string.Format("Cert {0} found in CRL {1}", element.Certificate.Subject, cdp)); } } } // otherwise we have to manually validate that the root in the chain we are testing is the root we downloaded if (rootCert.Thumbprint == certChain.ChainElements[certChain.ChainElements.Count - 1].Certificate.Thumbprint && // and that the number of elements in the chain accounts for what was in x5c plus the root we added certChain.ChainElements.Count == (blobCertStrings.Count + 1) && // and that the root cert has exactly one status listed against it certChain.ChainElements[certChain.ChainElements.Count - 1].ChainElementStatus.Length == 1 && // and that that status is a status of exactly UntrustedRoot certChain.ChainElements[certChain.ChainElements.Count - 1].ChainElementStatus[0].Status == X509ChainStatusFlags.UntrustedRoot) { // if we are good so far, that is a good sign certChainIsValid = true; for (var i = 0; i < certChain.ChainElements.Count - 1; i++) { // check each non-root cert to verify zero status listed against it, otherwise, invalidate chain if (0 != certChain.ChainElements[i].ChainElementStatus.Length) { certChainIsValid = false; } } } } if (!certChainIsValid) { throw new VerificationException("Failed to validate cert chain while parsing BLOB"); } var blobPayload = ((JwtSecurityToken)validatedToken).Payload.SerializeToJson(); var blob = Newtonsoft.Json.JsonConvert.DeserializeObject <MetadataBLOBPayload>(blobPayload); blob.JwtAlg = blobAlg; return(blob); }
private async Task <AuthResult> VerifyAndGenerateToken(TokenRequest tokenRequest) { var jwtTokenHandler = new JwtSecurityTokenHandler(); try { // Validation 1 - Validate JWT token format var tokenInVerification = jwtTokenHandler.ValidateToken(tokenRequest.Token, _tokenValidationParams, out var validatedToken); // Validation 2 - Validate encryption algorithm if (validatedToken is JwtSecurityToken jwtSecurityToken) { var result = jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase); if (result == false) { return(null); } } // Validation 3 - Validate expire date var utcExpireDate = long.Parse(tokenInVerification.Claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Exp).Value); var expireDate = UnixTimeStampToDateTime(utcExpireDate); if (expireDate > DateTime.UtcNow) { return(new AuthResult() { Success = false, Errors = new List <string>() { "Token has not yet expired" } }); } // Validation 4 - Validate existing of the token var storedToken = await _apiDbContext.RefreshTokens.FirstOrDefaultAsync(x => x.Token == tokenRequest.RefreshToken); if (storedToken == null) { return(new AuthResult() { Success = false, Errors = new List <string>() { "Token does not exist" } }); } // Validation 5 - Validate if used if (storedToken.IsUsed) { return(new AuthResult() { Success = false, Errors = new List <string>() { "Token has been used" } }); } // Validation 6 - Validate if revoked if (storedToken.IsRevoked) { return(new AuthResult() { Success = false, Errors = new List <string>() { "Token has been revoked" } }); } // Validation 7 - Validate id var jti = tokenInVerification.Claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Jti).Value; if (storedToken.JwtId != jti) { return(new AuthResult() { Success = false, Errors = new List <string>() { "Token does not match" } }); } // Update current token storedToken.IsUsed = true; _apiDbContext.RefreshTokens.Update(storedToken); await _apiDbContext.SaveChangesAsync(); // Generate a new token var dbUser = await _userManager.FindByIdAsync(storedToken.UserId); return(await GenerateJwtToken(dbUser)); } catch (Exception ex) { return(null); } }
public AuthenticationTicket Unprotect(string protectedText, string purpose) { var handler = new JwtSecurityTokenHandler(); ClaimsPrincipal principal = null; SecurityToken validToken = null; try { //Add breadcrumb for Sentry error monitoring var crumb = new Breadcrumb("AliseeksJwtCookieAuthentication"); crumb.Message = "Attempting to validate JWT token"; crumb.Data = new Dictionary <string, string>() { { "ValidationParameters", JsonConvert.SerializeObject(this.validationParameters) }, { "ProtectedText", protectedText } }; raven.AddTrail(crumb); principal = handler.ValidateToken(protectedText, this.validationParameters, out validToken); var validJwt = validToken as JwtSecurityToken; if (validJwt == null) { throw new ArgumentException("Invalid JWT"); } if (!validJwt.Header.Alg.Equals(algorithm, StringComparison.Ordinal)) { throw new ArgumentException($"Algorithm must be {algorithm}"); } //Append token value to identity var tokenClaim = new Claim[] { new Claim("Token", protectedText) }; principal.AddIdentity(new ClaimsIdentity(tokenClaim)); return(new AuthenticationTicket(principal, new Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties() { }, "AliseeksCookie")); } catch (SecurityTokenValidationException e) { //Blocking I/O raven.CaptureNetCoreEvent(e).Wait(); return(null); } catch (ArgumentException e) { //Blocking I/O raven.CaptureNetCoreEvent(e).Wait(); return(null); } catch (Exception e) { //Blocking I/O raven.CaptureNetCoreEvent(e).Wait(); return(null); } }
public override void Verify() { // Verify that attStmt is valid CBOR conforming to the syntax defined above and perform // CBOR decoding on it to extract the contained fields if ((CBORType.TextString != attStmt["ver"].Type) || (0 == attStmt["ver"].AsString().Length)) { throw new Fido2VerificationException("Invalid version in SafetyNet data"); } // Verify that response is a valid SafetyNet response of version ver var ver = attStmt["ver"].AsString(); if ((CBORType.ByteString != attStmt["response"].Type) || (0 == attStmt["response"].GetByteString().Length)) { throw new Fido2VerificationException("Invalid response in SafetyNet data"); } var response = attStmt["response"].GetByteString(); var signedAttestationStatement = Encoding.UTF8.GetString(response); var jwtToken = new JwtSecurityToken(signedAttestationStatement); X509SecurityKey[] keys = (jwtToken.Header["x5c"] as JArray) .Values <string>() .Select(x => new X509SecurityKey( new X509Certificate2(Convert.FromBase64String(x)))) .ToArray(); if ((null == keys) || (0 == keys.Count())) { throw new Fido2VerificationException("SafetyNet attestation missing x5c"); } var validationParameters = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = false, ValidateIssuerSigningKey = true, IssuerSigningKeys = keys }; var tokenHandler = new JwtSecurityTokenHandler(); tokenHandler.ValidateToken( signedAttestationStatement, validationParameters, out var validatedToken); if (false == (validatedToken.SigningKey is X509SecurityKey)) { throw new Fido2VerificationException("Safetynet signing key invalid"); } var nonce = ""; var payload = false; foreach (var claim in jwtToken.Claims) { if (("nonce" == claim.Type) && ("http://www.w3.org/2001/XMLSchema#string" == claim.ValueType) && (0 != claim.Value.Length)) { nonce = claim.Value; } if (("ctsProfileMatch" == claim.Type) && ("http://www.w3.org/2001/XMLSchema#boolean" == claim.ValueType)) { payload = bool.Parse(claim.Value); } if (("timestampMs" == claim.Type) && ("http://www.w3.org/2001/XMLSchema#integer64" == claim.ValueType)) { var dt = DateTimeHelper.UnixEpoch.AddMilliseconds(double.Parse(claim.Value)); if ((DateTime.UtcNow < dt) || (DateTime.UtcNow.AddMinutes(-1) > dt)) { throw new Fido2VerificationException("Android SafetyNet timestampMs must be between one minute ago and now"); } } } // Verify that the nonce in the response is identical to the SHA-256 hash of the concatenation of authenticatorData and clientDataHash if ("" == nonce) { throw new Fido2VerificationException("Nonce value not found in Android SafetyNet attestation"); } var dataHash = CryptoUtils.GetHasher(HashAlgorithmName.SHA256).ComputeHash(Data); var nonceHash = Convert.FromBase64String(nonce); if (false == dataHash.SequenceEqual(nonceHash)) { throw new Fido2VerificationException("Android SafetyNet hash value mismatch"); } // Verify that the attestation certificate is issued to the hostname "attest.android.com" var attCert = (validatedToken.SigningKey as X509SecurityKey).Certificate; var subject = attCert.GetNameInfo(X509NameType.DnsName, false); if (false == ("attest.android.com").Equals(subject)) { throw new Fido2VerificationException("Safetynet DnsName is not attest.android.com"); } // Verify that the ctsProfileMatch attribute in the payload of response is true if (true != payload) { throw new Fido2VerificationException("Android SafetyNet ctsProfileMatch must be true"); } }
/// <summary> /// Validates the token /// </summary> /// <param name="expectedAudience">The valid audience value to check</param> /// <returns></returns> public IdTokenValidationResult Validate(string expectedAudience) { IdTokenValidationResult result = new IdTokenValidationResult(); // Validate non-standard stuff // Is appctx valid? if (AppContext == null) { result.Message = "The app context claim is missing or invalid."; return(result); } // Token version if (string.Compare(AppContext.Version, "ExIdTok.V1", StringComparison.InvariantCulture) == 0) { result.VersionResult = "passed"; } // Use System.IdentityModel.Tokens.Jwt library to validate standard parts JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler(); TokenValidationParameters tvp = new TokenValidationParameters(); tvp.ValidateIssuer = false; tvp.ValidateAudience = true; tvp.ValidAudience = expectedAudience; tvp.ValidateIssuerSigningKey = true; tvp.IssuerSigningKeys = GetSigningKeys(); tvp.ValidateLifetime = true; try { var claimsPrincipal = tokenHandler.ValidateToken(RawData, tvp, out SecurityToken validatedToken); // If no exception, all standard checks passed result.LifetimeResult = result.SignatureResult = result.AudienceResult = "passed"; if (string.Compare(result.VersionResult, "passed", StringComparison.InvariantCulture) == 0) { result.IsValid = true; // Compute user ID result.ComputedUserId = GenerateUserId(AppContext.ExchangeUid, AppContext.MetadataUrl); } } catch (SecurityTokenInvalidAudienceException ex) { result.AudienceResult = "failed"; result.Message = ex.Message; } catch (SecurityTokenInvalidLifetimeException ex) { result.LifetimeResult = "failed"; result.Message = ex.Message; } catch (SecurityTokenExpiredException ex) { result.LifetimeResult = "failed"; result.Message = ex.Message; } catch (SecurityTokenInvalidSignatureException ex) { result.SignatureResult = "failed"; result.Message = ex.Message; } catch (SecurityTokenValidationException ex) { result.Message = ex.Message; } return(result); }
private static JwtSecurityToken ValidateIdTokenHint(string idTokenHintString) { JwtSecurityTokenHandler jwtSecurityTokenHandler = new JwtSecurityTokenHandler(); JwtSecurityToken parsedIdTokenHint = jwtSecurityTokenHandler.ReadJwtToken(idTokenHintString); if (parsedIdTokenHint == null) { throw new InvalidDataException("IdToken is not JwtToken."); } if (parsedIdTokenHint.Claims == null || !parsedIdTokenHint.Claims.Any()) { throw new Exception("Empty claims found in id token hint"); } string tenantId = parsedIdTokenHint.Claims.First(c => c.Type == "tid").Value; if (string.IsNullOrWhiteSpace(tenantId)) { throw new InvalidDataException("Missing tenantId claim."); } if (!Settings.Default.AllowedTenantIds.Contains(tenantId)) { throw new InvalidDataException("Invalid tenant id."); } string azureADTenantMetadataUri = string.Format(AzureADMetadataUriFormat, tenantId); OpenIdConnectCachingSecurityTokenProvider oidcTokenProvider = new OpenIdConnectCachingSecurityTokenProvider(azureADTenantMetadataUri); if (!oidcTokenProvider.SecurityKeys.Any()) { throw new Exception("Couldn't retrieve security keys from OIDC metadata endpoint."); } TokenValidationParameters validationParameters = new TokenValidationParameters(); validationParameters.ValidAudience = Settings.Default.AzureADExtensionClientId; validationParameters.ValidIssuer = oidcTokenProvider.Issuer; validationParameters.ClockSkew = TimeSpan.FromMinutes(5); validationParameters.IssuerSigningKeyResolver = (token, securityToken, kid, parameters) => { return(oidcTokenProvider.SecurityKeys.Where(k => k.KeyId == kid)); }; SecurityToken validatedSecurityToken; ClaimsPrincipal claimsPrincipal = jwtSecurityTokenHandler.ValidateToken( idTokenHintString, validationParameters, out validatedSecurityToken); JwtSecurityToken validatedIdTokenHint = validatedSecurityToken as JwtSecurityToken; if (validatedIdTokenHint == null) { throw new Exception("IdToken is not a JwtSecurityToken."); } return(validatedIdTokenHint); }
// SendAsync is used to validate incoming requests contain a valid access token, and sets the current user identity protected override Task <HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { string jwtToken; string issuer; List <X509SecurityToken> signingTokens; if (!TryRetrieveToken(request, out jwtToken)) { return(Task.FromResult <HttpResponseMessage>(new HttpResponseMessage(HttpStatusCode.Unauthorized))); } try { // Get tenant information that's used to validate incoming jwt tokens GetTenantInformation(string.Format("https://login.windows.net/{0}/federationmetadata/2007-06/federationmetadata.xml", domainName), out issuer, out signingTokens); } catch (Exception) { return(Task.FromResult <HttpResponseMessage>(new HttpResponseMessage(HttpStatusCode.InternalServerError))); } JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler() { CertificateValidator = X509CertificateValidator.None }; TokenValidationParameters validationParameters = new TokenValidationParameters { AllowedAudience = audience, ValidIssuer = issuer, SigningTokens = signingTokens }; try { // Validate token ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(jwtToken, validationParameters); //set the ClaimsPrincipal on the current thread. Thread.CurrentPrincipal = claimsPrincipal; // set the ClaimsPrincipal on HttpContext.Current if the app is running in web hosted environment. if (HttpContext.Current != null) { HttpContext.Current.User = claimsPrincipal; } // Verify that required permission is set in the scope claim if (ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/scope").Value != "user_impersonation") { return(Task.FromResult <HttpResponseMessage>(new HttpResponseMessage(HttpStatusCode.Unauthorized))); } return(base.SendAsync(request, cancellationToken)); } catch (SecurityTokenValidationException) { return(Task.FromResult <HttpResponseMessage>(new HttpResponseMessage(HttpStatusCode.Unauthorized))); } catch (Exception) { return(Task.FromResult <HttpResponseMessage>(new HttpResponseMessage(HttpStatusCode.InternalServerError))); } }
private async Task <MetadataBLOBPayload> DeserializeAndValidateBlobAsync(string rawBLOBJwt, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(rawBLOBJwt)) { throw new ArgumentNullException(nameof(rawBLOBJwt)); } var jwtParts = rawBLOBJwt.Split('.'); if (jwtParts.Length != 3) { throw new ArgumentException("The JWT does not have the 3 expected components"); } var blobHeaderString = jwtParts[0]; using var blobHeaderDoc = JsonDocument.Parse(Base64Url.Decode(blobHeaderString)); var blobHeader = blobHeaderDoc.RootElement; string blobAlg = blobHeader.TryGetProperty("alg", out var algEl) ? algEl.GetString() ! : throw new ArgumentNullException("No alg value was present in the BLOB header."); string[] keyStrings = blobHeader.TryGetProperty("x5c", out var x5cEl) && x5cEl.ValueKind is JsonValueKind.Array ? x5cEl.ToStringArray() : throw new ArgumentNullException("No x5c array was present in the BLOB header."); if (keyStrings.Length is 0) { throw new ArgumentException("No keys were present in the BLOB header."); } var rootCert = GetX509Certificate(ROOT_CERT); var blobCerts = new X509Certificate2[keyStrings.Length]; var keys = new SecurityKey[keyStrings.Length]; for (int i = 0; i < blobCerts.Length; i++) { var cert = GetX509Certificate(keyStrings[i]); blobCerts[i] = cert; if (cert.GetECDsaPublicKey() is ECDsa ecdsaPublicKey) { keys[i] = new ECDsaSecurityKey(ecdsaPublicKey); } else if (cert.GetRSAPublicKey() is RSA rsaPublicKey) { keys[i] = new RsaSecurityKey(rsaPublicKey); } else { throw new Fido2MetadataException("Unknown certificate algorithm"); } } var blobPublicKeys = keys.ToArray(); // defensive copy var certChain = new X509Chain(); certChain.ChainPolicy.ExtraStore.Add(rootCert); certChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; var validationParameters = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = false, ValidateIssuerSigningKey = true, IssuerSigningKeys = blobPublicKeys }; var tokenHandler = new JwtSecurityTokenHandler() { // 250k isn't enough bytes for conformance test tool // https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/1097 MaximumTokenSizeInBytes = rawBLOBJwt.Length }; tokenHandler.ValidateToken( rawBLOBJwt, validationParameters, out var validatedToken); if (blobCerts.Length > 1) { certChain.ChainPolicy.ExtraStore.AddRange(blobCerts.Skip(1).ToArray()); } var certChainIsValid = certChain.Build(blobCerts[0]); // if the root is trusted in the context we are running in, valid should be true here if (!certChainIsValid) { foreach (var element in certChain.ChainElements) { if (element.Certificate.Issuer != element.Certificate.Subject) { var cdp = CryptoUtils.CDPFromCertificateExts(element.Certificate.Extensions); var crlFile = await DownloadDataAsync(cdp, cancellationToken); if (CryptoUtils.IsCertInCRL(crlFile, element.Certificate)) { throw new Fido2VerificationException($"Cert {element.Certificate.Subject} found in CRL {cdp}"); } } } // otherwise we have to manually validate that the root in the chain we are testing is the root we downloaded if (rootCert.Thumbprint == certChain.ChainElements[^ 1].Certificate.Thumbprint && // and that the number of elements in the chain accounts for what was in x5c plus the root we added certChain.ChainElements.Count == (keyStrings.Length + 1) && // and that the root cert has exactly one status listed against it certChain.ChainElements[^ 1].ChainElementStatus.Length == 1 && // and that that status is a status of exactly UntrustedRoot certChain.ChainElements[^ 1].ChainElementStatus[0].Status == X509ChainStatusFlags.UntrustedRoot) { // if we are good so far, that is a good sign certChainIsValid = true; for (var i = 0; i < certChain.ChainElements.Count - 1; i++) { // check each non-root cert to verify zero status listed against it, otherwise, invalidate chain if (0 != certChain.ChainElements[i].ChainElementStatus.Length) { certChainIsValid = false; } } } } if (!certChainIsValid) { throw new Fido2VerificationException("Failed to validate cert chain while parsing BLOB"); } var blobPayload = ((JwtSecurityToken)validatedToken).Payload.SerializeToJson(); var blob = JsonSerializer.Deserialize <MetadataBLOBPayload>(blobPayload) !; blob.JwtAlg = blobAlg; return(blob); }
/// <summary> /// Handle the LTI POST request from the Authorization Server. /// </summary> /// <returns></returns> public async Task <IActionResult> OnPostAsync( string platformId, [FromForm(Name = "id_token")] string idToken, [FromForm(Name = "scope")] string scope = null, [FromForm(Name = "state")] string state = null, [FromForm(Name = "session_state")] string sessionState = null) { // Authenticate the request starting at step 5 in the OpenId Implicit Flow // See https://www.imsglobal.org/spec/security/v1p0/#platform-originating-messages // See https://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowSteps // The Platform MUST send the id_token via the OAuth 2 Form Post // See https://www.imsglobal.org/spec/security/v1p0/#successful-authentication // See http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html if (string.IsNullOrEmpty(idToken)) { Error = "id_token is missing or empty"; return(Page()); } var handler = new JwtSecurityTokenHandler(); if (!handler.CanReadToken(idToken)) { Error = "Cannot read id_token"; return(Page()); } var jwt = handler.ReadJwtToken(idToken); JwtHeader = jwt.Header; var messageType = jwt.Claims.SingleOrDefault(c => c.Type == Constants.LtiClaims.MessageType)?.Value; if (messageType.IsMissing()) { Error = $"{Constants.LtiClaims.MessageType} claim is missing."; return(Page()); } // Authentication Response Validation // See https://www.imsglobal.org/spec/security/v1p0/#authentication-response-validation // The ID Token MUST contain a nonce Claim. var nonce = jwt.Claims.SingleOrDefault(c => c.Type == "nonce")?.Value; if (string.IsNullOrEmpty(nonce)) { Error = "Nonce is missing from request."; return(Page()); } // If the launch was initiated with a 3rd party login, then there will be a state // entry for the nonce. var memorizedState = _stateContext.GetState(nonce); if (memorizedState == null) { Error = "Invalid nonce. Possible request replay."; return(Page()); } // The state should be echoed back by the AS without modification if (memorizedState.Value != state) { Error = "Invalid state."; return(Page()); } // Look for the platform with platformId in the redirect URI var platform = await _context.GetPlatformByPlatformId(platformId); if (platform == null) { Error = "Unknown platform."; return(Page()); } // Using the JwtSecurityTokenHandler.ValidateToken method, validate four things: // // 1. The Issuer Identifier for the Platform MUST exactly match the value of the iss // (Issuer) Claim (therefore the Tool MUST previously have been made aware of this // identifier. // 2. The Tool MUST Validate the signature of the ID Token according to JSON Web Signature // RFC 7515, Section 5; using the Public Key for the Platform which collected offline. // 3. The Tool MUST validate that the aud (audience) Claim contains its client_id value // registered as an audience with the Issuer identified by the iss (Issuer) Claim. The // aud (audience) Claim MAY contain an array with more than one element. The Tool MUST // reject the ID Token if it does not list the client_id as a valid audience, or if it // contains additional audiences not trusted by the Tool. // 4. The current time MUST be before the time represented by the exp Claim; RSAParameters rsaParameters; try { var httpClient = _httpClientFactory.CreateClient(); var keySetJson = await httpClient.GetStringAsync(platform.JwkSetUrl); var keySet = JsonConvert.DeserializeObject <JsonWebKeySet>(keySetJson); var key = keySet.Keys.SingleOrDefault(k => k.Kid == jwt.Header.Kid); if (key == null) { Error = "No matching key found."; return(Page()); } rsaParameters = new RSAParameters { Modulus = Base64UrlEncoder.DecodeBytes(key.N), Exponent = Base64UrlEncoder.DecodeBytes(key.E) }; } catch (Exception e) { Error = e.Message; return(Page()); } var validationParameters = new TokenValidationParameters { ValidateTokenReplay = true, ValidateAudience = true, ValidateIssuer = true, RequireSignedTokens = true, ValidateIssuerSigningKey = true, ValidAudience = Request.GetUri().GetLeftPart(UriPartial.Authority), ValidIssuer = platform.Issuer, IssuerSigningKey = new RsaSecurityKey(rsaParameters), ValidateLifetime = true, ClockSkew = TimeSpan.FromMinutes(5.0) }; try { handler.ValidateToken(idToken, validationParameters, out _); } catch (Exception e) { Error = e.Message; return(Page()); } if (messageType == Constants.Lti.LtiDeepLinkingRequestMessageType) { return(Post("/Catalog", new { idToken })); } IdToken = idToken; LtiRequest = new LtiResourceLinkRequest(jwt.Payload); if (LtiRequest.ResourceLink.Id.Contains("YT=")) { return(Post("/ResourcePresenters/YoutubePresenter", new { LtiRequest = JsonConvert.SerializeObject(LtiRequest) })); } else if (LtiRequest.ResourceLink.Id.Contains("VI=")) { return(Post("/ResourcePresenters/VimeoPresenter", new { LtiRequest = JsonConvert.SerializeObject(LtiRequest) })); } else if (LtiRequest.Custom.ContainsKey("videoId")) { var video = _context.Videos.FirstOrDefault(v => v.Id == int.Parse(LtiRequest.Custom["videoId"])); if (video != null && video.VideoType == VideoType.Youtube) { return(Post("/ResourcePresenters/YoutubePresenter", new { LtiRequest = JsonConvert.SerializeObject(LtiRequest) })); } else if (video != null && video.VideoType == VideoType.Vimeo) { return(Post("/ResourcePresenters/VimeoPresenter", new { LtiRequest = JsonConvert.SerializeObject(LtiRequest) })); } } return(Page()); }
/// <summary> /// Validates the token /// </summary> /// <param name="expectedAudience">The valid audience value to check</param> /// <returns></returns> public async Task <SsoTokenValidationResult> Validate(string expectedAudience) { SsoTokenValidationResult result = new SsoTokenValidationResult(); // Since add-in SSO tokens are issued by Azure, we can use the // well-known OpenID config to get signing keys string openIdConfig = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration"; ConfigurationManager <OpenIdConnectConfiguration> configManager = new ConfigurationManager <OpenIdConnectConfiguration>(openIdConfig, new OpenIdConnectConfigurationRetriever()); OpenIdConnectConfiguration config = await configManager.GetConfigurationAsync(); // Issuer will always be Azure, but it will contain the tenant ID of the // Office 365 organization the user belongs to. We can get that from the "tid" claim var tenantIdClaim = Claims.FirstOrDefault(claim => claim.Type == "tid"); if (tenantIdClaim == null) { result.Message = "Token is malformed, missing tid claim."; return(result); } var oidClaim = Claims.FirstOrDefault(claim => claim.Type == "oid"); if (oidClaim == null) { result.Message = "Token is malformed, missing oid claim."; return(result); } string expectedIssuer = string.Format("https://login.microsoftonline.com/{0}/v2.0", tenantIdClaim.Value); // Use System.IdentityModel.Tokens.Jwt library to validate the token JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler(); TokenValidationParameters tvp = new TokenValidationParameters(); tvp.ValidateIssuer = true; tvp.ValidIssuer = expectedIssuer; tvp.ValidateAudience = true; tvp.ValidAudience = expectedAudience; tvp.ValidateIssuerSigningKey = true; tvp.IssuerSigningKeys = config.SigningKeys as IEnumerable <SecurityKey>; tvp.ValidateLifetime = true; try { var claimsPrincipal = tokenHandler.ValidateToken(RawData, tvp, out SecurityToken validatedToken); // If no exception, all standard checks passed result.IsValid = true; result.LifetimeResult = result.SignatureResult = result.AudienceResult = result.IssuerResult = "passed"; result.ComputedUserId = GenerateUserId(oidClaim.Value, tenantIdClaim.Value); } catch (SecurityTokenInvalidAudienceException ex) { result.AudienceResult = "failed"; result.Message = ex.Message; } catch (SecurityTokenInvalidIssuerException ex) { result.IssuerResult = "failed"; result.Message = ex.Message; } catch (SecurityTokenInvalidLifetimeException ex) { result.LifetimeResult = "failed"; result.Message = ex.Message; } catch (SecurityTokenExpiredException ex) { result.LifetimeResult = "failed"; result.Message = ex.Message; } catch (SecurityTokenInvalidSignatureException ex) { result.SignatureResult = "failed"; result.Message = ex.Message; } catch (SecurityTokenValidationException ex) { result.Message = ex.Message; } return(result); }
/// <summary> /// Validates the request asynchronously. /// This method will be called any time we have an incoming request. /// Returning invalid result will trigger a Forbidden response. /// </summary> /// <param name="request">The request.</param> /// <returns> /// The <see cref="RequestValidationResult" /> structure. /// </returns> public async Task <RequestValidationResult> ValidateInboundRequestAsync(HttpRequestMessage request) { var token = request?.Headers?.Authorization?.Parameter; if (string.IsNullOrWhiteSpace(token)) { return(new RequestValidationResult { IsValid = false }); } // Currently the service does not sign outbound request using AAD, instead it is signed // with a private certificate. In order for us to be able to ensure the certificate is // valid we need to download the corresponding public keys from a trusted source. const string authDomain = "https://api.aps.skype.com/v1/.well-known/OpenIdConfiguration"; if (this.openIdConfiguration == null || DateTime.Now > this.prevOpenIdConfigUpdateTimestamp.Add(this.openIdConfigRefreshInterval)) { this.graphLogger.Info("Updating OpenID configuration"); // Download the OIDC configuration which contains the JWKS IConfigurationManager <OpenIdConnectConfiguration> configurationManager = new ConfigurationManager <OpenIdConnectConfiguration>( authDomain, new OpenIdConnectConfigurationRetriever()); this.openIdConfiguration = await configurationManager.GetConfigurationAsync(CancellationToken.None).ConfigureAwait(false); this.prevOpenIdConfigUpdateTimestamp = DateTime.Now; } // The incoming token should be issued by graph. var authIssuers = new[] { "https://graph.microsoft.com", "https://api.botframework.com", }; // Configure the TokenValidationParameters. // Aet the Issuer(s) and Audience(s) to validate and // assign the SigningKeys which were downloaded from AuthDomain. TokenValidationParameters validationParameters = new TokenValidationParameters { ValidIssuers = authIssuers, ValidAudience = this.appId, IssuerSigningKeys = this.openIdConfiguration.SigningKeys, }; ClaimsPrincipal claimsPrincipal; try { // Now validate the token. If the token is not valid for any reason, an exception will be thrown by the method JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler(); claimsPrincipal = handler.ValidateToken(token, validationParameters, out _); } // Token expired... should somehow return 401 (Unauthorized) // catch (SecurityTokenExpiredException ex) // Tampered token // catch (SecurityTokenInvalidSignatureException ex) // Some other validation error // catch (SecurityTokenValidationException ex) catch (Exception ex) { // Some other error this.graphLogger.Error(ex, $"Failed to validate token for client: {this.appId}."); return(new RequestValidationResult() { IsValid = false }); } const string ClaimType = "http://schemas.microsoft.com/identity/claims/tenantid"; var tenantClaim = claimsPrincipal.FindFirst(claim => claim.Type.Equals(ClaimType, StringComparison.Ordinal)); if (string.IsNullOrEmpty(tenantClaim?.Value)) { // No tenant claim given to us. reject the request. return(new RequestValidationResult { IsValid = false }); } return(new RequestValidationResult { IsValid = true, TenantId = tenantClaim.Value }); }
protected override Task <AuthenticateResult> HandleAuthenticateAsync() { try { if (Request.Path.HasValue && Request.Path.Value.ToUpper().Contains("/PUBLIC/")) { return(Task.FromResult(AuthenticateResult.NoResult())); } //Token de autenticação var tokenString = Request.Headers["Authorization"].ToString(); if (tokenString.Contains("Bearer")) { //Manipulador do token JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler(); ///Remove a string inválida tokenString = tokenString.Replace("Bearer", "").Trim(); ///Parametros de validação var validationParameters = new TokenValidationParameters() { //Emissor de confiança ValidIssuer = ServiceProvider.GetService <IConfiguration>().GetSection("Issuer").Value, //Chave privada simétrica para validação IssuerSigningKey = new SymmetricSecurityKey(Encoding.Default.GetBytes(ServiceProvider.GetService <IConfiguration>().GetSection("AppSecretKey").Value)), //Não valida a origem ValidateAudience = false, //Delegate que valida a assinatura do token SignatureValidator = delegate(string _token, TokenValidationParameters parameters) { //Pega a chave privada do app var clientSecret = ServiceProvider.GetService <IConfiguration>().GetSection("AppSecretKey").Value; //Inicia o objeto Jwt var jwt = new JwtSecurityToken(_token); //Calcula o hash da chave privada var hmac = new HMACSHA256(Encoding.Default.GetBytes(clientSecret)); //Obtem as credenciais var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(hmac.Key), SecurityAlgorithms.HmacSha256Signature, SecurityAlgorithms.Sha256Digest); //Obtem a classe com os dados da assinatura var signKey = signingCredentials.Key as SymmetricSecurityKey; //Token JWT var encodedData = jwt.EncodedHeader + "." + jwt.EncodedPayload; //Gera uma assinatura var compiledSignature = Encode(encodedData, signKey.Key); //Validar a assinatura jwt do cabeçalho e compara com a do token if (compiledSignature != jwt.RawSignature) { throw new Exception("Token signature validation failed."); } return(jwt); } }; ///Valida o token tokenHandler.ValidateToken(tokenString, validationParameters, out SecurityToken validatedToken); // if (validatedToken != null) { //Criamos uma identidade ClaimsIdentity claimsIdentity = new ClaimsIdentity(new JwtSecurityToken(tokenString).Claims) { Label = "Principal" }; //Cria o ticket de autenticação var ticket = new AuthenticationTicket(new ClaimsPrincipal(claimsIdentity), Scheme.Name); return(Task.FromResult(AuthenticateResult.Success(ticket))); } } //Por padrão aborta a requisição... Request.HttpContext.Abort(); return(null); } catch { Request.HttpContext.Abort(); return(null); } }
public static async Task <ClaimsPrincipal> ValidateAsync(HttpRequest req, ILogger log) { log.LogInformation("Entering auth..."); string authorizationHeader = req.Headers["Authorization"]; if (string.IsNullOrEmpty(authorizationHeader)) { return(null); } if (!authorizationHeader.StartsWith("Bearer ")) { return(null); } string bearerToken = authorizationHeader.Substring("Bearer ".Length); var config = await ConfigurationManager.GetConfigurationAsync(CancellationToken.None); var audience = Environment.GetEnvironmentVariable("audience"); var validationParameter = new TokenValidationParameters() { RequireSignedTokens = true, ValidAudience = audience, ValidateAudience = true, ValidIssuer = config.Issuer, ValidateIssuer = true, ValidateIssuerSigningKey = true, ValidateLifetime = true, IssuerSigningKeys = config.SigningKeys }; ClaimsPrincipal result = null; var tries = 0; while (result == null && tries <= 1) { try { log.LogInformation("Trying to validate claims..."); var handler = new JwtSecurityTokenHandler(); result = handler.ValidateToken(bearerToken, validationParameter, out SecurityToken _); if (result != null) { log.LogInformation($"Result: {result.ToString()}"); } else { log.LogInformation("Null result"); } } catch (SecurityTokenSignatureKeyNotFoundException ex) { log.LogInformation($"Security token signature key not found exception: {ex.Message}"); log.LogInformation($"Inner exception: {ex.InnerException}"); // This exception is thrown if the signature key of the JWT could not be found. // This could be the case when the issuer changed its signing keys, so we trigger a // refresh and retry validation. ConfigurationManager.RequestRefresh(); tries++; } catch (SecurityTokenException) { log.LogInformation("Security token exception"); return(null); } } return(result); }
/// <summary> /// Processes the request based on the path. /// </summary> /// <param name="context">Contains the request and response.</param> public void ProcessRequest(HttpContext context) { // Get the code from the request POST body. string accessToken = context.Request.Params["access_token"]; string idToken = context.Request.Params["id_token"]; // Validate the ID token if (idToken != null) { JwtSecurityToken token = new JwtSecurityToken(idToken); JwtSecurityTokenHandler jsth = new JwtSecurityTokenHandler(); // Configure validation Byte[][] certBytes = getCertBytes(); Dictionary <String, X509Certificate2> certificates = new Dictionary <String, X509Certificate2>(); for (int i = 0; i < certBytes.Length; i++) { X509Certificate2 certificate = new X509Certificate2(certBytes[i]); certificates.Add(certificate.Thumbprint, certificate); } { // Set up token validation TokenValidationParameters tvp = new TokenValidationParameters() { ValidateActor = false, // check the profile ID ValidateAudience = (CLIENT_ID != "YOUR_VALID_CLIENT_ID"), // check the client ID ValidAudience = CLIENT_ID, ValidateIssuer = true, // check token came from Google ValidIssuers = new List <string> { "accounts.google.com", "https://accounts.google.com" }, ValidateIssuerSigningKey = true, RequireSignedTokens = true, CertificateValidator = X509CertificateValidator.None, IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) => { return(identifier.Select(x => { // TODO: Consider returning null here if you have case sensitive JWTs. /*if (!certificates.ContainsKey(x.Id)) * { * return new X509SecurityKey(certificates[x.Id]); * }*/ if (certificates.ContainsKey(x.Id.ToUpper())) { return new X509SecurityKey(certificates[x.Id.ToUpper()]); } return null; }).First(x => x != null)); }, ValidateLifetime = true, RequireExpirationTime = true, ClockSkew = TimeSpan.FromHours(13) }; try { // Validate using the provider SecurityToken validatedToken; ClaimsPrincipal cp = jsth.ValidateToken(idToken, tvp, out validatedToken); if (cp != null) { its.valid = true; its.message = "Valid ID Token."; } } catch (Exception e) { // Multiple certificates are tested. if (its.valid != true) { its.message = "Invalid ID Token."; } if (e.Message.IndexOf("The token is expired") > 0) { // TODO: Check current time in the exception for clock skew. } } } // Get the Google+ id for this user from the "sub" claim. Claim[] claims = token.Claims.ToArray <Claim>(); for (int i = 0; i < claims.Length; i++) { if (claims[i].Type.Equals("sub")) { its.gplus_id = claims[i].Value; } } } // Use Tokeninfo to validate the user and the client. var tokeninfo_request = new Oauth2Service().Tokeninfo(); tokeninfo_request.AccessToken = accessToken; // Use Google as a trusted provider to validate the token. // Invalid values, including expired tokens, return 400 Tokeninfo tokeninfo = null; try { tokeninfo = tokeninfo_request.Execute(); if (tokeninfo.IssuedTo != CLIENT_ID) { ats.message = "Access Token not meant for this app."; } else { ats.valid = true; ats.message = "Valid Access Token."; ats.gplus_id = tokeninfo.UserId; } } catch (Exception stve) { ats.message = "Invalid Access Token: " + stve.Message; } // Use the wrapper to return JSON token_status_wrapper tsr = new token_status_wrapper(); tsr.id_token_status = its; tsr.access_token_status = ats; context.Response.StatusCode = 200; context.Response.ContentType = "text/json"; context.Response.Write(JsonConvert.SerializeObject(tsr)); }
private ClaimsPrincipal ValidateToken(string token) { var securityTokenHandler = new JwtSecurityTokenHandler(); return(securityTokenHandler.ValidateToken(token, JwtTokenHandler.GetTokenValidationParameters(), out _)); }
protected async Task <MetadataTOCPayload> DeserializeAndValidateToc(string toc) { if (string.IsNullOrWhiteSpace(toc)) { throw new ArgumentNullException(nameof(toc)); } var jwtParts = toc.Split('.'); if (jwtParts.Length != 3) { throw new ArgumentException("The JWT does not have the 3 expected components"); } var tocHeaderString = jwtParts.First(); var tocHeader = JObject.Parse(Encoding.UTF8.GetString(Base64Url.Decode(tocHeaderString))); _tocAlg = tocHeader["alg"]?.Value <string>(); if (_tocAlg == null) { throw new ArgumentNullException("No alg value was present in the TOC header."); } var x5cArray = tocHeader["x5c"] as JArray; if (x5cArray == null) { throw new Exception("No x5c array was present in the TOC header."); } var keyStrings = x5cArray.Values <string>().ToList(); if (keyStrings.Count == 0) { throw new ArgumentException("No keys were present in the TOC header."); } var rootCert = GetX509Certificate(ROOT_CERT); var tocCerts = keyStrings.Select(o => GetX509Certificate(o)).ToArray(); var tocPublicKeys = keyStrings.Select(o => GetECDsaPublicKey(o)).ToArray(); var certChain = new X509Chain(); certChain.ChainPolicy.ExtraStore.Add(rootCert); certChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; var validationParameters = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = false, ValidateIssuerSigningKey = true, IssuerSigningKeys = tocPublicKeys, }; var tokenHandler = new JwtSecurityTokenHandler(); tokenHandler.ValidateToken( toc, validationParameters, out var validatedToken); if (tocCerts.Length > 1) { certChain.ChainPolicy.ExtraStore.AddRange(tocCerts.Skip(1).ToArray()); } var certChainIsValid = certChain.Build(tocCerts.First()); // if the root is trusted in the context we are running in, valid should be true here if (!certChainIsValid) { foreach (var element in certChain.ChainElements) { if (element.Certificate.Issuer != element.Certificate.Subject) { var cdp = CryptoUtils.CDPFromCertificateExts(element.Certificate.Extensions); var crlFile = await DownloadDataAsync(cdp); if (true == CryptoUtils.IsCertInCRL(crlFile, element.Certificate)) { throw new VerificationException(string.Format("Cert {0} found in CRL {1}", element.Certificate.Subject, cdp)); } } } // otherwise we have to manually validate that the root in the chain we are testing is the root we downloaded if (rootCert.Thumbprint == certChain.ChainElements[certChain.ChainElements.Count - 1].Certificate.Thumbprint && // and that the number of elements in the chain accounts for what was in x5c plus the root we added certChain.ChainElements.Count == (keyStrings.Count + 1) && // and that the root cert has exactly one status listed against it certChain.ChainElements[certChain.ChainElements.Count - 1].ChainElementStatus.Length == 1 && // and that that status is a status of exactly UntrustedRoot certChain.ChainElements[certChain.ChainElements.Count - 1].ChainElementStatus[0].Status == X509ChainStatusFlags.UntrustedRoot) { // if we are good so far, that is a good sign certChainIsValid = true; for (var i = 0; i < certChain.ChainElements.Count - 1; i++) { // check each non-root cert to verify zero status listed against it, otherwise, invalidate chain if (0 != certChain.ChainElements[i].ChainElementStatus.Length) { certChainIsValid = false; } } } } if (!certChainIsValid) { throw new VerificationException("Failed to validate cert chain while parsing TOC"); } var tocPayload = ((JwtSecurityToken)validatedToken).Payload.SerializeToJson(); return(Newtonsoft.Json.JsonConvert.DeserializeObject <MetadataTOCPayload>(tocPayload)); }
public Task <JwtRequestValidationResult> ValidateAsync(Client client, string jwtTokenString) { if (client == null) { throw new ArgumentNullException(nameof(client)); } if (String.IsNullOrWhiteSpace(jwtTokenString)) { throw new ArgumentNullException(nameof(jwtTokenString)); } var fail = Task.FromResult(new JwtRequestValidationResult { IsError = true }); var enumeratedSecrets = client.ClientSecrets.ToList().AsReadOnly(); List <SecurityKey> trustedKeys; try { trustedKeys = GetKeys(enumeratedSecrets); } catch (Exception e) { _logger.LogError(e, "Could not parse client secrets"); return(fail); } if (!trustedKeys.Any()) { _logger.LogError("There are no keys available to validate JWT."); return(fail); } var tokenValidationParameters = new TokenValidationParameters { IssuerSigningKeys = trustedKeys, ValidateIssuerSigningKey = true, ValidIssuer = client.ClientId, ValidateIssuer = true, ValidAudience = _audienceUri, ValidateAudience = true, RequireSignedTokens = true, RequireExpirationTime = true }; try { var handler = new JwtSecurityTokenHandler(); handler.ValidateToken(jwtTokenString, tokenValidationParameters, out var token); var jwtSecurityToken = (JwtSecurityToken)token; // todo: IdentityModel update if (jwtSecurityToken.Payload.ContainsKey("request") || jwtSecurityToken.Payload.ContainsKey("request_uri")) { _logger.LogError("JWT payload must not contain request or request_uri"); return(fail); } // filter JWT validation values var payload = new Dictionary <string, string>(); foreach (var key in jwtSecurityToken.Payload.Keys) { if (!Constants.Filters.JwtRequestClaimTypesFilter.Contains(key)) { var value = jwtSecurityToken.Payload[key]; if (value is string s) { payload.Add(key, s); } else if (value is JObject jobj) { payload.Add(key, jobj.ToString(Formatting.None)); } else if (value is JArray jarr) { payload.Add(key, jarr.ToString(Formatting.None)); } } } var result = new JwtRequestValidationResult { IsError = false, Payload = payload }; _logger.LogDebug("JWT request object validation success."); return(Task.FromResult(result)); } catch (Exception e) { _logger.LogError(e, "JWT token validation error"); return(fail); } }
public void JwtSecurityTokenHandlerValidateToken() { _jwtSecurityTokenHandler.ValidateToken(_jwtToken, _tokenValidationParameters, out _); }
public long? ValidateToken(string accessToken) { var handler = new JwtSecurityTokenHandler(); if (!handler.CanValidateToken) return null; var signedCredentials = this.options.SigningCredentials ?? TokenProvider.DefaultSigningCredentials(); var validationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = this.options.Issuer, ValidateAudience = true, ValidAudience = this.options.Audience, ValidateLifetime = true, IssuerSigningKey = signedCredentials.Key }; long? result = null; try { SecurityToken validatedToken; var claimsPrincipal = handler.ValidateToken(accessToken, validationParameters, out validatedToken); foreach (var claim in claimsPrincipal.Claims) { if (claim.Type != "id") continue; long id; if (long.TryParse(claim.Value, out id)) result = id; break; } } catch(Exception e) { InColUn.Logger.Instance.Log(InColUn.LogLevel.Exception, string.Format("Token validation exception for: {0}", accessToken), e); } return result; }
private async Task<TokenValidationResult> ValidateJwtAsync(string jwt, string audience, IEnumerable<SymmetricSecurityKey> symmetricKeys, bool validateLifetime = true) { var handler = new JwtSecurityTokenHandler(); handler.InboundClaimTypeMap.Clear(); var parameters = new TokenValidationParameters { ValidIssuer = _context.GetIssuerUri(), IssuerSigningKeys = symmetricKeys, ValidateLifetime = validateLifetime, ValidAudience = audience }; try { SecurityToken jwtToken; var id = handler.ValidateToken(jwt, parameters, out jwtToken); // load the client that belongs to the client_id claim Client client = null; var clientId = id.FindFirst(JwtClaimTypes.ClientId); if (clientId != null) { client = await _clients.FindClientByIdAsync(clientId.Value); if (client == null) { throw new InvalidOperationException("Client does not exist anymore."); } } return new TokenValidationResult { IsError = false, Claims = id.Claims, Client = client, Jwt = jwt }; } catch (Exception ex) { _logger.LogError("JWT token validation error", ex); return Invalid(OidcConstants.ProtectedResourceErrors.InvalidToken); } }