using System.Security.Cryptography; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using Tiku.Application.Security; namespace Tiku.Infrastructure.Auth; internal sealed class JwtKeyRing : IJwtKeyRing, IDisposable { private readonly List keys = []; public JwtKeyRing(IOptions options) { var value = options.Value; var signingRsa = RSA.Create(3072); keys.Add(signingRsa); if (!string.IsNullOrWhiteSpace(value.PrivateKeyPem)) { signingRsa.ImportFromPem(value.PrivateKeyPem); } var signingKey = CreateKey(signingRsa, value.KeyId); SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.RsaSha256); var validationKeys = new List { signingKey }; foreach (var pair in value.PublicKeys.Where(pair => pair.Key != value.KeyId)) { var rsa = RSA.Create(); rsa.ImportFromPem(pair.Value); keys.Add(rsa); validationKeys.Add(CreateKey(rsa, pair.Key)); } ValidationKeys = validationKeys; } public SigningCredentials SigningCredentials { get; } public IReadOnlyCollection ValidationKeys { get; } private static RsaSecurityKey CreateKey(RSA rsa, string keyId) => new(rsa) { KeyId = keyId, // IdentityModel caches signature providers globally by key identity. A key ring owns // and disposes its RSA instances, so a provider retained by another in-process host // could otherwise reference an RSA instance that has already been disposed. CryptoProviderFactory = new CryptoProviderFactory { CacheSignatureProviders = false } }; public void Dispose() { foreach (var key in keys) { key.Dispose(); } } }