48 Commits

Author SHA1 Message Date
Developer 02
ad8d15314f chore(Security): Description fixed 2025-01-14 20:20:25 +01:00
Developer 02
f1efbae6a4 feat(Sicherheit): Paketdefinition hinzugefügt. 2025-01-14 20:14:59 +01:00
Developer 02
051567aa0a refactor(Abstraktionen): hochgestuft auf 3.1 2025-01-14 19:51:58 +01:00
Developer 02
287871ddc6 refactor(TokenDescriptor): ApiRoute-Eigenschaft entfernt 2025-01-14 19:45:44 +01:00
Developer 02
a0ad8d732d refactor(RSATokenDescriptor): Lifetime-Eigenschaft hinzugefügt 2025-01-14 17:04:54 +01:00
Developer 02
3ad08e2a86 feat(AsymmetricTokenValidator): SecurityKey-Eigenschaft hinzugefügt. 2025-01-13 09:52:52 +01:00
Developer 02
b90a52412c feat(IAsymmetricTokenDescriptor): Methode Validator.get mit Lazy Loading hinzugefügt. 2025-01-10 23:32:53 +01:00
Developer 02
39091ff5cf refactor(IAsymmetricKey): Id nullbar gemacht.
- Benennung der perm-Datei aktualisiert.
2025-01-10 15:47:02 +01:00
Developer 02
22040cf1e7 fix(CryptoFactoryParams): Die Zuweisung des Standard-Depotnamens wurde unter das Ereignis afterCreate verschoben. 2025-01-09 23:59:58 +01:00
Developer 02
af4b7d5438 fix(CryptoFactoryParams): Aktualisiert, um den ersten Decryptor zu verwenden, um die Standard-Depot-ID zu setzen. 2025-01-09 23:39:42 +01:00
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
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
36 changed files with 468 additions and 304 deletions

View File

@@ -9,7 +9,7 @@
<Authors>Digital Data GmbH</Authors>
<Company>Digital Data GmbH</Company>
<Product>DigitalData.Core.Abstractions</Product>
<Description>This package contains abstractions for the DigitalData.Core.Abstractions library, developed according to the principles of Clean Architecture. It promotes separation of concerns and enables independent core logic.</Description>
<Description>This package contains abstractions for the DigitalData.Core library, developed according to the principles of Clean Architecture. It promotes separation of concerns and enables independent core logic.</Description>
<PackageTags>digital data core abstractions clean architecture</PackageTags>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Copyright>Copyright 2024</Copyright>
@@ -17,9 +17,9 @@
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<PackAsTool>False</PackAsTool>
<PackageIcon>core_icon.png</PackageIcon>
<Version>3.0.0</Version>
<AssemblyVersion>3.0.0</AssemblyVersion>
<FileVersion>3.0.0</FileVersion>
<Version>3.1.0</Version>
<AssemblyVersion>3.1.0</AssemblyVersion>
<FileVersion>3.1.0</FileVersion>
</PropertyGroup>
<ItemGroup>

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

@@ -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

@@ -0,0 +1,9 @@
namespace DigitalData.Core.Abstractions.Security
{
public interface IAsymmetricKey
{
string? Id { get; }
string Content { 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);
IAsymmetricDecryptor CreateDecryptor(string pem, string? issuer = null, string? audience = null, bool encrypt = false, RSAEncryptionPadding? padding = null);
}
}

View File

@@ -0,0 +1,9 @@
namespace DigitalData.Core.Abstractions.Security
{
public interface IAsymmetricPrivateKey : IAsymmetricKey
{
bool IsEncrypted { get; }
IAsymmetricPublicKey PublicKey { get; }
}
}

View File

@@ -0,0 +1,6 @@
namespace DigitalData.Core.Abstractions.Security
{
public interface IAsymmetricPublicKey : IAsymmetricKey
{
}
}

View File

@@ -0,0 +1,74 @@
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
{
IAsymmetricTokenValidator Validator { get; }
TimeSpan Lifetime { get; init; }
#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,9 @@
using Microsoft.IdentityModel.Tokens;
namespace DigitalData.Core.Abstractions.Security
{
public interface IAsymmetricTokenValidator : IAsymmetricPublicKey
{
SecurityKey SecurityKey { get; }
}
}

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

@@ -6,12 +6,10 @@ namespace DigitalData.Core.Abstractions.Security
{
SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor);
SecurityToken CreateToken(TPrincipal subject, IAsymmetricTokenDescriptor descriptor);
SecurityToken CreateToken(TPrincipal subject, string issuer, string audience);
string WriteToken(SecurityToken token);
string WriteToken(SecurityTokenDescriptor descriptor);
string WriteToken(TPrincipal subject, string issuer, string audience);
}
}

