/// <summary>
        /// Tries to find a secret on the environment that can be used for authentication
        /// </summary>
        /// <param name="environment">The environment.</param>
        /// <returns>
        /// A parsed secret
        /// </returns>
        public async Task<ParsedSecret> ParseAsync(IDictionary<string, object> environment)
        {
            Logger.Debug("Start parsing for secret in post body");

            var context = new OwinContext(environment);
            var body = await context.ReadRequestFormAsync();

            if (body != null)
            {
                var id = body.Get("client_id");
                var secret = body.Get("client_secret");

                if (id.IsPresent() && secret.IsPresent())
                {
                    var parsedSecret = new ParsedSecret
                    {
                        Id = id,
                        Credential = secret,
                        Type = Constants.ParsedSecretTypes.SharedSecret
                    };

                    return parsedSecret;
                }
            }

            Logger.Debug("No secet in post body found");
            return null;
        }
        /// <summary>
        /// Validates a secret
        /// </summary>
        /// <param name="secrets">The stored secrets.</param>
        /// <param name="parsedSecret">The received secret.</param>
        /// <returns>
        /// A validation result
        /// </returns>
        /// <exception cref="System.ArgumentException">ParsedSecret.Credential is not an X509 Certificate</exception>
        public Task<SecretValidationResult> ValidateAsync(IEnumerable<Secret> secrets, ParsedSecret parsedSecret)
        {
            var fail = Task.FromResult(new SecretValidationResult { Success = false });
            var success = Task.FromResult(new SecretValidationResult { Success = true });

            if (parsedSecret.Type != Constants.ParsedSecretTypes.X509Certificate)
            {
                return fail;
            }

            var cert = parsedSecret.Credential as X509Certificate2;

            if (cert == null)
            {
                throw new ArgumentException("ParsedSecret.Credential is not an X509 Certificate");
            }

            var thumbprint = cert.Thumbprint;

            foreach (var secret in secrets)
            {
                if (secret.Type == Constants.SecretTypes.X509CertificateThumbprint)
                {
                    if (TimeConstantComparer.IsEqual(thumbprint.ToLowerInvariant(), secret.Value.ToLowerInvariant()))
                    {
                        return success;
                    }
                }
            }

            return fail;
        }
        public async Task Valid_Multiple_Secrets_No_Protection()
        {
            var clientId = "multiple_secrets_no_protection";
            var client = await _clients.FindClientByIdAsync(clientId);

            var secret = new ParsedSecret
            {
                Id = clientId,
                Credential = "secret",
                Type = Constants.ParsedSecretTypes.SharedSecret
            };

            var result = await _validator.ValidateAsync(client.ClientSecrets, secret);
            result.Success.Should().BeTrue();

            secret.Credential = "foobar";
            result = await _validator.ValidateAsync(client.ClientSecrets, secret);
            result.Success.Should().BeTrue();

            secret.Credential = "quux";
            result = await _validator.ValidateAsync(client.ClientSecrets, secret);
            result.Success.Should().BeTrue();

            secret.Credential = "notexpired";
            result = await _validator.ValidateAsync(client.ClientSecrets, secret);
            result.Success.Should().BeTrue();
        }
