Compare commits
8 Commits
66ed34b664
...
06260e0edb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06260e0edb | ||
|
|
2d675a16ad | ||
|
|
5469b20e4f | ||
|
|
6f5b4efefb | ||
|
|
b6b12c7702 | ||
|
|
ce716d2bab | ||
|
|
bf672d8b8c | ||
|
|
ed29c9f990 |
@@ -1,4 +1,5 @@
|
||||
using DigitalData.Core.Security.Cryptographer;
|
||||
using DigitalData.Core.Abstractions.Security;
|
||||
using DigitalData.Core.Security.Cryptographer;
|
||||
|
||||
namespace DigitalData.Core.Security.Config
|
||||
{
|
||||
@@ -55,9 +56,14 @@ 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() => AfterCreate += () =>
|
||||
public AsymCryptParams()
|
||||
{
|
||||
// init decryptors
|
||||
AfterCreate += () =>
|
||||
{
|
||||
// Create root folder if it does not exist
|
||||
if (!Directory.Exists(PemDirectory))
|
||||
@@ -92,5 +98,19 @@ 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
11
DigitalData.Core.Security/Config/ClaimDescriptor.cs
Normal file
11
DigitalData.Core.Security/Config/ClaimDescriptor.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace DigitalData.Core.Security.Config
|
||||
{
|
||||
public class ClaimDescriptor<TPrincipal>
|
||||
{
|
||||
public Func<TPrincipal, IDictionary<string, object>>? CreateClaims { get; init; }
|
||||
|
||||
public Func<TPrincipal, ClaimsIdentity>? CreateSubject { get; init; }
|
||||
}
|
||||
}
|
||||
13
DigitalData.Core.Security/Config/MappingProfile.cs
Normal file
13
DigitalData.Core.Security/Config/MappingProfile.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using AutoMapper;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace DigitalData.Core.Security.Config
|
||||
{
|
||||
public class MappingProfile : Profile
|
||||
{
|
||||
public MappingProfile()
|
||||
{
|
||||
CreateMap<TokenDescription, SecurityTokenDescriptor>();
|
||||
}
|
||||
}
|
||||
}
|
||||
90
DigitalData.Core.Security/Config/TokenDescription.cs
Normal file
90
DigitalData.Core.Security/Config/TokenDescription.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the value of the 'audience' claim.
|
||||
/// </summary>
|
||||
public string Audience { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines the compression algorithm that will be used to compress the JWT token payload.
|
||||
/// </summary>
|
||||
public string CompressionAlgorithm { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="EncryptingCredentials"/> used to create a encrypted security token.
|
||||
/// </summary>
|
||||
public EncryptingCredentials EncryptingCredentials { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value of the 'expiration' claim. This value should be in UTC.
|
||||
/// </summary>
|
||||
public DateTime? Expires { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the issuer of this <see cref="ITokenDescription"/>.
|
||||
/// </summary>
|
||||
public string Issuer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the time the security token was issued. This value should be in UTC.
|
||||
/// </summary>
|
||||
public DateTime? IssuedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the notbefore time for the security token. This value should be in UTC.
|
||||
/// </summary>
|
||||
public DateTime? NotBefore { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the token type.
|
||||
/// <remarks> If provided, this will be added as the value for the 'typ' header parameter. In the case of a JWE, this will be added to both the inner (JWS) and the outer token (JWE) header. By default, the value used is 'JWT'.
|
||||
/// If <see cref="AdditionalHeaderClaims"/> also contains 'typ' header claim value, it will override the TokenType provided here.
|
||||
/// This value is used only for JWT tokens and not for SAML/SAML2 tokens</remarks>
|
||||
/// </summary>
|
||||
public string TokenType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Dictionary{TKey, TValue}"/> which contains any custom header claims that need to be added to the JWT token header.
|
||||
/// The 'alg', 'kid', 'x5t', 'enc', and 'zip' claims are added by default based on the <see cref="SigningCredentials"/>,
|
||||
/// <see cref="EncryptingCredentials"/>, and/or <see cref="CompressionAlgorithm"/> provided and SHOULD NOT be included in this dictionary as this
|
||||
/// will result in an exception being thrown.
|
||||
/// <remarks> These claims are only added to the outer header (in case of a JWE).</remarks>
|
||||
/// </summary>
|
||||
public IDictionary<string, object> AdditionalHeaderClaims { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="Dictionary{TKey, TValue}"/> which contains any custom header claims that need to be added to the inner JWT token header.
|
||||
/// The 'alg', 'kid', 'x5t', 'enc', and 'zip' claims are added by default based on the <see cref="SigningCredentials"/>,
|
||||
/// <see cref="EncryptingCredentials"/>, and/or <see cref="CompressionAlgorithm"/> provided and SHOULD NOT be included in this dictionary as this
|
||||
/// will result in an exception being thrown.
|
||||
/// <remarks>
|
||||
/// For JsonWebTokenHandler, these claims are merged with <see cref="AdditionalHeaderClaims"/> while adding to the inner JWT header.
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
public IDictionary<string, object> AdditionalInnerHeaderClaims { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="SigningCredentials"/> used to create a security token.
|
||||
/// </summary>
|
||||
public SigningCredentials SigningCredentials { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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;
|
||||
|
||||
/// <summary>
|
||||
/// Optionally specifies the digest algorithm to be applied during the signing process for the <see cref="SigningCredentials"/>.
|
||||
/// If not provided, the default algorithm is used.
|
||||
/// </summary>
|
||||
public string? SigningDigest = null;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using DigitalData.Core.Abstractions.Security;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
@@ -24,8 +25,15 @@ namespace DigitalData.Core.Security.Cryptographer
|
||||
|
||||
public string Audience { get; init; } = string.Empty;
|
||||
|
||||
private readonly Lazy<RsaSecurityKey> _lazyRsaSecurityKey;
|
||||
|
||||
public RsaSecurityKey RsaSecurityKey => _lazyRsaSecurityKey.Value;
|
||||
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
internal RSACryptographer() { }
|
||||
internal RSACryptographer()
|
||||
{
|
||||
_lazyRsaSecurityKey = new(() => new RsaSecurityKey(RSA));
|
||||
}
|
||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using DigitalData.Core.Abstractions.Security;
|
||||
using DigitalData.Core.Security.Config;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace DigitalData.Core.Security.Cryptographer
|
||||
@@ -59,5 +60,8 @@ namespace DigitalData.Core.Security.Cryptographer
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -4,18 +4,40 @@ using DigitalData.Core.Security.Cryptographer;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Security.Claims;
|
||||
|
||||
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
|
||||
=> setAsDefault
|
||||
? services.AddParamsConfigureOptions<TAsymCryptParams>().AddSingleton<IAsymCryptService, AsymCryptService<TAsymCryptParams>>()
|
||||
: services.AddParamsConfigureOptions<TAsymCryptParams>().AddSingleton<IAsymCryptService<TAsymCryptParams>, AsymCryptService<TAsymCryptParams>>();
|
||||
{
|
||||
services.AddParamsConfigureOptions<TAsymCryptParams>().AddMappingProfile();
|
||||
return setAsDefault
|
||||
? services.AddSingleton<IAsymCryptService, AsymCryptService<TAsymCryptParams>>()
|
||||
: services.AddSingleton<IAsymCryptService<TAsymCryptParams>, AsymCryptService<TAsymCryptParams>>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a custom asym crypt service with specified parameters from the given configuration section.
|
||||
@@ -38,7 +60,7 @@ namespace DigitalData.Core.Security
|
||||
/// <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>
|
||||
@@ -68,8 +90,9 @@ namespace DigitalData.Core.Security
|
||||
/// <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())));
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Registers a custom RSA Factory with specified parameters from the given configuration section.
|
||||
/// </summary>
|
||||
@@ -81,7 +104,7 @@ namespace DigitalData.Core.Security
|
||||
public static IServiceCollection AddRSAFactory<TRSAFactoryParams>(this IServiceCollection services, IConfigurationSection section, bool setAsDefault = false)
|
||||
where TRSAFactoryParams : RSAFactoryParams
|
||||
{
|
||||
services.AddParamsConfigureOptions<TRSAFactoryParams>().Configure<TRSAFactoryParams>(section);
|
||||
services.AddMappingProfile().AddParamsConfigureOptions<TRSAFactoryParams>().Configure<TRSAFactoryParams>(section);
|
||||
return setAsDefault
|
||||
? services.AddSingleton<IRSAFactory, RSAFactory<TRSAFactoryParams>>()
|
||||
: services.AddSingleton<IRSAFactory<TRSAFactoryParams>, RSAFactory<TRSAFactoryParams>>();
|
||||
@@ -98,10 +121,23 @@ namespace DigitalData.Core.Security
|
||||
public static IServiceCollection AddRSAFactory<TRSAFactoryParams>(this IServiceCollection services, TRSAFactoryParams rsaParams, bool setAsDefault = false)
|
||||
where TRSAFactoryParams : RSAFactoryParams
|
||||
{
|
||||
services.AddSingleton(Options.Create(rsaParams));
|
||||
services.AddMappingProfile().AddSingleton(Options.Create(rsaParams));
|
||||
return setAsDefault
|
||||
? services.AddParamsConfigureOptions<TRSAFactoryParams>().AddSingleton<IRSAFactory, RSAFactory<TRSAFactoryParams>>()
|
||||
: services.AddParamsConfigureOptions<TRSAFactoryParams>().AddSingleton<IRSAFactory<TRSAFactoryParams>, RSAFactory<TRSAFactoryParams>>();
|
||||
}
|
||||
|
||||
private static IServiceCollection AddClaimDescriptor<TPrincipal>(this IServiceCollection services,
|
||||
Func<TPrincipal, IDictionary<string, object>>? claimsMapper = null,
|
||||
Func<TPrincipal, ClaimsIdentity>? subjectMapper = null)
|
||||
{
|
||||
var descriptor = new ClaimDescriptor<TPrincipal>
|
||||
{
|
||||
CreateClaims = claimsMapper,
|
||||
CreateSubject = subjectMapper
|
||||
};
|
||||
|
||||
return services.AddSingleton(sp => Options.Create(descriptor));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
30
DigitalData.Core.Security/JwtSignatureService.cs
Normal file
30
DigitalData.Core.Security/JwtSignatureService.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
19
DigitalData.Core.Security/TokenDescriptorProvider.cs
Normal file
19
DigitalData.Core.Security/TokenDescriptorProvider.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
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