View File

@@ -1,18 +0,0 @@
using Microsoft.IdentityModel.Tokens;
using System.Security.Cryptography;
namespace DigitalData.Core.Abstractions.Security
{
public interface IRSACryptographer : IUniqueSecurityContext
{
public string Pem { get; init; }
public RSAEncryptionPadding Padding { get; init; }
public new string Issuer { get; init; }
public new string Audience { get; init; }
public RsaSecurityKey RsaSecurityKey { get; }
}
}

View File

@@ -1,17 +0,0 @@
using Microsoft.IdentityModel.Tokens;
namespace DigitalData.Core.Abstractions.Security
{
public interface IRSADecryptor : IRSACryptographer
{
public bool IsEncrypted { get; init; }
IRSAEncryptor Encryptor { get; }
byte[] Decrypt(byte[] data);
string Decrypt(string data);
SigningCredentials CreateSigningCredentials(string algorithm = SecurityAlgorithms.RsaSha256, string? digest = null);
}
}

View File

@@ -1,11 +0,0 @@
namespace DigitalData.Core.Abstractions.Security
{
public interface IRSAEncryptor : IRSACryptographer
{
public byte[] Encrypt(byte[] data);
public string Encrypt(string data);
public bool Verify(string data, string signature) => Encrypt(data) == signature;
}
}

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,34 @@
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));
#endregion Jwt Signature Handler
}
}

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 CryptoFactoryParams : RSAFactoryParams
{
public string PemDirectory { get; init; } = string.Empty;
@@ -42,9 +41,11 @@ namespace DigitalData.Core.Security.Config
public IEnumerable<RSADecryptor> Decryptors { get; init; } = new List<RSADecryptor>();
public RSADecryptor? Vault { get; init; }
public IEnumerable<RSATokenDescriptor> TokenDescriptors { get; init; } = new List<RSATokenDescriptor>();
public AsymCryptParams()
public RSADecryptor? VaultDecryptor { get; init; }
public CryptoFactoryParams()
{
// init decryptors
AfterCreate += () =>
@@ -53,13 +54,29 @@ namespace DigitalData.Core.Security.Config
if (!Directory.Exists(PemDirectory))
Directory.CreateDirectory(PemDirectory);
foreach (var decryptor in Decryptors)
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)
// file name
var file_name_params = new List<object>();
if (privateKey.Id is not null)
file_name_params.Add(privateKey.Id);
else if (privateKey is RSATokenDescriptor descriptor)
file_name_params.Add(descriptor.Issuer);
file_name_params.Add(KeySizeInBits);
file_name_params.Add(DateTime.Now.ToTag(DateTagFormat));
if (privateKey.IsEncrypted)
file_name_params.Add(Secrets.Version);
var file_name = $"{string.Join(FileNameSeparator, file_name_params)}.{FileExtension}";
@@ -67,14 +84,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

@@ -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<IAsymmetricTokenDescriptor, SecurityTokenDescriptor>();
}
}
}

View File

@@ -1,27 +1,23 @@
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 CryptoFactory : RSAFactory<CryptoFactoryParams>, ICryptoFactory, IAsymmetricKeyFactory
{
public IEnumerable<IRSADecryptor> Decryptors { 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 IRSADecryptor Vault { get; }
public IAsymmetricDecryptor VaultDecryptor { get; }
private readonly Lazy<IEnumerable<IRSAEncryptor>> _lazyEncryptors;
public IEnumerable<IAsymmetricTokenDescriptor> TokenDescriptors { get; init; } = new List<IAsymmetricTokenDescriptor>();
public IEnumerable<IRSAEncryptor> Encryptors => _lazyEncryptors.Value;
public IEnumerable<TokenDescription> TokenDescriptions { get; init; } = new List<TokenDescription>();
public AsymCryptHandler(IOptions<AsymCryptParams> options, ILogger<AsymCryptHandler>? 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"));
@@ -34,9 +30,9 @@ namespace DigitalData.Core.Security
Decryptors = _params.Decryptors;
Vault = _params.Vault ?? Decryptors.First();
TokenDescriptors = _params.TokenDescriptors;
_lazyEncryptors = new(Decryptors.Select(decryptor => decryptor.Encryptor));
VaultDecryptor = _params.VaultDecryptor ?? Decryptors.First();
}
}
}

View File

