Compare commits
20 Commits
592b949f57
...
608d266d1c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
608d266d1c | ||
|
|
34e14fd2f5 | ||
|
|
dc45cf2c08 | ||
|
|
09a31b5a3d | ||
|
|
b5cecac745 | ||
|
|
0f4b5430a3 | ||
|
|
7f2d2dadfa | ||
|
|
ac0b6f739b | ||
|
|
d9d61368e3 | ||
|
|
e8c98115b6 | ||
|
|
09dae1b1ac | ||
|
|
9aafc9e467 | ||
|
|
4ce738957d | ||
|
|
5e1bf16b6d | ||
|
|
4f96d271f3 | ||
|
|
14485af448 | ||
|
|
c27e21a702 | ||
|
|
4874079b69 | ||
|
|
15e909064f | ||
|
|
d17c5ca6cd |
@@ -1,11 +0,0 @@
|
||||
namespace DigitalData.Core.Abstractions.Security
|
||||
{
|
||||
public interface IAsymCryptHandler : IRSAFactory
|
||||
{
|
||||
IEnumerable<IRSADecryptor> Decryptors { get; }
|
||||
|
||||
IRSADecryptor Vault { get; }
|
||||
|
||||
IEnumerable<IRSAEncryptor> Encryptors { get; }
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,14 @@ using System.Security.Cryptography;
|
||||
|
||||
namespace DigitalData.Core.Abstractions.Security
|
||||
{
|
||||
public interface IRSACryptographer : IUniqueSecurityContext
|
||||
public interface IAsymmetricKey : IUniqueSecurityContext
|
||||
{
|
||||
public string Pem { get; init; }
|
||||
|
||||
public RSAEncryptionPadding Padding { get; init; }
|
||||
public string Content { get; init; }
|
||||
|
||||
public new string Issuer { get; init; }
|
||||
|
||||
public new string Audience { get; init; }
|
||||
|
||||
public RsaSecurityKey RsaSecurityKey { get; }
|
||||
public SecurityKey SecurityKey { get; }
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace DigitalData.Core.Abstractions.Security
|
||||
{
|
||||
public interface IRSAFactory
|
||||
public interface IAsymmetricKeyFactory
|
||||
{
|
||||
string CreatePrivateKeyPem(int? keySizeInBits = null, bool encrypt = false);
|
||||
|
||||
@@ -18,6 +18,6 @@ namespace DigitalData.Core.Abstractions.Security
|
||||
int? keySizeInBits = null,
|
||||
string? password = null);
|
||||
|
||||
IRSADecryptor CreateDecryptor(string pem, string? issuer = null, string? audience = null, bool encrypt = false, RSAEncryptionPadding? padding = null);
|
||||
IAsymmetricPrivateKey CreatePrivateKey(string pem, string? issuer = null, string? audience = null, bool encrypt = false, RSAEncryptionPadding? padding = null);
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
namespace DigitalData.Core.Abstractions.Security
|
||||
{
|
||||
public interface IRSADecryptor : IRSACryptographer
|
||||
public interface IAsymmetricPrivateKey : IAsymmetricKey
|
||||
{
|
||||
public bool IsEncrypted { get; init; }
|
||||
|
||||
IRSAEncryptor Encryptor { get; }
|
||||
IAsymmetricPublicKey PublicKey { get; }
|
||||
|
||||
PrivateKeyTokenDescriptor? TokenDescriptor { get; init; }
|
||||
|
||||
byte[] Decrypt(byte[] data);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace DigitalData.Core.Abstractions.Security
|
||||
{
|
||||
public interface IRSAEncryptor : IRSACryptographer
|
||||
public interface IAsymmetricPublicKey : IAsymmetricKey
|
||||
{
|
||||
public byte[] Encrypt(byte[] data);
|
||||
|
||||
11
DigitalData.Core.Abstractions/Security/ICryptograph.cs
Normal file
11
DigitalData.Core.Abstractions/Security/ICryptograph.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace DigitalData.Core.Abstractions.Security
|
||||
{
|
||||
public interface ICryptograph : IAsymmetricKeyFactory
|
||||
{
|
||||
IEnumerable<IAsymmetricPrivateKey> PrivateKeys { get; }
|
||||
|
||||
IAsymmetricPrivateKey VaultPrivateKey { get; }
|
||||
|
||||
IEnumerable<IAsymmetricPublicKey> PublicKeys { get; }
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,14 @@ namespace DigitalData.Core.Abstractions.Security
|
||||
|
||||
SecurityToken CreateToken(TPrincipal subject, string issuer, string audience);
|
||||
|
||||
SecurityToken CreateToken(TPrincipal subject, string apiRoute);
|
||||
|
||||
string WriteToken(SecurityToken token);
|
||||
|
||||
string WriteToken(SecurityTokenDescriptor descriptor);
|
||||
|
||||
string WriteToken(TPrincipal subject, string issuer, string audience);
|
||||
|
||||
string WriteToken(TPrincipal subject, string apiRoute);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
using DigitalData.Core.Abstractions.Security;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace DigitalData.Core.Security.Config
|
||||
namespace DigitalData.Core.Abstractions.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains some information which used to create a security token. Designed to abstract <see cref="SecurityTokenDescriptor"/>
|
||||
/// </summary>
|
||||
public class TokenDescription : IUniqueSecurityContext
|
||||
public class PrivateKeyTokenDescriptor : IUniqueSecurityContext
|
||||
{
|
||||
public string? ApiRoute { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value of the 'audience' claim.
|
||||
/// </summary>
|
||||
@@ -29,7 +30,7 @@ namespace DigitalData.Core.Security.Config
|
||||
public DateTime? Expires { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the issuer of this <see cref="ITokenDescription"/>.
|
||||
/// Gets or sets the issuer of this <see cref="PrivateKeyTokenDescriptor"/>.
|
||||
/// </summary>
|
||||
public new string Issuer { get; set; }
|
||||
|
||||
@@ -80,7 +81,7 @@ namespace DigitalData.Core.Security.Config
|
||||
/// Specifies the signature algorithm to be applied to the <see cref="SigningCredentials"/>.
|
||||
/// Default is <see cref="SecurityAlgorithms.RsaSha256"/>.
|
||||
/// </summary>
|
||||
public string SigningAlgorithm { get; init; } = SecurityAlgorithms.RsaSha256;
|
||||
public string SigningAlgorithm { get; internal set; } = SecurityAlgorithms.RsaSha256;
|
||||
|
||||
/// <summary>
|
||||
/// Optionally specifies the digest algorithm to be applied during the signing process for the <see cref="SigningCredentials"/>.
|
||||
@@ -1,9 +1,8 @@
|
||||
using DigitalData.Core.Abstractions.Security;
|
||||
using DigitalData.Core.Security.Cryptographer;
|
||||
using DigitalData.Core.Security.RSAKey;
|
||||
|
||||
namespace DigitalData.Core.Security.Config
|
||||
{
|
||||
public class AsymCryptParams : RSAFactoryParams
|
||||
public class CryptographParams : RSAFactoryParams
|
||||
{
|
||||
public string PemDirectory { get; init; } = string.Empty;
|
||||
|
||||
@@ -40,11 +39,11 @@ namespace DigitalData.Core.Security.Config
|
||||
/// </summary>
|
||||
public string DateTagFormat { get; init; } = "MM//2";
|
||||
|
||||
public IEnumerable<RSADecryptor> Decryptors { get; init; } = new List<RSADecryptor>();
|
||||
public IEnumerable<RSAPrivateKey> PrivateKeys { get; init; } = new List<RSAPrivateKey>();
|
||||
|
||||
public RSADecryptor? Vault { get; init; }
|
||||
public RSAPrivateKey? VaultPrivateKey { get; init; }
|
||||
|
||||
public AsymCryptParams()
|
||||
public CryptographParams()
|
||||
{
|
||||
// init decryptors
|
||||
AfterCreate += () =>
|
||||
@@ -53,7 +52,7 @@ namespace DigitalData.Core.Security.Config
|
||||
if (!Directory.Exists(PemDirectory))
|
||||
Directory.CreateDirectory(PemDirectory);
|
||||
|
||||
foreach (var decryptor in Decryptors)
|
||||
foreach (var decryptor in PrivateKeys)
|
||||
{
|
||||
// set default path
|
||||
if (decryptor.IsPemNull)
|
||||
@@ -1,4 +1,5 @@
|
||||
using AutoMapper;
|
||||
using DigitalData.Core.Abstractions.Security;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace DigitalData.Core.Security.Config
|
||||
@@ -7,7 +8,7 @@ namespace DigitalData.Core.Security.Config
|
||||
{
|
||||
public MappingProfile()
|
||||
{
|
||||
CreateMap<TokenDescription, SecurityTokenDescriptor>();
|
||||
CreateMap<PrivateKeyTokenDescriptor, SecurityTokenDescriptor>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,42 @@
|
||||
using DigitalData.Core.Abstractions.Security;
|
||||
using DigitalData.Core.Security.Config;
|
||||
using DigitalData.Core.Security.Cryptographer;
|
||||
using DigitalData.Core.Security.RSAKey;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace DigitalData.Core.Security
|
||||
{
|
||||
public class AsymCryptHandler : RSAFactory<AsymCryptParams>, IAsymCryptHandler, IRSAFactory
|
||||
public class Cryptograph : RSAFactory<CryptographParams>, ICryptograph, IAsymmetricKeyFactory
|
||||
{
|
||||
public IEnumerable<IRSADecryptor> Decryptors { get; }
|
||||
public IEnumerable<IAsymmetricPrivateKey> PrivateKeys { get; }
|
||||
|
||||
/// <summary>
|
||||
/// It is a separate decryptor for permanently stored encrypted data. It is assigned to the first Default decryptor by default.
|
||||
/// </summary>
|
||||
public IRSADecryptor Vault { get; }
|
||||
public IAsymmetricPrivateKey VaultPrivateKey { get; }
|
||||
|
||||
private readonly Lazy<IEnumerable<IRSAEncryptor>> _lazyEncryptors;
|
||||
private readonly Lazy<IEnumerable<IAsymmetricPublicKey>> _lazyPublicKeys;
|
||||
|
||||
public IEnumerable<IRSAEncryptor> Encryptors => _lazyEncryptors.Value;
|
||||
public IEnumerable<IAsymmetricPublicKey> PublicKeys => _lazyPublicKeys.Value;
|
||||
|
||||
public IEnumerable<TokenDescription> TokenDescriptions { get; init; } = new List<TokenDescription>();
|
||||
public IEnumerable<PrivateKeyTokenDescriptor> TokenDescriptions { get; init; } = new List<PrivateKeyTokenDescriptor>();
|
||||
|
||||
public AsymCryptHandler(IOptions<AsymCryptParams> options, ILogger<AsymCryptHandler>? logger = null) : base(options)
|
||||
public Cryptograph(IOptions<CryptographParams> options, ILogger<Cryptograph>? logger = null) : base(options)
|
||||
{
|
||||
logger?.LogInformation("Core.Secrets version: {Version}, Created on: {CreationDate}.", Secrets.Version, Secrets.CreationDate.ToString("dd.MM.yyyy"));
|
||||
|
||||
if (!_params.Decryptors.Any())
|
||||
if (!_params.PrivateKeys.Any())
|
||||
throw new InvalidOperationException(
|
||||
"Any decryptor is not found. Ensure that at least one decryptor is configured in the provided parameters. " +
|
||||
"This issue typically arises if the configuration for decryptors is incomplete or missing. " +
|
||||
"Check the 'Decryptors' collection in the configuration and verify that it contains valid entries."
|
||||
);
|
||||
|
||||
Decryptors = _params.Decryptors;
|
||||
PrivateKeys = _params.PrivateKeys;
|
||||
|
||||
Vault = _params.Vault ?? Decryptors.First();
|
||||
VaultPrivateKey = _params.VaultPrivateKey ?? PrivateKeys.First();
|
||||
|
||||
_lazyEncryptors = new(Decryptors.Select(decryptor => decryptor.Encryptor));
|
||||
_lazyPublicKeys = new(PrivateKeys.Select(decryptor => decryptor.PublicKey));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using DigitalData.Core.Abstractions.Security;
|
||||
using DigitalData.Core.Security.Config;
|
||||
using DigitalData.Core.Security.Cryptographer;
|
||||
using DigitalData.Core.Security.RSAKey;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -13,10 +13,10 @@ namespace DigitalData.Core.Security
|
||||
private static IServiceCollection AddParamsConfigureOptions<TParams>(this IServiceCollection services) where TParams : RSAFactoryParams
|
||||
=> services.AddSingleton<IConfigureOptions<TParams>, ParamsConfigureOptions<TParams>>();
|
||||
|
||||
private static IServiceCollection AddAsymCryptService(this IServiceCollection services) => services
|
||||
.AddParamsConfigureOptions<AsymCryptParams>()
|
||||
private static IServiceCollection AddCryptograph(this IServiceCollection services) => services
|
||||
.AddParamsConfigureOptions<CryptographParams>()
|
||||
.AddAutoMapper(typeof(MappingProfile).Assembly)
|
||||
.AddSingleton<IAsymCryptHandler, AsymCryptHandler>();
|
||||
.AddSingleton<ICryptograph, Cryptograph>();
|
||||
|
||||
/// <summary>
|
||||
/// Registers a custom asym crypt service with specified parameters from the given configuration section.
|
||||
@@ -24,18 +24,18 @@ namespace DigitalData.Core.Security
|
||||
/// <param name="services"></param>
|
||||
/// <param name="section"></param>
|
||||
/// <returns>The updated <see cref="IServiceCollection"/> with the RSA Factory registered.</returns>
|
||||
public static IServiceCollection AddAsymCryptService(this IServiceCollection services, IConfigurationSection section) => services
|
||||
.Configure<AsymCryptParams>(section)
|
||||
.AddAsymCryptService();
|
||||
public static IServiceCollection AddCryptograph(this IServiceCollection services, IConfigurationSection section) => services
|
||||
.Configure<CryptographParams>(section)
|
||||
.AddCryptograph();
|
||||
|
||||
/// <summary>
|
||||
/// Registers an asym crypt service with the specified parameters from the given instance.
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <returns>The updated <see cref="IServiceCollection"/> with the RSA Factory registered.</returns>
|
||||
public static IServiceCollection AddAsymCryptService(this IServiceCollection services, AsymCryptParams? asymCryptParams = null) => services
|
||||
public static IServiceCollection AddCryptograph(this IServiceCollection services, CryptographParams? asymCryptParams = null) => services
|
||||
.AddSingleton(Options.Create(asymCryptParams ?? new()))
|
||||
.AddAsymCryptService();
|
||||
.AddCryptograph();
|
||||
|
||||
/// <summary>
|
||||
/// Registers a custom RSA Factory with specified parameters from the given configuration section.
|
||||
@@ -46,7 +46,7 @@ namespace DigitalData.Core.Security
|
||||
public static IServiceCollection AddRSAFactory(this IServiceCollection services, IConfigurationSection section) => services
|
||||
.AddParamsConfigureOptions<RSAFactoryParams>()
|
||||
.Configure<RSAFactoryParams>(section)
|
||||
.AddSingleton<IRSAFactory, RSAFactory<RSAFactoryParams>>();
|
||||
.AddSingleton<IAsymmetricKeyFactory, RSAFactory<RSAFactoryParams>>();
|
||||
|
||||
private static IServiceCollection AddClaimDescriptor<TPrincipal>(this IServiceCollection services,
|
||||
Func<TPrincipal, IDictionary<string, object>>? claimsMapper = null,
|
||||
@@ -61,23 +61,11 @@ namespace DigitalData.Core.Security
|
||||
return services.AddSingleton(sp => Options.Create(descriptor));
|
||||
}
|
||||
|
||||
public static IServiceCollection AddTokenDescriptions(this IServiceCollection services, IConfiguration configuration)
|
||||
=> services.Configure<IEnumerable<TokenDescription>>(configuration);
|
||||
|
||||
public static IServiceCollection AddTokenDescriptions(this IServiceCollection services, params TokenDescription[] tokenDescriptions)
|
||||
=> services.AddSingleton<IOptions<IEnumerable<TokenDescription>>>(Options.Create(tokenDescriptions));
|
||||
|
||||
public static IServiceCollection AddJwtSignatureHandler<TPrincipal>(this IServiceCollection services, Func<TPrincipal, IDictionary<string, object>>? claimsMapper = null, Func<TPrincipal, ClaimsIdentity>? subjectMapper = null, IConfiguration? tokenDescriptionconfig = null, params TokenDescription[]? tokenDescriptions)
|
||||
{
|
||||
if (tokenDescriptionconfig is not null)
|
||||
services.AddTokenDescriptions(tokenDescriptionconfig);
|
||||
|
||||
if (tokenDescriptions is not null)
|
||||
services.AddTokenDescriptions(tokenDescriptions);
|
||||
|
||||
return services
|
||||
public static IServiceCollection AddJwtSignatureHandler<TPrincipal>(this IServiceCollection services,
|
||||
Func<TPrincipal, IDictionary<string, object>>? claimsMapper = null,
|
||||
Func<TPrincipal, ClaimsIdentity>? subjectMapper = null)
|
||||
=> services
|
||||
.AddClaimDescriptor(claimsMapper: claimsMapper, subjectMapper: subjectMapper)
|
||||
.AddSingleton<IJwtSignatureHandler<TPrincipal>, JwtSignatureHandler<TPrincipal>>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using AutoMapper;
|
||||
using DigitalData.Core.Abstractions.Security;
|
||||
using DigitalData.Core.Security.Config;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
@@ -85,13 +86,13 @@ namespace DigitalData.Core.Security
|
||||
internal static string ToTag(this DateOnly date, string format) => date.ToDateTime(new()).ToTag(format);
|
||||
|
||||
/// <summary>
|
||||
/// Maps a <see cref="TokenDescription"/> to a <see cref="SecurityTokenDescriptor"/>.
|
||||
/// Maps a <see cref="RSATokenDescriptor"/> to a <see cref="SecurityTokenDescriptor"/>.
|
||||
/// </summary>
|
||||
/// <param name="mapper">The <see cref="IMapper"/> instance used for mapping.</param>
|
||||
/// <param name="description">The <see cref="TokenDescription"/> instance to be mapped.</param>
|
||||
/// <param name="description">The <see cref="RSATokenDescriptor"/> instance to be mapped.</param>
|
||||
/// <returns>A <see cref="SecurityTokenDescriptor"/> instance populated with the mapped values.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="mapper"/> or <paramref name="description"/> is <c>null</c>.</exception>
|
||||
internal static SecurityTokenDescriptor Map(this IMapper mapper, TokenDescription description)
|
||||
internal static SecurityTokenDescriptor Map(this IMapper mapper, PrivateKeyTokenDescriptor description)
|
||||
=> mapper.Map(description, new SecurityTokenDescriptor());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using AutoMapper;
|
||||
using DigitalData.Core.Abstractions.Security;
|
||||
using DigitalData.Core.Security.Config;
|
||||
using DigitalData.Core.Security.RSAKey;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
@@ -13,21 +14,20 @@ namespace DigitalData.Core.Security
|
||||
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
private readonly IEnumerable<TokenDescription>? _tokenDescriptions;
|
||||
private readonly ICryptograph _cryptograph;
|
||||
|
||||
private readonly IAsymCryptHandler _cryptHandler;
|
||||
|
||||
public JwtSignatureHandler(IOptions<ClaimDescriptor<TPrincipal>> claimDescriptorOptions, IMapper mapper, IOptions<IEnumerable<TokenDescription>>? tokenDescriptionOptions, IAsymCryptHandler asymCryptHandler)
|
||||
public JwtSignatureHandler(IOptions<ClaimDescriptor<TPrincipal>> claimDescriptorOptions, IMapper mapper, ICryptograph cryptograph)
|
||||
{
|
||||
_claimDescriptor = claimDescriptorOptions.Value;
|
||||
_mapper = mapper;
|
||||
_tokenDescriptions = tokenDescriptionOptions?.Value;
|
||||
_cryptHandler = asymCryptHandler;
|
||||
_cryptograph = cryptograph;
|
||||
}
|
||||
|
||||
public SecurityToken CreateToken(TPrincipal subject, TokenDescription description)
|
||||
public SecurityToken CreateToken(TPrincipal subject, RSAPrivateKey key)
|
||||
{
|
||||
var descriptor = _mapper.Map(description);
|
||||
if(key.TokenDescriptor is null)
|
||||
throw new InvalidOperationException($"No descriptor found for issuer '{key.Issuer}' and audience '{key.Audience}'.");
|
||||
var descriptor = _mapper.Map(key.TokenDescriptor);
|
||||
descriptor.Claims = _claimDescriptor.CreateClaims?.Invoke(subject);
|
||||
descriptor.Subject = _claimDescriptor.CreateSubject?.Invoke(subject);
|
||||
return CreateToken(descriptor);
|
||||
@@ -35,20 +35,25 @@ namespace DigitalData.Core.Security
|
||||
|
||||
public SecurityToken CreateToken(TPrincipal subject, string issuer, string audience)
|
||||
{
|
||||
var description = _tokenDescriptions?.Get(issuer: issuer, audience: audience)
|
||||
?? throw new InvalidOperationException($"No token description found for issuer '{issuer}' and audience '{audience}'.");
|
||||
var key = _cryptograph.PrivateKeys?.Get(issuer: issuer, audience: audience)
|
||||
?? throw new InvalidOperationException($"No or multiple token description found for issuer '{issuer}' and audience '{audience}'.");
|
||||
return CreateToken(subject: subject, key: (RSAPrivateKey)key);
|
||||
}
|
||||
|
||||
description.SigningCredentials = _cryptHandler.Decryptors
|
||||
.Get(issuer: issuer, audience: audience)
|
||||
.CreateSigningCredentials(algorithm: description.SigningAlgorithm, digest: description.SigningDigest);
|
||||
public SecurityToken CreateToken(TPrincipal subject, string apiRoute)
|
||||
{
|
||||
var key = _cryptograph.PrivateKeys.SingleOrDefault(key => ((RSAPrivateKey)key).TokenDescriptor?.ApiRoute == apiRoute)
|
||||
?? throw new InvalidOperationException($"No or multiple token description found for api route '{apiRoute}'.");
|
||||
|
||||
return CreateToken(subject: subject, description: description);
|
||||
return CreateToken(subject: subject, key: (RSAPrivateKey)key);
|
||||
}
|
||||
|
||||
public string WriteToken(SecurityTokenDescriptor descriptor) => WriteToken(CreateToken(descriptor));
|
||||
|
||||
public string WriteToken(TPrincipal subject, TokenDescription description) => WriteToken(CreateToken(subject: subject, description: description));
|
||||
public string WriteToken(TPrincipal subject, RSAPrivateKey key) => WriteToken(CreateToken(subject: subject, key: key));
|
||||
|
||||
public string WriteToken(TPrincipal subject, string issuer, string audience) => WriteToken(CreateToken(subject: subject, issuer: issuer, audience: audience));
|
||||
|
||||
public string WriteToken(TPrincipal subject, string apiRoute) => WriteToken(CreateToken(subject: subject, apiRoute: apiRoute));
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,9 @@ using DigitalData.Core.Security.Config;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace DigitalData.Core.Security.Cryptographer
|
||||
namespace DigitalData.Core.Security.RSAKey
|
||||
{
|
||||
public class RSAFactory<TRSAFactoryParams> : IRSAFactory where TRSAFactoryParams : RSAFactoryParams
|
||||
public class RSAFactory<TRSAFactoryParams> : IAsymmetricKeyFactory where TRSAFactoryParams : RSAFactoryParams
|
||||
{
|
||||
protected readonly TRSAFactoryParams _params;
|
||||
|
||||
@@ -56,9 +56,9 @@ namespace DigitalData.Core.Security.Cryptographer
|
||||
return new string(pemChars);
|
||||
}
|
||||
|
||||
public IRSADecryptor CreateDecryptor(string pem, string? issuer = null, string? audience = null, bool encrypt = false, RSAEncryptionPadding? padding = null) => new RSADecryptor()
|
||||
public IAsymmetricPrivateKey CreatePrivateKey(string pem, string? issuer = null, string? audience = null, bool encrypt = false, RSAEncryptionPadding? padding = null) => new RSAPrivateKey()
|
||||
{
|
||||
Pem = pem,
|
||||
Content = pem,
|
||||
Issuer = issuer ?? string.Empty,
|
||||
Audience = audience ?? string.Empty,
|
||||
IsEncrypted = encrypt,
|
||||
@@ -3,12 +3,11 @@ using Microsoft.IdentityModel.Tokens;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace DigitalData.Core.Security.Cryptographer
|
||||
namespace DigitalData.Core.Security.RSAKey
|
||||
{
|
||||
//TODO: Abstract RSA for future updates (using ECC, El Gamal or Lattice-based Cryptography)
|
||||
public class RSACryptographer : IRSACryptographer
|
||||
public class RSAKeyBase : IAsymmetricKey
|
||||
{
|
||||
public virtual string Pem { get; init; }
|
||||
public virtual string Content { get; init; }
|
||||
|
||||
public RSAEncryptionPadding Padding { get; init; } = RSAEncryptionPadding.OaepSHA256;
|
||||
|
||||
@@ -27,10 +26,10 @@ namespace DigitalData.Core.Security.Cryptographer
|
||||
|
||||
private readonly Lazy<RsaSecurityKey> _lazyRsaSecurityKey;
|
||||
|
||||
public RsaSecurityKey RsaSecurityKey => _lazyRsaSecurityKey.Value;
|
||||
public SecurityKey SecurityKey => _lazyRsaSecurityKey.Value;
|
||||
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
internal RSACryptographer()
|
||||
internal RSAKeyBase()
|
||||
{
|
||||
_lazyRsaSecurityKey = new(() => new RsaSecurityKey(RSA));
|
||||
}
|
||||
83
DigitalData.Core.Security/RSAKey/RSAPrivateKey.cs
Normal file
83
DigitalData.Core.Security/RSAKey/RSAPrivateKey.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using DigitalData.Core.Abstractions.Security;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace DigitalData.Core.Security.RSAKey
|
||||
{
|
||||
public class RSAPrivateKey : RSAKeyBase, IAsymmetricPrivateKey, IAsymmetricKey
|
||||
{
|
||||
private string? _pem;
|
||||
|
||||
public override string Content
|
||||
{
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
get => _pem;
|
||||
#pragma warning restore CS8603 // Possible null reference return.
|
||||
init
|
||||
{
|
||||
_pem = value;
|
||||
Init();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsPemNull => _pem is null;
|
||||
|
||||
public bool IsEncrypted { get; init; }
|
||||
|
||||
private readonly Lazy<IAsymmetricPublicKey> _lazyPublicKey;
|
||||
|
||||
public IAsymmetricPublicKey PublicKey => _lazyPublicKey.Value;
|
||||
|
||||
private PrivateKeyTokenDescriptor? _tokenDescriptor;
|
||||
|
||||
private readonly Lazy<PrivateKeyTokenDescriptor?> _descriptorInitiator;
|
||||
|
||||
public PrivateKeyTokenDescriptor? TokenDescriptor { get => _descriptorInitiator.Value; init => _tokenDescriptor = value; }
|
||||
|
||||
public RSAPrivateKey()
|
||||
{
|
||||
_lazyPublicKey = new(() => new RSAPublicKey()
|
||||
{
|
||||
Content = RSA.ExportRSAPublicKeyPem(),
|
||||
Padding = Padding
|
||||
});
|
||||
|
||||
_descriptorInitiator = new(() =>
|
||||
{
|
||||
if(_tokenDescriptor is not null)
|
||||
{
|
||||
_tokenDescriptor.Issuer = Issuer;
|
||||
_tokenDescriptor.Audience = Audience;
|
||||
_tokenDescriptor.SigningCredentials = CreateSigningCredentials();
|
||||
}
|
||||
return _tokenDescriptor;
|
||||
});
|
||||
}
|
||||
|
||||
public byte[] Decrypt(byte[] data) => RSA.Decrypt(data, Padding);
|
||||
|
||||
public string Decrypt(string data) => RSA.Decrypt(data.Base64ToByte(), Padding).BytesToString();
|
||||
|
||||
internal void SetPem(string pem)
|
||||
{
|
||||
_pem = pem;
|
||||
Init();
|
||||
}
|
||||
|
||||
private void Init()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_pem))
|
||||
throw PemIsNullException;
|
||||
|
||||
if (IsEncrypted)
|
||||
RSA.ImportFromEncryptedPem(Content, Secrets.PBE_PASSWORD.AsSpan());
|
||||
else
|
||||
RSA.ImportFromPem(Content);
|
||||
}
|
||||
|
||||
private InvalidOperationException PemIsNullException => new($"Content is null or empty. Issuer: {Issuer}, Audience: {Audience}.");
|
||||
|
||||
public SigningCredentials CreateSigningCredentials(string algorithm = SecurityAlgorithms.RsaSha256, string? digest = null)
|
||||
=> digest is null ? new(SecurityKey, algorithm) : new(SecurityKey, algorithm, digest);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
using DigitalData.Core.Abstractions.Security;
|
||||
|
||||
namespace DigitalData.Core.Security.Cryptographer
|
||||
namespace DigitalData.Core.Security.RSAKey
|
||||
{
|
||||
public class RSAEncryptor : RSACryptographer, IRSAEncryptor, IRSACryptographer
|
||||
public class RSAPublicKey : RSAKeyBase, IAsymmetricPublicKey, IAsymmetricKey
|
||||
{
|
||||
public override string Pem
|
||||
public override string Content
|
||||
{
|
||||
get => base.Pem;
|
||||
get => base.Content;
|
||||
init
|
||||
{
|
||||
base.Pem = value;
|
||||
base.Content = value;
|
||||
RSA.ImportFromPem(value);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user