using System.Security.Cryptography; using System.Text; namespace DigitalData.Core.Security { public class RSAFactory { private static readonly Lazy 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 KeyFileTags = new string[] { DefaultEncryptedPrivateKeyFileTag, DefaultPrivateKeyFileTag, DefaultPublicKeyFileTag }; public static readonly string PEMFileExtension = ".pem"; private static readonly Lazy> 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); var rsaKey = sb.Append(PEMFileExtension).ToString(); return rsaKey; } 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)); } 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); } } }