Skip to content

Commit 1eb9e93

Browse files
Client can have one or more secret
1 parent c7dbfad commit 1eb9e93

43 files changed

Lines changed: 769 additions & 111 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/IdServer/SimpleIdServer.IdServer.Domains/Client.cs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ public class Client : ITranslatable, IEquatable<Client>
2424
{
2525
[JsonPropertyName(OAuthClientParameters.Id)]
2626
public string Id { get; set; }
27+
public string ClientSecret { get; set; } = null!;
28+
public DateTime? ClientSecretExpirationTime { get; set; }
2729
[JsonPropertyName(OAuthClientParameters.Source)]
2830
public string? Source { get; set; }
2931
[JsonPropertyName(OAuthClientParameters.IsPublic)]
@@ -34,10 +36,10 @@ public class Client : ITranslatable, IEquatable<Client>
3436
[JsonPropertyName(OAuthClientParameters.ClientId)]
3537
public string ClientId { get; set; } = null!;
3638
/// <summary>
37-
/// Client secret.
39+
/// Client secrets.
3840
/// </summary>
39-
[JsonPropertyName(OAuthClientParameters.ClientSecret)]
40-
public string ClientSecret { get; set; } = null!;
41+
[JsonPropertyName(OAuthClientParameters.ClientSecrets)]
42+
public List<ClientSecret> Secrets { get; set; } = new List<ClientSecret>();
4143
/// <summary>
4244
/// String containing the access token to be used at the client configuration endpoint to perform subsequent operations upon the client registration.
4345
/// </summary>
@@ -178,12 +180,6 @@ public string? PolicyUri
178180
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
179181
public string? TlsClientAuthSanEmail { get; set; } = null;
180182
/// <summary>
181-
/// Client secret expiration time.
182-
/// </summary>
183-
[JsonPropertyName(OAuthClientParameters.ClientSecretExpiresAt)]
184-
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
185-
public DateTime? ClientSecretExpirationTime { get; set; }
186-
/// <summary>
187183
/// Update date time.
188184
/// </summary>
189185
[JsonPropertyName(OAuthClientParameters.UpdateDateTime)]
@@ -615,6 +611,14 @@ public JsonObject Parameters
615611
[JsonIgnore]
616612
public ICollection<DeviceAuthCode> DeviceAuthCodes { get; set; } = new List<DeviceAuthCode>();
617613

614+
public ClientSecret PlainSecret
615+
{
616+
get
617+
{
618+
return Secrets.SingleOrDefault(s => s.Alg == HashAlgs.PLAINTEXT && !s.IsExpired);
619+
}
620+
}
621+
618622
public double? GetDoubleParameter(string name)
619623
{
620624
if (!Parameters.ContainsKey(name)) return null;
@@ -634,6 +638,20 @@ public void Add(string keyId, JsonWebKey jsonWebKey, string usage, SecurityKeyTy
634638
});
635639
}
636640

641+
public void Add(ClientSecret secret)
642+
{
643+
if(secret.Alg == HashAlgs.PLAINTEXT)
644+
{
645+
var otherSecrets = Secrets.Where(s => s.Alg == HashAlgs.PLAINTEXT).ToList();
646+
foreach(var otherSecret in otherSecrets)
647+
{
648+
otherSecret.IsActive = false;
649+
}
650+
}
651+
652+
Secrets.Add(secret);
653+
}
654+
637655
public string GetStringParameter(string name) => Parameters[name].ToString();
638656

639657
public void UpdateClientName(string clientName)

src/IdServer/SimpleIdServer.IdServer.Domains/ClientSecret.cs

Lines changed: 111 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,128 @@ namespace SimpleIdServer.IdServer.Domains;
77