예제 #4
0
        public async Task<SecretValidationResult> ValidateAsync(ParsedSecret parsedSecret, IEnumerable<Secret> secrets)
        {
            var expiredSecrets = secrets.Where(s => s.Expiration.HasExpired());
            if (expiredSecrets.Any())
            {
                expiredSecrets.ToList().ForEach(
                    ex => Logger.InfoFormat("Secret [{0}] is expired", ex.Description ?? "no description"));
            }

            var currentSecrets = secrets.Where(s => !s.Expiration.HasExpired());

            // see if a registered validator can validate the secret
            foreach (var validator in _validators)
            {
                var secretValidationResult = await validator.ValidateAsync(currentSecrets, parsedSecret);

                if (secretValidationResult.Success)
                {
                    Logger.DebugFormat("Secret validator success: {0}", validator.GetType().Name);
                    return secretValidationResult;
                }
            }

            Logger.Info("Secret validators could not validate secret");
            return new SecretValidationResult { Success = false };
        }
        public async Task Invalid_Certificate_Thumbprint()
        {
            var clientId = "certificate_invalid";
            var client = await _clients.FindClientByIdAsync(clientId);

            var secret = new ParsedSecret
            {
                Id = clientId,
                Credential = TestCert.Load(),
                Type = Constants.ParsedSecretTypes.X509Certificate
            };

            var result = await _thumbprintValidator.ValidateAsync(client.ClientSecrets, secret);

            result.Success.Should().BeFalse();
        }
        public async Task Null_Certificate_Should_Throw()
        {
            var clientId = "certificate_valid";
            var client = await _clients.FindClientByIdAsync(clientId);

            var secret = new ParsedSecret
            {
                Id = clientId,
                Credential = null,
                Type = Constants.ParsedSecretTypes.X509Certificate
            };

            Func<Task> act = () => _thumbprintValidator.ValidateAsync(client.ClientSecrets, secret);

            act.ShouldThrow<ArgumentException>();
        }
        public async Task Valid_Single_Secret()
        {
            var clientId = "single_secret_no_protection_no_expiration";
            var client = await _clients.FindClientByIdAsync(clientId);

            var secret = new ParsedSecret
            {
                Id = clientId,
                Credential = "secret",
                Type = Constants.ParsedSecretTypes.SharedSecret
            };

            var result = await _validator.ValidateAsync(client.ClientSecrets, secret);

            result.Success.Should().BeTrue();
        }
        public async Task Invalid_Credential_Type()
        {
            var clientId = "single_secret_no_protection_no_expiration";
            var client = await _clients.FindClientByIdAsync(clientId);

            var secret = new ParsedSecret
            {
                Id = clientId,
                Credential = "secret",
                Type = "invalid"
            };

            var result = await _validator.ValidateAsync(client.ClientSecrets, secret);

            result.Success.Should().BeFalse();
        }
        /// <summary>
        /// Validates a secret
        /// </summary>
        /// <param name="secrets">The stored secrets.</param>
        /// <param name="parsedSecret">The received secret.</param>
        /// <returns>
        /// A validation result
        /// </returns>
        /// <exception cref="System.ArgumentException">id or credential is missing.</exception>
        public Task<SecretValidationResult> ValidateAsync(IEnumerable<Secret> secrets, ParsedSecret parsedSecret)
        {
            var fail = Task.FromResult(new SecretValidationResult { Success = false });
            var success = Task.FromResult(new SecretValidationResult { Success = true });

            if (parsedSecret.Type != Constants.ParsedSecretTypes.SharedSecret)
            {
                Logger.Debug(string.Format("Parsed secret should not be of type: {0}", parsedSecret.Type ?? "null"));
                return fail;
            }

            var sharedSecret = parsedSecret.Credential as string;

            if (parsedSecret.Id.IsMissing() || sharedSecret.IsMissing())
            {
                throw new ArgumentException("Id or Credential is missing.");
            }

            foreach (var secret in secrets)
            {
                var secretDescription = string.IsNullOrEmpty(secret.Description) ? "no description" : secret.Description;

                // this validator is only applicable to shared secrets
                if (secret.Type != Constants.SecretTypes.SharedSecret)
                {
                    Logger.Debug(string.Format("Skipping secret: {0}, secret is not of type {1}.", secretDescription, Constants.SecretTypes.SharedSecret));
                    continue;
                }

                // use time constant string comparison
                var isValid = TimeConstantComparer.IsEqual(sharedSecret, secret.Value);

                if (isValid)
                {
                    return success;
                }
            }

            Logger.Debug("No matching plain text secret found.");
            return fail;
        }
        /// <summary>
        /// Tries to find a JWT client assertion token on the environment that can be used for authentication
        /// Used for "private_key_jwt" client authentication method as defined in http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication
        /// </summary>
        /// <param name="environment">The environment.</param>
        /// <returns>
        /// A parsed secret
        /// </returns>
        public async Task<ParsedSecret> ParseAsync(IDictionary<string, object> environment)
        {
            Logger.Debug("Start parsing for JWT client assertion in post body");

            var context = new OwinContext(environment);
            var body = await context.ReadRequestFormAsync();

            if (body != null)
            {
                var clientId = body.Get(Constants.TokenRequest.ClientId);
                var clientAssertionType = body.Get(Constants.TokenRequest.ClientAssertionType);
                var clientAssertion = body.Get(Constants.TokenRequest.ClientAssertion);

                if (clientAssertion.IsPresent()
                    && clientAssertionType == Constants.ClientAssertionTypes.JwtBearer)
                {
                    if (!clientId.IsPresent())
                    {
                        // at least some clients (i.e. java com.nimbusds/oauth2-oidc-sdk) do not send client_id, but assume that token is enough (and it actually is)
                        clientId = GetClientIdFromToken(clientAssertion);
                        if (!clientId.IsPresent())
                        {
                            return null;
                        }
                    }
                    var parsedSecret = new ParsedSecret
                    {
                        Id = clientId,
                        Credential = clientAssertion,
                        Type = Constants.ParsedSecretTypes.JwtBearer
                    };

                    return parsedSecret;
                }
            }

            Logger.Debug("No JWT client assertion found in post body");
            return null;
        }
        /// <summary>
        /// Validates a secret
        /// </summary>
        /// <param name="secrets">The stored secrets.</param>
        /// <param name="parsedSecret">The received secret.</param>
        /// <returns>
        /// A validation result
        /// </returns>
        /// <exception cref="System.ArgumentException">id or credential is missing.</exception>
        public Task<SecretValidationResult> ValidateAsync(IEnumerable<Secret> secrets, ParsedSecret parsedSecret)
        {
            var fail = Task.FromResult(new SecretValidationResult { Success = false });
            var success = Task.FromResult(new SecretValidationResult { Success = true });

            if (parsedSecret.Type == Constants.ParsedSecretTypes.SharedSecret)
            {
                var sharedSecret = parsedSecret.Credential as string;

                if (parsedSecret.Id.IsMissing() || sharedSecret.IsMissing())
                {
                    throw new ArgumentException("id or credential is missing.");
                }

                foreach (var secret in secrets)
                {
                    // this validator is only applicable to shared secrets
                    if (secret.Type != Constants.SecretTypes.SharedSecret)
                    {
                        continue;
                    }

                    // check if client secret is still valid
                    if (secret.Expiration.HasExpired()) continue;

                    // use time constant string comparison
                    var isValid = TimeConstantComparer.IsEqual(sharedSecret, secret.Value);

                    if (isValid)
                    {
                        return success;
                    }
                }
            }

            return fail;
        }
        public async Task Invalid_Expired_Token()
        {
            var clientId = "certificate_valid";
            var client = await _clients.FindClientByIdAsync(clientId);

            var token = CreateToken(clientId, DateTime.Now.AddHours(-1));
            var secret = new ParsedSecret
            {
                Id = clientId,
                Credential = new JwtSecurityTokenHandler().WriteToken(token),
                Type = Constants.ParsedSecretTypes.JwtBearer
            };

            var result = await _validator.ValidateAsync(client.ClientSecrets, secret);

            result.Success.Should().BeFalse();
        }
        public async Task Invalid_Unsigned_Token()
        {
            var clientId = "certificate_valid";
            var client = await _clients.FindClientByIdAsync(clientId);

            var token = CreateToken(clientId);
            token.Header.Remove("alg");
            token.Header.Add("alg", "none");
            var secret = new ParsedSecret
            {
                Id = clientId,
                Credential = new JwtSecurityTokenHandler().WriteToken(token),
                Type = Constants.ParsedSecretTypes.JwtBearer
            };

            var result = await _validator.ValidateAsync(client.ClientSecrets, secret);

            result.Success.Should().BeFalse();
        }
        public async Task Valid_Certificate_Thumbprint()
        {
            var clientId = "certificate_valid";
            var client = await _clients.FindClientByIdAsync(clientId);

            var secret = new ParsedSecret
            {
                Id = clientId,
                Credential = new JwtSecurityTokenHandler().WriteToken(CreateToken(clientId)),
                Type = Constants.ParsedSecretTypes.JwtBearer
            };

            var result = await _validator.ValidateAsync(client.ClientSecrets, secret);

            result.Success.Should().BeTrue();
        }
        public async Task Client_with_no_Secret_Should_Throw()
        {
            var clientId = "no_secret_client";
            var client = await _clients.FindClientByIdAsync(clientId);

            var secret = new ParsedSecret
            {
                Id = clientId,
                Type = Constants.ParsedSecretTypes.SharedSecret
            };

            Func<Task> act = () => _validator.ValidateAsync(client.ClientSecrets, secret);

            act.ShouldThrow<ArgumentException>();
        }
        public async Task Invalid_Multiple_Secrets()
        {
            var clientId = "multiple_secrets_no_protection";
            var client = await _clients.FindClientByIdAsync(clientId);

            var secret = new ParsedSecret
            {
                Id = clientId,
                Credential = "invalid",
                Type = Constants.ParsedSecretTypes.SharedSecret
            };

            var result = await _validator.ValidateAsync(client.ClientSecrets, secret);
            result.Success.Should().BeFalse();
        }
        /// <summary>
        /// Validates a secret
        /// </summary>
        /// <param name="secrets">The stored secrets.</param>
        /// <param name="parsedSecret">The received secret.</param>
        /// <returns>
        /// A validation result
        /// </returns>
        /// <exception cref="System.ArgumentNullException">Id or cedential</exception>
        public Task<SecretValidationResult> ValidateAsync(IEnumerable<Secret> secrets, ParsedSecret parsedSecret)
        {
            var fail = Task.FromResult(new SecretValidationResult { Success = false });
            var success = Task.FromResult(new SecretValidationResult { Success = true });

            if (parsedSecret.Type != Constants.ParsedSecretTypes.SharedSecret)
            {
                Logger.Debug(string.Format("Parsed secret should not be of type {0}", parsedSecret.Type ?? "null"));
                return fail;
            }

            var sharedSecret = parsedSecret.Credential as string;

            if (parsedSecret.Id.IsMissing() || sharedSecret.IsMissing())
            {
                throw new ArgumentException("Id or Credential is missing.");
            }

            var secretSha256 = sharedSecret.Sha256();
            var secretSha512 = sharedSecret.Sha512();

            foreach (var secret in secrets)
            {
                var secretDescription = string.IsNullOrEmpty(secret.Description) ? "no description" : secret.Description;

                // this validator is only applicable to shared secrets
                if (secret.Type != Constants.SecretTypes.SharedSecret)
                {
                    Logger.Debug(string.Format("Skipping secret: {0}, secret is not of type {1}.", secretDescription, Constants.SecretTypes.SharedSecret));
                    continue;
                }

                // check if client secret is still valid
                if (secret.Expiration.HasExpired())
                {
                    Logger.Debug(string.Format("Skipping secret: {0}, secret is expired.", secretDescription));
                    continue;
                }

                bool isValid = false;
                byte[] secretBytes;

                try
                {
                    secretBytes = Convert.FromBase64String(secret.Value);
                }
                catch (FormatException)
                {
                    Logger.Error(string.Format("Secret: {0} uses invalid hashing algorithm.", secretDescription));
                    return fail;
                }

                if (secretBytes.Length == 32)
                {
                    isValid = TimeConstantComparer.IsEqual(secret.Value, secretSha256);
                }
                else if (secretBytes.Length == 64)
                {
                    isValid = TimeConstantComparer.IsEqual(secret.Value, secretSha512);
                }
                else
                {
                    Logger.Error(string.Format("Secret: {0} uses invalid hashing algorithm.", secretDescription));
                    return fail;
                }

                if (isValid)
                {
                    return success;
                }
            }

            Logger.Debug("No matching hashed secret found.");
            return fail;
        }
        /// <summary>
        /// Validates a secret
        /// </summary>
        /// <param name="secrets">The stored secrets.</param>
        /// <param name="parsedSecret">The received secret.</param>
        /// <returns>
        /// A validation result
        /// </returns>
        /// <exception cref="System.ArgumentNullException">Id or cedential</exception>
        public Task<SecretValidationResult> ValidateAsync(IEnumerable<Secret> secrets, ParsedSecret parsedSecret)
        {
            var fail = Task.FromResult(new SecretValidationResult { Success = false });
            var success = Task.FromResult(new SecretValidationResult { Success = true });

            if (parsedSecret.Type == Constants.ParsedSecretTypes.SharedSecret)
            {
                var sharedSecret = parsedSecret.Credential as string;

                if (parsedSecret.Id.IsMissing() || sharedSecret.IsMissing())
                {
                    throw new ArgumentNullException("Id or cedential");
                }

                var secretSha256 = sharedSecret.Sha256();
                var secretSha512 = sharedSecret.Sha512();

                foreach (var secret in secrets)
                {
                    // this validator is only applicable to shared secrets
                    if (secret.Type != Constants.SecretTypes.SharedSecret)
                    {
                        continue;
                    }

                    bool isValid = false;
                    byte[] secretBytes;

                    // check if client secret is still valid
                    if (secret.Expiration.HasExpired()) continue;

                    try
                    {
                        secretBytes = Convert.FromBase64String(secret.Value);
                    }
                    catch (FormatException)
                    {
                        Logger.Error("Secret uses invalid hashing algorithm");
                        return fail;
                    }

                    if (secretBytes.Length == 32)
                    {
                        isValid = TimeConstantComparer.IsEqual(secret.Value, secretSha256);
                    }
                    else if (secretBytes.Length == 64)
                    {
                        isValid = TimeConstantComparer.IsEqual(secret.Value, secretSha512);
                    }
                    else
                    {
                        Logger.Error("Secret uses invalid hashing algorithm");
                        return fail;
                    }

                    if (isValid)
                    {
                        return success;
                    }
                }
            }

            return fail;
        }
        public async Task Valid_Certificate_X5c_Only()
        {
            var clientId = "certificate_valid";
            var client = await _clients.FindClientByIdAsync(clientId);

            var token = CreateToken(clientId);
            token.Header.Remove(JwtHeaderParameterNames.X5t);
            var secret = new ParsedSecret
            {
                Id = clientId,
                Credential = new JwtSecurityTokenHandler().WriteToken(token),
                Type = Constants.ParsedSecretTypes.JwtBearer
            };

            var result = await _validator.ValidateAsync(client.ClientSecrets, secret);

            result.Success.Should().BeTrue();
        }
