Compare commits
27 Commits
06260e0edb
...
592b949f57
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
592b949f57 | ||
|
|
8850ac4ac9 | ||
|
|
8ccf6f31ae | ||
|
|
9875d023e3 | ||
|
|
62afba7c23 | ||
|
|
1d4882cfbc | ||
|
|
275b9ec858 | ||
|
|
2cf0eb3977 | ||
|
|
4ab5393deb | ||
|
|
a2dc59d5ef | ||
|
|
ed041bf7cb | ||
|
|
c70327e7f4 | ||
|
|
0a3ce89c0d | ||
|
|
389d64c25d | ||
|
|
a3931414e3 | ||
|
|
0dd897625a | ||
|
|
351a6732cf | ||
|
|
5a1808c6a6 | ||
|
|
50c42e9cdd | ||
|
|
ec126be2aa | ||
|
|
9953bbd2ef | ||
|
|
dbecfa92f4 | ||
|
|
e007f15bce | ||
|
|
79dffef528 | ||
|
|
af478e974c | ||
|
|
435c91955c | ||
|
|
4142d2d948 |
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
DigitalData.Core.Abstractions/Security/IAsymCryptHandler.cs
Normal file
11
DigitalData.Core.Abstractions/Security/IAsymCryptHandler.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
@@ -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> { }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 { }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
29
DigitalData.Core.Abstractions/Security/SecurityExtensions.cs
Normal file
29
DigitalData.Core.Abstractions/Security/SecurityExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
42
DigitalData.Core.Security/AsymCryptHandler.cs
Normal file
42
DigitalData.Core.Security/AsymCryptHandler.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using DigitalData.Core.Abstractions.Security;
|
||||
using DigitalData.Core.Security.Config;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using DigitalData.Core.Abstractions.Security;
|
||||
using DigitalData.Core.Security.Config;
|
||||
|
||||
namespace DigitalData.Core.Security.Cryptographer
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
8
DigitalData.Core.Security/GlobalSuppressions.cs
Normal file
8
DigitalData.Core.Security/GlobalSuppressions.cs
Normal 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)")]
|
||||
54
DigitalData.Core.Security/JwtSignatureHandler.cs
Normal file
54
DigitalData.Core.Security/JwtSignatureHandler.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user