18 Commits

Author SHA1 Message Date
Developer 02
211064d44e refactor(RSATokenDescriptor): Standard-Id mit Issuer und Audience hinzugefügt.
- Issuer und Audience erforderlich gemacht.
2025-01-09 23:25:17 +01:00
Developer 02
66e3c771dd refactor(IAsymmetricKey): Die Implementierung von IUniqueSecurityContext wurde entfernt und stattdessen die Eigenschaft Id hinzugefügt.
- Aktualisierte verwandte Implementierungen.
2025-01-09 22:57:04 +01:00
Developer 02
97c4f7bf8f refactor(CryptographParams): umbenannt in CryptoFactoryParams 2025-01-09 22:33:56 +01:00
Developer 02
5981ba7a8d refactor(Abstractions.Security): Unnötige public Schlüsselwörter in Schnittstellen entfernt. 2025-01-09 22:27:33 +01:00
Developer 02
21e164ceb7 refactor(IAsymmetricKey): Unnötige Initor-Methoden entfernt. 2025-01-09 22:15:45 +01:00
Developer 02
1875bf46fa feat(DIExtensions): Umbenennung der AddCryptograph-Methoden in AddCryptoFactory 2025-01-09 20:38:15 +01:00
Developer 02
7f9459f6cf feat(SecurityExtensions): Erforderliche WriteToken-Methoden über Erweiterungen anstelle der Schnittstellenimplementierung hinzugefügt 2025-01-09 20:30:38 +01:00
Developer 02
079f0c69c7 fix(Kryptograph): Umbenannt in Krypto-Fabrik. 2025-01-09 20:10:45 +01:00
Developer 02
d98b3f2867 fix(JwtSignatureHandler): Aktualisierte Methoden, um IAsymmetricTokenDescriptor verwenden zu können 2025-01-09 19:54:05 +01:00
Developer 02
3761c13dba refactor(ICryptograph): Eigenschaft „TokenDescriptors“ hinzugefügt. 2025-01-09 19:34:29 +01:00
Developer 02
8acbbaeb2e refactor(IAsymmetricTokenDescriptor): Erforderliche Proportionen für SecurityTokenDescriptor-Zuordnung hinzugefügt. 2025-01-09 19:16:56 +01:00
Developer 02
60e1ec78b3 refactor(Kryptograph): Entfernte Verschlüsselungen. 2025-01-09 18:52:59 +01:00
Developer 02
e623575fe8 refactor(Kryptograph): Aktualisiert, um TokenDescriptors aus CryptographParams hinzuzufügen. 2025-01-09 18:33:51 +01:00
Developer 02
60ae8de550 refactor(CryptographParams): Aktualisierung, um TokenDeskriptoren mit Decryptoren zu initialisieren 2025-01-09 18:28:20 +01:00
Developer 02
87ad45f42a refactor(RSATokenDescriptor): Die Klasse RSA Token Descriptor wurde erstellt und die Funktionen wurden dorthin verschoben, um eine einfache und saubere Konfiguration zu ermöglichen. 2025-01-09 17:59:13 +01:00
Developer 02
2557525f06 refactor(PrivateKeyTokenDescriptor): superwarning zur Deaktivierung der Warnung vor Null 2025-01-09 11:08:06 +01:00
Developer 02
7a938f0379 refactor(Privatekey): Die Klasse encryptor wurde erstellt und die Verschlüsselungsfunktionen wurden zur einfachen und sauberen Konfiguration dorthin verschoben. 2025-01-08 20:03:25 +01:00
Developer 02
9f0facc487 refactor(Privatekey): Die Klasse decryptor wurde erstellt und die Verschlüsselungsfunktionen für eine einfache und saubere Konfiguration dorthin verschoben. 2025-01-08 18:45:36 +01:00
24 changed files with 297 additions and 186 deletions

View File

@@ -0,0 +1,9 @@
namespace DigitalData.Core.Abstractions.Security
{
public interface IAsymmetricDecryptor : IAsymmetricPrivateKey
{
byte[] Decrypt(byte[] data);
IAsymmetricEncryptor Encryptor { get; }
}
}

