Skip to content

Commit 3062de6

Browse files
Ticket #874 : Add multiple algorithms to check the client secret
1 parent b0e3b5d commit 3062de6

12 files changed

Lines changed: 225 additions & 34 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) SimpleIdServer. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
using System.Security.Cryptography;
4+
using System.Text;
5+
6+
namespace SimpleIdServer.IdServer.Domains;
7+
8+
public class ClientSecret
9+
{
10+
private string _value;
11+
12+
public ClientSecret(string value)
13+
{
14+
_value = value;
15+
}
16+
17+
public string Sha256()
18+
{
19+
using (var sha256 = SHA256.Create())
20+
{
21+
var hashPayload = sha256.ComputeHash(Encoding.UTF8.GetBytes(_value));
22+
return Convert.ToBase64String(hashPayload);
23+
}
24+
}
25+
26+
public string Sha512()
27+
{
28+
using (var sha512 = SHA512.Create())
29+
{
30+
var hashPayload = sha512.ComputeHash(Encoding.UTF8.GetBytes(_value));
31+
return Convert.ToBase64String(hashPayload);
32+
}
33+
}
34+
}

src/IdServer/SimpleIdServer.IdServer.Migrations.Duende/DuendeMigrationService.cs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
using Microsoft.AspNetCore.Identity;
55
using Microsoft.EntityFrameworkCore;
66
using Microsoft.IdentityModel.Tokens;
7+
using SimpleIdServer.IdServer.Api.Authorization.ResponseTypes;
78
using SimpleIdServer.IdServer.Api.Token.Handlers;
9+
using SimpleIdServer.IdServer.Authenticate.Handlers;
810
using SimpleIdServer.IdServer.Config;
911
using SimpleIdServer.IdServer.Domains;
1012
using SimpleIdServer.IdServer.Stores;
@@ -18,7 +20,7 @@
1820
namespace SimpleIdServer.IdServer.Migrations.Duende;
1921

2022
public class DuendeMigrationService : IMigrationService
21-
{
23+
{
2224
public string Name => Constants.Name;
2325
private readonly ConfigurationDbContext _configurationDbcontext;
2426
private readonly ApplicationDbContext _applicationDbcontext;
@@ -259,11 +261,13 @@ private static Client Map(DuendeClient client, List<Scope> scopes)
259261
IsConsentDisabled = !client.RequireConsent,
260262
Scopes = scopes,
261263
SerializedJsonWebKeys = ResolveSerializedJsonWebKeys(client),
262-
ClientSecret = Guid.NewGuid().ToString(),
264+
ClientSecret = ResolveClientSecret(client),
263265
CreateDateTime = client.Created,
264266
UpdateDateTime = client.Updated ?? client.Created,
265267
AuthorizationCodeExpirationInSeconds = client.AuthorizationCodeLifetime
266268
};
269+
result.ResponseTypes = ResolveResponseTypes(result.ClientType);
270+
result.TokenEndPointAuthMethod = result.ClientType == ClientTypes.MACHINE || result.ClientType == ClientTypes.WEBSITE ? OAuthClientSecretPostAuthenticationHandler.AUTH_METHOD : null;
267271
result.UpdateClientName(client.ClientName, IdServer.Constants.DefaultLanguage);
268272
result.UpdateClientUri(client.ClientUri, IdServer.Constants.DefaultLanguage);
269273
result.UpdateLogoUri(client.LogoUri, IdServer.Constants.DefaultLanguage);
@@ -277,7 +281,6 @@ private static Client Map(DuendeClient client, List<Scope> scopes)
277281
AllowAccessTokensViaBrowser
278282
Enabled
279283
ProtocolType
280-
ClientSecrets
281284
RequireClientSecret
282285
Description
283286
AllowRememberConsent
@@ -350,6 +353,18 @@ private static int ResolveDPOPNonceLifetimeInSeconds(DuendeClient client)
350353
return (int)client.DPoPClockSkew.TotalSeconds;
351354
}
352355

356+
private static string ResolveClientSecret(DuendeClient client)
357+
{
358+
const string clientSecretType = "SharedSecret";
359+
var clientSecret = client.ClientSecrets.FirstOrDefault(s => s.Type == clientSecretType && s.Expiration == null);
360+
if (clientSecret == null)
361+
{
362+
clientSecret = client.ClientSecrets.FirstOrDefault(s => s.Type == clientSecretType && s.Expiration != null && s.Expiration >= DateTime.UtcNow);
363+
}
364+
365+
return clientSecret?.Value ?? Guid.NewGuid().ToString();
366+
}
367+
353368
private static UserClaim Map(IdentityUserClaim<string> userClaim)
354369
{
355370
return new UserClaim
@@ -445,6 +460,24 @@ private static ClientTypes ResolveClientType(DuendeClient client)
445460
return ClientTypes.SPA;
446461
}
447462

