16 Commits

Author SHA1 Message Date
Developer 02
f7193594b1 fix(RSAFactory): Dateiname und seine Erweiterung aus der Methode DefaultRSAKeyNameFormatter entfernt 2024-11-20 17:14:12 +01:00
Developer 02
9c7319634a fix(RSAExtensions): Schlüssel in Dateinamen umbenennen 2024-11-20 17:11:44 +01:00
Developer 02
3becb208ec fix(RSAExtensions): Falsche Methoden zur Schlüsselbenennung entfernt 2024-11-20 17:10:57 +01:00
Developer 02
1b00f9afa2 feat(CryptFactory): Der Parameter seperator der Funktionseigenschaft CryptFactory.RSAKeyNameFormatter ist jetzt nullbar. 2024-11-20 16:47:26 +01:00
Developer 02
b58d4aed2f feat(RSAFactory): Statische Readonly-Eigenschaft hinzugefügt, um den Standard-RSA-Schlüsselnamen-Separator zu speichern 2024-11-20 16:43:27 +01:00
Developer 02
5adc67edf2 feat (CryptFactory): Verschieben der Standardparameter des RSA-Namensformatierers in die RSAFactory 2024-11-20 16:40:24 +01:00
Developer 02
0ff0de8159 feat (CryptFactory.RSADecryptorKeyFormatter): aktualisiert, um die erforderlichen Parameter als Eingabe zu nehmen, anstatt IRSADecryptor direkt als Eingabe zu nehmen 2024-11-20 16:37:09 +01:00
Developer 02
49b49271f3 feat(CryptFactory): ValidateForbidden ve ValidateSeparator Methoden in DefaultRSADecryptorKeyFormatter hinzugefügt 2024-11-20 15:13:05 +01:00
Developer 02
5c5a6bd181 feat(CryptFactory): RSADecryptorKeyFormatter Funktionseigenschaft hinzugefügt, um standardisierte Schlüsselnamen zu erstellen 2024-11-20 14:18:55 +01:00
Developer 02
6ab1777f7c refactor(RSADecryptor): aktualisiert, um im Passwort- und Versions-Tupel-Format zu initieren, um Datenintegrität zu gewährleisten.
- password und PasswordVersion initter entfernt.
2024-11-20 12:49:36 +01:00
Developer 02
103ddf5c2e feat(RSADecryptor): PasswordVersion-Eigenschaft hinzugefügt. Password.get intern gemacht.
- Password.get entfernt und PasswordVersion-Eigenschaft in IRSADecryptor hinzugefügt
2024-11-20 11:17:38 +01:00
Developer 02
f9c94e8464 refactor(IRSADecryptor): HasEncryptedPem getter-Methode hinzugefügt 2024-11-20 10:52:39 +01:00
Developer 02
cdb0009e7c refactor(RSADecryptor): statt der Verwendung einer separaten init-Methode zur Initialisierung von RSA, wurde Lazy Loading verwendet. 2024-11-20 10:51:18 +01:00
Developer 02
5010224500 feat(RSADecryptor): Eigenschaft hinzugefügt, um zu prüfen, ob der RSADecryptor pem verschlüsselt hat. 2024-11-20 10:38:34 +01:00
Developer 02
1ebdd7e5bb feat(RSADecryptor): Öffentliche Constructure-Methode gemacht. 2024-11-20 10:33:11 +01:00
Developer 02
0e0513e640 feat(RSAExtensions): Methoden zum Speichern von IRSACryptographer.Pem erstellt. 2024-11-20 10:32:25 +01:00
8 changed files with 161 additions and 56 deletions

View File