View File

@@ -0,0 +1,7 @@
namespace DigitalData.Core.Abstractions.Security
{
public interface IAsymmetricEncryptor : IAsymmetricPublicKey
{
byte[] Encrypt(byte[] data);
}
}

View File

@@ -1,16 +1,9 @@
using Microsoft.IdentityModel.Tokens;
using System.Security.Cryptography;
namespace DigitalData.Core.Abstractions.Security
namespace DigitalData.Core.Abstractions.Security
{
public interface IAsymmetricKey : IUniqueSecurityContext
public interface IAsymmetricKey
{
public string Content { get; init; }
public new string Issuer { get; init; }
string Id { get; }
public new string Audience { get; init; }
public SecurityKey SecurityKey { get; }
string Content { get; }
}
}

View File

@@ -18,6 +18,6 @@ namespace DigitalData.Core.Abstractions.Security
int? keySizeInBits = null,
string? password = null);
IAsymmetricPrivateKey CreatePrivateKey(string pem, string? issuer = null, string? audience = null, bool encrypt = false, RSAEncryptionPadding? padding = null);
IAsymmetricDecryptor CreateDecryptor(string pem, string? issuer = null, string? audience = null, bool encrypt = false, RSAEncryptionPadding? padding = null);
}
}

View File

@@ -1,19 +1,9 @@
using Microsoft.IdentityModel.Tokens;
namespace DigitalData.Core.Abstractions.Security
namespace DigitalData.Core.Abstractions.Security
{
public interface IAsymmetricPrivateKey : IAsymmetricKey
{
public bool IsEncrypted { get; init; }
bool IsEncrypted { get; }
IAsymmetricPublicKey PublicKey { get; }
PrivateKeyTokenDescriptor? TokenDescriptor { get; init; }
byte[] Decrypt(byte[] data);
string Decrypt(string data);
SigningCredentials CreateSigningCredentials(string algorithm = SecurityAlgorithms.RsaSha256, string? digest = null);
}
}

View File

@@ -2,10 +2,5 @@
{
public interface IAsymmetricPublicKey : IAsymmetricKey
{
public byte[] Encrypt(byte[] data);
public string Encrypt(string data);
public bool Verify(string data, string signature) => Encrypt(data) == signature;
}
}

View File

