Browse Source

Implement client_secret hashing/key derivation

pull/27/merge
Kévin Chalet 10 years ago
parent
commit
4a318920ca
  1. 6
      samples/Mvc.Server/Startup.cs
  2. 2
      src/OpenIddict.Core/IOpenIddictStore.cs
  3. 40
      src/OpenIddict.Core/OpenIddictHelpers.cs
  4. 43
      src/OpenIddict.Core/OpenIddictManager.cs
  5. 10
      src/OpenIddict.Core/OpenIddictProvider.cs
  6. 4
      src/OpenIddict.Core/project.json
  7. 15
      src/OpenIddict.EF/OpenIddictStore.cs
  8. 2
      src/OpenIddict.Models/Application.cs
  9. 12
      src/OpenIddict.Models/ApplicationType.cs

6
samples/Mvc.Server/Startup.cs

@ -1,4 +1,5 @@
using System.Linq;
using CryptoHelper;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.Data.Entity;
@ -7,6 +8,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Mvc.Server.Models;
using Mvc.Server.Services;
using OpenIddict;
using OpenIddict.Models;
namespace Mvc.Server {
@ -75,8 +77,8 @@ namespace Mvc.Server {
DisplayName = "My client application",
RedirectUri = "http://localhost:53507/signin-oidc",
LogoutRedirectUri = "http://localhost:53507/",
Secret = "secret_secret_secret",
Type = ApplicationType.Confidential
Secret = Crypto.HashPassword("secret_secret_secret"),
Type = OpenIddictConstants.ApplicationTypes.Confidential
});
context.SaveChanges();

2
src/OpenIddict.Core/IOpenIddictStore.cs

@ -9,6 +9,6 @@ namespace OpenIddict {
Task<string> GetApplicationTypeAsync(TApplication application, CancellationToken cancellationToken);
Task<string> GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken);
Task<string> GetRedirectUriAsync(TApplication application, CancellationToken cancellationToken);
Task<bool> ValidateSecretAsync(TApplication application, string secret, CancellationToken cancellationToken);
Task<string> GetHashedSecretAsync(TApplication application, CancellationToken cancellationToken);
}
}

40
src/OpenIddict.Core/OpenIddictHelpers.cs

@ -0,0 +1,40 @@
using System;
using System.Threading.Tasks;
namespace OpenIddict {
public static class OpenIddictHelpers {
public static async Task<bool> IsConfidentialApplicationAsync<TUser, TApplication>(
this OpenIddictManager<TUser, TApplication> manager, TApplication application)
where TUser : class
where TApplication : class {
if (manager == null) {
throw new ArgumentNullException(nameof(manager));
}
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
var type = await manager.GetApplicationTypeAsync(application);
return string.Equals(type, OpenIddictConstants.ApplicationTypes.Confidential, StringComparison.OrdinalIgnoreCase);
}
public static async Task<bool> IsPublicApplicationAsync<TUser, TApplication>(
this OpenIddictManager<TUser, TApplication> manager, TApplication application)
where TUser : class
where TApplication : class {
if (manager == null) {
throw new ArgumentNullException(nameof(manager));
}
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
var type = await manager.GetApplicationTypeAsync(application);
return string.Equals(type, OpenIddictConstants.ApplicationTypes.Public, StringComparison.OrdinalIgnoreCase);
}
}
}

43
src/OpenIddict.Core/OpenIddictManager.cs

@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using CryptoHelper;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Identity;
using Microsoft.Extensions.DependencyInjection;
@ -50,12 +51,21 @@ namespace OpenIddict {
select claim.Value).FirstOrDefault();
}
public virtual Task<string> GetApplicationTypeAsync(TApplication application) {
public virtual async Task<string> GetApplicationTypeAsync(TApplication application) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
return Store.GetApplicationTypeAsync(application, Context.RequestAborted);
var type = await Store.GetApplicationTypeAsync(application, Context.RequestAborted);
// Ensure the application type returned by the store is supported by the manager.
if (!string.Equals(type, OpenIddictConstants.ApplicationTypes.Confidential, StringComparison.OrdinalIgnoreCase) &&
!string.Equals(type, OpenIddictConstants.ApplicationTypes.Public, StringComparison.OrdinalIgnoreCase)) {
throw new InvalidOperationException("Only 'confidential' or 'public' applications are " +
"supported by the default OpenIddict manager.");
}
return type;
}
public virtual Task<string> GetDisplayNameAsync(TApplication application) {
@ -66,20 +76,41 @@ namespace OpenIddict {
return Store.GetDisplayNameAsync(application, Context.RequestAborted);
}
public virtual Task<string> GetRedirectUriAsync(TApplication application) {
public virtual async Task<bool> ValidateRedirectUriAsync(TApplication application, string address) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
return Store.GetRedirectUriAsync(application, Context.RequestAborted);
if (!string.Equals(address, await Store.GetRedirectUriAsync(application, Context.RequestAborted), StringComparison.Ordinal)) {
Logger.LogWarning("Client validation failed because {RedirectUri} was not a valid redirect_uri " +
"for {Client}", address, await GetDisplayNameAsync(application));
return false;
}
return true;
}
public virtual Task<bool> ValidateSecretAsync(TApplication application, string secret) {
public virtual async Task<bool> ValidateSecretAsync(TApplication application, string secret) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
return Store.ValidateSecretAsync(application, secret, Context.RequestAborted);
var type = await GetApplicationTypeAsync(application);
if (!string.Equals(type, OpenIddictConstants.ApplicationTypes.Confidential, StringComparison.OrdinalIgnoreCase)) {
Logger.LogWarning("Client authentication cannot be enforced for non-confidential applications.");
return false;
}
var hash = await Store.GetHashedSecretAsync(application, Context.RequestAborted);
if (!Crypto.VerifyHashedPassword(hash, secret)) {
Logger.LogWarning("Client authentication failed for {Client}.", await GetDisplayNameAsync(application));
return false;
}
return true;
}
}
}

