Skip to content

Commit 06d627a

Browse files
authored
Merge pull request #2 from filipw/keymanager
Added support for automatic key management
2 parents 9483714 + bef770a commit 06d627a

File tree

10 files changed

+993
-12
lines changed

10 files changed

+993
-12
lines changed

samples/IdentityServer/HostingExtensions.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
using System.Reflection;
21
using System.Text.Json;
3-
using Duende.IdentityServer;
4-
using Duende.IdentityServer.Models;
2+
using Duende.IdentityServer.Configuration;
53
using Duende.IdentityServer.ResponseHandling;
6-
using Duende.IdentityServer.Services;
7-
using Duende.IdentityServer.Stores;
8-
using Microsoft.IdentityModel.Tokens;
4+
using Duende.IdentityServer.Services.KeyManagement;
95
using Strathweb.Dilithium.DuendeIdentityServer;
10-
using Strathweb.Dilithium.IdentityModel;
6+
using Strathweb.Dilithium.DuendeIdentityServer.KeyManagement;
117
using JsonWebKey = Microsoft.IdentityModel.Tokens.JsonWebKey;
128

139
namespace IdentityServer;
@@ -16,12 +12,16 @@ internal static class HostingExtensions
1612
{
1713
public static WebApplication ConfigureServices(this WebApplicationBuilder builder)
1814
{
19-
var rawJwk = File.ReadAllText(Path.Combine(Directory.GetCurrentDirectory(), "crydi3.json"));
20-
var jwk = JsonSerializer.Deserialize<JsonWebKey>(rawJwk);
15+
//var rawJwk = File.ReadAllText(Path.Combine(Directory.GetCurrentDirectory(), "crydi3.json"));
16+
//var jwk = JsonSerializer.Deserialize<JsonWebKey>(rawJwk);
2117

22-
builder.Services.AddIdentityServer(opt => opt.EmitStaticAudienceClaim = true)
23-
//.AddDilithiumSigningCredential(new DilithiumSecurityKey("CRYDI3")) // new key per startup
24-
.AddDilithiumSigningCredential(new DilithiumSecurityKey(jwk)) // key from the filesystem
18+
builder.Services.AddIdentityServer(opt =>
19+
{
20+
opt.EmitStaticAudienceClaim = true;
21+
})
22+
.AddDilithiumSupport() // automatic key management
23+
//.AddDilithiumSigningCredential(new DilithiumSecurityKey("CRYDI3")) // new fixed key per startup
24+
//.AddDilithiumSigningCredential(new DilithiumSecurityKey(jwk)) // fixed key from the filesystem / storage
2525
.AddInMemoryApiScopes(Config.ApiScopes)
2626
.AddInMemoryApiResources(Config.ApiResources)
2727
.AddInMemoryClients(Config.Clients);

samples/IdentityServer/IdentityServer.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,8 @@
1212
<ItemGroup>
1313
<ProjectReference Include="..\..\src\Strathweb.Dilithium.DuendeIdentityServer\Strathweb.Dilithium.DuendeIdentityServer.csproj" />
1414
</ItemGroup>
15+
16+
<ItemGroup>
17+
<_ContentIncludedByDefault Remove="keys\is-signing-key-30F471175932A17FAD74ABFBE856E9AA.json" />
18+
</ItemGroup>
1519
</Project>

src/Strathweb.Dilithium.DuendeIdentityServer/DilithiumIdentityServerExtensions.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,77 @@
11
using System.Text.Json;
2+
using Duende.IdentityServer.Configuration;
23
using Duende.IdentityServer.Models;
4+
using Duende.IdentityServer.ResponseHandling;
5+
using Duende.IdentityServer.Services.KeyManagement;
36
using Duende.IdentityServer.Stores;
47
using Microsoft.Extensions.DependencyInjection;
58
using Microsoft.IdentityModel.Tokens;
9+
using Strathweb.Dilithium.DuendeIdentityServer.KeyManagement;
610
using Strathweb.Dilithium.IdentityModel;
711
using JsonWebKey = Microsoft.IdentityModel.Tokens.JsonWebKey;
812

913
namespace Strathweb.Dilithium.DuendeIdentityServer;
1014

1115
public static class DilithiumIdentityServerExtensions
1216
{
17+
public static IIdentityServerBuilder AddDilithiumSupport(this IIdentityServerBuilder builder)
18+
{
19+
return builder.AddDilithiumSupport(new DilithiumSupportOptions());
20+
}
21+
22+
public static IIdentityServerBuilder AddDilithiumSupport(this IIdentityServerBuilder builder, DilithiumSupportOptions dilithiumSupportOptions)
23+
{
24+
if (builder == null) throw new ArgumentNullException(nameof(builder));
25+
if (dilithiumSupportOptions == null) throw new ArgumentNullException(nameof(dilithiumSupportOptions));
26+
27+
if (dilithiumSupportOptions.EnableKeyManagement)
28+
{
29+
if (dilithiumSupportOptions.StaticKey != null)
30+
{
31+
throw new ArgumentException(
32+
"It is not possible to use both automatic key management and a static key. Choose one or the other.");
33+
}
34+
35+
if (dilithiumSupportOptions.KeyManagementAlgorithm != "CRYDI2" && dilithiumSupportOptions.KeyManagementAlgorithm != "CRYDI3" && dilithiumSupportOptions.KeyManagementAlgorithm != "CRYDI5")
36+
{
37+
throw new NotSupportedException(
38+
$"Algorithm {dilithiumSupportOptions.KeyManagementAlgorithm} is not supported. Supported algorithms: CRYDI2, CRYDI3 and CRYDI5.");
39+
}
40+
41+
if (dilithiumSupportOptions.DisallowNonDilithiumKeys)
42+
{
43+
builder.Services.Configure((IdentityServerOptions identityServerOptions) =>
44+
{
45+
identityServerOptions.KeyManagement.Enabled = true;
46+
identityServerOptions.KeyManagement.SigningAlgorithms = new[]
47+
{
48+
new SigningAlgorithmOptions(dilithiumSupportOptions.KeyManagementAlgorithm)
49+
};
50+
});
51+
}
52+
else
53+
{
54+
builder.Services.Configure((IdentityServerOptions identityServerOptions) =>
55+
{
56+
identityServerOptions.KeyManagement.Enabled = true;
57+
var configuredAlgorithms = identityServerOptions.KeyManagement.SigningAlgorithms.ToList();
58+
configuredAlgorithms.Add(
59+
new SigningAlgorithmOptions(dilithiumSupportOptions.KeyManagementAlgorithm));
60+
identityServerOptions.KeyManagement.SigningAlgorithms = configuredAlgorithms;
61+
});
62+
}
63+
builder.Services.AddTransient<IKeyManager, DilithiumKeyManager>();
64+
builder.Services.AddTransient<ISigningKeyProtector, DilithiumDataProtectionKeyProtector>();
65+
builder.Services.AddTransient<IDiscoveryResponseGenerator, DilithiumAwareDiscoveryResponseGenerator>();
66+
}
67+
else if (dilithiumSupportOptions.StaticKey is { } fixedKey)
68+
{
69+
builder.AddDilithiumSigningCredential(fixedKey);
70+
}
71+
72+
return builder;
73+
}
74+
1375
public static IIdentityServerBuilder AddDilithiumSigningCredential(this IIdentityServerBuilder builder, DilithiumSecurityKey securityKey)
1476
{
1577
var credential = new SigningCredentials(securityKey, securityKey.SupportedAlgorithm);
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Strathweb.Dilithium.IdentityModel;
2+
3+
namespace Strathweb.Dilithium.DuendeIdentityServer;
4+
5+
public class DilithiumSupportOptions
6+
{
7+
public bool EnableKeyManagement { get; set; } = true;
8+
9+
public string KeyManagementAlgorithm { get; set; } = "CRYDI3";
10+
11+
public bool DisallowNonDilithiumKeys { get; set; } = true;
12+
13+
public DilithiumSecurityKey? StaticKey { get; set; }
14+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using Duende.IdentityServer.Configuration;
2+
using Duende.IdentityServer.Models;
3+
using Duende.IdentityServer.ResponseHandling;
4+
using Duende.IdentityServer.Services;
5+
using Duende.IdentityServer.Stores;
6+
using Duende.IdentityServer.Validation;
7+
using Microsoft.Extensions.Logging;
8+
using Strathweb.Dilithium.IdentityModel;
9+
10+
namespace Strathweb.Dilithium.DuendeIdentityServer.KeyManagement;
11+
12+
public class DilithiumAwareDiscoveryResponseGenerator : DiscoveryResponseGenerator
13+
{
14+
public DilithiumAwareDiscoveryResponseGenerator(IdentityServerOptions options, IResourceStore resourceStore, IKeyMaterialService keys, ExtensionGrantValidator extensionGrants, ISecretsListParser secretParsers, IResourceOwnerPasswordValidator resourceOwnerValidator, ILogger<DiscoveryResponseGenerator> logger) : base(options, resourceStore, keys, extensionGrants, secretParsers, resourceOwnerValidator, logger)
15+
{
16+
}
17+
18+
public override async Task<IEnumerable<JsonWebKey>> CreateJwkDocumentAsync()
19+
{
20+
var publishedKeys = (await base.CreateJwkDocumentAsync()).ToList();
21+
var dilithiumKeys = (await Keys.GetValidationKeysAsync()).Where(key => key.Key is DilithiumSecurityKey);
22+
23+
foreach (var dilithiumKey in dilithiumKeys)
24+
{
25+
var jsonWebKey = (dilithiumKey.Key as DilithiumSecurityKey)?.ToJsonWebKey(includePrivateKey: false);
26+
var webKey = new JsonWebKey
27+
{
28+
kty = jsonWebKey.Kty,
29+
use = jsonWebKey.Use ?? "sig",
30+
kid = jsonWebKey.Kid,
31+
x5t = jsonWebKey.X5t,
32+
e = jsonWebKey.E,
33+
n = jsonWebKey.N,
34+
x5c = jsonWebKey.X5c?.Count == 0 ? null : jsonWebKey.X5c.ToArray(),
35+
alg = jsonWebKey.Alg,
36+
crv = jsonWebKey.Crv,
37+
x = jsonWebKey.X,
38+
y = jsonWebKey.Y
39+
};
40+
publishedKeys.Add(webKey);
41+
}
42+
43+
return publishedKeys;
44+
}
45+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using Duende.IdentityServer.Configuration;
2+
using Duende.IdentityServer.Models;
3+
using Duende.IdentityServer.Services.KeyManagement;
4+
using Microsoft.AspNetCore.DataProtection;
5+
6+
namespace Strathweb.Dilithium.DuendeIdentityServer.KeyManagement;
7+
8+
public class DilithiumDataProtectionKeyProtector : ISigningKeyProtector
9+
{
10+
private readonly IDataProtector _dataProtectionProvider;
11+
private readonly KeyManagementOptions _options;
12+
13+
public DilithiumDataProtectionKeyProtector(KeyManagementOptions options, IDataProtectionProvider dataProtectionProvider)
14+
{
15+
_options = options;
16+
_dataProtectionProvider = dataProtectionProvider.CreateProtector(nameof(DataProtectionKeyProtector));
17+
}
18+
19+
public SerializedKey Protect(KeyContainer key)
20+
{
21+
var data = KeySerializer.Serialize(key);
22+
23+
if (_options.DataProtectKeys)
24+
{
25+
data = _dataProtectionProvider.Protect(data);
26+
}
27+
28+
return new SerializedKey
29+
{
30+
Version = 1,
31+
Created = DateTime.UtcNow,
32+
Id = key.Id,
33+
Algorithm = key.Algorithm,
34+
IsX509Certificate = key.HasX509Certificate,
35+
Data = data,
36+
DataProtected = _options.DataProtectKeys,
37+
};
38+
}
39+
40+
public KeyContainer Unprotect(SerializedKey key)
41+
{
42+
var data = key.DataProtected ?
43+
_dataProtectionProvider.Unprotect(key.Data) :
44+
key.Data;
45+
46+
if (key.IsX509Certificate)
47+
{
48+
return KeySerializer.Deserialize<X509KeyContainer>(data);
49+
}
50+
51+
if (key.Algorithm.StartsWith("R") || key.Algorithm.StartsWith("P"))
52+
{
53+
return KeySerializer.Deserialize<RsaKeyContainer>(data);
54+
}
55+
56+
if (key.Algorithm.StartsWith("E"))
57+
{
58+
return KeySerializer.Deserialize<EcKeyContainer>(data);
59+
}
60+
61+
if (key.Algorithm.StartsWith("CRYDI"))
62+
{
63+
Console.WriteLine("Deserializing protected dilithium key");
64+
return KeySerializer.Deserialize<DilithiumKeyContainer>(data);
65+
}
66+
67+
throw new Exception($"Invalid Algorithm: {key.Algorithm} for kid: {key.Id}");
68+
}
69+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using Duende.IdentityServer.Services.KeyManagement;
2+
using Microsoft.IdentityModel.Tokens;
3+
using Strathweb.Dilithium.IdentityModel;
4+
5+
namespace Strathweb.Dilithium.DuendeIdentityServer.KeyManagement;
6+
7+
public class DilithiumKeyContainer : KeyContainer
8+
{
9+
/// <summary>
10+
/// Constructor for DilithiumKeyContainer.
11+
/// </summary>
12+
public DilithiumKeyContainer() : base()
13+
{
14+
}
15+
16+
/// <summary>
17+
/// Constructor for DilithiumKeyContainer.
18+
/// </summary>
19+
public DilithiumKeyContainer(DilithiumSecurityKey key, string algorithm, DateTime created)
20+
: base(key.KeyId, algorithm, created)
21+
{
22+
D = key.PrivateKey.GetEncoded();
23+
X = key.PublicKey.GetEncoded();
24+
}
25+
26+
/// <summary>
27+
/// Private key
28+
/// </summary>
29+
public byte[] D { get; set; }
30+
31+
/// <summary>
32+
/// Public key
33+
/// </summary>
34+
public byte[] X { get; set; }
35+
36+
/// <inheritdoc/>
37+
public override AsymmetricSecurityKey ToSecurityKey()
38+
{
39+
return new DilithiumSecurityKey(Algorithm, Id, X, D);
40+
}
41+
}

0 commit comments

Comments
 (0)