@@ -0,0 +1,72 @@
using Microsoft.IdentityModel.Tokens;
namespace DigitalData.Core.Abstractions.Security
{
/// <summary>
/// Contains some information which used to create a security token. Designed to abstract <see cref="SecurityTokenDescriptor"/>
/// </summary>
public interface IAsymmetricTokenDescriptor : IAsymmetricPrivateKey, IUniqueSecurityContext
{
string? ApiRoute { get; }
#region SecurityTokenDescriptor Map
/// <summary>
/// Defines the compression algorithm that will be used to compress the JWT token payload.
/// </summary>
string CompressionAlgorithm { get; }
/// <summary>
/// Gets or sets the <see cref="EncryptingCredentials"/> used to create a encrypted security token.
/// </summary>
EncryptingCredentials EncryptingCredentials { get; }
/// <summary>
/// Gets or sets the value of the 'expiration' claim. This value should be in UTC.
/// </summary>
DateTime? Expires { get; }
/// <summary>
/// Gets or sets the time the security token was issued. This value should be in UTC.
/// </summary>
DateTime? IssuedAt { get; }
/// <summary>
/// Gets or sets the notbefore time for the security token. This value should be in UTC.
/// </summary>
DateTime? NotBefore { get; }
/// <summary>
/// Gets or sets the token type.
/// <remarks> If provided, this will be added as the value for the 'typ' header parameter. In the case of a JWE, this will be added to both the inner (JWS) and the outer token (JWE) header. By default, the value used is 'JWT'.
/// If <see cref="AdditionalHeaderClaims"/> also contains 'typ' header claim value, it will override the TokenType provided here.
/// This value is used only for JWT tokens and not for SAML/SAML2 tokens</remarks>
/// </summary>
string TokenType { get; }
/// <summary>
/// Gets or sets the <see cref="Dictionary{TKey, TValue}"/> which contains any custom header claims that need to be added to the JWT token header.
/// The 'alg', 'kid', 'x5t', 'enc', and 'zip' claims are added by default based on the <see cref="SigningCredentials"/>,
/// <see cref="EncryptingCredentials"/>, and/or <see cref="CompressionAlgorithm"/> provided and SHOULD NOT be included in this dictionary as this
/// will result in an exception being thrown.
/// <remarks> These claims are only added to the outer header (in case of a JWE).</remarks>
/// </summary>
IDictionary<string, object> AdditionalHeaderClaims { get; }
/// <summary>
/// Gets or sets the <see cref="Dictionary{TKey, TValue}"/> which contains any custom header claims that need to be added to the inner JWT token header.
/// The 'alg', 'kid', 'x5t', 'enc', and 'zip' claims are added by default based on the <see cref="SigningCredentials"/>,
/// <see cref="EncryptingCredentials"/>, and/or <see cref="CompressionAlgorithm"/> provided and SHOULD NOT be included in this dictionary as this
/// will result in an exception being thrown.
/// <remarks>
/// For JsonWebTokenHandler, these claims are merged with <see cref="AdditionalHeaderClaims"/> while adding to the inner JWT header.
/// </remarks>
/// </summary>
IDictionary<string, object> AdditionalInnerHeaderClaims { get; }
/// <summary>
/// Gets or sets the <see cref="SigningCredentials"/> used to create a security token.
/// </summary>
SigningCredentials SigningCredentials { get; }
#endregion SecurityTokenDescriptor
}
}

View File

@@ -0,0 +1,11 @@
namespace DigitalData.Core.Abstractions.Security
{
public interface ICryptoFactory : IAsymmetricKeyFactory
{
IEnumerable<IAsymmetricDecryptor> Decryptors { get; }
IAsymmetricDecryptor VaultDecryptor { get; }
IEnumerable<IAsymmetricTokenDescriptor> TokenDescriptors { get; }
}
}

View File

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

View File

@@ -6,16 +6,12 @@ namespace DigitalData.Core.Abstractions.Security
{
SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor);
SecurityToken CreateToken(TPrincipal subject, IAsymmetricTokenDescriptor descriptor);
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,7 +1,11 @@
namespace DigitalData.Core.Abstractions.Security
using Microsoft.IdentityModel.Tokens;
using System.Text;
namespace DigitalData.Core.Abstractions.Security
{
public static class SecurityExtensions
{
#region Unique Security Context
public static IEnumerable<TUniqueSecurityContext> GetByIssuer<TUniqueSecurityContext>(this IEnumerable<TUniqueSecurityContext> contextes, string issuer) where TUniqueSecurityContext: IUniqueSecurityContext
=> contextes.Where(c => c.Issuer == issuer);
@@ -25,5 +29,37 @@
public static bool TryMatch<TUniqueSecurityContext>(this IEnumerable<TUniqueSecurityContext> contextes, IUniqueSecurityContext lookupContext, out TUniqueSecurityContext context) where TUniqueSecurityContext : IUniqueSecurityContext
=> contextes.TryGet(lookupContext.Issuer, lookupContext.Audience, out context);
#endregion Unique Security Context
#region De/serilization
internal static byte[] Base64ToByte(this string base64String) => Convert.FromBase64String(base64String);
internal static string BytesToString(this byte[] bytes) => Encoding.UTF8.GetString(bytes);
internal static string ToBase64String(this byte[] bytes) => Convert.ToBase64String(bytes);
internal static byte[] ToBytes(this string str) => System.Text.Encoding.UTF8.GetBytes(str);
public static string Decrypt(this IAsymmetricDecryptor decryptor, string data) => decryptor
.Decrypt(data.Base64ToByte()).BytesToString();
#endregion De/serilization
#region Asymmetric Encryptor
public static string Encrypt(this IAsymmetricEncryptor encryptor, string data) => encryptor.Encrypt(data.ToBytes()).ToBase64String();
#endregion Asymmetric Encryptor
#region Jwt Signature Handler
public static string WriteToken<TPrincipal>(this IJwtSignatureHandler<TPrincipal> handler, SecurityTokenDescriptor descriptor)
=> handler.WriteToken(handler.CreateToken(descriptor));
public static string WriteToken<TPrincipal>(this IJwtSignatureHandler<TPrincipal> handler, TPrincipal subject, IAsymmetricTokenDescriptor descriptor)
=> handler.WriteToken(handler.CreateToken(subject: subject, descriptor: descriptor));
public static string WriteToken<TPrincipal>(this IJwtSignatureHandler<TPrincipal> handler, TPrincipal subject, string issuer, string audience)
=> handler.WriteToken(handler.CreateToken(subject: subject, issuer: issuer, audience: audience));
public static string WriteToken<TPrincipal>(this IJwtSignatureHandler<TPrincipal> handler, TPrincipal subject, string apiRoute)
=> handler.WriteToken(handler.CreateToken(subject: subject, apiRoute: apiRoute));
#endregion Jwt Signature Handler
}
}