10
src/OpenIddict.Core/OpenIddictProvider.cs

@ -51,7 +51,7 @@ namespace OpenIddict {
return;
}
if (!string.Equals(context.RedirectUri, await manager.GetRedirectUriAsync(application), StringComparison.Ordinal)) {
if (!await manager.ValidateRedirectUriAsync(application, context.RedirectUri)) {
context.Rejected(
error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Invalid redirect_uri");
@ -114,8 +114,7 @@ namespace OpenIddict {
// Reject tokens requests containing a client_secret
// if the client application is not confidential.
var type = await manager.GetApplicationTypeAsync(application);
if (type == OpenIddictConstants.ApplicationTypes.Public && !string.IsNullOrEmpty(context.ClientSecret)) {
if (await manager.IsPublicApplicationAsync(application) && !string.IsNullOrEmpty(context.ClientSecret)) {
context.Rejected(
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "Public clients are not allowed to send a client_secret");
@ -124,7 +123,7 @@ namespace OpenIddict {
}
// Confidential applications MUST authenticate.
else if (type == OpenIddictConstants.ApplicationTypes.Confidential &&
else if (await manager.IsConfidentialApplicationAsync(application) &&
!await manager.ValidateSecretAsync(application, context.ClientSecret)) {
context.Rejected(
error: OpenIdConnectConstants.Errors.InvalidClient,
@ -145,8 +144,7 @@ namespace OpenIddict {
// To prevent downgrade attacks, ensure that authorization requests using the hybrid/implicit
// flow are rejected if the client identifier corresponds to a confidential application.
var type = await manager.GetApplicationTypeAsync(application);
if (type == OpenIddictConstants.ApplicationTypes.Confidential && !context.Request.IsAuthorizationCodeFlow()) {
if (await manager.IsConfidentialApplicationAsync(application) && !context.Request.IsAuthorizationCodeFlow()) {
context.Rejected(
error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "Confidential clients can only use response_type=code.");

4
src/OpenIddict.Core/project.json

@ -16,7 +16,9 @@
"version": "1.0.0-*"
},
"AspNet.Security.OpenIdConnect.Server": "1.0.0-*"
"AspNet.Security.OpenIdConnect.Server": "1.0.0-*",
"CryptoHelper": "1.0.0-rc1-build03"
},
"frameworks": {

15
src/OpenIddict.EF/OpenIddictStore.cs

@ -33,16 +33,7 @@ namespace OpenIddict {
throw new ArgumentNullException(nameof(application));
}
switch (application.Type) {
case ApplicationType.Confidential:
return Task.FromResult(OpenIddictConstants.ApplicationTypes.Confidential);
case ApplicationType.Public:
return Task.FromResult(OpenIddictConstants.ApplicationTypes.Public);
default:
throw new InvalidOperationException($"Unsupported application type ('{application.Type.ToString()}').");
}
return Task.FromResult(application.Type);
}
public virtual Task<string> GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken) {
@ -61,12 +52,12 @@ namespace OpenIddict {
return Task.FromResult(application.RedirectUri);
}
public virtual Task<bool> ValidateSecretAsync(TApplication application, string secret, CancellationToken cancellationToken) {
public virtual Task<string> GetHashedSecretAsync(TApplication application, CancellationToken cancellationToken) {
if (application == null) {
throw new ArgumentNullException(nameof(application));
}
return Task.FromResult(string.Equals(application.Secret, secret, StringComparison.Ordinal));
return Task.FromResult(application.Secret);
}
}
}

2
src/OpenIddict.Models/Application.cs

@ -11,6 +11,6 @@ namespace OpenIddict.Models {
public string RedirectUri { get; set; }
public string LogoutRedirectUri { get; set; }
public string Secret { get; set; }
public ApplicationType Type { get; set; }
public string Type { get; set; }
}
}

12
src/OpenIddict.Models/ApplicationType.cs

@ -1,12 +0,0 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/core for more information concerning
* the license and the contributors participating to this project.
*/
namespace OpenIddict.Models {
public enum ApplicationType {
Public = 0,
Confidential = 1
}
}
Loading…
Cancel
Save