27 Commits

Author SHA1 Message Date
Developer 02
592b949f57 feat: Unterstützung für Token-Beschreibungen im JwtSignatureHandler hinzugefügt
- Methoden eingeführt, um Token-Beschreibungen im DI-Container zu konfigurieren und zu registrieren.
- Überladungen zu `AddJwtSignatureHandler` hinzugefügt, um sowohl konfigurationsbasierte als auch Inline-Token-Beschreibungen zu unterstützen.
2025-01-06 16:32:32 +01:00
Developer 02
8850ac4ac9 refactor(DIExtensions): Methode AddJwtSignatureHandler hinzugefügt 2025-01-06 16:08:45 +01:00
Developer 02
8ccf6f31ae refactor(JwtSignatureHandler): Umbenennung von CreateAndWriteToken in WriteToken 2025-01-06 15:35:51 +01:00
Developer 02
9875d023e3 Revert "refactor(TokenDescription): In die Abstraktionsschicht verschoben."
This reverts commit 2cf0eb3977.
2025-01-06 15:31:26 +01:00
Developer 02
62afba7c23 refactor: Entfernen redundanter Methodenüberladungen in IJwtSignatureHandler
- Entfernte doppelte Methodensignaturen für CreateToken und CreateAndWriteToken, die TokenDescription akzeptieren.
- Vereinfachte das Interface, um sich auf die wesentlichen Methoden für die Token-Erstellung und -Schreibung zu konzentrieren.
2025-01-06 15:26:31 +01:00
Developer 02
1d4882cfbc refactor(JwtSignatureService): umbenannt in JwtSignatureHandler.
- seine Schnittstelle umbenannt
2025-01-06 15:15:46 +01:00
Developer 02
275b9ec858 feat(IJwtSignatureService): Erstellt und implementiert 2025-01-06 15:06:31 +01:00
Developer 02
2cf0eb3977 refactor(TokenDescription): In die Abstraktionsschicht verschoben. 2025-01-06 14:39:45 +01:00
Developer 02
4ab5393deb refactor(JwtSignatureService): Injizieren von IAsymCryptHandler anstelle von AsymCryptHandler zur Abstraktion. 2025-01-06 13:13:57 +01:00
Developer 02
a2dc59d5ef refactor(AsymCryptParams): Entfernte TokenDescriptions 2025-01-06 13:11:05 +01:00
Developer 02
ed041bf7cb refactor(JwtSignatureService): CreateToken und CreateAndWriteToken Methoden mit Issuer und Audience Inputs hinzugefügt 2025-01-06 12:13:41 +01:00
Developer 02
c70327e7f4 refactor(IRSADecryptor): Methode CreateSigningCredentials hinzugefügt 2025-01-06 11:32:17 +01:00
Developer 02
0a3ce89c0d refactor(IRSACryptographer): RsaSecurityKey-Eigenschaft hinzugefügt 2025-01-06 11:29:19 +01:00
Developer 02
389d64c25d refactor(AsymCryptService): umbenannt in AsymCryptHandler 2025-01-06 10:44:03 +01:00
Developer 02
a3931414e3 refactor(AsymCryptService): Indexer und IEnumerable-Implementierung zur Vereinfachung entfernt 2024-12-21 10:10:50 +01:00
Developer 02
0dd897625a feat(SecurityExtensions): Die Match- und TryMatch-Erweiterungsmethoden wurden hinzugefügt, um die Funktionalität in SecurityExtensions zu erweitern. Diese Methoden vereinfachen das Matching von IUniqueSecurityContext durch direkte Verwendung eines Lookup-Kontexts. 2024-12-20 23:34:21 +01:00
Developer 02
351a6732cf refactor(SecurityExtensions): Nullbarkeit des out-Wertes in der TryGet-Methode wurde entfernt.
- Warnung ist deaktiviert.
 - Null-Prüfung in der Verwendung entfernt.