View File

@@ -2,7 +2,7 @@
namespace DigitalData.Core.Security.Config
{
public class CryptographParams : RSAFactoryParams
public class CryptoFactoryParams : RSAFactoryParams
{
public string PemDirectory { get; init; } = string.Empty;
@@ -39,12 +39,21 @@ namespace DigitalData.Core.Security.Config
/// </summary>
public string DateTagFormat { get; init; } = "MM//2";
public IEnumerable<RSAPrivateKey> PrivateKeys { get; init; } = new List<RSAPrivateKey>();
public IEnumerable<RSADecryptor> Decryptors { get; init; } = new List<RSADecryptor>();
public RSAPrivateKey? VaultPrivateKey { get; init; }
public IEnumerable<RSATokenDescriptor> TokenDescriptors { get; init; } = new List<RSATokenDescriptor>();
public CryptographParams()
public RSADecryptor? VaultDecryptor { get; init; }
public CryptoFactoryParams()
{
// set defaults
if (VaultDecryptor is not null)
VaultDecryptor.Id = "vault";
foreach (var descriptor in TokenDescriptors)
descriptor.IdSeparator = FileNameSeparator;
// init decryptors
AfterCreate += () =>
{
@@ -52,13 +61,19 @@ namespace DigitalData.Core.Security.Config
if (!Directory.Exists(PemDirectory))
Directory.CreateDirectory(PemDirectory);
foreach (var decryptor in PrivateKeys)
var privateKeys = new List<RSAPrivateKey>();
privateKeys.AddRange(Decryptors);
privateKeys.AddRange(TokenDescriptors);
if (VaultDecryptor is not null)
privateKeys.Add(VaultDecryptor);
foreach (var privateKey in privateKeys)
{
// set default path
if (decryptor.IsPemNull)
if (privateKey.IsPemNull)
{
var file_name_params = new List<object> { decryptor.Issuer, decryptor.Audience, KeySizeInBits, DateTime.Now.ToTag(DateTagFormat) };
if (decryptor.IsEncrypted)
var file_name_params = new List<object> { privateKey.Id, KeySizeInBits, DateTime.Now.ToTag(DateTagFormat) };
if (privateKey.IsEncrypted)
file_name_params.Add(Secrets.Version);
var file_name = $"{string.Join(FileNameSeparator, file_name_params)}.{FileExtension}";
@@ -66,14 +81,14 @@ namespace DigitalData.Core.Security.Config
var path = Path.Combine(PemDirectory, file_name);
if (File.Exists(path))
decryptor.SetPem(File.ReadAllText(path));
privateKey.SetPem(File.ReadAllText(path));
else
{
var pem = decryptor.IsEncrypted
var pem = privateKey.IsEncrypted
? Instance.RSAFactory.CreateEncryptedPrivateKeyPem(pbeParameters: PbeParameters, keySizeInBits: KeySizeInBits, password: Secrets.PBE_PASSWORD)
: Instance.RSAFactory.CreatePrivateKeyPem(keySizeInBits: KeySizeInBits);
decryptor.SetPem(pem);
privateKey.SetPem(pem);
// Save file in background
Task.Run(async () => await File.WriteAllTextAsync(path: path, pem));

View File

@@ -8,7 +8,7 @@ namespace DigitalData.Core.Security.Config
{
public MappingProfile()
{
CreateMap<PrivateKeyTokenDescriptor, SecurityTokenDescriptor>();
CreateMap<IAsymmetricTokenDescriptor, SecurityTokenDescriptor>();
}
}
}

View File

@@ -6,37 +6,33 @@ using Microsoft.Extensions.Options;
namespace DigitalData.Core.Security
{
public class Cryptograph : RSAFactory<CryptographParams>, ICryptograph, IAsymmetricKeyFactory
public class CryptoFactory : RSAFactory<CryptoFactoryParams>, ICryptoFactory, IAsymmetricKeyFactory
{
public IEnumerable<IAsymmetricPrivateKey> PrivateKeys { get; }
public IEnumerable<IAsymmetricDecryptor> Decryptors { get; }
/// <summary>
/// It is a separate decryptor for permanently stored encrypted data. It is assigned to the first Default decryptor by default.
/// </summary>
public IAsymmetricPrivateKey VaultPrivateKey { get; }
public IAsymmetricDecryptor VaultDecryptor { get; }
private readonly Lazy<IEnumerable<IAsymmetricPublicKey>> _lazyPublicKeys;
public IEnumerable<IAsymmetricTokenDescriptor> TokenDescriptors { get; init; } = new List<IAsymmetricTokenDescriptor>();
public IEnumerable<IAsymmetricPublicKey> PublicKeys => _lazyPublicKeys.Value;
public IEnumerable<PrivateKeyTokenDescriptor> TokenDescriptions { get; init; } = new List<PrivateKeyTokenDescriptor>();
public Cryptograph(IOptions<CryptographParams> options, ILogger<Cryptograph>? logger = null) : base(options)
public CryptoFactory(IOptions<CryptoFactoryParams> options, ILogger<CryptoFactory>? logger = null) : base(options)
{
logger?.LogInformation("Core.Secrets version: {Version}, Created on: {CreationDate}.", Secrets.Version, Secrets.CreationDate.ToString("dd.MM.yyyy"));
if (!_params.PrivateKeys.Any())
if (!_params.Decryptors.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."
);
PrivateKeys = _params.PrivateKeys;
Decryptors = _params.Decryptors;
VaultPrivateKey = _params.VaultPrivateKey ?? PrivateKeys.First();
TokenDescriptors = _params.TokenDescriptors;
_lazyPublicKeys = new(PrivateKeys.Select(decryptor => decryptor.PublicKey));
VaultDecryptor = _params.VaultDecryptor ?? Decryptors.First();
}
}
}

View File

@@ -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 AddCryptograph(this IServiceCollection services) => services
.AddParamsConfigureOptions<CryptographParams>()
private static IServiceCollection AddCryptoFactory(this IServiceCollection services) => services
.AddParamsConfigureOptions<CryptoFactoryParams>()
.AddAutoMapper(typeof(MappingProfile).Assembly)
.AddSingleton<ICryptograph, Cryptograph>();
.AddSingleton<ICryptoFactory, CryptoFactory>();
/// <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 AddCryptograph(this IServiceCollection services, IConfigurationSection section) => services
.Configure<CryptographParams>(section)
.AddCryptograph();
public static IServiceCollection AddCryptoFactory(this IServiceCollection services, IConfigurationSection section) => services
.Configure<CryptoFactoryParams>(section)
.AddCryptoFactory();
/// <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 AddCryptograph(this IServiceCollection services, CryptographParams? asymCryptParams = null) => services
.AddSingleton(Options.Create(asymCryptParams ?? new()))
.AddCryptograph();
public static IServiceCollection AddCryptoFactory(this IServiceCollection services, CryptoFactoryParams? factoryParams = null) => services
.AddSingleton(Options.Create(factoryParams ?? new()))
.AddCryptoFactory();
/// <summary>
/// Registers a custom RSA Factory with specified parameters from the given configuration section.

View File

@@ -1,20 +1,11 @@
using AutoMapper;
using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Config;
using Microsoft.IdentityModel.Tokens;
namespace DigitalData.Core.Security
{
internal static class Extension
{
internal static string ToBase64String(this byte[] bytes) => Convert.ToBase64String(bytes);
internal static byte[] Base64ToByte(this string base64String) => Convert.FromBase64String(base64String);
internal static byte[] ToBytes(this string str) => System.Text.Encoding.UTF8.GetBytes(str);
internal static string BytesToString(this byte[] bytes) => System.Text.Encoding.UTF8.GetString(bytes);
/// <summary>
/// Converts a <see cref="DateTime"/> to a formatted string based on the specified format string.
/// <br />
@@ -92,7 +83,7 @@ namespace DigitalData.Core.Security
/// <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, PrivateKeyTokenDescriptor description)
internal static SecurityTokenDescriptor Map(this IMapper mapper, IAsymmetricTokenDescriptor description)
=> mapper.Map(description, new SecurityTokenDescriptor());
}
}

View File

@@ -1,7 +1,6 @@
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;
@@ -14,46 +13,36 @@ namespace DigitalData.Core.Security
private readonly IMapper _mapper;
private readonly ICryptograph _cryptograph;
private readonly ICryptoFactory _cryptoFactory;
public JwtSignatureHandler(IOptions<ClaimDescriptor<TPrincipal>> claimDescriptorOptions, IMapper mapper, ICryptograph cryptograph)
public JwtSignatureHandler(IOptions<ClaimDescriptor<TPrincipal>> claimDescriptorOptions, IMapper mapper, ICryptoFactory cryptoFactory)
{
_claimDescriptor = claimDescriptorOptions.Value;
_mapper = mapper;
_cryptograph = cryptograph;
_cryptoFactory = cryptoFactory;
}
public SecurityToken CreateToken(TPrincipal subject, RSAPrivateKey key)
public SecurityToken CreateToken(TPrincipal subject, IAsymmetricTokenDescriptor descriptor)
{
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);
var sDescriptor = _mapper.Map(descriptor);
sDescriptor.Claims = _claimDescriptor.CreateClaims?.Invoke(subject);
sDescriptor.Subject = _claimDescriptor.CreateSubject?.Invoke(subject);
return CreateToken(sDescriptor);
}
public SecurityToken CreateToken(TPrincipal subject, string issuer, string audience)
{
var key = _cryptograph.PrivateKeys?.Get(issuer: issuer, audience: audience)
var descriptor = _cryptoFactory.TokenDescriptors.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);
return CreateToken(subject: subject, descriptor: descriptor);
}
public SecurityToken CreateToken(TPrincipal subject, string apiRoute)
{
var key = _cryptograph.PrivateKeys.SingleOrDefault(key => ((RSAPrivateKey)key).TokenDescriptor?.ApiRoute == apiRoute)
var desc = _cryptoFactory.TokenDescriptors.SingleOrDefault(desc => desc.ApiRoute == apiRoute)
?? throw new InvalidOperationException($"No or multiple token description found for api route '{apiRoute}'.");
return CreateToken(subject: subject, key: (RSAPrivateKey)key);
return CreateToken(subject: subject, descriptor: desc);
}
public string WriteToken(SecurityTokenDescriptor descriptor) => WriteToken(CreateToken(descriptor));
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

@@ -0,0 +1,33 @@
using DigitalData.Core.Abstractions.Security;
using System.Reflection;
using System.Security.Cryptography;
namespace DigitalData.Core.Security.RSAKey
{
public class RSADecryptor : RSAPrivateKey, IAsymmetricDecryptor
{
public RSAEncryptionPadding Padding { get; init; } = RSAEncryptionPadding.OaepSHA256;
// TODO: add as json converter to IConfigurIConfiguration.Config
public string PaddingName
{
get => Padding.ToString();
init => Padding = typeof(RSAEncryptionPadding).GetProperty(value, BindingFlags.Public | BindingFlags.Static)?.GetValue(null) as RSAEncryptionPadding ?? throw new ArgumentException($"Padding '{value}' not found.");
}
public byte[] Decrypt(byte[] data) => RSA.Decrypt(data, Padding);
private readonly Lazy<IAsymmetricEncryptor> _lazyEncryptor;
public IAsymmetricEncryptor Encryptor => _lazyEncryptor.Value;
public RSADecryptor()
{
_lazyEncryptor = new(() => new RSAEncryptor()
{
Content = RSA.ExportRSAPublicKeyPem(),
Padding = Padding
});
}
}
}

View File

@@ -0,0 +1,20 @@
using DigitalData.Core.Abstractions.Security;
using System.Reflection;
using System.Security.Cryptography;
namespace DigitalData.Core.Security.RSAKey
{
public class RSAEncryptor : RSAPublicKey, IAsymmetricEncryptor
{
public RSAEncryptionPadding Padding { get; init; } = RSAEncryptionPadding.OaepSHA256;
// TODO: add as json converter to IConfigurIConfiguration.Config
public string PaddingName
{
get => Padding.ToString();
init => Padding = typeof(RSAEncryptionPadding).GetProperty(value, BindingFlags.Public | BindingFlags.Static)?.GetValue(null) as RSAEncryptionPadding ?? throw new ArgumentException($"Padding '{value}' not found.");
}
public byte[] Encrypt(byte[] data) => RSA.Encrypt(data, Padding);
}
}

View File

@@ -56,11 +56,9 @@ namespace DigitalData.Core.Security.RSAKey
return new string(pemChars);
}
public IAsymmetricPrivateKey CreatePrivateKey(string pem, string? issuer = null, string? audience = null, bool encrypt = false, RSAEncryptionPadding? padding = null) => new RSAPrivateKey()
public IAsymmetricDecryptor CreateDecryptor(string pem, string? issuer = null, string? audience = null, bool encrypt = false, RSAEncryptionPadding? padding = null) => new RSADecryptor()
{
Content = pem,
Issuer = issuer ?? string.Empty,
Audience = audience ?? string.Empty,
IsEncrypted = encrypt,
Padding = padding ?? RSAEncryptionPadding.OaepSHA256
};

View File

@@ -1,38 +1,16 @@
using DigitalData.Core.Abstractions.Security;
using Microsoft.IdentityModel.Tokens;
using System.Reflection;
using System.Security.Cryptography;
namespace DigitalData.Core.Security.RSAKey
{
public class RSAKeyBase : IAsymmetricKey
{
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
public virtual string Content { get; init; }
public RSAEncryptionPadding Padding { get; init; } = RSAEncryptionPadding.OaepSHA256;
// TODO: add as json converter to IConfigurIConfiguration.Config
public string PaddingName
{
get => Padding.ToString();
init => Padding = typeof(RSAEncryptionPadding).GetProperty(value, BindingFlags.Public | BindingFlags.Static)?.GetValue(null) as RSAEncryptionPadding ?? throw new ArgumentException($"Padding '{value}' not found.");
}
public virtual string Id { get; internal set; }
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
protected virtual RSA RSA { get; } = RSA.Create();
public string Issuer { get; init; } = string.Empty;
public string Audience { get; init; } = string.Empty;
private readonly Lazy<RsaSecurityKey> _lazyRsaSecurityKey;
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 RSAKeyBase()
{
_lazyRsaSecurityKey = new(() => new RsaSecurityKey(RSA));
}
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
}
}

View File

@@ -28,36 +28,14 @@ namespace DigitalData.Core.Security.RSAKey
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;
Content = RSA.ExportRSAPublicKeyPem()
});
}
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;
@@ -67,17 +45,12 @@ namespace DigitalData.Core.Security.RSAKey
private void Init()
{
if (string.IsNullOrEmpty(_pem))
throw PemIsNullException;
throw new InvalidOperationException ($"The content of RSA private key is null or empty. Id: {Id}.");
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

@@ -13,11 +13,5 @@ namespace DigitalData.Core.Security.RSAKey
RSA.ImportFromPem(value);
}
}
public byte[] Encrypt(byte[] data) => RSA.Encrypt(data, Padding);
public string Encrypt(string data) => RSA.Encrypt(data.ToBytes(), Padding).ToBase64String();
public bool Verify(string data, string signature) => Encrypt(data) == signature;
}
}

