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.LogInformation("Secret [{description}] 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.LogTrace("Secret validator success: {0}", validator.GetType().Name); return secretValidationResult; } } _logger.LogInformation("Secret validators could not validate secret"); return new SecretValidationResult { Success = false }; }
public async Task Valid_Multiple_Secrets() { var clientId = "multiple_secrets_hashed"; 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(); }
public async Task Invalid_Credential_Type() { var clientId = "single_secret_hashed_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(); }
public async Task Valid_Single_Secret() { var clientId = "single_secret_hashed_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(); }
/// <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.LogVerbose("Parsed secret should not be of type: {type}", 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.LogVerbose("Skipping secret: {description}, secret is not of type {type}.", secretDescription, Constants.SecretTypes.SharedSecret); continue; } // use time constant string comparison var isValid = TimeConstantComparer.IsEqual(sharedSecret, secret.Value); if (isValid) { return success; } } _logger.LogVerbose("No matching plain text secret found."); 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(HttpContext context) { _logger.LogVerbose("Start parsing for secret in post body"); if (!context.Request.HasFormContentType) { _logger.LogWarning("Content type is not a form"); return Task.FromResult<ParsedSecret>(null); } var body = context.Request.Form; if (body != null) { var id = body["client_id"].FirstOrDefault(); var secret = body["client_secret"].FirstOrDefault(); if (id.IsPresent() && secret.IsPresent()) { if (id.Length > _options.InputLengthRestrictions.ClientId || secret.Length > _options.InputLengthRestrictions.ClientSecret) { _logger.LogWarning("Client ID or secret exceeds maximum lenght."); return Task.FromResult<ParsedSecret>(null); } var parsedSecret = new ParsedSecret { Id = id, Credential = secret, Type = Constants.ParsedSecretTypes.SharedSecret }; return Task.FromResult(parsedSecret); } } _logger.LogVerbose("No secret in post body found"); return Task.FromResult<ParsedSecret>(null); }
/// <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(HttpContext context) { _logger.LogTrace("Start parsing Basic Authentication secret"); var notfound = Task.FromResult<ParsedSecret>(null); var authorizationHeader = context.Request.Headers["Authorization"].FirstOrDefault(); if (authorizationHeader.IsMissing()) { 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.LogTrace("Malformed Basic Authentication credential."); return notfound; } catch (ArgumentException) { _logger.LogTrace("Malformed Basic Authentication credential."); return notfound; } var ix = pair.IndexOf(':'); if (ix == -1) { _logger.LogTrace("Malformed Basic Authentication credential."); return notfound; } var clientId = pair.Substring(0, ix); var secret = pair.Substring(ix + 1); if (clientId.IsPresent() && secret.IsPresent()) { if (clientId.Length > _options.InputLengthRestrictions.ClientId || secret.Length > _options.InputLengthRestrictions.ClientSecret) { _logger.LogError("Client ID or secret exceeds allowed length."); return notfound; } var parsedSecret = new ParsedSecret { Id = clientId, Credential = secret, Type = Constants.ParsedSecretTypes.SharedSecret }; return Task.FromResult(parsedSecret); } _logger.LogTrace("No Basic Authentication secret found"); return notfound; }
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_hashed"; 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.LogDebug("Parsed secret should not be of type {type}", 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.LogDebug("Skipping secret: {description}, secret is not of type {type}.", secretDescription, Constants.SecretTypes.SharedSecret); continue; } bool isValid = false; byte[] secretBytes; try { secretBytes = Convert.FromBase64String(secret.Value); } catch (FormatException) { _logger.LogError("Secret: {description} 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.LogError("Secret: {description} uses invalid hashing algorithm.", secretDescription); return fail; } if (isValid) { return success; } } _logger.LogTrace("No matching hashed secret found."); return fail; }