Compare commits
61 Commits
feat/api
...
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 |
@@ -19,7 +19,7 @@ namespace DigitalData.Core.API
|
|||||||
/// <param name="index">The key in the ViewData dictionary where the value will be stored.</param>
|
/// <param name="index">The key in the ViewData dictionary where the value will be stored.</param>
|
||||||
/// <param name="value">The value to be stored in the ViewData dictionary.</param>
|
/// <param name="value">The value to be stored in the ViewData dictionary.</param>
|
||||||
/// <returns>The same ViewResult object with updated ViewData, allowing for additional chained operations.</returns>
|
/// <returns>The same ViewResult object with updated ViewData, allowing for additional chained operations.</returns>
|
||||||
public static ViewResult WithData(this ViewResult viewResult, string index, object? value)
|
public static ViewResult WithData(this ViewResult viewResult, string index, object value)
|
||||||
{
|
{
|
||||||
viewResult.ViewData[index] = value;
|
viewResult.ViewData[index] = value;
|
||||||
return viewResult;
|
return viewResult;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<Description>This package provides a comprehensive set of API controllers and related utilities for the DigitalData.Core library. It includes generic CRUD controllers, localization extensions, middleware for security policies, and application model conventions.</Description>
|
<Description>This package provides a comprehensive set of API controllers and related utilities for the DigitalData.Core library. It includes generic CRUD controllers, localization extensions, middleware for security policies, and application model conventions.</Description>
|
||||||
<PackageId>DigitalData.Core.API</PackageId>
|
<PackageId>DigitalData.Core.API</PackageId>
|
||||||
<Version>2.0.1</Version>
|
<Version>2.0.0.0</Version>
|
||||||
<Authors>Digital Data GmbH</Authors>
|
<Authors>Digital Data GmbH</Authors>
|
||||||
<Company>Digital Data GmbH</Company>
|
<Company>Digital Data GmbH</Company>
|
||||||
<Product>DigitalData.Core.API</Product>
|
<Product>DigitalData.Core.API</Product>
|
||||||
@@ -16,8 +16,6 @@
|
|||||||
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
|
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
|
||||||
<PackageTags>digital data core api</PackageTags>
|
<PackageTags>digital data core api</PackageTags>
|
||||||
<PackageIcon>core_icon.png</PackageIcon>
|
<PackageIcon>core_icon.png</PackageIcon>
|
||||||
<AssemblyVersion>2.0.1</AssemblyVersion>
|
|
||||||
<FileVersion>2.0.1</FileVersion>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,12 +13,6 @@ namespace DigitalData.Core.Security.Extensions
|
|||||||
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