View File

@@ -1,18 +1,26 @@
using Microsoft.IdentityModel.Tokens;
using DigitalData.Core.Abstractions.Security;
using Microsoft.IdentityModel.Tokens;
namespace DigitalData.Core.Abstractions.Security
namespace DigitalData.Core.Security.RSAKey
{
/// <summary>
/// Contains some information which used to create a security token. Designed to abstract <see cref="SecurityTokenDescriptor"/>
/// </summary>
public class PrivateKeyTokenDescriptor : IUniqueSecurityContext
public class RSATokenDescriptor : RSAPrivateKey, IAsymmetricTokenDescriptor
{
internal string IdSeparator { get; set; } = "_-_";
private string? _id;
public override string Id { get => _id ?? $"{Issuer}{IdSeparator}{Audience}"; internal set => _id = value; }
public string? ApiRoute { get; init; }
#region SecurityTokenDescriptor Map
/// <summary>
/// Gets or sets the value of the 'audience' claim.
/// </summary>
public new string Audience { get; set; }
public required string Audience { get; set; }
/// <summary>
/// Defines the compression algorithm that will be used to compress the JWT token payload.
@@ -30,9 +38,9 @@ namespace DigitalData.Core.Abstractions.Security
public DateTime? Expires { get; set; }
/// <summary>
/// Gets or sets the issuer of this <see cref="PrivateKeyTokenDescriptor"/>.
/// Gets or sets the issuer of this <see cref="SecurityTokenDescriptor"/>.
/// </summary>
public new string Issuer { get; set; }
public required string Issuer { get; set; }
/// <summary>
/// Gets or sets the time the security token was issued. This value should be in UTC.
@@ -75,18 +83,36 @@ namespace DigitalData.Core.Abstractions.Security
/// <summary>
/// Gets or sets the <see cref="SigningCredentials"/> used to create a security token.
/// </summary>
public SigningCredentials SigningCredentials { get; set; }
public SigningCredentials SigningCredentials => _lazySigningCredentials.Value;
#endregion SecurityTokenDescriptor
private readonly Lazy<RsaSecurityKey> _lazyRsaSecurityKey;
public SecurityKey SecurityKey => _lazyRsaSecurityKey.Value;
private readonly Lazy<SigningCredentials> _lazySigningCredentials;
/// <summary>
/// Specifies the signature algorithm to be applied to the <see cref="SigningCredentials"/>.
/// Default is <see cref="SecurityAlgorithms.RsaSha256"/>.
/// </summary>
public string SigningAlgorithm { get; internal set; } = SecurityAlgorithms.RsaSha256;
public string SigningAlgorithm { get; init; } = SecurityAlgorithms.RsaSha256;
/// <summary>
/// Optionally specifies the digest algorithm to be applied during the signing process for the <see cref="SigningCredentials"/>.
/// If not provided, the default algorithm is used.
/// </summary>
public string? SigningDigest { get; init; }
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
public RSATokenDescriptor()
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
{
_lazyRsaSecurityKey = new(() => new RsaSecurityKey(RSA));
_lazySigningCredentials = new(() => SigningDigest is null
? new(SecurityKey, SigningAlgorithm)
: new(SecurityKey, SigningAlgorithm, SigningDigest));
}
}
}
}