20 Commits

Author SHA1 Message Date
Developer 02
608d266d1c refactor(IAsymmetricKey): Umwandlung von RsaSecurityKey in SecurityKey zur besseren Abstraktion.
- RSAEncryptionPadding entfernen
 - Pem als Inhalt Content
2025-01-07 16:53:05 +01:00
Developer 02
34e14fd2f5 refactor(RSATokenDescriptor): In die Abstraktionsschicht verschoben und in PrivateKeyTokenDescriptor umbenannt 2025-01-07 16:34:19 +01:00
Developer 02
dc45cf2c08 refactor(JwtSignatureHandler): Aktualisiert, um RSAPrivateKey anstelle des Deskriptors zu verwenden 2025-01-07 13:55:30 +01:00
Developer 02
09a31b5a3d refactor(TokenDescription): Nach RSAKey verschoben, um unter RSAPrivateKey definiert werden zu können 2025-01-07 13:22:45 +01:00
Developer 02
b5cecac745 refactor(DIExtensions): Umbenennung der Methode AddAsymCryptHandler in AddCryptograph 2025-01-07 12:19:42 +01:00
Developer 02
0f4b5430a3 refactor(AsymCryptParams): Umbenennen in CryptographParams. 2025-01-07 12:12:50 +01:00
Developer 02
7f2d2dadfa refactor(DigitalData.Core.Security): Umbenennung des Unternamensraums von Cryptographer in RSAKey 2025-01-07 12:09:34 +01:00
Developer 02
ac0b6f739b refactor(AsymCryptHandler): Renamed to Cryptograph 2025-01-07 12:03:01 +01:00
Developer 02
d9d61368e3 refactor(IAsymCryptHandler): Umbenannt in ICryptograph 2025-01-07 12:01:39 +01:00
Developer 02
e8c98115b6 refactor(IRSAFactory): umbenannt in IAsymmetricKey 2025-01-07 11:48:27 +01:00
Developer 02
09dae1b1ac refactor(IRSAEncryptor): umbenannt in RSAPublicKey 2025-01-07 11:39:12 +01:00
Developer 02
9aafc9e467 refactor(IRSAEncryptor): umbenannt in IAsymmetricPublicKey 2025-01-07 11:33:53 +01:00
Developer 02
4ce738957d refactor(RSADecryptor): umbenennen in RSAPrivateKey 2025-01-07 11:20:24 +01:00
Developer 02
5e1bf16b6d refactor(IRSADecryptor): Umbenennung in IAsymmetricPrivateKey 2025-01-07 11:16:12 +01:00
Developer 02
4f96d271f3 refactor(IRSACryptographer): Umbenennung in IAsymmetricKey 2025-01-07 11:03:14 +01:00
Developer 02
14485af448 fix(DIExtensions): Umbenennung der Methode AddAsymCryptService in AddAsymCryptHandler 2025-01-07 10:38:10 +01:00
Developer 02
c27e21a702 fix(JwtSignatureHandler): The nullability of TokenParams has been removed. 2025-01-07 10:36:17 +01:00
Developer 02
4874079b69 fix: TokenParams-Kaliber erstellt, um Token-Beschreibungen über IOptions zu konfigurieren 2025-01-07 10:21:25 +01:00
Developer 02
15e909064f feat(IJwtSignatureHandler): Unterstützung für die Erstellung von Token durch den Routenwert der Tokenbeschreibung hinzugefügt. 2025-01-07 09:35:09 +01:00
Developer 02
d17c5ca6cd feat(JwtSignatureHandler): Unterstützung für die Erstellung von Token durch den Routenwert der Tokenbeschreibung hinzugefügt. 2025-01-07 09:30:33 +01:00
20 changed files with 189 additions and 174 deletions

View File

@@ -1,11 +0,0 @@
namespace DigitalData.Core.Abstractions.Security
{
public interface IAsymCryptHandler : IRSAFactory
{
IEnumerable<IRSADecryptor> Decryptors { get; }
IRSADecryptor Vault { get; }
IEnumerable<IRSAEncryptor> Encryptors { get; }
}
}

View File

@@ -3,16 +3,14 @@ using System.Security.Cryptography;
namespace DigitalData.Core.Abstractions.Security namespace DigitalData.Core.Abstractions.Security
{ {
public interface IRSACryptographer : IUniqueSecurityContext public interface IAsymmetricKey : IUniqueSecurityContext
{ {
public string Pem { get; init; } public string Content { get; init; }
public RSAEncryptionPadding Padding { get; init; }
public new string Issuer { get; init; } public new string Issuer { get; init; }
public new string Audience { get; init; } public new string Audience { get; init; }
public RsaSecurityKey RsaSecurityKey { get; } public SecurityKey SecurityKey { get; }
} }
} }

