Compare commits
10 Commits
d013d3edfa
...
6a92466490
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a92466490 | ||
|
|
5d9d756b91 | ||
|
|
f14aaa75e1 | ||
|
|
249f5a0ae5 | ||
|
|
30177cf0c7 | ||
|
|
68ef0a7537 | ||
|
|
fe2ee78d14 | ||
|
|
53e6f37a09 | ||
|
|
7ec85b4e30 | ||
|
|
a9ebc406f3 |
@@ -3,6 +3,8 @@
|
|||||||
public interface IAsymCryptService : IRSAFactory
|
public interface IAsymCryptService : IRSAFactory
|
||||||
{
|
{
|
||||||
public IEnumerable<IRSADecryptor> Decryptors { get; }
|
public IEnumerable<IRSADecryptor> Decryptors { get; }
|
||||||
|
|
||||||
|
public IRSADecryptor this[string key] { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IAsymCryptService<TParams> : IAsymCryptService, IRSAFactory<TParams> { }
|
public interface IAsymCryptService<TParams> : IAsymCryptService, IRSAFactory<TParams> { }
|
||||||
|
|||||||
@@ -11,7 +11,5 @@ namespace DigitalData.Core.Abstractions.Security
|
|||||||
public string Issuer { get; init; }
|
public string Issuer { get; init; }
|
||||||
|
|
||||||
public string Audience { get; init; }
|
public string Audience { get; init; }
|
||||||
|
|
||||||
public void Init();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
{
|
{
|
||||||
public interface IRSADecryptor : IRSACryptographer
|
public interface IRSADecryptor : IRSACryptographer
|
||||||
{
|
{
|
||||||
public bool Encrypt { get; init; }
|
public bool IsEncrypted { get; init; }
|
||||||
|
|
||||||
IRSAEncryptor Encryptor { get; }
|
IRSAEncryptor Encryptor { get; }
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,19 @@ namespace DigitalData.Core.Abstractions.Security
|
|||||||
{
|
{
|
||||||
public interface IRSAFactory
|
public interface IRSAFactory
|
||||||
{
|
{
|
||||||
string CreateRSAPrivateKeyPem(int? keySizeInBits = null);
|
string CreatePrivateKeyPem(int? keySizeInBits = null);
|
||||||
|
|
||||||
string CreateEncryptedPrivateKeyPem(
|
public string CreateEncryptedPrivateKeyPem(
|
||||||
int? keySizeInBits = null,
|
|
||||||
string? password = null,
|
|
||||||
PbeEncryptionAlgorithm? pbeEncryptionAlgorithm = null,
|
PbeEncryptionAlgorithm? pbeEncryptionAlgorithm = null,
|
||||||
HashAlgorithmName? hashAlgorithmName = null,
|
HashAlgorithmName? hashAlgorithmName = null,
|
||||||
int? iterationCount = null);
|
int? iterationCount = null,
|
||||||
|
int? keySizeInBits = null,
|
||||||
|
string? password = null);
|
||||||
|
|
||||||
|
public string CreateEncryptedPrivateKeyPem(
|
||||||
|
PbeParameters pbeParameters,
|
||||||
|
int? keySizeInBits = null,
|
||||||
|
string? password = null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IRSAFactory<TParams> : IRSAFactory { }
|
public interface IRSAFactory<TParams> : IRSAFactory { }
|
||||||
|
|||||||
@@ -3,16 +3,47 @@ using DigitalData.Core.Security.Config;
|
|||||||
using DigitalData.Core.Security.Cryptographer;
|
using DigitalData.Core.Security.Cryptographer;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
namespace DigitalData.Core.Security
|
namespace DigitalData.Core.Security
|
||||||
{
|
{
|
||||||
public class AsymCryptService<TAsymCryptParams> : RSAFactory<TAsymCryptParams>, IAsymCryptService<TAsymCryptParams>, IRSAFactory<TAsymCryptParams> where TAsymCryptParams : AsymCryptParams
|
public class AsymCryptService<TAsymCryptParams> : RSAFactory<TAsymCryptParams>, IAsymCryptService<TAsymCryptParams>, IRSAFactory<TAsymCryptParams>, IEnumerable<IRSADecryptor>
|
||||||
|
where TAsymCryptParams : AsymCryptParams
|
||||||
{
|
{
|
||||||
public IEnumerable<IRSADecryptor> Decryptors => _params.Decryptors;
|
public IEnumerable<IRSADecryptor> Decryptors => _params.Decryptors;
|
||||||
|
|
||||||
|
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 AsymCryptService(IOptions<TAsymCryptParams> options, ILogger<AsymCryptService<TAsymCryptParams>>? logger = null) : base(options)
|
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"));
|
logger?.LogInformation("Core.Secrets version: {Version}, Created on: {CreationDate}.", Secrets.Version, Secrets.CreationDate.ToString("dd.MM.yyyy"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,19 +6,35 @@ namespace DigitalData.Core.Security.Config
|
|||||||
{
|
{
|
||||||
public string PemDirectory { get; init; } = string.Empty;
|
public string PemDirectory { get; init; } = string.Empty;
|
||||||
|
|
||||||
public string Separator { get; init; } = "_-_";
|
/// <summary>
|
||||||
|
/// Represents the separator used to concatenate the components of a file-related token string.
|
||||||
public IEnumerable<RSADecryptor> Decryptors { get; init; } = new List<RSADecryptor>();
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The resulting file-related token string is constructed as follows:
|
||||||
|
/// <c>string.Join(FileNameSeparator, 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>FileNameSeparator = "_-_"</c>, the output might look like:
|
||||||
|
/// <c>"Issuer_-_Audience_-_Secret_version"</c>.
|
||||||
|
/// </example>
|
||||||
|
public string FileNameSeparator { get; init; } = "_-_";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 0: Issuer - 1: Audience - 2: Secret version (if is encrypted)
|
/// Represents the separator used to concatenate the components of a key-related token string.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private string CreateFileName(params object[] objs) => string.Join(Separator, objs);
|
/// <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; } = ":";
|
||||||
|
|
||||||
private string CreatePem(bool isEncrypted) => isEncrypted
|
public IEnumerable<RSADecryptor> Decryptors { get; init; } = new List<RSADecryptor>();
|
||||||
? Instance.RSAFactory.CreateEncryptedPrivateKeyPem(keySizeInBits: KeySizeInBits, password: Secrets.PBE_PASSWORD,
|
|
||||||
pbeEncryptionAlgorithm: PbeEncryptionAlgorithm, hashAlgorithmName: PbeHashAlgorithmName, iterationCount: PbeIterationCount)
|
|
||||||
: Instance.RSAFactory.CreateRSAPrivateKeyPem(keySizeInBits: KeySizeInBits);
|
|
||||||
|
|
||||||
public override void OnDeserialized()
|
public override void OnDeserialized()
|
||||||
{
|
{
|
||||||
@@ -28,29 +44,33 @@ namespace DigitalData.Core.Security.Config
|
|||||||
if (!Directory.Exists(PemDirectory))
|
if (!Directory.Exists(PemDirectory))
|
||||||
Directory.CreateDirectory(PemDirectory);
|
Directory.CreateDirectory(PemDirectory);
|
||||||
|
|
||||||
foreach (var crypt in Decryptors)
|
foreach (var decryptor in Decryptors)
|
||||||
{
|
{
|
||||||
// set default path
|
// set default path
|
||||||
if (crypt.IsPemNull)
|
if (decryptor.IsPemNull)
|
||||||
{
|
{
|
||||||
var file_name_params = new List<object> { crypt.Issuer, crypt.Audience };
|
var file_name_params = new List<object> { decryptor.Issuer, decryptor.Audience };
|
||||||
if (crypt.Encrypt)
|
if (decryptor.IsEncrypted)
|
||||||
file_name_params.Add(Secrets.Version);
|
file_name_params.Add(Secrets.Version);
|
||||||
|
|
||||||
var file_name = CreateFileName(file_name_params);
|
var path = Path.Combine(PemDirectory, string.Join(FileNameSeparator, file_name_params));
|
||||||
var path = Path.Combine(PemDirectory, file_name);
|
|
||||||
|
|
||||||
if (File.Exists(path))
|
if (File.Exists(path))
|
||||||
crypt.SetPem(File.ReadAllText(path));
|
decryptor.SetPem(File.ReadAllText(path));
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var pem = CreatePem(crypt.Encrypt);
|
var pem = decryptor.IsEncrypted
|
||||||
crypt.SetPem(File.ReadAllText(pem));
|
? Instance.RSAFactory.CreateEncryptedPrivateKeyPem(pbeParameters: PbeParameters, keySizeInBits: KeySizeInBits, password: Secrets.PBE_PASSWORD)
|
||||||
|
: Instance.RSAFactory.CreatePrivateKeyPem(keySizeInBits: KeySizeInBits);
|
||||||
|
|
||||||
|
decryptor.SetPem(File.ReadAllText(pem));
|
||||||
|
|
||||||
|
// Save file in background
|
||||||
Task.Run(async () => await File.WriteAllTextAsync(path: path, pem));
|
Task.Run(async () => await File.WriteAllTextAsync(path: path, pem));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
crypt.Init();
|
decryptor.Init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,18 +5,7 @@ namespace DigitalData.Core.Security.Cryptographer
|
|||||||
{
|
{
|
||||||
public class RSACryptographer : IRSACryptographer
|
public class RSACryptographer : IRSACryptographer
|
||||||
{
|
{
|
||||||
protected string? _pem;
|
public virtual string Pem { get; init; }
|
||||||
|
|
||||||
public string Pem
|
|
||||||
{
|
|
||||||
get => _pem
|
|
||||||
?? throw PemIsNullException;
|
|
||||||
init => _pem = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal bool IsPemNull => _pem is null;
|
|
||||||
|
|
||||||
private InvalidOperationException PemIsNullException => new($"Pem is not initialized. Please ensure that the PEM is set or properly loaded from the file. Issuer: {Issuer}, Audience: {Audience}.");
|
|
||||||
|
|
||||||
public RSAEncryptionPadding Padding { get; init; } = RSAEncryptionPadding.OaepSHA256;
|
public RSAEncryptionPadding Padding { get; init; } = RSAEncryptionPadding.OaepSHA256;
|
||||||
|
|
||||||
@@ -26,14 +15,8 @@ namespace DigitalData.Core.Security.Cryptographer
|
|||||||
|
|
||||||
public string Audience { get; init; } = string.Empty;
|
public string Audience { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||||
internal RSACryptographer() { }
|
internal RSACryptographer() { }
|
||||||
|
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||||
internal void SetPem(string pem) => _pem = pem;
|
|
||||||
|
|
||||||
public virtual void Init()
|
|
||||||
{
|
|
||||||
if (_pem is null)
|
|
||||||
throw PemIsNullException;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,13 @@ namespace DigitalData.Core.Security.Cryptographer
|
|||||||
{
|
{
|
||||||
public class RSADecryptor : RSACryptographer, IRSADecryptor, IRSACryptographer
|
public class RSADecryptor : RSACryptographer, IRSADecryptor, IRSACryptographer
|
||||||
{
|
{
|
||||||
public bool Encrypt { get; init; }
|
private string? _pem;
|
||||||
|
|
||||||
|
public override string Pem { get => _pem ?? throw PemIsNullException; init => _pem = value; }
|
||||||
|
|
||||||
|
public bool IsPemNull => _pem is null;
|
||||||
|
|
||||||
|
public bool IsEncrypted { get; init; }
|
||||||
|
|
||||||
private readonly Lazy<IRSAEncryptor> _lazyEncryptor;
|
private readonly Lazy<IRSAEncryptor> _lazyEncryptor;
|
||||||
|
|
||||||
@@ -25,13 +31,19 @@ namespace DigitalData.Core.Security.Cryptographer
|
|||||||
|
|
||||||
public string Decrypt(string data) => RSA.Decrypt(data.Base64ToByte(), Padding).BytesToString();
|
public string Decrypt(string data) => RSA.Decrypt(data.Base64ToByte(), Padding).BytesToString();
|
||||||
|
|
||||||
public override void Init()
|
internal void SetPem(string pem) => _pem = pem;
|
||||||
|
|
||||||
|
public void Init()
|
||||||
{
|
{
|
||||||
base.Init();
|
if (_pem is null)
|
||||||
if (Encrypt)
|
throw PemIsNullException;
|
||||||
|
|
||||||
|
if (IsEncrypted)
|
||||||
RSA.ImportFromEncryptedPem(Pem, Secrets.PBE_PASSWORD.AsSpan());
|
RSA.ImportFromEncryptedPem(Pem, Secrets.PBE_PASSWORD.AsSpan());
|
||||||
else
|
else
|
||||||
RSA.ImportFromPem(Pem);
|
RSA.ImportFromPem(Pem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private InvalidOperationException PemIsNullException => new($"Pem is not initialized. Please ensure that the PEM is set or properly loaded from the file. Issuer: {Issuer}, Audience: {Audience}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,16 +5,20 @@ namespace DigitalData.Core.Security.Cryptographer
|
|||||||
{
|
{
|
||||||
public class RSAEncryptor : RSACryptographer, IRSAEncryptor, IRSACryptographer
|
public class RSAEncryptor : RSACryptographer, IRSAEncryptor, IRSACryptographer
|
||||||
{
|
{
|
||||||
|
public override string Pem
|
||||||
|
{
|
||||||
|
get => base.Pem;
|
||||||
|
init
|
||||||
|
{
|
||||||
|
base.Pem = value;
|
||||||
|
RSA.ImportFromPem(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] Encrypt(byte[] data) => RSA.Encrypt(data, Padding);
|
public byte[] Encrypt(byte[] data) => RSA.Encrypt(data, Padding);
|
||||||
|
|
||||||
public string Encrypt(string data) => RSA.Encrypt(data.Base64ToByte(), Padding).BytesToString();
|
public string Encrypt(string data) => RSA.Encrypt(data.Base64ToByte(), Padding).BytesToString();
|
||||||
|
|
||||||
public bool Verify(string data, string signature) => Encrypt(data) == signature;
|
public bool Verify(string data, string signature) => Encrypt(data) == signature;
|
||||||
|
|
||||||
public override void Init()
|
|
||||||
{
|
|
||||||
base.Init();
|
|
||||||
RSA.ImportFromPem(base.Pem);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,15 +11,15 @@ namespace DigitalData.Core.Security.Cryptographer
|
|||||||
|
|
||||||
public RSAFactory(IOptions<TRSAFactoryParams> options) => _params = options.Value;
|
public RSAFactory(IOptions<TRSAFactoryParams> options) => _params = options.Value;
|
||||||
|
|
||||||
public string CreateRSAPrivateKeyPem(int? keySizeInBits = null)
|
public string CreatePrivateKeyPem(int? keySizeInBits = null)
|
||||||
=> RSA.Create(keySizeInBits ?? _params.KeySizeInBits).ExportRSAPrivateKeyPem();
|
=> RSA.Create(keySizeInBits ?? _params.KeySizeInBits).ExportRSAPrivateKeyPem();
|
||||||
|
|
||||||
public string CreateEncryptedPrivateKeyPem(
|
public string CreateEncryptedPrivateKeyPem(
|
||||||
int? keySizeInBits = null,
|
|
||||||
string? password = null,
|
|
||||||
PbeEncryptionAlgorithm? pbeEncryptionAlgorithm = null,
|
PbeEncryptionAlgorithm? pbeEncryptionAlgorithm = null,
|
||||||
HashAlgorithmName? hashAlgorithmName = null,
|
HashAlgorithmName? hashAlgorithmName = null,
|
||||||
int? iterationCount = null)
|
int? iterationCount = null,
|
||||||
|
int? keySizeInBits = null,
|
||||||
|
string? password = null)
|
||||||
{
|
{
|
||||||
password ??= _params.PbePassword;
|
password ??= _params.PbePassword;
|
||||||
|
|
||||||
@@ -36,5 +36,19 @@ namespace DigitalData.Core.Security.Cryptographer
|
|||||||
|
|
||||||
return new string(pemChars);
|
return new string(pemChars);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string CreateEncryptedPrivateKeyPem(
|
||||||
|
PbeParameters pbeParameters,
|
||||||
|
int? keySizeInBits = null,
|
||||||
|
string? password = null)
|
||||||
|
{
|
||||||
|
password ??= _params.PbePassword;
|
||||||
|
|
||||||
|
var encryptedPrivateKey = RSA.Create(keySizeInBits ?? _params.KeySizeInBits).ExportEncryptedPkcs8PrivateKey(password.AsSpan(), pbeParameters);
|
||||||
|
|
||||||
|
var pemChars = PemEncoding.Write(_params.EncryptedPrivateKeyPemLabel, encryptedPrivateKey);
|
||||||
|
|
||||||
|
return new string(pemChars);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user