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

2
src/OpenIddict.Core/IOpenIddictStore.cs

@ -9,6 +9,6 @@ namespace OpenIddict {
Task<string> GetApplicationTypeAsync(TApplication application, CancellationToken cancellationToken); Task<string> GetApplicationTypeAsync(TApplication application, CancellationToken cancellationToken);
Task<string> GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken); Task<string> GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken);
Task<string> GetRedirectUriAsync(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;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using CryptoHelper;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -50,12 +51,21 @@ namespace OpenIddict {
select claim.Value).FirstOrDefault(); select claim.Value).FirstOrDefault();
} }
public virtual Task<string> GetApplicationTypeAsync(TApplication application) { public virtual async Task<string> GetApplicationTypeAsync(TApplication application) {
if (application == null) { if (application == null) {
throw new ArgumentNullException(nameof(application)); 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) { public virtual Task<string> GetDisplayNameAsync(TApplication application) {
@ -66,20 +76,41 @@ namespace OpenIddict {
return Store.GetDisplayNameAsync(application, Context.RequestAborted); 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) { if (application == null) {
throw new ArgumentNullException(nameof(application)); 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) { if (application == null) {
throw new ArgumentNullException(nameof(application)); 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; return;
} }
if (!string.Equals(context.RedirectUri, await manager.GetRedirectUriAsync(application), StringComparison.Ordinal)) { if (!await manager.ValidateRedirectUriAsync(application, context.RedirectUri)) {
context.Rejected( context.Rejected(
error: OpenIdConnectConstants.Errors.InvalidClient, error: OpenIdConnectConstants.Errors.InvalidClient,
description: "Invalid redirect_uri"); description: "Invalid redirect_uri");
@ -114,8 +114,7 @@ namespace OpenIddict {
// Reject tokens requests containing a client_secret // Reject tokens requests containing a client_secret
// if the client application is not confidential. // if the client application is not confidential.
var type = await manager.GetApplicationTypeAsync(application); if (await manager.IsPublicApplicationAsync(application) && !string.IsNullOrEmpty(context.ClientSecret)) {
if (type == OpenIddictConstants.ApplicationTypes.Public && !string.IsNullOrEmpty(context.ClientSecret)) {
context.Rejected( context.Rejected(
error: OpenIdConnectConstants.Errors.InvalidRequest, error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "Public clients are not allowed to send a client_secret"); description: "Public clients are not allowed to send a client_secret");
@ -124,7 +123,7 @@ namespace OpenIddict {
} }
// Confidential applications MUST authenticate. // Confidential applications MUST authenticate.
else if (type == OpenIddictConstants.ApplicationTypes.Confidential && else if (await manager.IsConfidentialApplicationAsync(application) &&
!await manager.ValidateSecretAsync(application, context.ClientSecret)) { !await manager.ValidateSecretAsync(application, context.ClientSecret)) {
context.Rejected( context.Rejected(
error: OpenIdConnectConstants.Errors.InvalidClient, error: OpenIdConnectConstants.Errors.InvalidClient,
@ -145,8 +144,7 @@ namespace OpenIddict {
// To prevent downgrade attacks, ensure that authorization requests using the hybrid/implicit // To prevent downgrade attacks, ensure that authorization requests using the hybrid/implicit
// flow are rejected if the client identifier corresponds to a confidential application. // flow are rejected if the client identifier corresponds to a confidential application.
var type = await manager.GetApplicationTypeAsync(application); if (await manager.IsConfidentialApplicationAsync(application) && !context.Request.IsAuthorizationCodeFlow()) {
if (type == OpenIddictConstants.ApplicationTypes.Confidential && !context.Request.IsAuthorizationCodeFlow()) {
context.Rejected( context.Rejected(
error: OpenIdConnectConstants.Errors.InvalidRequest, error: OpenIdConnectConstants.Errors.InvalidRequest,
description: "Confidential clients can only use response_type=code."); description: "Confidential clients can only use response_type=code.");

4
src/OpenIddict.Core/project.json

@ -16,7 +16,9 @@
"version": "1.0.0-*" "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": { "frameworks": {

15
src/OpenIddict.EF/OpenIddictStore.cs

@ -33,16 +33,7 @@ namespace OpenIddict {
throw new ArgumentNullException(nameof(application)); throw new ArgumentNullException(nameof(application));
} }
switch (application.Type) { return Task.FromResult(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()}').");
}
} }
public virtual Task<string> GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken) { public virtual Task<string> GetDisplayNameAsync(TApplication application, CancellationToken cancellationToken) {
@ -61,12 +52,12 @@ namespace OpenIddict {
return Task.FromResult(application.RedirectUri); 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) { if (application == null) {
throw new ArgumentNullException(nameof(application)); 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 RedirectUri { get; set; }
public string LogoutRedirectUri { get; set; } public string LogoutRedirectUri { get; set; }
public string Secret { 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