diff --git a/DigitalData.Core.Abstractions/Security/IRSAFactory.cs b/DigitalData.Core.Abstractions/Security/IRSAFactory.cs index 6293b66..0dfea9d 100644 --- a/DigitalData.Core.Abstractions/Security/IRSAFactory.cs +++ b/DigitalData.Core.Abstractions/Security/IRSAFactory.cs @@ -2,22 +2,8 @@ namespace DigitalData.Core.Abstractions.Security { - public interface IRSAFactory + public interface IRSAFactory { - 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; } - string CreateRSAPrivateKeyPem(int? keySizeInBits = null); string CreateEncryptedPrivateKeyPem( diff --git a/DigitalData.Core.Security/AsymCryptService.cs b/DigitalData.Core.Security/AsymCryptService.cs index 2b867a4..32d65fb 100644 --- a/DigitalData.Core.Security/AsymCryptService.cs +++ b/DigitalData.Core.Security/AsymCryptService.cs @@ -1,9 +1,10 @@ using DigitalData.Core.Abstractions.Security; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace DigitalData.Core.Security { - public class AsymCryptService : RSAFactory, IAsymCryptService, IRSAFactory + public class AsymCryptService : RSAFactory, IAsymCryptService, IRSAFactory where TRSAFactoryParams : RSAFactoryParams { private readonly IDictionary _decryptors; @@ -11,7 +12,7 @@ namespace DigitalData.Core.Security public Func RSAKeyNameFormatter { get; } - public AsymCryptService(IDictionary decryptors, Func rsaKeyNameFormatter, ILogger? logger = null) : base() + public AsymCryptService(IOptions options, IDictionary decryptors, Func rsaKeyNameFormatter, ILogger>? logger = null) : base(options) { _decryptors = decryptors ?? new Dictionary(); diff --git a/DigitalData.Core.Security/RSAFactory.cs b/DigitalData.Core.Security/RSAFactory.cs index 846f3a5..e5c0250 100644 --- a/DigitalData.Core.Security/RSAFactory.cs +++ b/DigitalData.Core.Security/RSAFactory.cs @@ -1,35 +1,53 @@ using DigitalData.Core.Abstractions.Security; +using Microsoft.Extensions.Options; using System.Security.Cryptography; using System.Text; namespace DigitalData.Core.Security { - public class RSAFactory : IRSAFactory + public class RSAFactory : IRSAFactory where TRSAFactoryParams : RSAFactoryParams { - private static readonly Lazy LazyInstance = new(() => new()); + private static readonly Lazy> LazyInstance = new(() => new(Options.Create(new()))); - public static RSAFactory Static => LazyInstance.Value; + public static RSAFactory Static => LazyInstance.Value; - public static readonly string DefaultEncryptedPrivateKeyFileTag = "enc-private"; + private readonly RSAFactoryParams _params; - public static readonly string DefaultPrivateKeyFileTag = "private"; - - public static readonly string DefaultPublicKeyFileTag = "public"; - - public static readonly IEnumerable KeyFileTags = new string[] { DefaultEncryptedPrivateKeyFileTag, DefaultPrivateKeyFileTag, DefaultPublicKeyFileTag }; - - private static readonly Lazy> LazyLowerFileTags = new(() => KeyFileTags.Select(tag => tag.ToLower())); - - public static readonly string DefaultRSAKeyNameSeparator = "-_-"; + private readonly IEnumerable _lowerFileTags; //TODO: make the validation using regex - public static string DefaultRSAKeyNameFormatter(string issuer, string audience, bool isPrivate = true, Version? passwordVersion = null, string? separator = null) + public static string DefaultRSAKeyNameFormatter(string separator, string issuer, string audience, string encryptedPrivateKeyFileTag, string privateKeyFileTag, string publicKeyFileTag, bool isPrivate = true, Version? passwordVersion = null) { - separator ??= DefaultRSAKeyNameSeparator; + 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(privateKeyFileTag); + else if (isPrivate) + sb.Append(encryptedPrivateKeyFileTag).Append(separator).Append(passwordVersion); + else if (passwordVersion is null) + sb.Append(publicKeyFileTag); + else + sb.Append(publicKeyFileTag).Append(separator).Append(passwordVersion); + + return sb.ToString(); + } + + private readonly PbeParameters _pbeParameters; + + public RSAFactory(IOptions options) + { + _params = options.Value; + var keyFileTags = new string[] { _params.EncryptedPrivateKeyFileTag, _params.PrivateKeyFileTag, _params.PublicKeyFileTag }; + _lowerFileTags = keyFileTags.Select(tag => tag.ToLower()); + _pbeParameters = new PbeParameters(_params.PbeEncryptionAlgorithm, _params.PbeHashAlgorithmName, _params.PbeIterationCount); + } + + public void ValidateFormatterParams(string issuer, string audience) + { void ValidateForbidden(string value, string paramName) { - if (Path.GetInvalidFileNameChars().Any(value.Contains) || LazyLowerFileTags.Value.Any(tag => value.ToLower().Contains(tag))) + if (Path.GetInvalidFileNameChars().Any(value.Contains) || _lowerFileTags.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); } @@ -41,49 +59,14 @@ namespace DigitalData.Core.Security ValidateForbidden(issuer, nameof(issuer)); ValidateForbidden(audience, nameof(audience)); - ValidateForbidden(separator, nameof(separator)); + ValidateForbidden(_params.RSAKeyNameSeparator, nameof(_params.RSAKeyNameSeparator)); - 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 _lazyPbeParameters; - - public PbeParameters PbeParameters => _lazyPbeParameters.Value; - - public string EncryptedPrivateKeyPemLabel { get; init; } = "ENCRYPTED PRIVATE KEY"; - - internal RSAFactory() - { - _lazyPbeParameters = new(() => new PbeParameters(PbeEncryptionAlgorithm, PbeHashAlgorithmName, PbeIterationCount)); + ValidateSeparator(issuer, nameof(issuer), _params.RSAKeyNameSeparator); + ValidateSeparator(audience, nameof(audience), _params.RSAKeyNameSeparator); } public string CreateRSAPrivateKeyPem(int? keySizeInBits = null) - => RSA.Create(keySizeInBits ?? KeySizeInBits).ExportRSAPrivateKeyPem(); + => RSA.Create(keySizeInBits ?? _params.KeySizeInBits).ExportRSAPrivateKeyPem(); public string CreateEncryptedPrivateKeyPem( int? keySizeInBits = null, @@ -92,18 +75,18 @@ namespace DigitalData.Core.Security HashAlgorithmName? hashAlgorithmName = null, int? iterationCount = null) { - password ??= PbePassword; + password ??= _params.PbePassword; var pbeParameters = (pbeEncryptionAlgorithm is null && hashAlgorithmName is null && iterationCount is null) ? new PbeParameters( - pbeEncryptionAlgorithm ?? PbeEncryptionAlgorithm, - hashAlgorithmName ?? PbeHashAlgorithmName, - iterationCount ?? PbeIterationCount) - : PbeParameters; + pbeEncryptionAlgorithm ?? _params.PbeEncryptionAlgorithm, + hashAlgorithmName ?? _params.PbeHashAlgorithmName, + iterationCount ?? _params.PbeIterationCount) + : _pbeParameters; - var encryptedPrivateKey = RSA.Create(keySizeInBits ?? KeySizeInBits).ExportEncryptedPkcs8PrivateKey(password.AsSpan(), pbeParameters); + var encryptedPrivateKey = RSA.Create(keySizeInBits ?? _params.KeySizeInBits).ExportEncryptedPkcs8PrivateKey(password.AsSpan(), pbeParameters); - var pemChars = PemEncoding.Write(EncryptedPrivateKeyPemLabel, encryptedPrivateKey); + var pemChars = PemEncoding.Write(_params.EncryptedPrivateKeyPemLabel, encryptedPrivateKey); return new string(pemChars); } diff --git a/DigitalData.Core.Security/RSAFactoryParams.cs b/DigitalData.Core.Security/RSAFactoryParams.cs new file mode 100644 index 0000000..6673a90 --- /dev/null +++ b/DigitalData.Core.Security/RSAFactoryParams.cs @@ -0,0 +1,27 @@ +using System.Security.Cryptography; + +namespace DigitalData.Core.Security +{ + public class RSAFactoryParams + { + public string EncryptedPrivateKeyFileTag { get; init; } = "enc-private"; + + public string PrivateKeyFileTag { get; init; } = "private"; + + public string PublicKeyFileTag { get; init; } = "public"; + + public string RSAKeyNameSeparator { get; init; } = "-_-"; + + 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"; + } +} \ No newline at end of file