예제 #20
0
        public async Task Expired_Secret()
        {
            var clientId = "multiple_secrets_hashed";
            var client = await _clients.FindClientByIdAsync(clientId);

            var secret = new ParsedSecret
            {
                Id = clientId,
                Credential = "expired",
                Type = Constants.ParsedSecretTypes.SharedSecret
            };

            var result = await _validator.ValidateAsync(secret, client.ClientSecrets);
            result.Success.Should().BeFalse();
        }
        public async Task Invalid_Subject()
        {
            var clientId = "certificate_valid";
            var client = await _clients.FindClientByIdAsync(clientId);

            var token = CreateToken(clientId);
            token.Payload.Remove(JwtClaimTypes.Subject);
            token.Payload.Add(JwtClaimTypes.Subject, "invalid");
            var secret = new ParsedSecret
            {
                Id = clientId,
                Credential = new JwtSecurityTokenHandler().WriteToken(token),
                Type = Constants.ParsedSecretTypes.JwtBearer
            };

            var result = await _validator.ValidateAsync(client.ClientSecrets, secret);

            result.Success.Should().BeFalse();
        }
        /// <summary>
        /// Validates a secret
        /// </summary>
        /// <param name="secrets">The stored secrets.</param>
        /// <param name="parsedSecret">The received secret.</param>
        /// <returns>
        /// A validation result
        /// </returns>
        /// <exception cref="System.ArgumentException">ParsedSecret.Credential is not a JWT token</exception>
        public Task<SecretValidationResult> ValidateAsync(IEnumerable<Secret> secrets, ParsedSecret parsedSecret)
        {
            var fail = Task.FromResult(new SecretValidationResult { Success = false });
            var success = Task.FromResult(new SecretValidationResult { Success = true });

            if (parsedSecret.Type != Constants.ParsedSecretTypes.JwtBearer)
            {
                return fail;
            }

            var jwtTokenString = parsedSecret.Credential as string;

            if (jwtTokenString == null)
            {
                throw new ArgumentException("ParsedSecret.Credential is not a string.");
            }

            var enumeratedSecrets = secrets.ToList().AsReadOnly();

            var trustedKeys = GetTrustedKeys(enumeratedSecrets, jwtTokenString);

            if (!trustedKeys.Any())
            {
                Logger.Warn("There are no certificates available to validate client assertion.");
                return fail;
            }

            var tokenValidationParameters = new TokenValidationParameters
            {
                IssuerSigningKeys = trustedKeys,
                ValidateIssuerSigningKey = true,

                ValidIssuer = parsedSecret.Id,
                ValidateIssuer = true,

                ValidAudience = audienceUri,
                ValidateAudience = true,

                RequireSignedTokens = true,
                RequireExpirationTime = true
            };
            try
            {
                SecurityToken token;
                var handler = new EmbeddedCertificateJwtSecurityTokenHandler();
                handler.ValidateToken(jwtTokenString, tokenValidationParameters, out token);

                var jwtToken = (JwtSecurityToken)token;

                if (jwtToken.Subject != jwtToken.Issuer)
                {
                    Logger.Warn("Both 'sub' and 'iss' in the client assertion token must have a value of client_id.");
                    return fail;
                }

                return success;
            }
            catch (Exception e)
            {
                Logger.Debug("JWT token validation error: " + e.Message);
                return fail;
            }
        }
        /// <summary>
        /// Tries to find a secret on the environment that can be used for authentication
        /// </summary>
        /// <param name="environment">The environment.</param>
        /// <returns>
        /// A parsed secret
        /// </returns>
        public Task<ParsedSecret> ParseAsync(IDictionary<string, object> environment)
        {
            Logger.Debug("Start parsing Basic Authentication secret");

            var notfound = Task.FromResult<ParsedSecret>(null);
            var context = new OwinContext(environment);
            var authorizationHeader = context.Request.Headers.Get("Authorization");

            if (authorizationHeader == null)
            {
                return notfound;
            }

            if (!authorizationHeader.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase))
            {
                return notfound;
            }

            var parameter = authorizationHeader.Substring("Basic ".Length);

            string pair;
            try
            {
                pair = Encoding.UTF8.GetString(
                    Convert.FromBase64String(parameter));
            }
            catch (FormatException)
            {
                Logger.Debug("Malformed Basic Authentication credential.");
                return notfound;
            }
            catch (ArgumentException)
            {
                Logger.Debug("Malformed Basic Authentication credential.");
                return notfound;
            }

            var ix = pair.IndexOf(':');
            if (ix == -1)
            {
                Logger.Debug("Malformed Basic Authentication credential.");
                return notfound;
            }

            var clientId = pair.Substring(0, ix);
            var secret = pair.Substring(ix + 1);

            if (clientId.IsPresent() && secret.IsPresent())
            {
                var parsedSecret = new ParsedSecret
                {
                    Id = clientId,
                    Credential = secret,
                    Type = Constants.ParsedSecretTypes.SharedSecret
                };

                return Task.FromResult(parsedSecret);
            }

            Logger.Debug("No Basic Authentication secret found");
            return notfound;
        }