463+
if (!client.RequirePkce && grantTypes.Contains(AuthorizationCodeHandler.GRANT_TYPE))
464+
{
465+
return ClientTypes.WEBSITE;
466+
}
467+
448468
return ClientTypes.MACHINE;
449469
}
470+
471+
private static List<string> ResolveResponseTypes(ClientTypes? type)
472+
{
473+
if (type == null || type == ClientTypes.MACHINE)
474+
{
475+
return new List<string>();
476+
}
477+
478+
return new List<string>
479+
{
480+
AuthorizationCodeResponseTypeHandler.RESPONSE_TYPE,
481+
};
482+
}
450483
}

src/IdServer/SimpleIdServer.IdServer.Website/Pages/Migrations/Migrations.razor

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -54,23 +54,30 @@
5454
</RadzenDataGridColumn>
5555
<RadzenDataGridColumn Title="@Global.Status" TItem="MigrationExecution" Filterable="false" Sortable="false" TextAlign="TextAlign.Center">
5656
<Template Context="data">
57-
<div style="display: flex;">
57+
<div style="display: flex; justify-content: center">
5858
<ul class="stateDig">
59-
<li @onmouseenter="@( _ => ShowStep(extractedElements[data.Name], string.Format(Global.ExtractedScopes, data.TotalMigratedScopes)) )" @onclick="(_ => ShowError(data, MigrationExecutionHistoryTypes.APISCOPES, MigrationExecutionHistoryTypes.IDENTITYSCOPES))" class=@(GetNodeClassName(data, MigrationExecutionHistoryTypes.APISCOPES, MigrationExecutionHistoryTypes.IDENTITYSCOPES))>
60-
<div @ref=extractedElements[data.Name] class="title">@Global.Scopes</div>
61-
</li>
62-
<li @onmouseenter="@( _ => ShowStep(extractedElements[data.Name], string.Format(Global.ExtractedApiResources, data.TotalMigratedApiResources)) )" @onclick="(_ => ShowError(data, MigrationExecutionHistoryTypes.APIRESOURCES))" class=@(GetNodeClassName(data, MigrationExecutionHistoryTypes.APIRESOURCES))>
63-
<div class="title">@Global.ApiResources</div>
64-
</li>
65-
<li @onmouseenter="@( _ => ShowStep(extractedElements[data.Name], string.Format(Global.ExtractedClients, data.TotalMigratedApiResources)) )" @onclick="(_ => ShowError(data, MigrationExecutionHistoryTypes.CLIENTS))" class=@(GetNodeClassName(data, MigrationExecutionHistoryTypes.CLIENTS))>
66-
<div class="title">@Global.Clients</div>
67-
</li>
68-
<li @onmouseenter="@( _ => ShowStep(extractedElements[data.Name], string.Format(Global.ExtractedGroups, data.TotalMigratedGroups)) )" @onclick="(_ => ShowError(data, MigrationExecutionHistoryTypes.GROUPS))" class=@(GetNodeClassName(data, MigrationExecutionHistoryTypes.GROUPS))>
69-
<div class="title">@Global.Groups</div>
70-
</li>
71-
<li @onmouseenter="@( _ => ShowStep(extractedElements[data.Name], string.Format(Global.ExtractedUsers, data.TotalMigratedUsers)) )" @onclick="(_ => ShowError(data, MigrationExecutionHistoryTypes.USERS))" class=@(GetNodeClassName(data, MigrationExecutionHistoryTypes.USERS))>
72-
<div class="title">@Global.Users</div>
73-
</li>
59+
@{
60+
var scopesName = data.Name + "_scopes";
61+
<li @onmouseenter="@( _ => ShowStep(extractedElements[scopesName], string.Format(Global.ExtractedScopes, data.TotalMigratedScopes)) )" @onclick="(_ => ShowError(data, MigrationExecutionHistoryTypes.APISCOPES, MigrationExecutionHistoryTypes.IDENTITYSCOPES))" class=@(GetNodeClassName(data, MigrationExecutionHistoryTypes.APISCOPES, MigrationExecutionHistoryTypes.IDENTITYSCOPES))>
62+
<div @ref=extractedElements[scopesName] class="title">@Global.Scopes</div>
63+
</li>
64+
var apiResourcesName = data.Name + "_apiresources";
65+
<li @onmouseenter="@( _ => ShowStep(extractedElements[apiResourcesName], string.Format(Global.ExtractedApiResources, data.TotalMigratedApiResources)) )" @onclick="(_ => ShowError(data, MigrationExecutionHistoryTypes.APIRESOURCES))" class=@(GetNodeClassName(data, MigrationExecutionHistoryTypes.APIRESOURCES))>
66+
<div @ref=extractedElements[apiResourcesName] class="title">@Global.ApiResources</div>
67+
</li>
68+
var clientsName = data.Name + "_clients";
69+
<li @onmouseenter="@( _ => ShowStep(extractedElements[clientsName], string.Format(Global.ExtractedClients, data.TotalMigratedClients)) )" @onclick="(_ => ShowError(data, MigrationExecutionHistoryTypes.CLIENTS))" class=@(GetNodeClassName(data, MigrationExecutionHistoryTypes.CLIENTS))>
70+
<div @ref=extractedElements[clientsName] class="title">@Global.Clients</div>
71+
</li>
72+
var groupsName = data.Name + "_groups";
73+
<li @onmouseenter="@( _ => ShowStep(extractedElements[groupsName], string.Format(Global.ExtractedGroups, data.TotalMigratedGroups)) )" @onclick="(_ => ShowError(data, MigrationExecutionHistoryTypes.GROUPS))" class=@(GetNodeClassName(data, MigrationExecutionHistoryTypes.GROUPS))>
74+
<div @ref=extractedElements[groupsName] class="title">@Global.Groups</div>
75+
</li>
76+
var usersName = data.Name + "_users";
77+
<li @onmouseenter="@( _ => ShowStep(extractedElements[usersName], string.Format(Global.ExtractedUsers, data.TotalMigratedUsers)) )" @onclick="(_ => ShowError(data, MigrationExecutionHistoryTypes.USERS))" class=@(GetNodeClassName(data, MigrationExecutionHistoryTypes.USERS))>
78+
<div @ref=extractedElements[usersName] class="title">@Global.Users</div>
79+
</li>
80+
}
7481
</ul>
7582
</div>
7683
</Template>