2024-12-20 22:59:21 +01:00
Developer 02
5a1808c6a6 refactor(CryptographerExtensions): Aktualisiert zur Verwendung von IUniqueSecurityContext anstelle von IRSACryptographer.
- umbenannt in SecurityExtensions.
2024-12-20 18:54:33 +01:00
Developer 02
50c42e9cdd rename(ISecurityIdentifier): Umbenannt in IUniqueSecurityContext und Kommentare zur Dokumentation hinzugefügt. 2024-12-20 14:42:07 +01:00
Developer 02
ec126be2aa feat(ISecurityIdentifier): Implementiert in IRSACryptographer und TokenDescription 2024-12-20 14:35:10 +01:00
Developer 02
9953bbd2ef feat(ISecurityIdentifier): Wird als Basisbezeichner in den Sicherheitsklassen erstellt. 2024-12-20 14:11:16 +01:00
Developer 02
dbecfa92f4 refactor(Extension): Zusammenlegung der Erweiterungsklassen zur Vereinfachung.
- Methoden und Klasse intern gemacht, um Komplexität zu vermeiden.
2024-12-20 10:48:37 +01:00
Developer 02
e007f15bce refactor(JwtSignatureService): removed primary constructor because this feature is not available in C# 11.0.
- Added GlobalSuppressions to avoid editor to offer this.
2024-12-20 10:40:35 +01:00
Developer 02
79dffef528 Refactor: Entfernung der generischen IRSAFactory und IAsymCryptService.
- RSAFactory und AsymCryptService aktualisiert.
 - Aktualisierte DI-Erweiterungen
2024-12-20 10:30:12 +01:00
Developer 02
af478e974c refactor(TokenDescriptorProvider): entfernt und eine Mapper-Erweiterungsmethode mit derselben Funktionalität zur Vereinfachung hinzugefügt. 2024-12-20 09:44:04 +01:00
Developer 02
435c91955c refactor(JwtSignatureService): verwendete primäre Struktur. 2024-12-20 09:29:49 +01:00
Developer 02
4142d2d948 refactor(TokenDescriptorProvider): verwendete Primärstruktur. 2024-12-20 09:27:08 +01:00
22 changed files with 265 additions and 306 deletions

View File

@@ -1,21 +0,0 @@
namespace DigitalData.Core.Abstractions.Security
{
public static class CryptographerExtensions
{
public static IEnumerable<TRSACryptographer> GetByIssuer<TRSACryptographer>(this IEnumerable<TRSACryptographer> cryptographers, string issuer) where TRSACryptographer: IRSACryptographer
=> cryptographers.Where(c => c.Issuer == issuer);
public static IEnumerable<TRSACryptographer> GetByAudience<TRSACryptographer>(this IEnumerable<TRSACryptographer> cryptographers, string audience) where TRSACryptographer : IRSACryptographer
=> cryptographers.Where(c => c.Audience == audience);
public static TRSACryptographer Get<TRSACryptographer>(this IEnumerable<TRSACryptographer> cryptographers, string issuer, string audience) where TRSACryptographer : IRSACryptographer
=> cryptographers.Where(c => c.Issuer == issuer && c.Audience == audience).SingleOrDefault()
?? throw new InvalidOperationException($"No {typeof(TRSACryptographer).GetType().Name.TrimStart('I')} found with Issuer: {issuer} and Audience: {audience}.");
public static bool TryGet<TRSACryptographer>(this IEnumerable<TRSACryptographer> cryptographers, string issuer, string audience, out TRSACryptographer? cryptographer) where TRSACryptographer : IRSACryptographer
{
cryptographer = cryptographers.SingleOrDefault(c => c.Issuer == issuer && c.Audience == audience);
return cryptographer is not null;
}
}
}

View File

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

View File

@@ -1,15 +0,0 @@
namespace DigitalData.Core.Abstractions.Security
{
public interface IAsymCryptService : IRSAFactory, IEnumerable<IRSADecryptor>
{
IEnumerable<IRSADecryptor> Decryptors { get; }
IRSADecryptor Vault { get; }
IRSADecryptor this[string key] { get; }
IEnumerable<IRSAEncryptor> Encryptors { get; }
}
public interface IAsymCryptService<TParams> : IAsymCryptService, IRSAFactory<TParams> { }
}

View File