@@ -18,6 +18,20 @@ namespace DigitalData.Core.Abstractions.Security
string EncryptedPrivateKeyPemLabel { get; init; } string EncryptedPrivateKeyPemLabel { get; init; }
/// <summary>
/// Gets the formatter function for generating RSA key names.
/// This formatter takes an issuer, audience, isPrivate, and optional version and separator
/// to produce a formatted string used for the key naming convention.
/// </summary>
/// <param name="issuer">A string representing the issuer of the key. It should not contain invalid file name characters or the separator.</param>
/// <param name="audience">A string representing the audience for which the key is intended. It should not contain invalid file name characters or the separator.</param>
/// <param name="isPrivate">An bool to check if the key is private.</param>
/// <param name="version">An instance of the <see cref="Version?"/> interface, which is used to keep the version of Pbe password.</param>
/// <param name="separator">An optional string separator used to separate the issuer and audience. The default is "-_-". It should not be included in the issuer or audience strings.</param>
/// <returns>A formatted string combining the issuer, audience, and separator, which adheres to valid file naming rules.</returns>
/// <exception cref="ArgumentException">Thrown when the issuer, audience, or separator contains invalid characters or when the separator is present within the issuer or audience.</exception>
Func<string, string, bool, Version?, string?, string> RSAKeyNameFormatter { get; }
string CreateRSAPrivateKeyPem(int? keySizeInBits = null); string CreateRSAPrivateKeyPem(int? keySizeInBits = null);
string CreateEncryptedPrivateKeyPem( string CreateEncryptedPrivateKeyPem(

View File

@@ -2,12 +2,16 @@
{ {
public interface IRSADecryptor : IRSACryptographer public interface IRSADecryptor : IRSACryptographer
{ {
public string? Password { get; init; } (string Value, Version Version) VersionedPassword { init; }
public IRSAEncryptor Encryptor { get; } Version? PasswordVersion { get; }
public byte[] Decrypt(byte[] data); bool HasEncryptedPem { get; }
public string Decrypt(string data); IRSAEncryptor Encryptor { get; }
byte[] Decrypt(byte[] data);
string Decrypt(string data);
} }
} }

View File

@@ -1,4 +1,5 @@
using DigitalData.Core.Abstractions.Security; using DigitalData.Core.Abstractions.Security;
using System.Collections.Concurrent;
using System.Security.Cryptography; using System.Security.Cryptography;
namespace DigitalData.Core.Security.Extensions namespace DigitalData.Core.Security.Extensions
@@ -12,32 +13,53 @@ namespace DigitalData.Core.Security.Extensions
return rsa; return rsa;
} }
public static bool TryGetEncryptor(this IDictionary<string, IRSAEncryptor> pairs, string issuer, string audience, out IRSAEncryptor? encryptor) public static IRSADecryptor GetRSADecryptor(this ICryptFactory factory, string issuer, string audience, Version? version = null, string? seperator = null)
=> pairs.TryGetValue($"{issuer}:{audience}", out encryptor); => factory[factory.RSAKeyNameFormatter(issuer, audience, true, version, seperator)];
public static IRSAEncryptor? GetEncryptor(this IDictionary<string, IRSAEncryptor> pairs, string issuer, string audience) public static bool TryGetRSADecryptor(this ICryptFactory factory, string issuer, string audience, out IRSADecryptor? decryptor, Version? version = null, string? seperator = null)
=> pairs.TryGetEncryptor(issuer: issuer, audience: audience, out var encryptor) ? encryptor : null; => factory.TryGetRSADecryptor(factory.RSAKeyNameFormatter(issuer, audience, true, version, seperator), out decryptor);
public static IRSADecryptor GetRSADecryptor(this ICryptFactory factory, string issuer, string audience) private static string CreatePath(string filename, string? directory = null)
=> factory[$"{issuer}:{audience}"];
public static bool TryGetRSADecryptor(this ICryptFactory factory, string issuer, string audience, out IRSADecryptor? decryptor)
=> factory.TryGetRSADecryptor($"{issuer}:{audience}", out decryptor);
public static IRSAEncryptor GetRSAEncryptor(this ICryptFactory factory, string issuer, string audience)
=> factory[$"{issuer}:{audience}"].Encryptor;
public static bool TryGetRSADecryptor(this ICryptFactory factory, string issuer, string audience, out IRSAEncryptor? encryptor)
{ {
if(factory.TryGetRSADecryptor($"{issuer}:{audience}", out var decryptor) && decryptor is not null) directory ??= Environment.CurrentDirectory;
if (!Directory.Exists(directory))
{ {
encryptor = decryptor.Encryptor; Directory.CreateDirectory(directory);
return true;
} }
else
return Path.Combine(directory, $"{filename}.pem");
}
private static readonly ConcurrentDictionary<string, SemaphoreSlim> FileLocks = new();
public static void SavePem(this IRSACryptographer decryptor, string key, string? directory = null)
{ {
encryptor = null; var filePath = CreatePath(filename: key, directory : directory);
return false; var fileLock = FileLocks.GetOrAdd(filePath, _ => new (1, 1));
fileLock.Wait();
try
{
File.WriteAllText(filePath, decryptor.Pem);
}
finally
{
fileLock.Release();
}
}
public static async Task SavePemAsync(this IRSACryptographer decryptor, string key, string? directory = null)
{
var filePath = CreatePath(filename: key, directory: directory);
var fileLock = FileLocks.GetOrAdd(filePath, _ => new (1, 1));
await fileLock.WaitAsync();
try
{
await File.WriteAllTextAsync(filePath, decryptor.Pem);
}
finally
{
fileLock.Release();
} }
} }
} }

