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
            });
        }
Esempio n. 3
0
        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)));
        }
Esempio n. 4
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));
            }
        }
Esempio n. 5
0
        /// <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);
        }
Esempio n. 6
0
 public ClaimsPrincipal ValidateToken(string token)
 {
     return(mJwtSecurityTokenHandler.ValidateToken(token, mTokenValidation, out var securityToken));
 }
Esempio n. 7
0
        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);
        }
Esempio n. 9
0
        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);
        }
Esempio n. 10
0
        public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
        {
            var principal = _securityTokenHandler.ValidateToken(securityToken, validationParameters, out validatedToken);

            return(principal);
        }
Esempio n. 11
0
        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");
            }
        }
Esempio n. 12
0
        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);
        }
Esempio n. 13
0
        /// <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);
            }
        }
Esempio n. 16
0
        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);
        }
Esempio n. 19
0
        // 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)));
            }
        }
Esempio n. 20
0
        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);
        }
Esempio n. 21
0
        /// <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);
        }
Esempio n. 23
0
        /// <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
            });
        }
Esempio n. 24
0
        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);
            }
        }
Esempio n. 25
0
        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);
        }
Esempio n. 26
0
        /// <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));
        }
Esempio n. 27
0
        private ClaimsPrincipal ValidateToken(string token)
        {
            var securityTokenHandler = new JwtSecurityTokenHandler();

            return(securityTokenHandler.ValidateToken(token, JwtTokenHandler.GetTokenValidationParameters(), out _));
        }
Esempio n. 28
0
        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);
            }
        }
Esempio n. 30
0
 public void JwtSecurityTokenHandlerValidateToken()
 {
     _jwtSecurityTokenHandler.ValidateToken(_jwtToken, _tokenValidationParameters, out _);
 }
Esempio n. 31
0
        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);
            }
        }