@@ -0,0 +1,17 @@
using Microsoft.IdentityModel.Tokens;
namespace DigitalData.Core.Abstractions.Security
{
public interface IJwtSignatureHandler<TPrincipal>
{
SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor);
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,15 +1,18 @@
using System.Security.Cryptography;
using Microsoft.IdentityModel.Tokens;
using System.Security.Cryptography;
namespace DigitalData.Core.Abstractions.Security
{
public interface IRSACryptographer
public interface IRSACryptographer : IUniqueSecurityContext
{
public string Pem { get; init; }
public RSAEncryptionPadding Padding { get; init; }
public string Issuer { get; init; }
public new string Issuer { get; init; }
public string Audience { get; init; }
public new string Audience { get; init; }
public RsaSecurityKey RsaSecurityKey { get; }
}
}

View File

@@ -1,4 +1,6 @@
namespace DigitalData.Core.Abstractions.Security
using Microsoft.IdentityModel.Tokens;
namespace DigitalData.Core.Abstractions.Security
{
public interface IRSADecryptor : IRSACryptographer
{
@@ -9,5 +11,7 @@
byte[] Decrypt(byte[] data);
string Decrypt(string data);
SigningCredentials CreateSigningCredentials(string algorithm = SecurityAlgorithms.RsaSha256, string? digest = null);
}
}

View File

@@ -20,6 +20,4 @@ namespace DigitalData.Core.Abstractions.Security
IRSADecryptor CreateDecryptor(string pem, string? issuer = null, string? audience = null, bool encrypt = false, RSAEncryptionPadding? padding = null);
}
public interface IRSAFactory<TParams> : IRSAFactory { }
}

View File

@@ -0,0 +1,24 @@
namespace DigitalData.Core.Abstractions.Security
{
/// <summary>
/// Represents a unique security context that identifies an issuer and an audience.
/// </summary>
public interface IUniqueSecurityContext
{
/// <summary>
/// Gets the issuer identifier for this security context.
/// </summary>
/// <remarks>
/// The issuer typically represents the entity that issues a token or a cryptographic key.
/// </remarks>
string Issuer { get; }
/// <summary>
/// Gets the audience identifier for this security context.
/// </summary>
/// <remarks>
/// The audience typically represents the intended recipient or target of a token or cryptographic operation.
/// </remarks>
string Audience { get; }
}
}

View File

@@ -0,0 +1,29 @@
namespace DigitalData.Core.Abstractions.Security
{
public static class SecurityExtensions
{
public static IEnumerable<TUniqueSecurityContext> GetByIssuer<TUniqueSecurityContext>(this IEnumerable<TUniqueSecurityContext> contextes, string issuer) where TUniqueSecurityContext: IUniqueSecurityContext
=> contextes.Where(c => c.Issuer == issuer);
public static IEnumerable<TUniqueSecurityContext> GetByAudience<TUniqueSecurityContext>(this IEnumerable<TUniqueSecurityContext> contextes, string audience) where TUniqueSecurityContext : IUniqueSecurityContext
=> contextes.Where(c => c.Audience == audience);
public static TUniqueSecurityContext Get<TUniqueSecurityContext>(this IEnumerable<TUniqueSecurityContext> contextes, string issuer, string audience) where TUniqueSecurityContext : IUniqueSecurityContext
=> contextes.Where(c => c.Issuer == issuer && c.Audience == audience).SingleOrDefault()
?? throw new InvalidOperationException($"Exactly one {typeof(TUniqueSecurityContext).Name} must exist with Issuer: '{issuer}' and Audience: '{audience}'.");
public static bool TryGet<TUniqueSecurityContext>(this IEnumerable<TUniqueSecurityContext> contextes, string issuer, string audience, out TUniqueSecurityContext context) where TUniqueSecurityContext : IUniqueSecurityContext
{
#pragma warning disable CS8601 // Possible null reference assignment.
context = contextes.SingleOrDefault(c => c.Issuer == issuer && c.Audience == audience);
#pragma warning restore CS8601 // Possible null reference assignment.
return context is not null;
}
public static TUniqueSecurityContext Match<TUniqueSecurityContext>(this IEnumerable<TUniqueSecurityContext> contextes, IUniqueSecurityContext lookupContext) where TUniqueSecurityContext : IUniqueSecurityContext
=> contextes.Get(lookupContext.Issuer, lookupContext.Audience);
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);
}
}