View File

@@ -9,10 +9,14 @@ namespace DigitalData.Core.Security
public IRSADecryptor this[string key] { get => _decryptors[key]; set => _decryptors[key] = value; } public IRSADecryptor this[string key] { get => _decryptors[key]; set => _decryptors[key] = value; }
public CryptFactory(ILogger<CryptFactory> logger, IDictionary<string, IRSADecryptor> decryptors) : base() public Func<string, string, bool, Version?, string?, string> RSAKeyNameFormatter { get; }
public CryptFactory(ILogger<CryptFactory> logger, IDictionary<string, IRSADecryptor> decryptors, Func<string, string, bool, Version?, string?, string> rsaKeyNameFormatter) : base()
{ {
_decryptors = decryptors ?? new Dictionary<string, IRSADecryptor>(); _decryptors = decryptors ?? new Dictionary<string, IRSADecryptor>();
RSAKeyNameFormatter = rsaKeyNameFormatter;
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"));
} }

View File

@@ -9,7 +9,7 @@ namespace DigitalData.Core.Security
public RSAEncryptionPadding Padding { get; init; } = RSAEncryptionPadding.OaepSHA256; public RSAEncryptionPadding Padding { get; init; } = RSAEncryptionPadding.OaepSHA256;
protected readonly RSA _rsa = RSA.Create(); protected virtual RSA RSA { get; } = RSA.Create();
internal RSACryptographer() { } internal RSACryptographer() { }
} }

View File

@@ -1,46 +1,58 @@
using DigitalData.Core.Abstractions.Security; using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Extensions; using DigitalData.Core.Security.Extensions;
using System.Runtime.Serialization; using System.Security.Cryptography;
namespace DigitalData.Core.Security namespace DigitalData.Core.Security
{ {
public class RSADecryptor : RSACryptographer, IRSADecryptor, IRSACryptographer public class RSADecryptor : RSACryptographer, IRSADecryptor, IRSACryptographer
{ {
public string? Password { get; init; } public (string Value, Version Version) VersionedPassword
{
init
{
_password = value.Value;
PasswordVersion = value.Version;
}
}
public bool IsEncrypted => Password is not null; private string? _password;
public Version? PasswordVersion { get; private init; } = null;
public bool HasEncryptedPem => _password is not null;
public bool IsEncrypted => _password is not null;
private readonly Lazy<IRSAEncryptor> _lazyEncryptor; private readonly Lazy<IRSAEncryptor> _lazyEncryptor;
public IRSAEncryptor Encryptor => _lazyEncryptor.Value; public IRSAEncryptor Encryptor => _lazyEncryptor.Value;
internal RSADecryptor() private readonly Lazy<RSA> lazyRSA;
protected override RSA RSA => lazyRSA.Value;
public RSADecryptor()
{ {
_lazyEncryptor = new(() => new RSAEncryptor() _lazyEncryptor = new(() => new RSAEncryptor()
{ {
Pem = _rsa.ExportRSAPublicKeyPem(), Pem = RSA.ExportRSAPublicKeyPem(),
Padding = Padding Padding = Padding
}); });
lazyRSA = new(() =>
{
var rsa = RSA.Create();
if (_password is null)
RSA.ImportFromPem(Pem);
else
RSA.ImportFromEncryptedPem(Pem, _password.AsSpan());
return rsa;
});
} }
[OnDeserialized] public byte[] Decrypt(byte[] data) => RSA.Decrypt(data, Padding);
private void OnDeserialized(StreamingContext context) => Init();
private IRSADecryptor Init() public string Decrypt(string data) => RSA.Decrypt(data.Base64ToByte(), Padding).BytesToString();
{
if (string.IsNullOrWhiteSpace(Pem))
throw new InvalidOperationException("Pem cannot be null or empty.");
if (Password is null)
_rsa.ImportFromPem(Pem);
else
_rsa.ImportFromEncryptedPem(Pem, Password.AsSpan());
return this;
}
public byte[] Decrypt(byte[] data) => _rsa.Decrypt(data, Padding);
public string Decrypt(string data) => _rsa.Decrypt(data.Base64ToByte(), Padding).BytesToString();
} }
} }