View File

@@ -2,7 +2,7 @@
namespace DigitalData.Core.Abstractions.Security namespace DigitalData.Core.Abstractions.Security
{ {
public interface IRSAFactory public interface IAsymmetricKeyFactory
{ {
string CreatePrivateKeyPem(int? keySizeInBits = null, bool encrypt = false); string CreatePrivateKeyPem(int? keySizeInBits = null, bool encrypt = false);
@@ -18,6 +18,6 @@ namespace DigitalData.Core.Abstractions.Security
int? keySizeInBits = null, int? keySizeInBits = null,
string? password = null); string? password = null);
IRSADecryptor CreateDecryptor(string pem, string? issuer = null, string? audience = null, bool encrypt = false, RSAEncryptionPadding? padding = null); IAsymmetricPrivateKey CreatePrivateKey(string pem, string? issuer = null, string? audience = null, bool encrypt = false, RSAEncryptionPadding? padding = null);
} }
} }

View File

@@ -2,11 +2,13 @@
namespace DigitalData.Core.Abstractions.Security namespace DigitalData.Core.Abstractions.Security
{ {
public interface IRSADecryptor : IRSACryptographer public interface IAsymmetricPrivateKey : IAsymmetricKey
{ {
public bool IsEncrypted { get; init; } public bool IsEncrypted { get; init; }
IRSAEncryptor Encryptor { get; } IAsymmetricPublicKey PublicKey { get; }
PrivateKeyTokenDescriptor? TokenDescriptor { get; init; }
byte[] Decrypt(byte[] data); byte[] Decrypt(byte[] data);

View File

@@ -1,6 +1,6 @@
namespace DigitalData.Core.Abstractions.Security namespace DigitalData.Core.Abstractions.Security
{ {
public interface IRSAEncryptor : IRSACryptographer public interface IAsymmetricPublicKey : IAsymmetricKey
{ {
public byte[] Encrypt(byte[] data); public byte[] Encrypt(byte[] data);

View File

@@ -0,0 +1,11 @@
namespace DigitalData.Core.Abstractions.Security
{
public interface ICryptograph : IAsymmetricKeyFactory
{
IEnumerable<IAsymmetricPrivateKey> PrivateKeys { get; }
IAsymmetricPrivateKey VaultPrivateKey { get; }
IEnumerable<IAsymmetricPublicKey> PublicKeys { get; }
}
}

View File

@@ -8,10 +8,14 @@ namespace DigitalData.Core.Abstractions.Security
SecurityToken CreateToken(TPrincipal subject, string issuer, string audience); SecurityToken CreateToken(TPrincipal subject, string issuer, string audience);
SecurityToken CreateToken(TPrincipal subject, string apiRoute);
string WriteToken(SecurityToken token); string WriteToken(SecurityToken token);
string WriteToken(SecurityTokenDescriptor descriptor); string WriteToken(SecurityTokenDescriptor descriptor);
string WriteToken(TPrincipal subject, string issuer, string audience); string WriteToken(TPrincipal subject, string issuer, string audience);
string WriteToken(TPrincipal subject, string apiRoute);
} }
} }

View File

@@ -1,13 +1,14 @@
using DigitalData.Core.Abstractions.Security; using Microsoft.IdentityModel.Tokens;
using Microsoft.IdentityModel.Tokens;
namespace DigitalData.Core.Security.Config namespace DigitalData.Core.Abstractions.Security
{ {
/// <summary> /// <summary>
/// Contains some information which used to create a security token. Designed to abstract <see cref="SecurityTokenDescriptor"/> /// Contains some information which used to create a security token. Designed to abstract <see cref="SecurityTokenDescriptor"/>
/// </summary> /// </summary>
public class TokenDescription : IUniqueSecurityContext public class PrivateKeyTokenDescriptor : IUniqueSecurityContext
{ {
public string? ApiRoute { get; init; }
/// <summary> /// <summary>
/// Gets or sets the value of the 'audience' claim. /// Gets or sets the value of the 'audience' claim.
/// </summary> /// </summary>
@@ -29,7 +30,7 @@ namespace DigitalData.Core.Security.Config
public DateTime? Expires { get; set; } public DateTime? Expires { get; set; }
/// <summary> /// <summary>
/// Gets or sets the issuer of this <see cref="ITokenDescription"/>. /// Gets or sets the issuer of this <see cref="PrivateKeyTokenDescriptor"/>.
/// </summary> /// </summary>
public new string Issuer { get; set; } public new string Issuer { get; set; }
@@ -80,7 +81,7 @@ namespace DigitalData.Core.Security.Config
/// Specifies the signature algorithm to be applied to the <see cref="SigningCredentials"/>. /// Specifies the signature algorithm to be applied to the <see cref="SigningCredentials"/>.
/// Default is <see cref="SecurityAlgorithms.RsaSha256"/>. /// Default is <see cref="SecurityAlgorithms.RsaSha256"/>.
/// </summary> /// </summary>
public string SigningAlgorithm { get; init; } = SecurityAlgorithms.RsaSha256; public string SigningAlgorithm { get; internal set; } = SecurityAlgorithms.RsaSha256;
/// <summary> /// <summary>
/// Optionally specifies the digest algorithm to be applied during the signing process for the <see cref="SigningCredentials"/>. /// Optionally specifies the digest algorithm to be applied during the signing process for the <see cref="SigningCredentials"/>.

View File

@@ -1,9 +1,8 @@
using DigitalData.Core.Abstractions.Security; using DigitalData.Core.Security.RSAKey;
using DigitalData.Core.Security.Cryptographer;
namespace DigitalData.Core.Security.Config namespace DigitalData.Core.Security.Config
{ {
public class AsymCryptParams : RSAFactoryParams public class CryptographParams : RSAFactoryParams
{ {
public string PemDirectory { get; init; } = string.Empty; public string PemDirectory { get; init; } = string.Empty;
@@ -40,11 +39,11 @@ namespace DigitalData.Core.Security.Config
/// </summary> /// </summary>
public string DateTagFormat { get; init; } = "MM//2"; public string DateTagFormat { get; init; } = "MM//2";
public IEnumerable<RSADecryptor> Decryptors { get; init; } = new List<RSADecryptor>(); public IEnumerable<RSAPrivateKey> PrivateKeys { get; init; } = new List<RSAPrivateKey>();
public RSADecryptor? Vault { get; init; } public RSAPrivateKey? VaultPrivateKey { get; init; }
public AsymCryptParams() public CryptographParams()
{ {
// init decryptors // init decryptors
AfterCreate += () => AfterCreate += () =>
@@ -53,7 +52,7 @@ namespace DigitalData.Core.Security.Config
if (!Directory.Exists(PemDirectory)) if (!Directory.Exists(PemDirectory))
Directory.CreateDirectory(PemDirectory); Directory.CreateDirectory(PemDirectory);
foreach (var decryptor in Decryptors) foreach (var decryptor in PrivateKeys)
{ {
// set default path // set default path
if (decryptor.IsPemNull) if (decryptor.IsPemNull)

View File

@@ -1,4 +1,5 @@
using AutoMapper; using AutoMapper;
using DigitalData.Core.Abstractions.Security;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
namespace DigitalData.Core.Security.Config namespace DigitalData.Core.Security.Config
@@ -7,7 +8,7 @@ namespace DigitalData.Core.Security.Config
{ {
public MappingProfile() public MappingProfile()
{ {
CreateMap<TokenDescription, SecurityTokenDescriptor>(); CreateMap<PrivateKeyTokenDescriptor, SecurityTokenDescriptor>();
} }
} }
} }

View File

@@ -1,42 +1,42 @@
using DigitalData.Core.Abstractions.Security; using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Config; using DigitalData.Core.Security.Config;
using DigitalData.Core.Security.Cryptographer; using DigitalData.Core.Security.RSAKey;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
namespace DigitalData.Core.Security namespace DigitalData.Core.Security
{ {
public class AsymCryptHandler : RSAFactory<AsymCryptParams>, IAsymCryptHandler, IRSAFactory public class Cryptograph : RSAFactory<CryptographParams>, ICryptograph, IAsymmetricKeyFactory
{ {
public IEnumerable<IRSADecryptor> Decryptors { get; } public IEnumerable<IAsymmetricPrivateKey> PrivateKeys { get; }
/// <summary> /// <summary>
/// It is a separate decryptor for permanently stored encrypted data. It is assigned to the first Default decryptor by default. /// It is a separate decryptor for permanently stored encrypted data. It is assigned to the first Default decryptor by default.
/// </summary> /// </summary>
public IRSADecryptor Vault { get; } public IAsymmetricPrivateKey VaultPrivateKey { get; }
private readonly Lazy<IEnumerable<IRSAEncryptor>> _lazyEncryptors; private readonly Lazy<IEnumerable<IAsymmetricPublicKey>> _lazyPublicKeys;
public IEnumerable<IRSAEncryptor> Encryptors => _lazyEncryptors.Value; public IEnumerable<IAsymmetricPublicKey> PublicKeys => _lazyPublicKeys.Value;
public IEnumerable<TokenDescription> TokenDescriptions { get; init; } = new List<TokenDescription>(); public IEnumerable<PrivateKeyTokenDescriptor> TokenDescriptions { get; init; } = new List<PrivateKeyTokenDescriptor>();
public AsymCryptHandler(IOptions<AsymCryptParams> options, ILogger<AsymCryptHandler>? logger = null) : base(options) public Cryptograph(IOptions<CryptographParams> options, ILogger<Cryptograph>? logger = null) : base(options)
{ {
logger?.LogInformation("Core.Secrets version: {Version}, Created on: {CreationDate}.", Secrets.Version, Secrets.CreationDate.ToString("dd.MM.yyyy")); logger?.LogInformation("Core.Secrets version: {Version}, Created on: {CreationDate}.", Secrets.Version, Secrets.CreationDate.ToString("dd.MM.yyyy"));
if (!_params.Decryptors.Any()) if (!_params.PrivateKeys.Any())
throw new InvalidOperationException( throw new InvalidOperationException(
"Any decryptor is not found. Ensure that at least one decryptor is configured in the provided parameters. " + "Any decryptor is not found. Ensure that at least one decryptor is configured in the provided parameters. " +
"This issue typically arises if the configuration for decryptors is incomplete or missing. " + "This issue typically arises if the configuration for decryptors is incomplete or missing. " +
"Check the 'Decryptors' collection in the configuration and verify that it contains valid entries." "Check the 'Decryptors' collection in the configuration and verify that it contains valid entries."
); );
Decryptors = _params.Decryptors; PrivateKeys = _params.PrivateKeys;
Vault = _params.Vault ?? Decryptors.First(); VaultPrivateKey = _params.VaultPrivateKey ?? PrivateKeys.First();
_lazyEncryptors = new(Decryptors.Select(decryptor => decryptor.Encryptor)); _lazyPublicKeys = new(PrivateKeys.Select(decryptor => decryptor.PublicKey));
} }
} }
} }

View File

@@ -1,66 +0,0 @@
using DigitalData.Core.Abstractions.Security;
using Microsoft.IdentityModel.Tokens;
using System.Security.Cryptography;
namespace DigitalData.Core.Security.Cryptographer
{
public class RSADecryptor : RSACryptographer, IRSADecryptor, IRSACryptographer
{
private string? _pem;
public override string Pem
{
#pragma warning disable CS8603 // Possible null reference return.
get => _pem;
#pragma warning restore CS8603 // Possible null reference return.
init
{
_pem = value;
Init();
}
}
public bool IsPemNull => _pem is null;
public bool IsEncrypted { get; init; }
private readonly Lazy<IRSAEncryptor> _lazyEncryptor;
public IRSAEncryptor Encryptor => _lazyEncryptor.Value;
public RSADecryptor()
{
_lazyEncryptor = new(() => new RSAEncryptor()
{
Pem = RSA.ExportRSAPublicKeyPem(),
Padding = Padding
});
}
public byte[] Decrypt(byte[] data) => RSA.Decrypt(data, Padding);
public string Decrypt(string data) => RSA.Decrypt(data.Base64ToByte(), Padding).BytesToString();
internal void SetPem(string pem)
{
_pem = pem;
Init();
}
private void Init()
{
if (string.IsNullOrEmpty(_pem))
throw PemIsNullException;
if (IsEncrypted)
RSA.ImportFromEncryptedPem(Pem, Secrets.PBE_PASSWORD.AsSpan());
else
RSA.ImportFromPem(Pem);
}
private InvalidOperationException PemIsNullException => new($"Pem is null or empty. Issuer: {Issuer}, Audience: {Audience}.");
public SigningCredentials CreateSigningCredentials(string algorithm = SecurityAlgorithms.RsaSha256, string? digest = null)
=> digest is null ? new(RsaSecurityKey, algorithm) : new(RsaSecurityKey, algorithm, digest);
}
}

View File

@@ -1,6 +1,6 @@
using DigitalData.Core.Abstractions.Security; using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Config; using DigitalData.Core.Security.Config;
using DigitalData.Core.Security.Cryptographer; using DigitalData.Core.Security.RSAKey;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@@ -13,10 +13,10 @@ namespace DigitalData.Core.Security
private static IServiceCollection AddParamsConfigureOptions<TParams>(this IServiceCollection services) where TParams : RSAFactoryParams private static IServiceCollection AddParamsConfigureOptions<TParams>(this IServiceCollection services) where TParams : RSAFactoryParams
=> services.AddSingleton<IConfigureOptions<TParams>, ParamsConfigureOptions<TParams>>(); => services.AddSingleton<IConfigureOptions<TParams>, ParamsConfigureOptions<TParams>>();
private static IServiceCollection AddAsymCryptService(this IServiceCollection services) => services private static IServiceCollection AddCryptograph(this IServiceCollection services) => services
.AddParamsConfigureOptions<AsymCryptParams>() .AddParamsConfigureOptions<CryptographParams>()
.AddAutoMapper(typeof(MappingProfile).Assembly) .AddAutoMapper(typeof(MappingProfile).Assembly)
.AddSingleton<IAsymCryptHandler, AsymCryptHandler>(); .AddSingleton<ICryptograph, Cryptograph>();
/// <summary> /// <summary>
/// Registers a custom asym crypt service with specified parameters from the given configuration section. /// Registers a custom asym crypt service with specified parameters from the given configuration section.
@@ -24,18 +24,18 @@ namespace DigitalData.Core.Security
/// <param name="services"></param> /// <param name="services"></param>
/// <param name="section"></param> /// <param name="section"></param>
/// <returns>The updated <see cref="IServiceCollection"/> with the RSA Factory registered.</returns> /// <returns>The updated <see cref="IServiceCollection"/> with the RSA Factory registered.</returns>
public static IServiceCollection AddAsymCryptService(this IServiceCollection services, IConfigurationSection section) => services public static IServiceCollection AddCryptograph(this IServiceCollection services, IConfigurationSection section) => services
.Configure<AsymCryptParams>(section) .Configure<CryptographParams>(section)
.AddAsymCryptService(); .AddCryptograph();
/// <summary> /// <summary>
/// Registers an asym crypt service with the specified parameters from the given instance. /// Registers an asym crypt service with the specified parameters from the given instance.
/// </summary> /// </summary>
/// <param name="services"></param> /// <param name="services"></param>
/// <returns>The updated <see cref="IServiceCollection"/> with the RSA Factory registered.</returns> /// <returns>The updated <see cref="IServiceCollection"/> with the RSA Factory registered.</returns>
public static IServiceCollection AddAsymCryptService(this IServiceCollection services, AsymCryptParams? asymCryptParams = null) => services public static IServiceCollection AddCryptograph(this IServiceCollection services, CryptographParams? asymCryptParams = null) => services
.AddSingleton(Options.Create(asymCryptParams ?? new())) .AddSingleton(Options.Create(asymCryptParams ?? new()))
.AddAsymCryptService(); .AddCryptograph();
/// <summary> /// <summary>
/// Registers a custom RSA Factory with specified parameters from the given configuration section. /// Registers a custom RSA Factory with specified parameters from the given configuration section.
@@ -46,7 +46,7 @@ namespace DigitalData.Core.Security
public static IServiceCollection AddRSAFactory(this IServiceCollection services, IConfigurationSection section) => services public static IServiceCollection AddRSAFactory(this IServiceCollection services, IConfigurationSection section) => services
.AddParamsConfigureOptions<RSAFactoryParams>() .AddParamsConfigureOptions<RSAFactoryParams>()
.Configure<RSAFactoryParams>(section) .Configure<RSAFactoryParams>(section)
.AddSingleton<IRSAFactory, RSAFactory<RSAFactoryParams>>(); .AddSingleton<IAsymmetricKeyFactory, RSAFactory<RSAFactoryParams>>();
private static IServiceCollection AddClaimDescriptor<TPrincipal>(this IServiceCollection services, private static IServiceCollection AddClaimDescriptor<TPrincipal>(this IServiceCollection services,
Func<TPrincipal, IDictionary<string, object>>? claimsMapper = null, Func<TPrincipal, IDictionary<string, object>>? claimsMapper = null,
@@ -61,23 +61,11 @@ namespace DigitalData.Core.Security
return services.AddSingleton(sp => Options.Create(descriptor)); return services.AddSingleton(sp => Options.Create(descriptor));
} }
public static IServiceCollection AddTokenDescriptions(this IServiceCollection services, IConfiguration configuration) public static IServiceCollection AddJwtSignatureHandler<TPrincipal>(this IServiceCollection services,
=> services.Configure<IEnumerable<TokenDescription>>(configuration); Func<TPrincipal, IDictionary<string, object>>? claimsMapper = null,
Func<TPrincipal, ClaimsIdentity>? subjectMapper = null)
public static IServiceCollection AddTokenDescriptions(this IServiceCollection services, params TokenDescription[] tokenDescriptions) => services
=> services.AddSingleton<IOptions<IEnumerable<TokenDescription>>>(Options.Create(tokenDescriptions));
public static IServiceCollection AddJwtSignatureHandler<TPrincipal>(this IServiceCollection services, Func<TPrincipal, IDictionary<string, object>>? claimsMapper = null, Func<TPrincipal, ClaimsIdentity>? subjectMapper = null, IConfiguration? tokenDescriptionconfig = null, params TokenDescription[]? tokenDescriptions)
{
if (tokenDescriptionconfig is not null)
services.AddTokenDescriptions(tokenDescriptionconfig);
if (tokenDescriptions is not null)
services.AddTokenDescriptions(tokenDescriptions);
return services
.AddClaimDescriptor(claimsMapper: claimsMapper, subjectMapper: subjectMapper) .AddClaimDescriptor(claimsMapper: claimsMapper, subjectMapper: subjectMapper)
.AddSingleton<IJwtSignatureHandler<TPrincipal>, JwtSignatureHandler<TPrincipal>>(); .AddSingleton<IJwtSignatureHandler<TPrincipal>, JwtSignatureHandler<TPrincipal>>();
}
} }
} }

View File

@@ -1,4 +1,5 @@
using AutoMapper; using AutoMapper;
using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Config; using DigitalData.Core.Security.Config;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
@@ -85,13 +86,13 @@ namespace DigitalData.Core.Security
internal static string ToTag(this DateOnly date, string format) => date.ToDateTime(new()).ToTag(format); internal static string ToTag(this DateOnly date, string format) => date.ToDateTime(new()).ToTag(format);
/// <summary> /// <summary>
/// Maps a <see cref="TokenDescription"/> to a <see cref="SecurityTokenDescriptor"/>. /// Maps a <see cref="RSATokenDescriptor"/> to a <see cref="SecurityTokenDescriptor"/>.
/// </summary> /// </summary>
/// <param name="mapper">The <see cref="IMapper"/> instance used for mapping.</param> /// <param name="mapper">The <see cref="IMapper"/> instance used for mapping.</param>
/// <param name="description">The <see cref="TokenDescription"/> instance to be mapped.</param> /// <param name="description">The <see cref="RSATokenDescriptor"/> instance to be mapped.</param>
/// <returns>A <see cref="SecurityTokenDescriptor"/> instance populated with the mapped values.</returns> /// <returns>A <see cref="SecurityTokenDescriptor"/> instance populated with the mapped values.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="mapper"/> or <paramref name="description"/> is <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="mapper"/> or <paramref name="description"/> is <c>null</c>.</exception>
internal static SecurityTokenDescriptor Map(this IMapper mapper, TokenDescription description) internal static SecurityTokenDescriptor Map(this IMapper mapper, PrivateKeyTokenDescriptor description)
=> mapper.Map(description, new SecurityTokenDescriptor()); => mapper.Map(description, new SecurityTokenDescriptor());
} }
} }

View File

@@ -1,6 +1,6 @@
using DigitalData.Core.Abstractions.Security; using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Config; using DigitalData.Core.Security.Config;
using DigitalData.Core.Security.Cryptographer; using DigitalData.Core.Security.RSAKey;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
namespace DigitalData.Core.Security namespace DigitalData.Core.Security
@@ -9,6 +9,6 @@ namespace DigitalData.Core.Security
{ {
private static readonly Lazy<RSAFactory<RSAFactoryParams>> LazyInstance = new(() => new(Options.Create<RSAFactoryParams>(new()))); private static readonly Lazy<RSAFactory<RSAFactoryParams>> LazyInstance = new(() => new(Options.Create<RSAFactoryParams>(new())));
public static IRSAFactory RSAFactory => LazyInstance.Value; public static IAsymmetricKeyFactory RSAFactory => LazyInstance.Value;
} }
} }

View File

@@ -1,6 +1,7 @@
using AutoMapper; using AutoMapper;
using DigitalData.Core.Abstractions.Security; using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Config; using DigitalData.Core.Security.Config;
using DigitalData.Core.Security.RSAKey;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
@@ -13,21 +14,20 @@ namespace DigitalData.Core.Security
private readonly IMapper _mapper; private readonly IMapper _mapper;
private readonly IEnumerable<TokenDescription>? _tokenDescriptions; private readonly ICryptograph _cryptograph;
private readonly IAsymCryptHandler _cryptHandler; public JwtSignatureHandler(IOptions<ClaimDescriptor<TPrincipal>> claimDescriptorOptions, IMapper mapper, ICryptograph cryptograph)
public JwtSignatureHandler(IOptions<ClaimDescriptor<TPrincipal>> claimDescriptorOptions, IMapper mapper, IOptions<IEnumerable<TokenDescription>>? tokenDescriptionOptions, IAsymCryptHandler asymCryptHandler)
{ {
_claimDescriptor = claimDescriptorOptions.Value; _claimDescriptor = claimDescriptorOptions.Value;
_mapper = mapper; _mapper = mapper;
_tokenDescriptions = tokenDescriptionOptions?.Value; _cryptograph = cryptograph;
_cryptHandler = asymCryptHandler;
} }
public SecurityToken CreateToken(TPrincipal subject, TokenDescription description) public SecurityToken CreateToken(TPrincipal subject, RSAPrivateKey key)
{ {
var descriptor = _mapper.Map(description); if(key.TokenDescriptor is null)
throw new InvalidOperationException($"No descriptor found for issuer '{key.Issuer}' and audience '{key.Audience}'.");
var descriptor = _mapper.Map(key.TokenDescriptor);
descriptor.Claims = _claimDescriptor.CreateClaims?.Invoke(subject); descriptor.Claims = _claimDescriptor.CreateClaims?.Invoke(subject);
descriptor.Subject = _claimDescriptor.CreateSubject?.Invoke(subject); descriptor.Subject = _claimDescriptor.CreateSubject?.Invoke(subject);
return CreateToken(descriptor); return CreateToken(descriptor);
@@ -35,20 +35,25 @@ namespace DigitalData.Core.Security
public SecurityToken CreateToken(TPrincipal subject, string issuer, string audience) public SecurityToken CreateToken(TPrincipal subject, string issuer, string audience)
{ {
var description = _tokenDescriptions?.Get(issuer: issuer, audience: audience) var key = _cryptograph.PrivateKeys?.Get(issuer: issuer, audience: audience)
?? throw new InvalidOperationException($"No token description found for issuer '{issuer}' and audience '{audience}'."); ?? throw new InvalidOperationException($"No or multiple token description found for issuer '{issuer}' and audience '{audience}'.");
return CreateToken(subject: subject, key: (RSAPrivateKey)key);
}
description.SigningCredentials = _cryptHandler.Decryptors public SecurityToken CreateToken(TPrincipal subject, string apiRoute)
.Get(issuer: issuer, audience: audience) {
.CreateSigningCredentials(algorithm: description.SigningAlgorithm, digest: description.SigningDigest); var key = _cryptograph.PrivateKeys.SingleOrDefault(key => ((RSAPrivateKey)key).TokenDescriptor?.ApiRoute == apiRoute)
?? throw new InvalidOperationException($"No or multiple token description found for api route '{apiRoute}'.");
return CreateToken(subject: subject, description: description); return CreateToken(subject: subject, key: (RSAPrivateKey)key);
} }
public string WriteToken(SecurityTokenDescriptor descriptor) => WriteToken(CreateToken(descriptor)); public string WriteToken(SecurityTokenDescriptor descriptor) => WriteToken(CreateToken(descriptor));
public string WriteToken(TPrincipal subject, TokenDescription description) => WriteToken(CreateToken(subject: subject, description: description)); public string WriteToken(TPrincipal subject, RSAPrivateKey key) => WriteToken(CreateToken(subject: subject, key: key));
public string WriteToken(TPrincipal subject, string issuer, string audience) => WriteToken(CreateToken(subject: subject, issuer: issuer, audience: audience)); public string WriteToken(TPrincipal subject, string issuer, string audience) => WriteToken(CreateToken(subject: subject, issuer: issuer, audience: audience));
public string WriteToken(TPrincipal subject, string apiRoute) => WriteToken(CreateToken(subject: subject, apiRoute: apiRoute));
} }
} }

View File

@@ -3,9 +3,9 @@ using DigitalData.Core.Security.Config;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System.Security.Cryptography; using System.Security.Cryptography;
namespace DigitalData.Core.Security.Cryptographer namespace DigitalData.Core.Security.RSAKey
{ {
public class RSAFactory<TRSAFactoryParams> : IRSAFactory where TRSAFactoryParams : RSAFactoryParams public class RSAFactory<TRSAFactoryParams> : IAsymmetricKeyFactory where TRSAFactoryParams : RSAFactoryParams
{ {
protected readonly TRSAFactoryParams _params; protected readonly TRSAFactoryParams _params;
@@ -56,9 +56,9 @@ namespace DigitalData.Core.Security.Cryptographer
return new string(pemChars); return new string(pemChars);
} }
public IRSADecryptor CreateDecryptor(string pem, string? issuer = null, string? audience = null, bool encrypt = false, RSAEncryptionPadding? padding = null) => new RSADecryptor() public IAsymmetricPrivateKey CreatePrivateKey(string pem, string? issuer = null, string? audience = null, bool encrypt = false, RSAEncryptionPadding? padding = null) => new RSAPrivateKey()
{ {
Pem = pem, Content = pem,
Issuer = issuer ?? string.Empty, Issuer = issuer ?? string.Empty,
Audience = audience ?? string.Empty, Audience = audience ?? string.Empty,
IsEncrypted = encrypt, IsEncrypted = encrypt,

View File

@@ -3,12 +3,11 @@ using Microsoft.IdentityModel.Tokens;
using System.Reflection; using System.Reflection;
using System.Security.Cryptography; using System.Security.Cryptography;
namespace DigitalData.Core.Security.Cryptographer namespace DigitalData.Core.Security.RSAKey
{ {
//TODO: Abstract RSA for future updates (using ECC, El Gamal or Lattice-based Cryptography) public class RSAKeyBase : IAsymmetricKey
public class RSACryptographer : IRSACryptographer
{ {
public virtual string Pem { get; init; } public virtual string Content { get; init; }
public RSAEncryptionPadding Padding { get; init; } = RSAEncryptionPadding.OaepSHA256; public RSAEncryptionPadding Padding { get; init; } = RSAEncryptionPadding.OaepSHA256;
@@ -27,10 +26,10 @@ namespace DigitalData.Core.Security.Cryptographer
private readonly Lazy<RsaSecurityKey> _lazyRsaSecurityKey; private readonly Lazy<RsaSecurityKey> _lazyRsaSecurityKey;
public RsaSecurityKey RsaSecurityKey => _lazyRsaSecurityKey.Value; public SecurityKey SecurityKey => _lazyRsaSecurityKey.Value;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
internal RSACryptographer() internal RSAKeyBase()
{ {
_lazyRsaSecurityKey = new(() => new RsaSecurityKey(RSA)); _lazyRsaSecurityKey = new(() => new RsaSecurityKey(RSA));
} }

View File

@@ -0,0 +1,83 @@
using DigitalData.Core.Abstractions.Security;
using Microsoft.IdentityModel.Tokens;
using System.Security.Cryptography;
namespace DigitalData.Core.Security.RSAKey
{
public class RSAPrivateKey : RSAKeyBase, IAsymmetricPrivateKey, IAsymmetricKey
{
private string? _pem;
public override string Content
{
#pragma warning disable CS8603 // Possible null reference return.
get => _pem;
#pragma warning restore CS8603 // Possible null reference return.
init
{
_pem = value;
Init();
}
}
public bool IsPemNull => _pem is null;
public bool IsEncrypted { get; init; }
private readonly Lazy<IAsymmetricPublicKey> _lazyPublicKey;
public IAsymmetricPublicKey PublicKey => _lazyPublicKey.Value;
private PrivateKeyTokenDescriptor? _tokenDescriptor;
private readonly Lazy<PrivateKeyTokenDescriptor?> _descriptorInitiator;
public PrivateKeyTokenDescriptor? TokenDescriptor { get => _descriptorInitiator.Value; init => _tokenDescriptor = value; }
public RSAPrivateKey()
{
_lazyPublicKey = new(() => new RSAPublicKey()
{
Content = RSA.ExportRSAPublicKeyPem(),
Padding = Padding
});
_descriptorInitiator = new(() =>
{
if(_tokenDescriptor is not null)
{
_tokenDescriptor.Issuer = Issuer;
_tokenDescriptor.Audience = Audience;
_tokenDescriptor.SigningCredentials = CreateSigningCredentials();
}
return _tokenDescriptor;
});
}
public byte[] Decrypt(byte[] data) => RSA.Decrypt(data, Padding);
public string Decrypt(string data) => RSA.Decrypt(data.Base64ToByte(), Padding).BytesToString();
internal void SetPem(string pem)
{
_pem = pem;
Init();
}
private void Init()
{
if (string.IsNullOrEmpty(_pem))
throw PemIsNullException;
if (IsEncrypted)
RSA.ImportFromEncryptedPem(Content, Secrets.PBE_PASSWORD.AsSpan());
else
RSA.ImportFromPem(Content);
}
private InvalidOperationException PemIsNullException => new($"Content is null or empty. Issuer: {Issuer}, Audience: {Audience}.");
public SigningCredentials CreateSigningCredentials(string algorithm = SecurityAlgorithms.RsaSha256, string? digest = null)
=> digest is null ? new(SecurityKey, algorithm) : new(SecurityKey, algorithm, digest);
}
}

View File

@@ -1,15 +1,15 @@
using DigitalData.Core.Abstractions.Security; using DigitalData.Core.Abstractions.Security;
namespace DigitalData.Core.Security.Cryptographer namespace DigitalData.Core.Security.RSAKey
{ {
public class RSAEncryptor : RSACryptographer, IRSAEncryptor, IRSACryptographer public class RSAPublicKey : RSAKeyBase, IAsymmetricPublicKey, IAsymmetricKey
{ {
public override string Pem public override string Content
{ {
get => base.Pem; get => base.Content;
init init
{ {
base.Pem = value; base.Content = value;
RSA.ImportFromPem(value); RSA.ImportFromPem(value);
} }
} }