View File

@@ -0,0 +1,42 @@
using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Config;
using DigitalData.Core.Security.Cryptographer;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace DigitalData.Core.Security
{
public class AsymCryptHandler : RSAFactory<AsymCryptParams>, IAsymCryptHandler, IRSAFactory
{
public IEnumerable<IRSADecryptor> 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; }
private readonly Lazy<IEnumerable<IRSAEncryptor>> _lazyEncryptors;
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)
{
logger?.LogInformation("Core.Secrets version: {Version}, Created on: {CreationDate}.", Secrets.Version, Secrets.CreationDate.ToString("dd.MM.yyyy"));
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."
);
Decryptors = _params.Decryptors;
Vault = _params.Vault ?? Decryptors.First();
_lazyEncryptors = new(Decryptors.Select(decryptor => decryptor.Encryptor));
}
}
}

View File

@@ -1,70 +0,0 @@
using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Config;
using DigitalData.Core.Security.Cryptographer;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections;
namespace DigitalData.Core.Security
{
public class AsymCryptService<TAsymCryptParams> : RSAFactory<TAsymCryptParams>, IAsymCryptService<TAsymCryptParams>, IRSAFactory<TAsymCryptParams>, IEnumerable<IRSADecryptor>
where TAsymCryptParams : AsymCryptParams
{
public IEnumerable<IRSADecryptor> 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 IRSADecryptor this[string key]
{
get
{
var key_params = key.Split(_params.KeyNameSeparator);
if (key_params.Length != 2)
throw new ArgumentException($"Invalid key format. Expected two segments separated by '{_params.KeyNameSeparator}', but received: '{key}'.", nameof(key));
return _params.Decryptors.FirstOrDefault(d => d.Issuer == key_params[0] && d.Audience == key_params[1])
?? throw new KeyNotFoundException($"No decryptor found matching the issuer '{key_params[0]}' and audience '{key_params[1]}'.");
}
}
public IRSADecryptor this[int index] => index < 0 || index >= Decryptors.Count()
? Decryptors.ElementAt(index)
: throw new ArgumentOutOfRangeException(
nameof(index),
index,
$"The index {index} is out of range. The valid indices for {GetType()} are between 0 and {Decryptors.Count() - 1} (inclusive). Please ensure the index is within this range.");
public AsymCryptService(IOptions<TAsymCryptParams> options, ILogger<AsymCryptService<TAsymCryptParams>>? 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())
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;
Vault = _params.Vault ?? Decryptors.First();
}
public IEnumerator<IRSADecryptor> GetEnumerator() => Decryptors.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => Decryptors.GetEnumerator();
public IEnumerable<IRSAEncryptor> Encryptors
{
get
{
foreach (var decryptor in Decryptors)
yield return decryptor.Encryptor;
}
}
}
}

View File

@@ -23,20 +23,6 @@ namespace DigitalData.Core.Security.Config
public string FileExtension { get; init; } = "pem";
/// <summary>
/// Represents the separator used to concatenate the components of a key-related token string.
/// </summary>
/// <remarks>
/// The resulting key-related token string is constructed as follows:
/// <c>string.Join(KeyNameSeparator, Issuer, Audience, Secret_version)</c>.
/// If <c>Secret_version</c> is not null, it will be included in the concatenation.
/// </remarks>
/// <example>
/// For example, if <c>KeyNameSeparator = ":"</c>, the output might look like:
/// <c>"Issuer:Audience:Secret_version"</c>.
/// </example>
public string KeyNameSeparator { get; init; } = ":";
/// <summary>
///This is the subtext of the pem file name. For the file to be automatically renewed, the name must be assigned to change periodically. For example, by default MM/2 will be refreshed every 2 months.
/// <br />
@@ -56,8 +42,6 @@ namespace DigitalData.Core.Security.Config
public IEnumerable<RSADecryptor> Decryptors { get; init; } = new List<RSADecryptor>();
public IEnumerable<TokenDescription> TokenDescriptions { get; init; } = new List<TokenDescription>();
public RSADecryptor? Vault { get; init; }
public AsymCryptParams()
@@ -98,19 +82,6 @@ namespace DigitalData.Core.Security.Config
}
}
};
// set signing credentials of token descriptions
AfterCreate += () =>
{
foreach(var tDesc in TokenDescriptions)
{
if (!Decryptors.TryGet(issuer: tDesc.Issuer, tDesc.Audience, out var decryptor) || decryptor is null)
throw new InvalidOperationException(
$"Decryptor for Issuer '{tDesc.Issuer}' and Audience '{tDesc.Audience}' could not be found or is null.");
tDesc.SigningCredentials = decryptor.CreateSigningCredentials(algorithm: tDesc.SigningAlgorithm, digest: tDesc.SigningDigest);
}
};
}
}
}