View File

@@ -10,17 +10,14 @@ namespace DigitalData.Core.Security
get => base.Pem; get => base.Pem;
init init
{ {
if (string.IsNullOrWhiteSpace(Pem)) RSA.ImportFromPem(base.Pem);
throw new InvalidOperationException("Pem cannot be null or empty.");
_rsa.ImportFromPem(base.Pem);
base.Pem = value; base.Pem = 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;
} }

View File

@@ -1,4 +1,5 @@
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text;
namespace DigitalData.Core.Security namespace DigitalData.Core.Security
{ {
@@ -8,6 +9,57 @@ namespace DigitalData.Core.Security
public static RSAFactory Static => LazyInstance.Value; public static RSAFactory Static => LazyInstance.Value;
public static readonly string DefaultEncryptedPrivateKeyFileTag = "enc-private";
public static readonly string DefaultPrivateKeyFileTag = "private";
public static readonly string DefaultPublicKeyFileTag = "public";
public static readonly IEnumerable<string> KeyFileTags = new string[] { DefaultEncryptedPrivateKeyFileTag, DefaultPrivateKeyFileTag, DefaultPublicKeyFileTag };
private static readonly Lazy<IEnumerable<string>> LazyLowerFileTags = new(() => KeyFileTags.Select(tag => tag.ToLower()));
public static readonly string DefaultRSAKeyNameSeparator = "-_-";
//TODO: make the validation using regex
public static string DefaultRSAKeyNameFormatter(string issuer, string audience, bool isPrivate = true, Version? passwordVersion = null, string? separator = null)
{
separator ??= DefaultRSAKeyNameSeparator;
void ValidateForbidden(string value, string paramName)
{
if (Path.GetInvalidFileNameChars().Any(value.Contains) || LazyLowerFileTags.Value.Any(tag => value.ToLower().Contains(tag)))
throw new ArgumentException($"RSA decryptor key name creation is forbidden. The {paramName} contains forbidden characters that are not allowed in file naming.", paramName);
}
static void ValidateSeparator(string value, string paramName, string separator)
{
if (value.Contains(separator))
throw new ArgumentException($"RSA decryptor key name creation is forbidden. The {paramName} contains separator characters ({separator}) that are not allowed in file naming.", paramName);
}
ValidateForbidden(issuer, nameof(issuer));
ValidateForbidden(audience, nameof(audience));
ValidateForbidden(separator, nameof(separator));
ValidateSeparator(issuer, nameof(issuer), separator);
ValidateSeparator(audience, nameof(audience), separator);
var sb = new StringBuilder(issuer.Length + audience.Length + separator.Length * 2 + 20);
sb.Append(issuer).Append(separator).Append(audience).Append(separator);
if (passwordVersion is null && isPrivate)
sb.Append(DefaultPrivateKeyFileTag);
else if (isPrivate)
sb.Append(DefaultEncryptedPrivateKeyFileTag).Append(separator).Append(passwordVersion);
else if (passwordVersion is null)
sb.Append(DefaultPublicKeyFileTag);
else
sb.Append(DefaultPublicKeyFileTag).Append(separator).Append(passwordVersion);
return sb.ToString();
}
public int KeySizeInBits { get; init; } = 2048; public int KeySizeInBits { get; init; } = 2048;
public string PbePassword { private get; init; } = Secrets.PBE_PASSWORD; public string PbePassword { private get; init; } = Secrets.PBE_PASSWORD;