@@ -1,39 +0,0 @@
using DigitalData.Core.Abstractions.Security;
using Microsoft.IdentityModel.Tokens;
using System.Reflection;
using System.Security.Cryptography;
namespace DigitalData.Core.Security.Cryptographer
{
//TODO: Abstract RSA for future updates (using ECC, El Gamal or Lattice-based Cryptography)
public class RSACryptographer : IRSACryptographer
{
public virtual string Pem { 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.");
}
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 RsaSecurityKey RsaSecurityKey => _lazyRsaSecurityKey.Value;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
internal RSACryptographer()
{
_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

@@ -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,23 +0,0 @@
using DigitalData.Core.Abstractions.Security;
namespace DigitalData.Core.Security.Cryptographer
{
public class RSAEncryptor : RSACryptographer, IRSAEncryptor, IRSACryptographer
{
public override string Pem
{
get => base.Pem;
init
{
base.Pem = value;
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,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 AddCryptoFactory(this IServiceCollection services) => services
.AddParamsConfigureOptions<CryptoFactoryParams>()
.AddAutoMapper(typeof(MappingProfile).Assembly)
.AddSingleton<IAsymCryptHandler, AsymCryptHandler>();
.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 AddAsymCryptService(this IServiceCollection services, IConfigurationSection section) => services
.Configure<AsymCryptParams>(section)
.AddAsymCryptService();
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 AddAsymCryptService(this IServiceCollection services, AsymCryptParams? asymCryptParams = null) => services
.AddSingleton(Options.Create(asymCryptParams ?? new()))
.AddAsymCryptService();
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.
@@ -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

@@ -4,8 +4,28 @@
<TargetFrameworks>net7.0;net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageId>DigitalData.Core.Security</PackageId>
<Version>1.0.0</Version>
<Company>Digital Data GmbH</Company>
<Product>Digital Data GmbH</Product>
<Description>This package provides RSA-based security functionalities as an implementation of the DigitalData.Core.Abstractions.Security library. It supports robust encryption and decryption operations, as well as JWT signing and validation for secure authentication and data integrity.</Description>
<Authors>Digital Data GmbH</Authors>
<Copyright>Copyright 2025</Copyright>
<PackageProjectUrl></PackageProjectUrl>
<PackageIcon>core_icon.png</PackageIcon>
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<PackageTags>digital data core security</PackageTags>
<AssemblyVersion>1.0.0</AssemblyVersion>
<FileVersion>1.0.0</FileVersion>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\nuget-package-icons\core_icon.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />

View File

@@ -1,19 +1,11 @@
using AutoMapper;
using DigitalData.Core.Security.Config;
using DigitalData.Core.Abstractions.Security;
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 />
@@ -85,13 +77,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, IAsymmetricTokenDescriptor 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

@@ -13,42 +13,28 @@ namespace DigitalData.Core.Security
private readonly IMapper _mapper;
private readonly IEnumerable<TokenDescription>? _tokenDescriptions;
private readonly ICryptoFactory _cryptoFactory;
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, ICryptoFactory cryptoFactory)
{
_claimDescriptor = claimDescriptorOptions.Value;
_mapper = mapper;
_tokenDescriptions = tokenDescriptionOptions?.Value;
_cryptHandler = asymCryptHandler;
_cryptoFactory = cryptoFactory;
}
public SecurityToken CreateToken(TPrincipal subject, TokenDescription description)
public SecurityToken CreateToken(TPrincipal subject, IAsymmetricTokenDescriptor descriptor)
{
var descriptor = _mapper.Map(description);
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 description = _tokenDescriptions?.Get(issuer: issuer, audience: audience)
?? throw new InvalidOperationException($"No token description found for issuer '{issuer}' and audience '{audience}'.");
description.SigningCredentials = _cryptHandler.Decryptors
.Get(issuer: issuer, audience: audience)
.CreateSigningCredentials(algorithm: description.SigningAlgorithm, digest: description.SigningDigest);
return CreateToken(subject: subject, description: description);
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, descriptor: 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, string issuer, string audience) => WriteToken(CreateToken(subject: subject, issuer: issuer, audience: audience));
}
}

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

@@ -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,11 +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 IAsymmetricDecryptor CreateDecryptor(string pem, string? issuer = null, string? audience = null, bool encrypt = false, RSAEncryptionPadding? padding = null) => new RSADecryptor()
{
Pem = pem,
Issuer = issuer ?? string.Empty,
Audience = audience ?? string.Empty,
Content = pem,
IsEncrypted = encrypt,
Padding = padding ?? RSAEncryptionPadding.OaepSHA256
};

View File

@@ -0,0 +1,16 @@
using DigitalData.Core.Abstractions.Security;
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; }
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
public string? Id { get; init; }
protected virtual RSA RSA { get; } = RSA.Create();
}
}

View File

@@ -0,0 +1,55 @@
using DigitalData.Core.Abstractions.Security;
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; }
protected TPublicKey CreatePublicKey<TPublicKey>() where TPublicKey : RSAPublicKey, new()
=> new() { Content = RSA.ExportRSAPublicKeyPem() };
private readonly Lazy<RSAPublicKey> _lazyPublicKey;
public IAsymmetricPublicKey PublicKey => _lazyPublicKey.Value;
public RSAPrivateKey()
{
_lazyPublicKey = new(CreatePublicKey<RSAPublicKey>);
}
internal void SetPem(string pem)
{
_pem = pem;
Init();
}
private void Init()
{
if (string.IsNullOrEmpty(_pem))
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);
}
}
}