View File

@@ -1,16 +1,17 @@
using Microsoft.IdentityModel.Tokens;
using DigitalData.Core.Abstractions.Security;
using Microsoft.IdentityModel.Tokens;
namespace DigitalData.Core.Security.Config
{
/// <summary>
/// Contains some information which used to create a security token. Designed to abstract <see cref="SecurityTokenDescriptor"/>
/// </summary>
public class TokenDescription
public class TokenDescription : IUniqueSecurityContext
{
/// <summary>
/// Gets or sets the value of the 'audience' claim.
/// </summary>
public string Audience { get; set; }
public new string Audience { get; set; }
/// <summary>
/// Defines the compression algorithm that will be used to compress the JWT token payload.
@@ -30,7 +31,7 @@ namespace DigitalData.Core.Security.Config
/// <summary>
/// Gets or sets the issuer of this <see cref="ITokenDescription"/>.
/// </summary>
public string Issuer { get; set; }
public new string Issuer { get; set; }
/// <summary>
/// Gets or sets the time the security token was issued. This value should be in UTC.
@@ -85,6 +86,6 @@ namespace DigitalData.Core.Security.Config
/// 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 = null;
public string? SigningDigest { get; init; }
}
}

View File

@@ -1,5 +1,4 @@
using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Config;
using Microsoft.IdentityModel.Tokens;
using System.Security.Cryptography;

View File