src/IdServer/SimpleIdServer.IdServer.Website/wwwroot/css/stateDig.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,8 @@
4949
margin: auto;
5050
width: 20px;
5151
}
52+
53+
.stateDig > li.node:last-child::after {
54+
content: none;
55+
border: none;
56+
}
Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,34 @@
11
// Copyright (c) SimpleIdServer. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
using SimpleIdServer.IdServer.Authenticate.Validations;
34
using SimpleIdServer.IdServer.Domains;
45
using System;
56
using System.Threading;
67
using System.Threading.Tasks;
78

8-
namespace SimpleIdServer.IdServer.Authenticate.Handlers
9+
namespace SimpleIdServer.IdServer.Authenticate.Handlers;
10+
11+
public class OAuthClientSecretBasicAuthenticationHandler : IOAuthClientAuthenticationHandler
912
{
10-
public class OAuthClientSecretBasicAuthenticationHandler : IOAuthClientAuthenticationHandler
13+
private readonly IClientSecretValidator _clientSecretValidator;
14+
15+
public OAuthClientSecretBasicAuthenticationHandler(IClientSecretValidator clientSecretValidator)
1116
{
12-
public OAuthClientSecretBasicAuthenticationHandler() { }
17+
_clientSecretValidator = clientSecretValidator;
18+
}
1319

14-
public string AuthMethod => AUTH_METHOD;
15-
public const string AUTH_METHOD = "client_secret_basic";
20+
public string AuthMethod => AUTH_METHOD;
21+
public const string AUTH_METHOD = "client_secret_basic";
1622

17-
public Task<bool> Handle(AuthenticateInstruction authenticateInstruction, Client client, string expectedIssuer, CancellationToken cancellationToken, string errorCode = ErrorCodes.INVALID_CLIENT)
23+
public Task<bool> Handle(AuthenticateInstruction authenticateInstruction, Client client, string expectedIssuer, CancellationToken cancellationToken, string errorCode = ErrorCodes.INVALID_CLIENT)
24+
{
25+
if (authenticateInstruction == null) throw new ArgumentNullException(nameof(authenticateInstruction));
26+
if (client == null) throw new ArgumentNullException(nameof(client));
27+
if (string.IsNullOrWhiteSpace(client.ClientSecret))
1828
{
19-
if (authenticateInstruction == null) throw new ArgumentNullException(nameof(authenticateInstruction));
20-
if (client == null) throw new ArgumentNullException(nameof(client));
21-
if (string.IsNullOrWhiteSpace(client.ClientSecret)) return Task.FromResult(false);
22-
var result = string.Compare(client.ClientSecret, authenticateInstruction.ClientSecretFromAuthorizationHeader, StringComparison.CurrentCultureIgnoreCase) == 0;
23-
return Task.FromResult(result);
29+
return Task.FromResult(false);
2430
}
31+
32+
return Task.FromResult(_clientSecretValidator.IsValid(client, authenticateInstruction.ClientSecretFromAuthorizationHeader));
2533
}
2634
}
Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Copyright (c) SimpleIdServer. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
using SimpleIdServer.IdServer.Authenticate.Validations;
34
using SimpleIdServer.IdServer.Domains;
45
using System;
56
using System.Threading;
@@ -9,7 +10,12 @@ namespace SimpleIdServer.IdServer.Authenticate.Handlers
910
{
1011
public class OAuthClientSecretPostAuthenticationHandler : IOAuthClientAuthenticationHandler
1112
{
12-
public OAuthClientSecretPostAuthenticationHandler() { }
13+
private readonly IClientSecretValidator _clientSecretValidator;
14+
15+
public OAuthClientSecretPostAuthenticationHandler(IClientSecretValidator clientSecretValidator)
16+
{
17+
_clientSecretValidator = clientSecretValidator;
18+
}
1319

1420
public string AuthMethod => AUTH_METHOD;
1521
public const string AUTH_METHOD = "client_secret_post";
@@ -18,9 +24,12 @@ public Task<bool> Handle(AuthenticateInstruction authenticateInstruction, Client
1824
{
1925
if (authenticateInstruction == null) throw new ArgumentNullException(nameof(authenticateInstruction));
2026
if (client == null) throw new ArgumentNullException(nameof(client));
21-
if (string.IsNullOrWhiteSpace(client.ClientSecret)) return Task.FromResult(false);
22-
var result = string.Compare(client.ClientSecret, authenticateInstruction.ClientSecretFromHttpRequestBody, StringComparison.CurrentCultureIgnoreCase) == 0;
23-
return Task.FromResult(result);
27+
if (string.IsNullOrWhiteSpace(client.ClientSecret))
28+
{
29+
return Task.FromResult(false);
30+
}
31+
32+
return Task.FromResult(_clientSecretValidator.IsValid(client, authenticateInstruction.ClientSecretFromHttpRequestBody));
2433
}
2534
}
2635
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) SimpleIdServer. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
using SimpleIdServer.IdServer.Domains;
5+
using System.Collections.Generic;
6+
7+
namespace SimpleIdServer.IdServer.Authenticate.Validations;
8+
9+
public interface IClientSecretValidator
10+
{
11+
bool IsValid(Client client, string clientSecret);
12+
}
13+
14+
public class ClientSecretValidator : IClientSecretValidator
15+
{
16+
private readonly IEnumerable<IAlgClientSecretValidator> _validators;
17+
18+
public ClientSecretValidator(IEnumerable<IAlgClientSecretValidator> validators)
19+
{
20+
_validators = validators;
21+
}
22+
23+
public bool IsValid(Client client, string clientSecret)
24+
{
25+
if(string.IsNullOrWhiteSpace(clientSecret))
26+
{
27+
return false;
28+
}
29+
30+
foreach (var validator in _validators)
31+
{
32+
if (validator.IsValid(client, clientSecret))
33+
{
34+
return true;
35+
}
36+
}
37+
38+
return false;
39+
}
40+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright (c) SimpleIdServer. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
using SimpleIdServer.IdServer.Domains;
4+
5+
namespace SimpleIdServer.IdServer.Authenticate.Validations;
6+
7+
public interface IAlgClientSecretValidator
8+
{
9+
bool IsValid(Client client, string clientSecret);
10+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) SimpleIdServer. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
using SimpleIdServer.IdServer.Domains;
4+
using System;
5+
6+
namespace SimpleIdServer.IdServer.Authenticate.Validations;
7+
8+
public class PlainTextClientSecretValidator : IAlgClientSecretValidator
9+
{
10+
public bool IsValid(Client client, string clientSecret)
11+
{
12+
return string.Compare(client.ClientSecret, clientSecret, StringComparison.CurrentCultureIgnoreCase) == 0;
13+
}
14+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) SimpleIdServer. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
using SimpleIdServer.IdServer.Domains;
4+
5+
namespace SimpleIdServer.IdServer.Authenticate.Validations;
6+
7+
public class Sha256ClientSecretValidator : IAlgClientSecretValidator
8+
{
9+
public bool IsValid(Client client, string clientSecret)
10+
{
11+
return new ClientSecret(clientSecret).Sha256() == client.ClientSecret;
12+
}
13+
}

0 commit comments

Comments
 (0)