View File

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

View File

@@ -1,17 +1,24 @@
using DigitalData.Core.Abstractions.Security;
using Microsoft.IdentityModel.Tokens;
namespace DigitalData.Core.Security.Config
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 TokenDescription : IUniqueSecurityContext
public class RSATokenDescriptor : RSAPrivateKey, IAsymmetricTokenDescriptor
{
private readonly Lazy<RSATokenValidator> _lazyTokenValidator;
public IAsymmetricTokenValidator Validator => _lazyTokenValidator.Value;
public required TimeSpan Lifetime { 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.
@@ -25,13 +32,14 @@ namespace DigitalData.Core.Security.Config
/// <summary>
/// Gets or sets the value of the 'expiration' claim. This value should be in UTC.
/// The expiration time is the sum of DateTime.Now and LifeTime.
/// </summary>
public DateTime? Expires { get; set; }
public DateTime? Expires => DateTime.Now.AddTicks(Lifetime.Ticks);
/// <summary>
/// Gets or sets the issuer of this <see cref="ITokenDescription"/>.
/// 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.
@@ -74,7 +82,14 @@ namespace DigitalData.Core.Security.Config
/// <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"/>.
@@ -87,5 +102,18 @@ namespace DigitalData.Core.Security.Config
/// 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.
{
_lazyTokenValidator = new(CreatePublicKey<RSATokenValidator>);
_lazyRsaSecurityKey = new(() => new RsaSecurityKey(RSA));
_lazySigningCredentials = new(() => SigningDigest is null
? new(SecurityKey, SigningAlgorithm)
: new(SecurityKey, SigningAlgorithm, SigningDigest));
}
}
}
}

View File

@@ -0,0 +1,17 @@
using DigitalData.Core.Abstractions.Security;
using Microsoft.IdentityModel.Tokens;
namespace DigitalData.Core.Security.RSAKey
{
public class RSATokenValidator : RSAPublicKey, IAsymmetricTokenValidator
{
private readonly Lazy<RsaSecurityKey> _lazyRsaSecurityKey;
public SecurityKey SecurityKey => _lazyRsaSecurityKey.Value;
public RSATokenValidator()
{
_lazyRsaSecurityKey = new(() => new RsaSecurityKey(RSA));
}
}
}

View File

@@ -25,7 +25,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DigitalData.Core.Security",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DigitalData.Core.Terminal", "DigitalData.Core.Terminal\DigitalData.Core.Terminal.csproj", "{0FA93730-8084-4907-B172-87D610323796}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigitalData.Core.Tests.API", "DigitalData.Core.Tests.API\DigitalData.Core.Tests.API.csproj", "{9BC2DEC5-E89D-48CC-9A51-4D94496EE4A6}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DigitalData.Core.Tests.API", "DigitalData.Core.Tests.API\DigitalData.Core.Tests.API.csproj", "{9BC2DEC5-E89D-48CC-9A51-4D94496EE4A6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -68,8 +68,8 @@ Global
{E009A053-A9F4-48F2-984F-EF5C376A9B14}.Debug|Any CPU.Build.0 = Release|Any CPU
{E009A053-A9F4-48F2-984F-EF5C376A9B14}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E009A053-A9F4-48F2-984F-EF5C376A9B14}.Release|Any CPU.Build.0 = Release|Any CPU
{47D80C65-74A2-4EB8-96A5-D571A9108FB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{47D80C65-74A2-4EB8-96A5-D571A9108FB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{47D80C65-74A2-4EB8-96A5-D571A9108FB3}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{47D80C65-74A2-4EB8-96A5-D571A9108FB3}.Debug|Any CPU.Build.0 = Release|Any CPU
{47D80C65-74A2-4EB8-96A5-D571A9108FB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{47D80C65-74A2-4EB8-96A5-D571A9108FB3}.Release|Any CPU.Build.0 = Release|Any CPU
{0FA93730-8084-4907-B172-87D610323796}.Debug|Any CPU.ActiveCfg = Debug|Any CPU