@@ -1,5 +1,4 @@
using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Config;
namespace DigitalData.Core.Security.Cryptographer
{

View File

@@ -5,7 +5,7 @@ using System.Security.Cryptography;
namespace DigitalData.Core.Security.Cryptographer
{
public class RSAFactory<TRSAFactoryParams> : IRSAFactory<TRSAFactoryParams> where TRSAFactoryParams : RSAFactoryParams
public class RSAFactory<TRSAFactoryParams> : IRSAFactory where TRSAFactoryParams : RSAFactoryParams
{
protected readonly TRSAFactoryParams _params;

View File

@@ -10,122 +10,43 @@ namespace DigitalData.Core.Security
{
public static class DIExtensions
{
private static (bool Added, object Lock) _mappingProfile = (false, new());
private static IServiceCollection AddMappingProfile(this IServiceCollection services)
{
if (_mappingProfile.Added)
return services;
lock (_mappingProfile.Lock)
{
if (_mappingProfile.Added)
return services;
_mappingProfile.Added = true;
return services
.AddAutoMapper(typeof(MappingProfile).Assembly)
.AddSingleton<TokenDescriptorProvider>();
}
}
private static IServiceCollection AddParamsConfigureOptions<TParams>(this IServiceCollection services) where TParams : RSAFactoryParams
=> services.AddSingleton<IConfigureOptions<TParams>, ParamsConfigureOptions<TParams>>();
private static IServiceCollection AddAsymCryptService<TAsymCryptParams>(this IServiceCollection services, bool setAsDefault = false) where TAsymCryptParams : AsymCryptParams
{
services.AddParamsConfigureOptions<TAsymCryptParams>().AddMappingProfile();
return setAsDefault
? services.AddSingleton<IAsymCryptService, AsymCryptService<TAsymCryptParams>>()
: services.AddSingleton<IAsymCryptService<TAsymCryptParams>, AsymCryptService<TAsymCryptParams>>();
}
private static IServiceCollection AddAsymCryptService(this IServiceCollection services) => services
.AddParamsConfigureOptions<AsymCryptParams>()
.AddAutoMapper(typeof(MappingProfile).Assembly)
.AddSingleton<IAsymCryptHandler, AsymCryptHandler>();
/// <summary>
/// Registers a custom asym crypt service with specified parameters from the given configuration section.
/// </summary>
/// <typeparam name="TAsymCryptParams"></typeparam>
/// <param name="services"></param>
/// <param name="section"></param>
/// <param name="setAsDefault">If true, the factory is registered as the default <see cref="IRSAFactory"/>. Otherwise, it is registered as <see cref="IRSAFactory{TRSAFactoryParams}"/>.</param>
/// <returns></returns>
public static IServiceCollection AddAsymCryptService<TAsymCryptParams>(this IServiceCollection services, IConfigurationSection section, bool setAsDefault = false) where TAsymCryptParams : AsymCryptParams => services
.Configure<TAsymCryptParams>(section)
.AddAsymCryptService<TAsymCryptParams>(setAsDefault: setAsDefault);
/// <summary>
/// Registers a custom asym crypt service with default parameters from the given configuration section.
/// </summary>
/// <param name="services"></param>
/// <param name="section"></param>
/// <param name="setAsDefault"></param>
/// <returns></returns>
public static IServiceCollection AddAsymCryptService(this IServiceCollection services, IConfigurationSection section, bool setAsDefault = false)
=> services.Configure<AsymCryptParams>(section).AddAsymCryptService<AsymCryptParams>(setAsDefault: setAsDefault);
/// <summary>
/// Registers an asym crypt service with the specified parameters from the given instance. Optionally, sets it as the default factory.
/// </summary>
/// <typeparam name="TAsymCryptParams"></typeparam>
/// <param name="services"></param>
/// <param name="param"></param>
/// <param name="setAsDefault">If true, the factory is registered as the default <see cref="IRSAFactory"/>. Otherwise, it is registered as <see cref="IRSAFactory{TRSAFactoryParams}"/>.</param>
/// <returns></returns>
public static IServiceCollection AddAsymCryptService<TAsymCryptParams>(this IServiceCollection services, TAsymCryptParams param, bool setAsDefault = false) where TAsymCryptParams : AsymCryptParams => services
.AddSingleton(Options.Create(param))
.AddAsymCryptService<TAsymCryptParams>(setAsDefault: setAsDefault);
/// <summary>
/// Registers default asym crypt service with the specified parameters from the given instance.
/// </summary>
/// <param name="services"></param>
/// <param name="param"></param>
/// <returns></returns>
public static IServiceCollection AddAsymCryptService(this IServiceCollection services, AsymCryptParams param) => services
.AddAsymCryptService(param: param, setAsDefault: true);
/// <summary>
/// Registers default RSA Factory instance with default params
/// </summary>
/// <param name="services"></param>
/// <param name="factoryParams"></param>
/// <returns>The updated <see cref="IServiceCollection"/> with the RSA Factory registered.</returns>
public static IServiceCollection AddRSAFactory(this IServiceCollection services, RSAFactoryParams? factoryParams = null) => services
.AddParamsConfigureOptions<RSAFactoryParams>()
.AddMappingProfile()
.AddScoped<IRSAFactory>(_ => new RSAFactory<RSAFactoryParams>(Options.Create(factoryParams ?? new())));
public static IServiceCollection AddAsymCryptService(this IServiceCollection services, IConfigurationSection section) => services
.Configure<AsymCryptParams>(section)
.AddAsymCryptService();
/// <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();
/// <summary>
/// Registers a custom RSA Factory with specified parameters from the given configuration section.
/// </summary>
/// <typeparam name="TRSAFactoryParams"></typeparam>
/// <param name="services"></param>
/// <param name="section"></param>
/// <param name="setAsDefault">If true, the factory is registered as the default <see cref="IRSAFactory"/>. Otherwise, it is registered as <see cref="IRSAFactory{TRSAFactoryParams}"/>.</param>
/// <returns>The updated <see cref="IServiceCollection"/> with the RSA Factory registered.</returns>
public static IServiceCollection AddRSAFactory<TRSAFactoryParams>(this IServiceCollection services, IConfigurationSection section, bool setAsDefault = false)
where TRSAFactoryParams : RSAFactoryParams
{
services.AddMappingProfile().AddParamsConfigureOptions<TRSAFactoryParams>().Configure<TRSAFactoryParams>(section);
return setAsDefault
? services.AddSingleton<IRSAFactory, RSAFactory<TRSAFactoryParams>>()
: services.AddSingleton<IRSAFactory<TRSAFactoryParams>, RSAFactory<TRSAFactoryParams>>();
}
/// <summary>
/// Registers an RSA Factory with the specified parameters from the given instance. Optionally, sets it as the default factory.
/// </summary>
/// <typeparam name="TRSAFactoryParams">The type of the RSA factory parameters.</typeparam>
/// <param name="services"></param>
/// <param name="rsaParams"></param>
/// <param name="setAsDefault">If true, the factory is registered as the default <see cref="IRSAFactory"/>. Otherwise, it is registered as <see cref="IRSAFactory{TRSAFactoryParams}"/>.</param>
/// <returns>The updated <see cref="IServiceCollection"/> with the RSA Factory registered.</returns>
public static IServiceCollection AddRSAFactory<TRSAFactoryParams>(this IServiceCollection services, TRSAFactoryParams rsaParams, bool setAsDefault = false)
where TRSAFactoryParams : RSAFactoryParams
{
services.AddMappingProfile().AddSingleton(Options.Create(rsaParams));
return setAsDefault
? services.AddParamsConfigureOptions<TRSAFactoryParams>().AddSingleton<IRSAFactory, RSAFactory<TRSAFactoryParams>>()
: services.AddParamsConfigureOptions<TRSAFactoryParams>().AddSingleton<IRSAFactory<TRSAFactoryParams>, RSAFactory<TRSAFactoryParams>>();
}
public static IServiceCollection AddRSAFactory(this IServiceCollection services, IConfigurationSection section) => services
.AddParamsConfigureOptions<RSAFactoryParams>()
.Configure<RSAFactoryParams>(section)
.AddSingleton<IRSAFactory, RSAFactory<RSAFactoryParams>>();
private static IServiceCollection AddClaimDescriptor<TPrincipal>(this IServiceCollection services,
Func<TPrincipal, IDictionary<string, object>>? claimsMapper = null,
@@ -139,5 +60,24 @@ 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
.AddClaimDescriptor(claimsMapper: claimsMapper, subjectMapper: subjectMapper)
.AddSingleton<IJwtSignatureHandler<TPrincipal>, JwtSignatureHandler<TPrincipal>>();
}
}
}

View File

@@ -1,14 +1,18 @@
namespace DigitalData.Core.Security.Config
using AutoMapper;
using DigitalData.Core.Security.Config;
using Microsoft.IdentityModel.Tokens;
namespace DigitalData.Core.Security
{
internal static class StringExtensions
internal static class Extension
{
public static string ToBase64String(this byte[] bytes) => Convert.ToBase64String(bytes);
internal static string ToBase64String(this byte[] bytes) => Convert.ToBase64String(bytes);
public static byte[] Base64ToByte(this string base64String) => Convert.FromBase64String(base64String);
internal static byte[] Base64ToByte(this string base64String) => Convert.FromBase64String(base64String);
public static byte[] ToBytes(this string str) => System.Text.Encoding.UTF8.GetBytes(str);
internal static byte[] ToBytes(this string str) => System.Text.Encoding.UTF8.GetBytes(str);
public static string BytesToString(this byte[] bytes) => System.Text.Encoding.UTF8.GetString(bytes);
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.
@@ -30,7 +34,7 @@
/// <exception cref="ArgumentException">Thrown if the format string is invalid, such as having an incorrect number of parts after "//".</exception>
/// <exception cref="DivideByZeroException">Thrown if the right side of the "//" contains a zero, resulting in division by zero.</exception>
/// <exception cref="FormatException">Thrown if either the left-side or right-side value of "//" cannot be parsed as an integer.</exception>
public static string ToTag(this DateTime date, string format)
internal static string ToTag(this DateTime date, string format)
{
if (format is not null && format.Contains("//"))
{
@@ -78,6 +82,16 @@
/// <exception cref="ArgumentException">Thrown if the format string is invalid, such as having an incorrect number of parts after "//".</exception>
/// <exception cref="DivideByZeroException">Thrown if the right side of the "//" contains a zero, resulting in division by zero.</exception>
/// <exception cref="FormatException">Thrown if either the left-side or right-side value of "//" cannot be parsed as an integer.</exception>
public static string ToTag(this DateOnly date, string format) => date.ToDateTime(new()).ToTag(format);
internal static string ToTag(this DateOnly date, string format) => date.ToDateTime(new()).ToTag(format);
/// <summary>
/// Maps a <see cref="TokenDescription"/> 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>
/// <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)
=> mapper.Map(description, new SecurityTokenDescriptor());
}
}

View File

@@ -0,0 +1,8 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "<Pending>", Scope = "member", Target = "~M:DigitalData.Core.Security.JwtSignatureHandler`1.#ctor(Microsoft.Extensions.Options.IOptions{DigitalData.Core.Security.Config.ClaimDescriptor{`0}},AutoMapper.IMapper)")]

View File

@@ -0,0 +1,54 @@
using AutoMapper;
using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Config;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
namespace DigitalData.Core.Security
{
public class JwtSignatureHandler<TPrincipal> : JwtSecurityTokenHandler, IJwtSignatureHandler<TPrincipal>
{
private readonly ClaimDescriptor<TPrincipal> _claimDescriptor;
private readonly IMapper _mapper;
private readonly IEnumerable<TokenDescription>? _tokenDescriptions;
private readonly IAsymCryptHandler _cryptHandler;
public JwtSignatureHandler(IOptions<ClaimDescriptor<TPrincipal>> claimDescriptorOptions, IMapper mapper, IOptions<IEnumerable<TokenDescription>>? tokenDescriptionOptions, IAsymCryptHandler asymCryptHandler)
{
_claimDescriptor = claimDescriptorOptions.Value;
_mapper = mapper;
_tokenDescriptions = tokenDescriptionOptions?.Value;
_cryptHandler = asymCryptHandler;
}
public SecurityToken CreateToken(TPrincipal subject, TokenDescription description)
{
var descriptor = _mapper.Map(description);
descriptor.Claims = _claimDescriptor.CreateClaims?.Invoke(subject);
descriptor.Subject = _claimDescriptor.CreateSubject?.Invoke(subject);
return CreateToken(descriptor);
}
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);
}
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

@@ -1,30 +0,0 @@
using DigitalData.Core.Security.Config;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
namespace DigitalData.Core.Security
{
public class JwtSignatureService<TPrincipal> : JwtSecurityTokenHandler
{
private readonly ClaimDescriptor<TPrincipal> _claimDescriptor;
private readonly TokenDescriptorProvider _descriptorProvider;
public JwtSignatureService(IOptions<ClaimDescriptor<TPrincipal>> claimDescriptorOptions, TokenDescriptorProvider descriptorProvider)
{
_claimDescriptor = claimDescriptorOptions.Value;
_descriptorProvider = descriptorProvider;
}
public SecurityToken CreateToken(TPrincipal subject, TokenDescription description)
{
var descriptor = _descriptorProvider.Create(description: description);
descriptor.Claims = _claimDescriptor.CreateClaims?.Invoke(subject);
descriptor.Subject = _claimDescriptor.CreateSubject?.Invoke(subject);
return CreateToken(descriptor);
}
public string CreateAndWriteToken(TPrincipal subject, TokenDescription description) => WriteToken(CreateToken(subject, description));
}
}

View File

@@ -1,19 +0,0 @@
using AutoMapper;
using DigitalData.Core.Security.Config;
using Microsoft.IdentityModel.Tokens;
namespace DigitalData.Core.Security
{
public class TokenDescriptorProvider
{
private readonly IMapper _mapper;
public TokenDescriptorProvider(IMapper mapper)
{
_mapper = mapper;
}
public SecurityTokenDescriptor Create(TokenDescription description)
=> _mapper.Map(description, new SecurityTokenDescriptor());
}
}