88
public class ClientSecret
99
{
10-
private string _value;
10+
private static Dictionary<HashAlgs, int> _mappingAlgToSize = new Dictionary<HashAlgs, int>
11+
{
12+
{ HashAlgs.MD5, MD5.HashSizeInBytes },
13+
{ HashAlgs.SHA1, SHA1.HashSizeInBytes },
14+
{ HashAlgs.SHA256, SHA256.HashSizeInBytes },
15+
{ HashAlgs.SHA384, SHA384.HashSizeInBytes },
16+
{ HashAlgs.SHA512, SHA512.HashSizeInBytes }
17+
};
18+
19+
private static (HashAlgs Alg, Func<string, string> HashFunc)[] _hashAlgs = new (HashAlgs Alg, Func<string, string> HashFunc)[]
20+
{
21+
(HashAlgs.SHA1, (string pwd) => Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(pwd)))),
22+
(HashAlgs.SHA256, (string pwd) => Convert.ToBase64String(SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(pwd)))),
23+
(HashAlgs.SHA384, (string pwd) => Convert.ToBase64String(SHA384.Create().ComputeHash(Encoding.UTF8.GetBytes(pwd)))),
24+
(HashAlgs.SHA512, (string pwd) => Convert.ToBase64String(SHA512.Create().ComputeHash(Encoding.UTF8.GetBytes(pwd)))),
25+
(HashAlgs.MD5, (string pwd) => Convert.ToBase64String(MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(pwd)))),
26+
(HashAlgs.PLAINTEXT, (string pwd) => pwd)
27+
};
28+
29+
public string Id
30+
{
31+
get; set;
32+
}
33+
34+
public string Value
35+
{
36+
get; set;
37+
}
38+
39+
public HashAlgs Alg
40+
{
41+
get; set;
42+
}
43+
44+
public DateTime? ExpirationDateTime
45+
{
46+
get; set;
47+
}
48+
49+
public DateTime CreateDateTime
50+
{
51+
get; set;
52+
}
1153

12-
public ClientSecret(string value)
54+
public bool IsActive
1355
{
14-
_value = value;
56+
get; set;
57+
} = true;
58+
59+
public bool IsExpired
60+
{
61+
get
62+
{
63+
if (ExpirationDateTime.HasValue == false) return false;
64+
return ExpirationDateTime.Value < DateTime.UtcNow && IsActive;
65+
}
1566
}
1667

17-
public string Sha256()
68+
public static ClientSecret Create(string pwd, HashAlgs alg, DateTime? expirationTime = null)
1869
{
19-
using (var sha256 = SHA256.Create())
70+
var hashedPwd = _hashAlgs.Single((a) => a.Alg == alg).HashFunc(pwd);
71+
return new ClientSecret
2072
{
21-
var hashPayload = sha256.ComputeHash(Encoding.UTF8.GetBytes(_value));
22-
return Convert.ToBase64String(hashPayload);
73+
Id = Guid.NewGuid().ToString(),
74+
Alg = alg,
75+
Value = hashedPwd,
76+
CreateDateTime = DateTime.UtcNow,
77+
IsActive = true,
78+
ExpirationDateTime = expirationTime
79+
};
80+
}
81+
82+
public static ClientSecret Resolve(string str)
83+
{
84+
var alg = HashAlgs.PLAINTEXT;
85+
if (TryDecodeBase64(str, out byte[] payload))
86+
{
87+
var size = payload.Length;
88+
if (_mappingAlgToSize.ContainsValue(size))
89+
{
90+
alg = _mappingAlgToSize.Single(kvp => kvp.Value == size).Key;
91+
}
92+
2393
}
94+
95+
return new ClientSecret
96+
{
97+
Id = Guid.NewGuid().ToString(),
98+
Alg = alg,
99+
Value = str,
100+
CreateDateTime = DateTime.UtcNow
101+
};
24102
}
25103

