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
{
public interface IRSACryptographer : IUniqueSecurityContext
public interface IAsymmetricKey : IUniqueSecurityContext
{
public string Pem { get; init; }
public RSAEncryptionPadding Padding { get; init; }
public string Content { get; init; }
public new string Issuer { 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
{
public interface IRSAFactory
public interface IAsymmetricKeyFactory
{
string CreatePrivateKeyPem(int? keySizeInBits = null, bool encrypt = false);
@@ -18,6 +18,6 @@ namespace DigitalData.Core.Abstractions.Security
int? keySizeInBits = 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
{
public interface IRSADecryptor : IRSACryptographer
public interface IAsymmetricPrivateKey : IAsymmetricKey
{
public bool IsEncrypted { get; init; }
IRSAEncryptor Encryptor { get; }
IAsymmetricPublicKey PublicKey { get; }
PrivateKeyTokenDescriptor? TokenDescriptor { get; init; }
byte[] Decrypt(byte[] data);

View File

@@ -1,6 +1,6 @@
namespace DigitalData.Core.Abstractions.Security
{
public interface IRSAEncryptor : IRSACryptographer
public interface IAsymmetricPublicKey : IAsymmetricKey
{
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 apiRoute);
string WriteToken(SecurityToken token);
string WriteToken(SecurityTokenDescriptor descriptor);
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>
/// Contains some information which used to create a security token. Designed to abstract <see cref="SecurityTokenDescriptor"/>
/// </summary>
public class TokenDescription : IUniqueSecurityContext
public class PrivateKeyTokenDescriptor : IUniqueSecurityContext
{
public string? ApiRoute { get; init; }
/// <summary>
/// Gets or sets the value of the 'audience' claim.
/// </summary>
@@ -29,7 +30,7 @@ namespace DigitalData.Core.Security.Config
public DateTime? Expires { get; set; }
/// <summary>
/// Gets or sets the issuer of this <see cref="ITokenDescription"/>.
/// Gets or sets the issuer of this <see cref="PrivateKeyTokenDescriptor"/>.
/// </summary>
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"/>.
/// Default is <see cref="SecurityAlgorithms.RsaSha256"/>.
/// </summary>
public string SigningAlgorithm { get; init; } = SecurityAlgorithms.RsaSha256;
public string SigningAlgorithm { get; internal set; } = SecurityAlgorithms.RsaSha256;
/// <summary>
/// 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.Cryptographer;
using DigitalData.Core.Security.RSAKey;
namespace DigitalData.Core.Security.Config
{
public class AsymCryptParams : RSAFactoryParams
public class CryptographParams : RSAFactoryParams
{
public string PemDirectory { get; init; } = string.Empty;
@@ -40,11 +39,11 @@ namespace DigitalData.Core.Security.Config
/// </summary>
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
AfterCreate += () =>
@@ -53,7 +52,7 @@ namespace DigitalData.Core.Security.Config
if (!Directory.Exists(PemDirectory))
Directory.CreateDirectory(PemDirectory);
foreach (var decryptor in Decryptors)
foreach (var decryptor in PrivateKeys)
{
// set default path
if (decryptor.IsPemNull)

View File

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

View File

@@ -1,42 +1,42 @@
using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Config;
using DigitalData.Core.Security.Cryptographer;
using DigitalData.Core.Security.RSAKey;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
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>
/// It is a separate decryptor for permanently stored encrypted data. It is assigned to the first Default decryptor by default.
/// </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"));
if (!_params.Decryptors.Any())
if (!_params.PrivateKeys.Any())
throw new InvalidOperationException(
"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. " +
"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.Security.Config;
using DigitalData.Core.Security.Cryptographer;
using DigitalData.Core.Security.RSAKey;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
@@ -13,10 +13,10 @@ namespace DigitalData.Core.Security
private static IServiceCollection AddParamsConfigureOptions<TParams>(this IServiceCollection services) where TParams : RSAFactoryParams
=> services.AddSingleton<IConfigureOptions<TParams>, ParamsConfigureOptions<TParams>>();
private static IServiceCollection AddAsymCryptService(this IServiceCollection services) => services
.AddParamsConfigureOptions<AsymCryptParams>()
private static IServiceCollection AddCryptograph(this IServiceCollection services) => services
.AddParamsConfigureOptions<CryptographParams>()
.AddAutoMapper(typeof(MappingProfile).Assembly)
.AddSingleton<IAsymCryptHandler, AsymCryptHandler>();
.AddSingleton<ICryptograph, Cryptograph>();
/// <summary>
/// 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="section"></param>
/// <returns>The updated <see cref="IServiceCollection"/> with the RSA Factory registered.</returns>
public static IServiceCollection AddAsymCryptService(this IServiceCollection services, IConfigurationSection section) => services
.Configure<AsymCryptParams>(section)
.AddAsymCryptService();
public static IServiceCollection AddCryptograph(this IServiceCollection services, IConfigurationSection section) => services
.Configure<CryptographParams>(section)
.AddCryptograph();
/// <summary>
/// Registers an asym crypt service with the specified parameters from the given instance.
/// </summary>
/// <param name="services"></param>
/// <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()))
.AddAsymCryptService();
.AddCryptograph();
/// <summary>
/// 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
.AddParamsConfigureOptions<RSAFactoryParams>()
.Configure<RSAFactoryParams>(section)
.AddSingleton<IRSAFactory, RSAFactory<RSAFactoryParams>>();
.AddSingleton<IAsymmetricKeyFactory, RSAFactory<RSAFactoryParams>>();
private static IServiceCollection AddClaimDescriptor<TPrincipal>(this IServiceCollection services,
Func<TPrincipal, IDictionary<string, object>>? claimsMapper = null,
@@ -61,23 +61,11 @@ namespace DigitalData.Core.Security
return services.AddSingleton(sp => Options.Create(descriptor));
}
public static IServiceCollection AddTokenDescriptions(this IServiceCollection services, IConfiguration configuration)
=> services.Configure<IEnumerable<TokenDescription>>(configuration);
public static IServiceCollection AddTokenDescriptions(this IServiceCollection services, params TokenDescription[] tokenDescriptions)
=> 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
public static IServiceCollection AddJwtSignatureHandler<TPrincipal>(this IServiceCollection services,
Func<TPrincipal, IDictionary<string, object>>? claimsMapper = null,
Func<TPrincipal, ClaimsIdentity>? subjectMapper = null)
=> services
.AddClaimDescriptor(claimsMapper: claimsMapper, subjectMapper: subjectMapper)
.AddSingleton<IJwtSignatureHandler<TPrincipal>, JwtSignatureHandler<TPrincipal>>();
}
}
}

View File

@@ -1,4 +1,5 @@
using AutoMapper;
using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Config;
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);
/// <summary>
/// Maps a <see cref="TokenDescription"/> to a <see cref="SecurityTokenDescriptor"/>.
/// Maps a <see cref="RSATokenDescriptor"/> to a <see cref="SecurityTokenDescriptor"/>.
/// </summary>
/// <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>
/// <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());
}
}

View File

@@ -1,6 +1,6 @@
using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Config;
using DigitalData.Core.Security.Cryptographer;
using DigitalData.Core.Security.RSAKey;
using Microsoft.Extensions.Options;
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())));
public static IRSAFactory RSAFactory => LazyInstance.Value;
public static IAsymmetricKeyFactory RSAFactory => LazyInstance.Value;
}
}

View File

@@ -1,6 +1,7 @@
using AutoMapper;
using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Config;
using DigitalData.Core.Security.RSAKey;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
@@ -13,21 +14,20 @@ namespace DigitalData.Core.Security
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, IOptions<IEnumerable<TokenDescription>>? tokenDescriptionOptions, IAsymCryptHandler asymCryptHandler)
public JwtSignatureHandler(IOptions<ClaimDescriptor<TPrincipal>> claimDescriptorOptions, IMapper mapper, ICryptograph cryptograph)
{
_claimDescriptor = claimDescriptorOptions.Value;
_mapper = mapper;
_tokenDescriptions = tokenDescriptionOptions?.Value;
_cryptHandler = asymCryptHandler;
_cryptograph = cryptograph;
}
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.Subject = _claimDescriptor.CreateSubject?.Invoke(subject);
return CreateToken(descriptor);
@@ -35,20 +35,25 @@ namespace DigitalData.Core.Security
public SecurityToken CreateToken(TPrincipal subject, string issuer, string audience)
{
var description = _tokenDescriptions?.Get(issuer: issuer, audience: audience)
?? throw new InvalidOperationException($"No token description found for issuer '{issuer}' and audience '{audience}'.");
var key = _cryptograph.PrivateKeys?.Get(issuer: issuer, 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
.Get(issuer: issuer, audience: audience)
.CreateSigningCredentials(algorithm: description.SigningAlgorithm, digest: description.SigningDigest);
public SecurityToken CreateToken(TPrincipal subject, string apiRoute)
{
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(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 apiRoute) => WriteToken(CreateToken(subject: subject, apiRoute: apiRoute));
}
}

View File

@@ -3,9 +3,9 @@ using DigitalData.Core.Security.Config;
using Microsoft.Extensions.Options;
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;
@@ -56,9 +56,9 @@ namespace DigitalData.Core.Security.Cryptographer
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,
Audience = audience ?? string.Empty,
IsEncrypted = encrypt,

View File

@@ -3,12 +3,11 @@ using Microsoft.IdentityModel.Tokens;
using System.Reflection;
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 RSACryptographer : IRSACryptographer
public class RSAKeyBase : IAsymmetricKey
{
public virtual string Pem { get; init; }
public virtual string Content { get; init; }
public RSAEncryptionPadding Padding { get; init; } = RSAEncryptionPadding.OaepSHA256;
@@ -27,10 +26,10 @@ namespace DigitalData.Core.Security.Cryptographer
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.
internal RSACryptographer()
internal RSAKeyBase()
{
_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;
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
{
base.Pem = value;
base.Content = value;
RSA.ImportFromPem(value);
}
}