Compare commits
62 Commits
4a64a31d47
...
feat/clien
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
988d1e2b16 | ||
|
|
4e0e907313 | ||
|
|
0bfec426d4 | ||
|
|
08ffe821ff | ||
|
|
fa5d0f1b26 | ||
|
|
38bd23d012 | ||
|
|
50e2581727 | ||
|
|
5c09d7775b | ||
|
|
dbfee49dee | ||
|
|
0c6c84852d | ||
|
|
3f61b5064c | ||
|
|
f79d2e2352 | ||
|
|
201da81aa5 | ||
|
|
bea57a25e8 | ||
|
|
0ff89b4906 | ||
|
|
600d17ef40 | ||
|
|
16565eca4d | ||
|
|
8787c04917 | ||
|
|
b3568216a0 | ||
|
|
6f520732dd | ||
|
|
8003cffb9b | ||
|
|
b02f93b38d | ||
|
|
2f0c6a905a | ||
|
|
baf1f5e045 | ||
|
|
b8a4a1f2b5 | ||
|
|
a69f610ef4 | ||
|
|
016d8bdcf2 | ||
|
|
738005f5dc | ||
|
|
c96af25e23 | ||
|
|
35e2fef046 | ||
|
|
b8fb45d4a3 | ||
|
|
fa60147507 | ||
|
|
e9d408a717 | ||
|
|
5fd3fa2fc6 | ||
|
|
0d5bcedc01 | ||
|
|
2e68a37944 | ||
|
|
8076efb934 | ||
|
|
c38f7dcf72 | ||
|
|
6e4942c885 | ||
|
|
d0dfd834b0 | ||
|
|
aa9951f242 | ||
|
|
506685a0b5 | ||
|
|
c9548238bb | ||
|
|
3ffdd49a47 | ||
|
|
609cd29dc5 | ||
|
|
cc3d1f58d3 | ||
|
|
c03f39c1a9 | ||
|
|
750f7bc20c | ||
|
|
65989b23b3 | ||
|
|
c895d2df0e | ||
|
|
0c451cb834 | ||
|
|
9396f48f46 | ||
|
|
1a941b4728 | ||
|
|
c6942164e2 | ||
|
|
343560ed62 | ||
|
|
6873bac8a1 | ||
|
|
09406ca505 | ||
|
|
3aa5ad782f | ||
|
|
5991444efd | ||
|
|
f720ea9cd6 | ||
|
|
a4b96c2f3e | ||
|
|
816d5835f1 |
@@ -0,0 +1,21 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace DigitalData.Core.Abstractions.Security
|
||||||
|
{
|
||||||
|
public interface IAsymCryptService<TParams> : IRSAFactory<TParams>
|
||||||
|
{
|
||||||
|
public IEnumerable<IRSADecryptor> Decryptors { get; }
|
||||||
|
|
||||||
|
public IEnumerable<IRSAEncryptor> Encryptors { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
namespace DigitalData.Core.Abstractions.Security
|
|
||||||
{
|
|
||||||
public interface ICryptFactory
|
|
||||||
{
|
|
||||||
int KeySizeInBits { get; init; }
|
|
||||||
|
|
||||||
string PbePassword { init; }
|
|
||||||
|
|
||||||
PbeEncryptionAlgorithm PbeEncryptionAlgorithm { get; init; }
|
|
||||||
|
|
||||||
HashAlgorithmName PbeHashAlgorithmName { get; init; }
|
|
||||||
|
|
||||||
int PbeIterationCount { get; init; }
|
|
||||||
|
|
||||||
PbeParameters PbeParameters { get; }
|
|
||||||
|
|
||||||
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 CreateEncryptedPrivateKeyPem(
|
|
||||||
int? keySizeInBits = null,
|
|
||||||
string? password = null,
|
|
||||||
PbeEncryptionAlgorithm? pbeEncryptionAlgorithm = null,
|
|
||||||
HashAlgorithmName? hashAlgorithmName = null,
|
|
||||||
int? iterationCount = null);
|
|
||||||
|
|
||||||
IRSADecryptor this[string key] { get; }
|
|
||||||
|
|
||||||
bool TryGetRSADecryptor(string key, out IRSADecryptor? decryptor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,5 +7,15 @@ namespace DigitalData.Core.Abstractions.Security
|
|||||||
public string Pem { get; init; }
|
public string Pem { get; init; }
|
||||||
|
|
||||||
public RSAEncryptionPadding Padding { get; init; }
|
public RSAEncryptionPadding Padding { get; init; }
|
||||||
|
|
||||||
|
public string? Directory { get; set; }
|
||||||
|
|
||||||
|
public string? FileName { get; set; }
|
||||||
|
|
||||||
|
public string Issuer { get; init; }
|
||||||
|
|
||||||
|
public string Audience { get; init; }
|
||||||
|
|
||||||
|
public void Init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,11 +2,7 @@
|
|||||||
{
|
{
|
||||||
public interface IRSADecryptor : IRSACryptographer
|
public interface IRSADecryptor : IRSACryptographer
|
||||||
{
|
{
|
||||||
(string Value, Version Version)? VersionedPassword { init; }
|
public bool Encrypt { get; init; }
|
||||||
|
|
||||||
Version? PasswordVersion { get; }
|
|
||||||
|
|
||||||
bool HasEncryptedPem { get; }
|
|
||||||
|
|
||||||
IRSAEncryptor Encryptor { get; }
|
IRSAEncryptor Encryptor { get; }
|
||||||
|
|
||||||
|
|||||||
16
DigitalData.Core.Abstractions/Security/IRSAFactory.cs
Normal file
16
DigitalData.Core.Abstractions/Security/IRSAFactory.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
namespace DigitalData.Core.Abstractions.Security
|
||||||
|
{
|
||||||
|
public interface IRSAFactory<TParams>
|
||||||
|
{
|
||||||
|
string CreateRSAPrivateKeyPem(int? keySizeInBits = null);
|
||||||
|
|
||||||
|
string CreateEncryptedPrivateKeyPem(
|
||||||
|
int? keySizeInBits = null,
|
||||||
|
string? password = null,
|
||||||
|
PbeEncryptionAlgorithm? pbeEncryptionAlgorithm = null,
|
||||||
|
HashAlgorithmName? hashAlgorithmName = null,
|
||||||
|
int? iterationCount = null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -86,7 +86,7 @@ namespace DigitalData.Core.Client
|
|||||||
{
|
{
|
||||||
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
|
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
|
||||||
|
|
||||||
var flagParams = queryParams.Where(param => param.Value is null).Select(param => HttpUtility.UrlEncode(param.Key));
|
var flagParams = queryParams.Where(param => param.Value is null).Select(param => param.Key);
|
||||||
|
|
||||||
var valueParams = queryParams.Where(param => param.Value is not null);
|
var valueParams = queryParams.Where(param => param.Value is not null);
|
||||||
|
|
||||||
@@ -94,12 +94,12 @@ namespace DigitalData.Core.Client
|
|||||||
query[param.Key] = param.Value switch
|
query[param.Key] = param.Value switch
|
||||||
{
|
{
|
||||||
bool b => b.ToString().ToLower(),
|
bool b => b.ToString().ToLower(),
|
||||||
_ => HttpUtility.UrlEncode(param.Value.ToString())
|
_ => param.Value.ToString()
|
||||||
};
|
};
|
||||||
|
|
||||||
var flagQuery = string.Join(QUERY_SEPARATOR, flagParams);
|
if (flagParams.Any())
|
||||||
|
uriBuilder.Query = string.Join(QUERY_SEPARATOR, query.ToString(), string.Join(QUERY_SEPARATOR, flagParams));
|
||||||
uriBuilder.Query = string.Join(QUERY_SEPARATOR, query.ToString(), flagQuery);
|
else uriBuilder.Query = query.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
var requestUri = uriBuilder.Uri;
|
var requestUri = uriBuilder.Uri;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<Description>This package provides HTTP client extension methods for the DigitalData.Core library, offering simplified and asynchronous methods for fetching and handling HTTP responses. It includes utility methods for sending GET requests, reading response content as text or JSON, and deserializing JSON into dynamic or strongly-typed objects using Newtonsoft.Json. These extensions facilitate efficient and easy-to-read HTTP interactions in client applications.</Description>
|
<Description>This package provides HTTP client extension methods for the DigitalData.Core library, offering simplified and asynchronous methods for fetching and handling HTTP responses. It includes utility methods for sending GET requests, reading response content as text or JSON, and deserializing JSON into dynamic or strongly-typed objects using Newtonsoft.Json. These extensions facilitate efficient and easy-to-read HTTP interactions in client applications.</Description>
|
||||||
<PackageId>DigitalData.Core.Client</PackageId>
|
<PackageId>DigitalData.Core.Client</PackageId>
|
||||||
<Version>2.0.1</Version>
|
<Version>2.0.3</Version>
|
||||||
<Authors>Digital Data GmbH</Authors>
|
<Authors>Digital Data GmbH</Authors>
|
||||||
<Company>Digital Data GmbH</Company>
|
<Company>Digital Data GmbH</Company>
|
||||||
<Product>Digital Data GmbH</Product>
|
<Product>Digital Data GmbH</Product>
|
||||||
@@ -15,8 +15,8 @@
|
|||||||
<PackageIcon>core_icon.png</PackageIcon>
|
<PackageIcon>core_icon.png</PackageIcon>
|
||||||
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
|
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
|
||||||
<PackageTags>digital data core http client json serilization</PackageTags>
|
<PackageTags>digital data core http client json serilization</PackageTags>
|
||||||
<AssemblyVersion>2.0.1</AssemblyVersion>
|
<AssemblyVersion>2.0.3</AssemblyVersion>
|
||||||
<FileVersion>2.0.1</FileVersion>
|
<FileVersion>2.0.3</FileVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -12,13 +12,7 @@ namespace DigitalData.Core.Security.Extensions
|
|||||||
rsa.ImportFromPem(pem);
|
rsa.ImportFromPem(pem);
|
||||||
return rsa;
|
return rsa;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IRSADecryptor GetRSADecryptor(this ICryptFactory factory, string issuer, string audience, Version? version = null, string? seperator = null)
|
|
||||||
=> factory[factory.RSAKeyNameFormatter(issuer, audience, true, version, seperator)];
|
|
||||||
|
|
||||||
public static bool TryGetRSADecryptor(this ICryptFactory factory, string issuer, string audience, out IRSADecryptor? decryptor, Version? version = null, string? seperator = null)
|
|
||||||
=> factory.TryGetRSADecryptor(factory.RSAKeyNameFormatter(issuer, audience, true, version, seperator), out decryptor);
|
|
||||||
|
|
||||||
private static string CreatePath(string filename, string? directory = null)
|
private static string CreatePath(string filename, string? directory = null)
|
||||||
{
|
{
|
||||||
directory ??= Environment.CurrentDirectory;
|
directory ??= Environment.CurrentDirectory;
|
||||||
|
|||||||
20
DigitalData.Core.Security/AsymCryptService.cs
Normal file
20
DigitalData.Core.Security/AsymCryptService.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
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 AsymCryptService<TAsymCryptParams> : RSAFactory<TAsymCryptParams>, IAsymCryptService<TAsymCryptParams>, IRSAFactory<TAsymCryptParams> where TAsymCryptParams : AsymCryptParams
|
||||||
|
{
|
||||||
|
public IEnumerable<IRSADecryptor> Decryptors => _params.Decryptors;
|
||||||
|
|
||||||
|
public IEnumerable<IRSAEncryptor> Encryptors => _params.Encryptors;
|
||||||
|
|
||||||
|
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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
59
DigitalData.Core.Security/Config/AsymCryptParams.cs
Normal file
59
DigitalData.Core.Security/Config/AsymCryptParams.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
using DigitalData.Core.Abstractions.Security;
|
||||||
|
|
||||||
|
namespace DigitalData.Core.Security.Config
|
||||||
|
{
|
||||||
|
public class AsymCryptParams : RSAFactoryParams
|
||||||
|
{
|
||||||
|
public string Directory { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 0: Issuer - 1: Audience - 2: Type tag - 3: Version
|
||||||
|
/// </summary>
|
||||||
|
public string FileNameFormat { get; init; } = "{0}_-_{1}_-_{2}_-_{3}.pem";
|
||||||
|
|
||||||
|
public string EncryptorTag { get; init; } = "public";
|
||||||
|
|
||||||
|
public string DecryptorTag { get; init; } = "private";
|
||||||
|
|
||||||
|
public string EncryptedDecryptorTag { get; init; } = "enc-private";
|
||||||
|
|
||||||
|
public IEnumerable<IRSADecryptor> Decryptors { get; init; } = new List<IRSADecryptor>();
|
||||||
|
|
||||||
|
public IEnumerable<IRSAEncryptor> Encryptors { get; init; } = new List<IRSAEncryptor>();
|
||||||
|
|
||||||
|
private string TypeTagOf(IRSACryptographer crypt)
|
||||||
|
{
|
||||||
|
if (crypt is IRSAEncryptor)
|
||||||
|
return EncryptorTag;
|
||||||
|
else if (crypt is IRSADecryptor decryptor)
|
||||||
|
return decryptor.Encrypt ? EncryptedDecryptorTag : DecryptorTag;
|
||||||
|
else
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Unknown cryptographer type. The crypt parameter must be either IRSAEncryptor or IRSADecryptor.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnDeserialized()
|
||||||
|
{
|
||||||
|
base.OnDeserialized();
|
||||||
|
|
||||||
|
var cryptographers = Encryptors.Cast<IRSACryptographer>().Concat(Decryptors.Cast<IRSACryptographer>());
|
||||||
|
|
||||||
|
foreach (var crypt in cryptographers)
|
||||||
|
{
|
||||||
|
// set default path
|
||||||
|
if (crypt.Pem is null)
|
||||||
|
{
|
||||||
|
crypt.Directory ??= Directory;
|
||||||
|
crypt.FileName ??= string.Format(
|
||||||
|
FileNameFormat,
|
||||||
|
crypt.Issuer,
|
||||||
|
crypt.Audience,
|
||||||
|
TypeTagOf(crypt),
|
||||||
|
Secrets.Version);
|
||||||
|
}
|
||||||
|
|
||||||
|
crypt.Init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
DigitalData.Core.Security/Config/RSAFactoryParams.cs
Normal file
27
DigitalData.Core.Security/Config/RSAFactoryParams.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace DigitalData.Core.Security.Config
|
||||||
|
{
|
||||||
|
public class RSAFactoryParams : IJsonOnDeserialized
|
||||||
|
{
|
||||||
|
public int KeySizeInBits { get; init; } = 2048;
|
||||||
|
|
||||||
|
public string PbePassword { internal get; init; } = Secrets.PBE_PASSWORD;
|
||||||
|
|
||||||
|
public PbeEncryptionAlgorithm PbeEncryptionAlgorithm { get; init; } = PbeEncryptionAlgorithm.Aes256Cbc;
|
||||||
|
|
||||||
|
public HashAlgorithmName PbeHashAlgorithmName { get; init; } = HashAlgorithmName.SHA256;
|
||||||
|
|
||||||
|
public int PbeIterationCount { get; init; } = 100_000;
|
||||||
|
|
||||||
|
public string EncryptedPrivateKeyPemLabel { get; init; } = "ENCRYPTED PRIVATE KEY";
|
||||||
|
|
||||||
|
private PbeParameters? _pbeParameters;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public PbeParameters PbeParameters => _pbeParameters!;
|
||||||
|
|
||||||
|
public virtual void OnDeserialized() => _pbeParameters = new PbeParameters(PbeEncryptionAlgorithm, PbeHashAlgorithmName, PbeIterationCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
using DigitalData.Core.Abstractions.Security;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace DigitalData.Core.Security
|
|
||||||
{
|
|
||||||
public class CryptFactory : RSAFactory, ICryptFactory
|
|
||||||
{
|
|
||||||
private readonly IDictionary<string, IRSADecryptor> _decryptors;
|
|
||||||
|
|
||||||
public IRSADecryptor this[string key] { get => _decryptors[key]; set => _decryptors[key] = value; }
|
|
||||||
|
|
||||||
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>();
|
|
||||||
|
|
||||||
RSAKeyNameFormatter = rsaKeyNameFormatter;
|
|
||||||
|
|
||||||
logger?.LogInformation("Core.Secrets version: {Version}, Created on: {CreationDate}.", Secrets.Version, Secrets.CreationDate.ToString("dd.MM.yyyy"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryGetRSADecryptor(string key, out IRSADecryptor? decryptor) => _decryptors.TryGetValue(key, out decryptor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
53
DigitalData.Core.Security/Cryptographer/RSACryptographer.cs
Normal file
53
DigitalData.Core.Security/Cryptographer/RSACryptographer.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
using DigitalData.Core.Abstractions.Security;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
namespace DigitalData.Core.Security.Cryptographer
|
||||||
|
{
|
||||||
|
public class RSACryptographer : IRSACryptographer
|
||||||
|
{
|
||||||
|
protected string? _pem;
|
||||||
|
|
||||||
|
public string Pem
|
||||||
|
{
|
||||||
|
get => _pem
|
||||||
|
?? throw new InvalidOperationException($"Pem is not initialized. Please ensure that the PEM is set or properly loaded from the file. Issuer: {Issuer}, Audience: {Audience}.");
|
||||||
|
init => _pem = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? PemPath => FileName is null ? null : Path.Combine(Directory ?? string.Empty, FileName);
|
||||||
|
|
||||||
|
public string? Directory { get; set; }
|
||||||
|
|
||||||
|
public string? FileName { get; set; }
|
||||||
|
|
||||||
|
public RSAEncryptionPadding Padding { get; init; } = RSAEncryptionPadding.OaepSHA256;
|
||||||
|
|
||||||
|
protected virtual RSA RSA { get; } = RSA.Create();
|
||||||
|
|
||||||
|
public string Issuer { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
public string Audience { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
internal RSACryptographer() { }
|
||||||
|
|
||||||
|
public virtual void UnableToInitPemEvent() => throw new InvalidOperationException(
|
||||||
|
$"Pem is not initialized and pem file is null. Issuer is {Issuer} and audience {Audience}.");
|
||||||
|
|
||||||
|
public virtual void FileNotFoundEvent() => throw new FileNotFoundException(
|
||||||
|
$"Pem is not initialized and pem file is not found in {PemPath}. Issuer is {Issuer} and audience {Audience}.");
|
||||||
|
|
||||||
|
// TODO: make file read asynchronous, consider multiple routing
|
||||||
|
public virtual void Init()
|
||||||
|
{
|
||||||
|
if(_pem is null)
|
||||||
|
{
|
||||||
|
if(PemPath is null)
|
||||||
|
UnableToInitPemEvent();
|
||||||
|
if (File.Exists(PemPath))
|
||||||
|
_pem = File.ReadAllText(PemPath);
|
||||||
|
else
|
||||||
|
FileNotFoundEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
DigitalData.Core.Security/Cryptographer/RSADecryptor.cs
Normal file
55
DigitalData.Core.Security/Cryptographer/RSADecryptor.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
using DigitalData.Core.Abstractions.Security;
|
||||||
|
using DigitalData.Core.Security.Config;
|
||||||
|
using DigitalData.Core.Security.Extensions;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
namespace DigitalData.Core.Security.Cryptographer
|
||||||
|
{
|
||||||
|
public class RSADecryptor : RSACryptographer, IRSADecryptor, IRSACryptographer
|
||||||
|
{
|
||||||
|
public bool Encrypt { 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();
|
||||||
|
|
||||||
|
public override void Init()
|
||||||
|
{
|
||||||
|
base.Init();
|
||||||
|
if (Encrypt)
|
||||||
|
RSA.ImportFromEncryptedPem(Pem, Secrets.PBE_PASSWORD.AsSpan());
|
||||||
|
else
|
||||||
|
RSA.ImportFromPem(Pem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FileNotFoundEvent()
|
||||||
|
{
|
||||||
|
var new_decryptor = new RSADecryptor()
|
||||||
|
{
|
||||||
|
Pem = RSAFactory<RSAFactoryParams>.Static.CreateRSAPrivateKeyPem(),
|
||||||
|
Encrypt = Encrypt
|
||||||
|
};
|
||||||
|
|
||||||
|
_pem = new_decryptor.Pem;
|
||||||
|
|
||||||
|
if (PemPath is not null)
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await File.WriteAllTextAsync(_pem, PemPath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
DigitalData.Core.Security/Cryptographer/RSAEncryptor.cs
Normal file
37
DigitalData.Core.Security/Cryptographer/RSAEncryptor.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using DigitalData.Core.Abstractions.Security;
|
||||||
|
using DigitalData.Core.Security.Config;
|
||||||
|
using DigitalData.Core.Security.Extensions;
|
||||||
|
|
||||||
|
namespace DigitalData.Core.Security.Cryptographer
|
||||||
|
{
|
||||||
|
public class RSAEncryptor : RSACryptographer, IRSAEncryptor, IRSACryptographer
|
||||||
|
{
|
||||||
|
public byte[] Encrypt(byte[] data) => RSA.Encrypt(data, Padding);
|
||||||
|
|
||||||
|
public string Encrypt(string data) => RSA.Encrypt(data.Base64ToByte(), Padding).BytesToString();
|
||||||
|
|
||||||
|
public bool Verify(string data, string signature) => Encrypt(data) == signature;
|
||||||
|
|
||||||
|
public override void Init()
|
||||||
|
{
|
||||||
|
base.Init();
|
||||||
|
RSA.ImportFromPem(base.Pem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FileNotFoundEvent()
|
||||||
|
{
|
||||||
|
var new_decryptor = new RSADecryptor()
|
||||||
|
{
|
||||||
|
Pem = RSAFactory<RSAFactoryParams>.Static.CreateRSAPrivateKeyPem()
|
||||||
|
};
|
||||||
|
|
||||||
|
_pem = new_decryptor.Encryptor.Pem;
|
||||||
|
|
||||||
|
if (PemPath is not null)
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await File.WriteAllTextAsync(_pem, PemPath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
DigitalData.Core.Security/Cryptographer/RSAFactory.cs
Normal file
44
DigitalData.Core.Security/Cryptographer/RSAFactory.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using DigitalData.Core.Abstractions.Security;
|
||||||
|
using DigitalData.Core.Security.Config;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
namespace DigitalData.Core.Security.Cryptographer
|
||||||
|
{
|
||||||
|
public class RSAFactory<TRSAFactoryParams> : IRSAFactory<TRSAFactoryParams> where TRSAFactoryParams : RSAFactoryParams
|
||||||
|
{
|
||||||
|
private static readonly Lazy<RSAFactory<RSAFactoryParams>> LazyInstance = new(() => new(Options.Create<RSAFactoryParams>(new())));
|
||||||
|
|
||||||
|
public static RSAFactory<RSAFactoryParams> Static => LazyInstance.Value;
|
||||||
|
|
||||||
|
protected readonly TRSAFactoryParams _params;
|
||||||
|
|
||||||
|
public RSAFactory(IOptions<TRSAFactoryParams> options) => _params = options.Value;
|
||||||
|
|
||||||
|
public string CreateRSAPrivateKeyPem(int? keySizeInBits = null)
|
||||||
|
=> RSA.Create(keySizeInBits ?? _params.KeySizeInBits).ExportRSAPrivateKeyPem();
|
||||||
|
|
||||||
|
public string CreateEncryptedPrivateKeyPem(
|
||||||
|
int? keySizeInBits = null,
|
||||||
|
string? password = null,
|
||||||
|
PbeEncryptionAlgorithm? pbeEncryptionAlgorithm = null,
|
||||||
|
HashAlgorithmName? hashAlgorithmName = null,
|
||||||
|
int? iterationCount = null)
|
||||||
|
{
|
||||||
|
password ??= _params.PbePassword;
|
||||||
|
|
||||||
|
var pbeParameters = pbeEncryptionAlgorithm is null && hashAlgorithmName is null && iterationCount is null
|
||||||
|
? new PbeParameters(
|
||||||
|
pbeEncryptionAlgorithm ?? _params.PbeEncryptionAlgorithm,
|
||||||
|
hashAlgorithmName ?? _params.PbeHashAlgorithmName,
|
||||||
|
iterationCount ?? _params.PbeIterationCount)
|
||||||
|
: _params.PbeParameters;
|
||||||
|
|
||||||
|
var encryptedPrivateKey = RSA.Create(keySizeInBits ?? _params.KeySizeInBits).ExportEncryptedPkcs8PrivateKey(password.AsSpan(), pbeParameters);
|
||||||
|
|
||||||
|
var pemChars = PemEncoding.Write(_params.EncryptedPrivateKeyPemLabel, encryptedPrivateKey);
|
||||||
|
|
||||||
|
return new string(pemChars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,55 @@
|
|||||||
using DigitalData.Core.Abstractions.Security;
|
using DigitalData.Core.Abstractions.Security;
|
||||||
|
using DigitalData.Core.Security.Config;
|
||||||
|
using DigitalData.Core.Security.Cryptographer;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace DigitalData.Core.Security
|
namespace DigitalData.Core.Security
|
||||||
{
|
{
|
||||||
public static class DIExtensions
|
public static class DIExtensions
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddSecurity(this IServiceCollection services)
|
public static JsonSerializerOptions AddCryptographerConverter(this JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
services.TryAddScoped<ICryptFactory, CryptFactory>();
|
if (!options.Converters.OfType<HashAlgorithmNameConverter>().Any())
|
||||||
|
options.Converters.Add(new HashAlgorithmNameConverter());
|
||||||
|
|
||||||
|
if (!options.Converters.OfType<JsonStringEnumConverter>().Any())
|
||||||
|
options.Converters.Add(new JsonStringEnumConverter());
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IServiceCollection AddAsymCryptService<TAsymCryptParams>(this IServiceCollection services)
|
||||||
|
where TAsymCryptParams : AsymCryptParams
|
||||||
|
{
|
||||||
|
services.TryAddScoped<IAsymCryptService<TAsymCryptParams>, AsymCryptService<TAsymCryptParams>>();
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddAsymCryptService<TAsymCryptParams>(this IServiceCollection services, IConfigurationSection section)
|
||||||
|
where TAsymCryptParams : AsymCryptParams
|
||||||
|
=> services.Configure<TAsymCryptParams>(section).AddAsymCryptService<TAsymCryptParams>();
|
||||||
|
|
||||||
|
public static IServiceCollection AddAsymCryptService<TAsymCryptParams>(this IServiceCollection services, TAsymCryptParams param)
|
||||||
|
where TAsymCryptParams : AsymCryptParams
|
||||||
|
=> services.AddSingleton(Options.Create(param)).AddAsymCryptService<TAsymCryptParams>();
|
||||||
|
|
||||||
|
private static IServiceCollection AddRSAFactory<TRSAFactoryParams>(this IServiceCollection services)
|
||||||
|
where TRSAFactoryParams : RSAFactoryParams
|
||||||
|
{
|
||||||
|
services.TryAddScoped<IRSAFactory<TRSAFactoryParams>, RSAFactory<TRSAFactoryParams>>();
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddRSAFactory<TRSAFactoryParams>(this IServiceCollection services, IConfigurationSection section)
|
||||||
|
where TRSAFactoryParams : RSAFactoryParams
|
||||||
|
=> services.Configure<TRSAFactoryParams>(section).AddRSAFactory<TRSAFactoryParams>();
|
||||||
|
|
||||||
|
public static IServiceCollection AddRSAFactory<TRSAFactoryParams>(this IServiceCollection services, TRSAFactoryParams param)
|
||||||
|
where TRSAFactoryParams : RSAFactoryParams
|
||||||
|
=> services.AddSingleton(Options.Create(param)).AddRSAFactory<TRSAFactoryParams>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,10 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\DigitalData.Core.Abstractions\DigitalData.Core.Abstractions.csproj" />
|
<ProjectReference Include="..\DigitalData.Core.Abstractions\DigitalData.Core.Abstractions.csproj" />
|
||||||
<ProjectReference Include="..\DigitalData.Core.Security.Extensions\DigitalData.Core.Security.Extensions.csproj" />
|
<ProjectReference Include="..\DigitalData.Core.Security.Extensions\DigitalData.Core.Security.Extensions.csproj" />
|
||||||
|
|||||||
13
DigitalData.Core.Security/HashAlgorithmNameConverter.cs
Normal file
13
DigitalData.Core.Security/HashAlgorithmNameConverter.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace DigitalData.Core.Security
|
||||||
|
{
|
||||||
|
public class HashAlgorithmNameConverter : JsonConverter<HashAlgorithmName>
|
||||||
|
{
|
||||||
|
public override HashAlgorithmName Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => new(reader.GetString() ?? string.Empty);
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, HashAlgorithmName value, JsonSerializerOptions options) => writer.WriteStringValue(value.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using DigitalData.Core.Abstractions.Security;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
namespace DigitalData.Core.Security
|
|
||||||
{
|
|
||||||
public class RSACryptographer : IRSACryptographer
|
|
||||||
{
|
|
||||||
public required virtual string Pem { get; init; }
|
|
||||||
|
|
||||||
public RSAEncryptionPadding Padding { get; init; } = RSAEncryptionPadding.OaepSHA256;
|
|
||||||
|
|
||||||
protected virtual RSA RSA { get; } = RSA.Create();
|
|
||||||
|
|
||||||
internal RSACryptographer() { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
using DigitalData.Core.Abstractions.Security;
|
|
||||||
using DigitalData.Core.Security.Extensions;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
namespace DigitalData.Core.Security
|
|
||||||
{
|
|
||||||
public class RSADecryptor : RSACryptographer, IRSADecryptor, IRSACryptographer
|
|
||||||
{
|
|
||||||
public (string Value, Version Version)? VersionedPassword
|
|
||||||
{
|
|
||||||
init
|
|
||||||
{
|
|
||||||
_password = value?.Value;
|
|
||||||
PasswordVersion = value?.Version;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
public IRSAEncryptor Encryptor => _lazyEncryptor.Value;
|
|
||||||
|
|
||||||
private readonly Lazy<RSA> lazyRSA;
|
|
||||||
|
|
||||||
protected override RSA RSA => lazyRSA.Value;
|
|
||||||
|
|
||||||
public RSADecryptor()
|
|
||||||
{
|
|
||||||
_lazyEncryptor = new(() => new RSAEncryptor()
|
|
||||||
{
|
|
||||||
Pem = RSA.ExportRSAPublicKeyPem(),
|
|
||||||
Padding = Padding
|
|
||||||
});
|
|
||||||
|
|
||||||
lazyRSA = new(() =>
|
|
||||||
{
|
|
||||||
var rsa = RSA.Create();
|
|
||||||
if (_password is null)
|
|
||||||
RSA.ImportFromPem(Pem);
|
|
||||||
else
|
|
||||||
RSA.ImportFromEncryptedPem(Pem, _password.AsSpan());
|
|
||||||
|
|
||||||
return rsa;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] Decrypt(byte[] data) => RSA.Decrypt(data, Padding);
|
|
||||||
|
|
||||||
public string Decrypt(string data) => RSA.Decrypt(data.Base64ToByte(), Padding).BytesToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using DigitalData.Core.Abstractions.Security;
|
|
||||||
using DigitalData.Core.Security.Extensions;
|
|
||||||
|
|
||||||
namespace DigitalData.Core.Security
|
|
||||||
{
|
|
||||||
public class RSAEncryptor : RSACryptographer, IRSAEncryptor, IRSACryptographer
|
|
||||||
{
|
|
||||||
public override required string Pem
|
|
||||||
{
|
|
||||||
get => base.Pem;
|
|
||||||
init
|
|
||||||
{
|
|
||||||
RSA.ImportFromPem(base.Pem);
|
|
||||||
base.Pem = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] Encrypt(byte[] data) => RSA.Encrypt(data, Padding);
|
|
||||||
|
|
||||||
public string Encrypt(string data) => RSA.Encrypt(data.Base64ToByte(), Padding).BytesToString();
|
|
||||||
|
|
||||||
public bool Verify(string data, string signature) => Encrypt(data) == signature;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
using DigitalData.Core.Abstractions.Security;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace DigitalData.Core.Security
|
|
||||||
{
|
|
||||||
public class RSAFactory
|
|
||||||
{
|
|
||||||
private static readonly Lazy<RSAFactory> LazyInstance = new(() => new());
|
|
||||||
|
|
||||||
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 string PbePassword { private get; init; } = Secrets.PBE_PASSWORD;
|
|
||||||
|
|
||||||
public PbeEncryptionAlgorithm PbeEncryptionAlgorithm { get; init; } = PbeEncryptionAlgorithm.Aes256Cbc;
|
|
||||||
|
|
||||||
public HashAlgorithmName PbeHashAlgorithmName { get; init; } = HashAlgorithmName.SHA256;
|
|
||||||
|
|
||||||
public int PbeIterationCount { get; init; } = 100_000;
|
|
||||||
|
|
||||||
private readonly Lazy<PbeParameters> _lazyPbeParameters;
|
|
||||||
|
|
||||||
public PbeParameters PbeParameters => _lazyPbeParameters.Value;
|
|
||||||
|
|
||||||
public string EncryptedPrivateKeyPemLabel { get; init; } = "ENCRYPTED PRIVATE KEY";
|
|
||||||
|
|
||||||
internal RSAFactory()
|
|
||||||
{
|
|
||||||
_lazyPbeParameters = new(() => new PbeParameters(PbeEncryptionAlgorithm, PbeHashAlgorithmName, PbeIterationCount));
|
|
||||||
}
|
|
||||||
|
|
||||||
public string CreateRSAPrivateKeyPem(int? keySizeInBits = null)
|
|
||||||
=> RSA.Create(keySizeInBits ?? KeySizeInBits).ExportRSAPrivateKeyPem();
|
|
||||||
|
|
||||||
public string CreateEncryptedPrivateKeyPem(
|
|
||||||
int? keySizeInBits = null,
|
|
||||||
string? password = null,
|
|
||||||
PbeEncryptionAlgorithm? pbeEncryptionAlgorithm = null,
|
|
||||||
HashAlgorithmName? hashAlgorithmName = null,
|
|
||||||
int? iterationCount = null)
|
|
||||||
{
|
|
||||||
password ??= PbePassword;
|
|
||||||
|
|
||||||
var pbeParameters = (pbeEncryptionAlgorithm is null && hashAlgorithmName is null && iterationCount is null)
|
|
||||||
? new PbeParameters(
|
|
||||||
pbeEncryptionAlgorithm ?? PbeEncryptionAlgorithm,
|
|
||||||
hashAlgorithmName ?? PbeHashAlgorithmName,
|
|
||||||
iterationCount ?? PbeIterationCount)
|
|
||||||
: PbeParameters;
|
|
||||||
|
|
||||||
var encryptedPrivateKey = RSA.Create(keySizeInBits ?? KeySizeInBits).ExportEncryptedPkcs8PrivateKey(password.AsSpan(), pbeParameters);
|
|
||||||
|
|
||||||
var pemChars = PemEncoding.Write(EncryptedPrivateKeyPemLabel, encryptedPrivateKey);
|
|
||||||
|
|
||||||
return new string(pemChars);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IRSADecryptor> ReadRSADecryptorAsync(string path, Version? version = null, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
var pem = await File.ReadAllTextAsync(path, cancellationToken);
|
|
||||||
|
|
||||||
(string Value, Version Version)? versionedPassword = null;
|
|
||||||
|
|
||||||
if(version is not null)
|
|
||||||
{
|
|
||||||
if (version != Secrets.Version)
|
|
||||||
throw new InvalidOperationException($"The provided version {version} does not match the expected version {Secrets.Version}.");
|
|
||||||
|
|
||||||
versionedPassword = (Secrets.PBE_PASSWORD, Secrets.Version);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new RSADecryptor()
|
|
||||||
{
|
|
||||||
Pem = pem,
|
|
||||||
VersionedPassword = versionedPassword
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user