26-
public string Sha512()
104+
public static bool TryDecodeBase64(string s, out byte[] result)
27105
{
28-
using (var sha512 = SHA512.Create())
106+
if (s.Length % 4 != 0)
29107
{
30-
var hashPayload = sha512.ComputeHash(Encoding.UTF8.GetBytes(_value));
31-
return Convert.ToBase64String(hashPayload);
108+
result = null;
109+
return false;
110+
}
111+
112+
try
113+
{
114+
result = Convert.FromBase64String(s);
115+
return true;
116+
}
117+
catch (FormatException)
118+
{
119+
result = null;
120+
return false;
32121
}
33122
}
34123
}
124+
125+
126+
public enum HashAlgs
127+
{
128+
PLAINTEXT = 0,
129+
MD5 = 1,
130+
SHA1 = 2,
131+
SHA256 = 3,
132+
SHA384 = 4,
133+
SHA512 = 5
134+
}
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+
4+
using System.Diagnostics.Contracts;
5+
6+
namespace SimpleIdServer.IdServer.Domains.DTOs;
7+
8+
public class ClientSecretNames
9+
{
10+
public const string Alg = "alg";
11+
public const string Value = "value";
12+
public const string Id = "id";
13+
}

src/IdServer/SimpleIdServer.IdServer.Domains/DTOs/OAuthClientParameters.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ public class OAuthClientParameters
88
public const string IsPublic = "is_public";
99
public const string ClientId = "client_id";
1010
public const string ClientType = "client_type";
11-
public const string ClientSecret = "client_secret";
11+
public const string ClientSecrets = "client_secrets";
1212
public const string ClientIdIssuedAt = "client_id_issued_at";
13-
public const string ClientSecretExpiresAt = "client_secret_expires_at";
1413
public const string RegistrationAccessToken = "registration_access_token";
1514
public const string GrantTypes = "grant_types";
1615
public const string RedirectUris = "redirect_uris";

src/IdServer/SimpleIdServer.IdServer.Federation/ClientRegistrationService.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,10 @@ private async Task<Client> AddClient(string realm, (Client, OpenidTrustChain) re
166166
};
167167
newClient.Scopes = await _scopeRepository.GetByNames(realm, newClient.Scopes.Select(s => s.Name).ToList(), cancellationToken);
168168
newClient.ClientType = ClientTypes.FEDERATION;
169-
newClient.ClientSecret = Guid.NewGuid().ToString();
169+
newClient.Secrets = new List<ClientSecret>
170+
{
171+
ClientSecret.Create(Guid.NewGuid().ToString(), HashAlgs.PLAINTEXT)
172+
};
170173
newClient.ExpirationDateTime = trustChain.ExpirationDateTime;
171174
newClient.UpdateDateTime = DateTime.UtcNow;
172175
newClient.CreateDateTime = DateTime.UtcNow;

src/IdServer/SimpleIdServer.IdServer.IntegrationEvents/ClientCredentialUpdatedFailureEvent.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ public class ClientCredentialUpdatedFailureEvent : IIntegrationEvent
88
public string Realm { get; set; }
99
public string ClientId { get; set; }
1010
public string TokenEndpointAuthMethod { get; set; }
11-
public string ClientSecret { get; set; }
1211
public string TlsClientAuthSubjectDN { get; set; }
1312
public string TlsClientAuthSanDNS { get; set; }
1413
public string TlsClientAuthSanEmail { get; set; }

src/IdServer/SimpleIdServer.IdServer.IntegrationEvents/ClientCredentialUpdatedSuccessEvent.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ public class ClientCredentialUpdatedSuccessEvent : IIntegrationEvent
77
public string Realm { get; set; }
88
public string ClientId { get; set; }
99
public string TokenEndpointAuthMethod { get; set; }
10-
public string ClientSecret { get; set; }
1110
public string TlsClientAuthSubjectDN { get; set; }
1211
public string TlsClientAuthSanDNS { get; set; }
1312
public string TlsClientAuthSanEmail { get; set; }

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ private static Client Map(DuendeClient client, List<Scope> scopes)
261261
IsConsentDisabled = !client.RequireConsent,
262262
Scopes = scopes,
263263
SerializedJsonWebKeys = ResolveSerializedJsonWebKeys(client),
264-
ClientSecret = ResolveClientSecret(client),
264+
Secrets = ResolveClientSecrets(client),
265265
CreateDateTime = client.Created,
266266
UpdateDateTime = client.Updated ?? client.Created,
267267
AuthorizationCodeExpirationInSeconds = client.AuthorizationCodeLifetime,
@@ -354,16 +354,17 @@ private static int ResolveDPOPNonceLifetimeInSeconds(DuendeClient client)
354354
return (int)client.DPoPClockSkew.TotalSeconds;
355355
}
356356

357-
private static string ResolveClientSecret(DuendeClient client)
357+
private static List<ClientSecret> ResolveClientSecrets(DuendeClient client)
358358
{
359359
const string clientSecretType = "SharedSecret";
360-
var clientSecret = client.ClientSecrets.FirstOrDefault(s => s.Type == clientSecretType && s.Expiration == null);
361-
if (clientSecret == null)
360+
var clientSecrets = client.ClientSecrets.Where(s => s.Type == clientSecretType);
361+
return clientSecrets.Select(s =>
362362
{
363-
clientSecret = client.ClientSecrets.FirstOrDefault(s => s.Type == clientSecretType && s.Expiration != null && s.Expiration >= DateTime.UtcNow);
364-
}
365-
366-
return clientSecret?.Value ?? Guid.NewGuid().ToString();
363+
var result = ClientSecret.Resolve(s.Value);
364+
result.ExpirationDateTime = s.Expiration;
365+
result.CreateDateTime = s.Created;
366+
return result;
367+
}).ToList();
367368
}
368369

369370
private static UserClaim Map(IdentityUserClaim<string> userClaim)

src/IdServer/SimpleIdServer.IdServer.Saml.Idp/Builders/SamlSpClientBuilder.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,14 @@ public static SamlSpClientBuilder BuildSamlSpClient(string clientId, string meta
2323
var client = new Client
2424
{
2525
Id = Guid.NewGuid().ToString(),
26-
ClientSecret = Guid.NewGuid().ToString(),
2726
ClientId = clientId,
2827
ClientType = ClientTypes.SAML,
2928
CreateDateTime = DateTime.UtcNow,
30-
UpdateDateTime = DateTime.UtcNow
29+
UpdateDateTime = DateTime.UtcNow,
30+
Secrets = new List<ClientSecret>
31+
{
32+
ClientSecret.Create(Guid.NewGuid().ToString(), HashAlgs.PLAINTEXT)
33+
}
3134
};
3235
if (realm == null) client.Realms.Add(Config.DefaultRealms.Master);
3336
else client.Realms.Add(realm);

src/IdServer/SimpleIdServer.IdServer.Startup/Program.cs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,16 @@
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.
33
using Microsoft.AspNetCore.Builder;
4-
using Microsoft.AspNetCore.Identity;
54
using Microsoft.Extensions.Configuration;
65
using Microsoft.Extensions.DependencyInjection;
76
using Microsoft.Extensions.Hosting;
8-
using SimpleIdServer.IdServer.Domains;
97
using SimpleIdServer.IdServer.Startup;
108
using SimpleIdServer.IdServer.Startup.Conf;
9+
using System;
1110

1211

13-
var hashedPwd = "AQAAAAIAAYagAAAAEJwcFDmZdWMYEMLLTWiEfYvLsAUFtsMPR3/JMKIE9Zh4o76Wl2020g4stjHqv05zwg==";
14-
var pp = "Pass123$";
15-
var hasher = new PasswordHasher<User>();
16-
var b = hasher.VerifyHashedPassword(new User(), hashedPwd, pp);
17-
if(b == PasswordVerificationResult.Success)
18-
{
19-
20-
}
12+
var hashedPwd = "o90IbCACXKUkunXoa18cODcLKnQTbjOo5ihEw9j58+8=";
13+
var s = Convert.FromBase64String(hashedPwd);
2114

2215
var webApplicationBuilder = WebApplication.CreateBuilder(args);
2316
webApplicationBuilder.Services.AddCors(options => options.AddPolicy("AllowAll", p => p.AllowAnyOrigin()

0 commit comments

Comments
 (0)