From ebf60a2ec3d03828521c82ffdd36bb4faa2e36ee Mon Sep 17 00:00:00 2001 From: BlankDev117 Date: Sun, 27 Aug 2023 01:11:47 -0400 Subject: [PATCH] Implement Non-windows RSA Algorithm (CSC-2) (#3) * Common base SecurityKey class * Dependency Injection Adjustments * Switch to BouncyCastle RSA Implementation * Switch build agent to non-windows * Update project file --- .github/workflows/cd-workflow.yml | 2 +- .../IntegrationTests.cs | 6 +- .../Keys/Aes/Internal/AesKeyGeneratorTests.cs | 14 ++- .../Keys/Aes/Internal/AesSecurityKeyTests.cs | 14 +-- .../Keys/Rsa/Internal/RsaKeyGeneratorTests.cs | 12 +- .../Keys/Rsa/Internal/RsaSecurityKeyTests.cs | 17 +-- .../Keys/SecurityKeyBaseTests.cs | 14 ++- .../Common.Security.Cryptography.csproj | 5 +- .../Aes/Internal/Services/AesKeyGenerator.cs | 26 +++- .../Aes/Internal/Services/AesSecurityKey.cs | 89 +++---------- .../Keys/Aes/Models/AesKeyInformation.cs | 8 +- .../Keys/Aes/ServiceCollectionExtensions.cs | 15 +++ .../Rsa/AsymmetricKeyParameterExtensions.cs | 20 +++ .../Rsa/Internal/Services/RsaKeyGenerator.cs | 71 ++++++----- .../Rsa/Internal/Services/RsaSecurityKey.cs | 119 +++++++----------- .../Rsa/Models/RsaKeyGenerationParameters.cs | 2 +- .../Keys/Rsa/Models/RsaKeyInformation.cs | 18 +-- .../Keys/Rsa/Ports/IPrimeNumberGenerator.cs | 11 ++ .../Keys/Rsa/ServiceCollectionExtensions.cs | 15 +++ .../Keys/SecurityKey.cs | 79 ++++++++++++ .../Keys/SecurityKeyGenerator.cs | 1 - .../Keys/ServiceCollectionExtensions.cs | 8 +- .../SecurityKeyHelper.cs | 2 +- 23 files changed, 325 insertions(+), 243 deletions(-) create mode 100644 src/Common.Security.Cryptography/Keys/Aes/ServiceCollectionExtensions.cs create mode 100644 src/Common.Security.Cryptography/Keys/Rsa/AsymmetricKeyParameterExtensions.cs create mode 100644 src/Common.Security.Cryptography/Keys/Rsa/Ports/IPrimeNumberGenerator.cs create mode 100644 src/Common.Security.Cryptography/Keys/Rsa/ServiceCollectionExtensions.cs create mode 100644 src/Common.Security.Cryptography/Keys/SecurityKey.cs diff --git a/.github/workflows/cd-workflow.yml b/.github/workflows/cd-workflow.yml index e214be0..f41746e 100644 --- a/.github/workflows/cd-workflow.yml +++ b/.github/workflows/cd-workflow.yml @@ -13,7 +13,7 @@ concurrency: jobs: build: - runs-on: windows-latest + runs-on: ubuntu-latest strategy: matrix: dotnet-version: [ '6.0.x' ] diff --git a/src/Common.Security.Cryptography.UnitTests/IntegrationTests.cs b/src/Common.Security.Cryptography.UnitTests/IntegrationTests.cs index a0897f9..a753987 100644 --- a/src/Common.Security.Cryptography.UnitTests/IntegrationTests.cs +++ b/src/Common.Security.Cryptography.UnitTests/IntegrationTests.cs @@ -14,15 +14,15 @@ public class IntegrationTests #region End to End [Fact] - public async Task EndToEnd_Testing() + public async Task EndToEnd_SecurityKeyExchange_Testing() { var serviceCollection = new ServiceCollection(); serviceCollection.AddCryptography(); var serviceProvider = serviceCollection.BuildServiceProvider(); var cryptographyService = serviceProvider.GetRequiredService(); - var personalKey = cryptographyService.CreateKey(1024, new RsaKeyGenerationParameters()); - var otherKey = cryptographyService.CreateKey(1024, new RsaKeyGenerationParameters()); + var personalKey = cryptographyService.CreateKey(512, new RsaKeyGenerationParameters()); + var otherKey = cryptographyService.CreateKey(512, new RsaKeyGenerationParameters()); var personalExchangeKeyInformation = personalKey.KeyInformation.GetKeyExhangeInformation() as RsaKeyExchangeInformation; var otherExchangeKeyInformation = otherKey.KeyInformation.GetKeyExhangeInformation() as RsaKeyExchangeInformation; diff --git a/src/Common.Security.Cryptography.UnitTests/Keys/Aes/Internal/AesKeyGeneratorTests.cs b/src/Common.Security.Cryptography.UnitTests/Keys/Aes/Internal/AesKeyGeneratorTests.cs index 8d29aa9..74d1232 100644 --- a/src/Common.Security.Cryptography.UnitTests/Keys/Aes/Internal/AesKeyGeneratorTests.cs +++ b/src/Common.Security.Cryptography.UnitTests/Keys/Aes/Internal/AesKeyGeneratorTests.cs @@ -1,11 +1,10 @@ using Common.Security.Cryptography.Exceptions; using Common.Security.Cryptography.Keys.Aes.Internal.Services; using Common.Security.Cryptography.Keys.Aes.Models; +using Common.Security.Cryptography.Ports; using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Xunit; namespace Common.Security.Cryptography.UnitTests.Keys.Aes.Internal @@ -52,10 +51,15 @@ public void Generate_GenerationParameters_InvalidKeySize_ThrowsKeySizeException( public void Generate_GenerationParameters_GeneratesNewKey() { // Arrange/Act - using var key = _generator.GenerateKey(128, new AesKeyGenerationParameters()); - + var keys = new List(); + for (var i = 0; i < 100; i++) + { + using var key = _generator.GenerateKey(128, new AesKeyGenerationParameters()); + keys.Add(BitConverter.ToInt64(key.KeyInformation.RawKey)); + } + // Assert - Assert.NotNull(key); + Assert.Equal(keys.Count, keys.Distinct().Count()); } #endregion diff --git a/src/Common.Security.Cryptography.UnitTests/Keys/Aes/Internal/AesSecurityKeyTests.cs b/src/Common.Security.Cryptography.UnitTests/Keys/Aes/Internal/AesSecurityKeyTests.cs index d6380e4..d9e9f81 100644 --- a/src/Common.Security.Cryptography.UnitTests/Keys/Aes/Internal/AesSecurityKeyTests.cs +++ b/src/Common.Security.Cryptography.UnitTests/Keys/Aes/Internal/AesSecurityKeyTests.cs @@ -1,17 +1,9 @@ -using Common.Security.Cryptography.Exceptions; -using Common.Security.Cryptography.Keys.Aes.Internal.Services; -using Common.Security.Cryptography.Keys.Aes.Models; -using Common.Security.Cryptography.Keys.Rsa.Internal.Services; +using Common.Security.Cryptography.Keys.Aes.Internal.Services; using Common.Security.Cryptography.Ports; -using Common.Security.Cryptography.SecurityKeys.Aes.Internal.Services; -using Common.Security.Cryptography.SecurityKeys.Aes.Models; -using System; -using System.Security.Cryptography; -using Xunit; -namespace Common.Security.Cryptography.UnitTests.SecurityKeys.Aes.Internal +namespace Common.Security.Cryptography.UnitTests.Keys.Aes.Internal { - public class AesSecurityKeyTests: SecurityKeyBaseTests + public class AesSecurityKeyTests : SecurityKeyBaseTests { #region SecurityKeyBaseTests Overrides diff --git a/src/Common.Security.Cryptography.UnitTests/Keys/Rsa/Internal/RsaKeyGeneratorTests.cs b/src/Common.Security.Cryptography.UnitTests/Keys/Rsa/Internal/RsaKeyGeneratorTests.cs index 0beae83..16a7a0e 100644 --- a/src/Common.Security.Cryptography.UnitTests/Keys/Rsa/Internal/RsaKeyGeneratorTests.cs +++ b/src/Common.Security.Cryptography.UnitTests/Keys/Rsa/Internal/RsaKeyGeneratorTests.cs @@ -1,13 +1,10 @@ using Common.Security.Cryptography.Exceptions; -using Common.Security.Cryptography.Keys.Aes.Internal.Services; using Common.Security.Cryptography.Keys.Aes.Models; using Common.Security.Cryptography.Keys.Rsa.Internal.Services; using Common.Security.Cryptography.Keys.Rsa.Models; using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Xunit; namespace Common.Security.Cryptography.UnitTests.Keys.Rsa.Internal @@ -54,10 +51,15 @@ public void Generate_GenerationParameters_InvalidKeySize_ThrowsKeySizeException( public void Generate_GenerationParameters_GeneratesNewKey() { // Arrange/Act - using var key = _generator.GenerateKey(1024, new RsaKeyGenerationParameters()); + var keys = new List(); + for (var i = 0; i < 1; i++) + { + using var key = _generator.GenerateKey(128, new RsaKeyGenerationParameters()); + keys.Add(BitConverter.ToInt64(key.KeyInformation.RawKey)); + } // Assert - Assert.NotNull(key); + Assert.Equal(keys.Count, keys.Distinct().Count()); } #endregion diff --git a/src/Common.Security.Cryptography.UnitTests/Keys/Rsa/Internal/RsaSecurityKeyTests.cs b/src/Common.Security.Cryptography.UnitTests/Keys/Rsa/Internal/RsaSecurityKeyTests.cs index c8c2eef..c7ba704 100644 --- a/src/Common.Security.Cryptography.UnitTests/Keys/Rsa/Internal/RsaSecurityKeyTests.cs +++ b/src/Common.Security.Cryptography.UnitTests/Keys/Rsa/Internal/RsaSecurityKeyTests.cs @@ -1,23 +1,14 @@ -using Common.Security.Cryptography.Exceptions; -using Common.Security.Cryptography.Keys.Rsa.Internal.Services; -using Common.Security.Cryptography.Keys.Rsa.Models; +using Common.Security.Cryptography.Keys.Rsa.Internal.Services; using Common.Security.Cryptography.Ports; -using Common.Security.Cryptography.SecurityKeys.Aes.Internal.Services; -using Common.Security.Cryptography.SecurityKeys.Aes.Models; -using Common.Security.Cryptography.SecurityKeys.Rsa.Internal.Services; -using Common.Security.Cryptography.SecurityKeys.Rsa.Models; -using System; -using System.Security.Cryptography; -using Xunit; -namespace Common.Security.Cryptography.UnitTests.SecurityKeys.Rsa.Internal +namespace Common.Security.Cryptography.UnitTests.Keys.Rsa.Internal { - public class RsaSecurityKeyTests: SecurityKeyBaseTests + public class RsaSecurityKeyTests : SecurityKeyBaseTests { #region SecurityKeyBaseTests Overrides protected override ISecurityKey GetSecurityKey() - => RsaKeyGenerator.GenerateKey(1024); + => RsaKeyGenerator.GenerateKey(128); #endregion } diff --git a/src/Common.Security.Cryptography.UnitTests/Keys/SecurityKeyBaseTests.cs b/src/Common.Security.Cryptography.UnitTests/Keys/SecurityKeyBaseTests.cs index 577e7e5..3f97fc4 100644 --- a/src/Common.Security.Cryptography.UnitTests/Keys/SecurityKeyBaseTests.cs +++ b/src/Common.Security.Cryptography.UnitTests/Keys/SecurityKeyBaseTests.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Xunit; -namespace Common.Security.Cryptography.UnitTests.SecurityKeys +namespace Common.Security.Cryptography.UnitTests.Keys { public abstract class SecurityKeyBaseTests { @@ -121,17 +121,21 @@ public async Task ValidateSignatureAsync_NullSignature_ThrowsArgumentNullExcepti await Assert.ThrowsAsync(() => key.ValidateSignatureAsync(new byte[0], null, HashAlgorithmName.SHA256)); } - [Fact] - public async Task ValidateSignatureAsync_InvalidSignature_ReturnsFalse() + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task ValidateSignatureAsync_InvalidSignature_ReturnsFalse(bool wrongHash) { // Arrange var key = GetSecurityKey(); var data = Encoding.UTF8.GetBytes("A day in the life of a unit test."); - var signedData = await key.SignAsync(data, HashAlgorithmName.SHA512); + var signedData = await key.SignAsync(data, HashAlgorithmName.SHA256); // Act - var validationResult = await key.ValidateSignatureAsync(data, signedData, HashAlgorithmName.SHA256); + var validationResult = await key.ValidateSignatureAsync(data, + wrongHash ? signedData : new byte[0], + wrongHash ? HashAlgorithmName.SHA512 : HashAlgorithmName.SHA256); // Assert Assert.False(validationResult); diff --git a/src/Common.Security.Cryptography/Common.Security.Cryptography.csproj b/src/Common.Security.Cryptography/Common.Security.Cryptography.csproj index a68f79c..92a6644 100644 --- a/src/Common.Security.Cryptography/Common.Security.Cryptography.csproj +++ b/src/Common.Security.Cryptography/Common.Security.Cryptography.csproj @@ -4,9 +4,9 @@ netstandard2.1 https://github.com/BlankDev117/Common.Security.Cryptography.git git - Easy to use and flexible cryptographic library for .NET. + Easy to use, flexible, and machine agnostic cryptographic library for .NET. README.md - Security, Cryptography, Encryption, Decryption, Sign, Validate, Crypto, Symmetric, Asymmetric, Keys + Security, Cryptography, Encryption, Decryption, Sign, Validate, Crypto, Symmetric, Asymmetric, Keys, AES, RSA BlankDev117 Common.Security.Cryptography @@ -17,6 +17,7 @@ + diff --git a/src/Common.Security.Cryptography/Keys/Aes/Internal/Services/AesKeyGenerator.cs b/src/Common.Security.Cryptography/Keys/Aes/Internal/Services/AesKeyGenerator.cs index ede2109..62cf2a8 100644 --- a/src/Common.Security.Cryptography/Keys/Aes/Internal/Services/AesKeyGenerator.cs +++ b/src/Common.Security.Cryptography/Keys/Aes/Internal/Services/AesKeyGenerator.cs @@ -1,13 +1,22 @@ using Common.Security.Cryptography.Keys.Aes.Models; using Common.Security.Cryptography.Ports; -using Common.Security.Cryptography.SecurityKeys.Aes.Internal.Services; -using Common.Security.Cryptography.SecurityKeys.Aes.Models; using System; namespace Common.Security.Cryptography.Keys.Aes.Internal.Services { internal class AesKeyGenerator : SecurityKeyGenerator { + #region Static + + internal static ISecurityKey GenerateKey(int keySize) + { + return new AesKeyGenerator().GenerateKey(keySize, new AesKeyGenerationParameters()); + } + + #endregion + + #region SecurityKeyGenerator Overrides + protected override ISecurityKey GenerateKey(int keySize, AesKeyGenerationParameters keyGenerationParameters) { if (keyGenerationParameters == null) @@ -26,6 +35,14 @@ protected override ISecurityKey GenerateKey(int keySize, AesKeyGenerationParamet protected override ISecurityKey GenerateKey(byte[] key, AesKeyExchangeInformation keyExchangeInformation) { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (keyExchangeInformation == null) + { + throw new ArgumentNullException(nameof(keyExchangeInformation)); + } if (keyExchangeInformation.IV == null) { throw new ArgumentNullException(nameof(keyExchangeInformation.IV)); @@ -40,9 +57,6 @@ protected override ISecurityKey GenerateKey(AesKeyInformation keyInformation) return new AesSecurityKey(keyInformation); } - internal static ISecurityKey GenerateKey(int keySize) - { - return new AesKeyGenerator().GenerateKey(keySize, new AesKeyGenerationParameters()); - } + #endregion } } diff --git a/src/Common.Security.Cryptography/Keys/Aes/Internal/Services/AesSecurityKey.cs b/src/Common.Security.Cryptography/Keys/Aes/Internal/Services/AesSecurityKey.cs index 9485cda..3e0444b 100644 --- a/src/Common.Security.Cryptography/Keys/Aes/Internal/Services/AesSecurityKey.cs +++ b/src/Common.Security.Cryptography/Keys/Aes/Internal/Services/AesSecurityKey.cs @@ -1,37 +1,26 @@ -using Common.Security.Cryptography.Model; -using Common.Security.Cryptography.Ports; -using Common.Security.Cryptography.SecurityKeys.Aes.Models; +using Common.Security.Cryptography.Keys.Aes.Models; using System; using System.IO; -using System.Linq; using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; -namespace Common.Security.Cryptography.SecurityKeys.Aes.Internal.Services +namespace Common.Security.Cryptography.Keys.Aes.Internal.Services { - internal class AesSecurityKey : ISecurityKey + internal class AesSecurityKey : SecurityKey { - #region Variables - - private AesKeyInformation _securityKeyInformation; - - #endregion - #region Constructors public AesSecurityKey(AesKeyInformation aesKeyInformation) + : base(aesKeyInformation) { - _securityKeyInformation = aesKeyInformation ?? throw new ArgumentNullException(nameof(aesKeyInformation)); } #endregion #region ISecurityKey - public SecurityKeyInformation KeyInformation => _securityKeyInformation; - - public async Task EncryptAsync(byte[] data, CancellationToken cancellationToken = default) + public override async Task EncryptAsync(byte[] data, CancellationToken cancellationToken = default) { if (data == null) { @@ -39,11 +28,11 @@ public async Task EncryptAsync(byte[] data, CancellationToken cancellati } using var aes = new AesManaged(); - aes.BlockSize = _securityKeyInformation.BlockSize; - aes.Padding = _securityKeyInformation.PaddingMode; - aes.Key = _securityKeyInformation.Key; - aes.IV = _securityKeyInformation.IV; - aes.Mode = _securityKeyInformation.CipherMode; + aes.BlockSize = SecurityKeyInformation.BlockSize; + aes.Padding = SecurityKeyInformation.PaddingMode; + aes.Key = SecurityKeyInformation.Key; + aes.IV = SecurityKeyInformation.IV; + aes.Mode = SecurityKeyInformation.CipherMode; using var dataStream = new MemoryStream(data); var encryptedDataStream = new MemoryStream(); @@ -53,7 +42,7 @@ public async Task EncryptAsync(byte[] data, CancellationToken cancellati return encryptedDataStream.ToArray(); } - public async Task DecryptAsync(byte[] data, CancellationToken cancellationToken = default) + public override async Task DecryptAsync(byte[] data, CancellationToken cancellationToken = default) { if (data == null) { @@ -61,11 +50,11 @@ public async Task DecryptAsync(byte[] data, CancellationToken cancellati } using var aes = new AesManaged(); - aes.BlockSize = _securityKeyInformation.BlockSize; - aes.Padding = _securityKeyInformation.PaddingMode; - aes.Key = _securityKeyInformation.Key; - aes.IV = _securityKeyInformation.IV; - aes.Mode = _securityKeyInformation.CipherMode; + aes.BlockSize = SecurityKeyInformation.BlockSize; + aes.Padding = SecurityKeyInformation.PaddingMode; + aes.Key = SecurityKeyInformation.Key; + aes.IV = SecurityKeyInformation.IV; + aes.Mode = SecurityKeyInformation.CipherMode; using var encryptedDataStream = new MemoryStream(data); using var cryptoStream = new CryptoStream(encryptedDataStream, aes.CreateDecryptor(), CryptoStreamMode.Read); @@ -74,53 +63,15 @@ public async Task DecryptAsync(byte[] data, CancellationToken cancellati return decryptedStream.ToArray(); } - public Task SignAsync(byte[] data, HashAlgorithmName hashAlgorithmName, CancellationToken cancellationToken = default) - { - if (data == null) - { - throw new ArgumentNullException(nameof(data)); - } - - var hashAlgorithm = HashAlgorithm.Create(hashAlgorithmName.Name); - if (hashAlgorithm == null) - { - throw new InvalidOperationException($"The hash algorithm {hashAlgorithmName.Name} is not supported."); - } - - var dataHash = hashAlgorithm.ComputeHash(data); - return EncryptAsync(dataHash, cancellationToken); - } - - public async Task ValidateSignatureAsync(byte[] data, byte[] signedData, HashAlgorithmName hashAlgorithmName, CancellationToken cancellationToken = default) - { - if (data == null) - { - throw new ArgumentNullException(nameof(data)); - } - if (signedData == null) - { - throw new ArgumentNullException(nameof(signedData)); - } - - var hashAlgorithm = HashAlgorithm.Create(hashAlgorithmName.Name); - if (hashAlgorithm == null) - { - throw new InvalidOperationException($"The hash algorithm {hashAlgorithmName.Name} is not supported."); - } - - var decryptedDataHash = await DecryptAsync(signedData, cancellationToken); - return decryptedDataHash.SequenceEqual(hashAlgorithm.ComputeHash(data)); - } - - public void Dispose() + public override void Dispose() { - if (_securityKeyInformation == null) + if (SecurityKeyInformation == null) { throw new ObjectDisposedException(nameof(AesSecurityKey)); } - Array.Clear(_securityKeyInformation.Key, 0, _securityKeyInformation.Key.Length); - _securityKeyInformation = null; + Array.Clear(SecurityKeyInformation.Key, 0, SecurityKeyInformation.Key.Length); + SecurityKeyInformation = null; } #endregion diff --git a/src/Common.Security.Cryptography/Keys/Aes/Models/AesKeyInformation.cs b/src/Common.Security.Cryptography/Keys/Aes/Models/AesKeyInformation.cs index b300975..152cd0f 100644 --- a/src/Common.Security.Cryptography/Keys/Aes/Models/AesKeyInformation.cs +++ b/src/Common.Security.Cryptography/Keys/Aes/Models/AesKeyInformation.cs @@ -1,10 +1,8 @@ -using Common.Security.Cryptography.Keys.Aes.Models; -using Common.Security.Cryptography.Model; +using Common.Security.Cryptography.Model; using System; using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; -namespace Common.Security.Cryptography.SecurityKeys.Aes.Models +namespace Common.Security.Cryptography.Keys.Aes.Models { public class AesKeyInformation : SecurityKeyInformation { @@ -24,7 +22,7 @@ public class AesKeyInformation : SecurityKeyInformation #region Constructors - public AesKeyInformation(byte[] key, byte[] iv, int blockSize, PaddingMode paddingMode, + public AesKeyInformation(byte[] key, byte[] iv, int blockSize, PaddingMode paddingMode, CipherMode cipherMode) : base(SecurityKeyUsageType.Personal) { diff --git a/src/Common.Security.Cryptography/Keys/Aes/ServiceCollectionExtensions.cs b/src/Common.Security.Cryptography/Keys/Aes/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..c50a61d --- /dev/null +++ b/src/Common.Security.Cryptography/Keys/Aes/ServiceCollectionExtensions.cs @@ -0,0 +1,15 @@ +using Common.Security.Cryptography.Keys.Aes.Internal.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace Common.Security.Cryptography.Keys.Aes +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection AddAes(this IServiceCollection services) + { + services.AddSecurityKeyDescriptor(); + + return services; + } + } +} diff --git a/src/Common.Security.Cryptography/Keys/Rsa/AsymmetricKeyParameterExtensions.cs b/src/Common.Security.Cryptography/Keys/Rsa/AsymmetricKeyParameterExtensions.cs new file mode 100644 index 0000000..a9f8084 --- /dev/null +++ b/src/Common.Security.Cryptography/Keys/Rsa/AsymmetricKeyParameterExtensions.cs @@ -0,0 +1,20 @@ +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.X509; +using System; + +namespace Common.Security.Cryptography.Keys.Rsa +{ + public static class AsymmetricKeyParameterExtensions + { + public static byte[] ToArray(this AsymmetricKeyParameter parameter) => + parameter switch + { + RsaPrivateCrtKeyParameters rsaPrivateKey => PrivateKeyInfoFactory.CreatePrivateKeyInfo(rsaPrivateKey).ToAsn1Object().GetEncoded(), + RsaKeyParameters rsaPublicKey => SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(rsaPublicKey).ToAsn1Object().GetEncoded(), + null => throw new ArgumentNullException(nameof(parameter)), + _ => throw new NotSupportedException($"The asymmetric parameter type {parameter.GetType().FullName} is not currently supported for byte retrieval.") + }; + } +} diff --git a/src/Common.Security.Cryptography/Keys/Rsa/Internal/Services/RsaKeyGenerator.cs b/src/Common.Security.Cryptography/Keys/Rsa/Internal/Services/RsaKeyGenerator.cs index 92a2fae..c051986 100644 --- a/src/Common.Security.Cryptography/Keys/Rsa/Internal/Services/RsaKeyGenerator.cs +++ b/src/Common.Security.Cryptography/Keys/Rsa/Internal/Services/RsaKeyGenerator.cs @@ -1,9 +1,9 @@ -using Common.Security.Cryptography.Keys.Aes.Internal.Services; -using Common.Security.Cryptography.Keys.Aes.Models; -using Common.Security.Cryptography.Keys.Rsa.Models; +using Common.Security.Cryptography.Keys.Rsa.Models; using Common.Security.Cryptography.Ports; -using Common.Security.Cryptography.SecurityKeys.Rsa.Internal.Services; -using Common.Security.Cryptography.SecurityKeys.Rsa.Models; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; using System; using System.Security.Cryptography; @@ -11,41 +11,53 @@ namespace Common.Security.Cryptography.Keys.Rsa.Internal.Services { internal class RsaKeyGenerator : SecurityKeyGenerator { - protected override ISecurityKey GenerateKey(int keySize, RsaKeyGenerationParameters keyGenerationParameters) + #region Static + + internal static ISecurityKey GenerateKey(int keySize) { - using var rsa = new RSACryptoServiceProvider(new CspParameters() - { - Flags = CspProviderFlags.UseMachineKeyStore - }) - { - PersistKeyInCsp = false - }; + return new RsaKeyGenerator().GenerateKey(keySize, new RsaKeyGenerationParameters()); + } - try - { - SecurityKeyHelper.ValidateKeySize(keySize, rsa.LegalKeySizes); - } - catch (Exception) - { - rsa.Clear(); - throw; - } + #endregion + + #region Variables - var key = new RsaSecurityKey(new RsaKeyInformation(rsa.ExportCspBlob(false), rsa.ExportCspBlob(true), - keyGenerationParameters.EncryptionPadding, keyGenerationParameters.SignaturePadding)); + private const long DefaultPublicExponent = 65537; + private const int NumberOfTestsForPrime = 500; + private static readonly KeySizes ValidKeySizes = new KeySizes(128, 512, 64); - rsa.Clear(); - return key; + #endregion + + #region SecurityKeyGenerator Overrides + + protected override ISecurityKey GenerateKey(int keySize, RsaKeyGenerationParameters keyGenerationParameters) + { + SecurityKeyHelper.ValidateKeySize(keySize, ValidKeySizes); + + var rsaKeyGenerator = new RsaKeyPairGenerator(); + rsaKeyGenerator.Init(new Org.BouncyCastle.Crypto.Parameters.RsaKeyGenerationParameters(BigInteger.ValueOf(DefaultPublicExponent), + new SecureRandom(), + keySize * 8, // BouncyCastle uses bit length + NumberOfTestsForPrime)); + + var key = rsaKeyGenerator.GenerateKeyPair(); + return new RsaSecurityKey(new RsaKeyInformation(key.Public, key.Private, keyGenerationParameters.EncryptionPadding, keyGenerationParameters.SignaturePadding)); } protected override ISecurityKey GenerateKey(byte[] privateKey, RsaKeyExchangeInformation keyExchangeInformation) { + if (privateKey == null) + { + throw new ArgumentNullException(nameof(privateKey)); + } if (keyExchangeInformation.PublicKey == null) { throw new ArgumentNullException(nameof(keyExchangeInformation.PublicKey)); } - return new RsaSecurityKey(new RsaKeyInformation(keyExchangeInformation.PublicKey, privateKey, + var rsaPublicKey = PublicKeyFactory.CreateKey(keyExchangeInformation.PublicKey); + var rsaPrivateKey = PrivateKeyFactory.CreateKey(privateKey); + return new RsaSecurityKey(new RsaKeyInformation(rsaPublicKey, rsaPrivateKey, keyExchangeInformation.EncryptionPadding, keyExchangeInformation.SignaturePadding)); } @@ -54,9 +66,6 @@ protected override ISecurityKey GenerateKey(RsaKeyInformation keyInformation) return new RsaSecurityKey(keyInformation); } - internal static ISecurityKey GenerateKey(int keySize) - { - return new RsaKeyGenerator().GenerateKey(keySize, new RsaKeyGenerationParameters()); - } + #endregion } } diff --git a/src/Common.Security.Cryptography/Keys/Rsa/Internal/Services/RsaSecurityKey.cs b/src/Common.Security.Cryptography/Keys/Rsa/Internal/Services/RsaSecurityKey.cs index 1ec18c1..38aa3a1 100644 --- a/src/Common.Security.Cryptography/Keys/Rsa/Internal/Services/RsaSecurityKey.cs +++ b/src/Common.Security.Cryptography/Keys/Rsa/Internal/Services/RsaSecurityKey.cs @@ -1,98 +1,67 @@ -using Common.Security.Cryptography.Model; -using Common.Security.Cryptography.Ports; -using Common.Security.Cryptography.SecurityKeys.Rsa.Models; +using Common.Security.Cryptography.Keys.Rsa.Models; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Encodings; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Signers; +using Org.BouncyCastle.Security; using System; using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; -namespace Common.Security.Cryptography.SecurityKeys.Rsa.Internal.Services +namespace Common.Security.Cryptography.Keys.Rsa.Internal.Services { - internal class RsaSecurityKey : ISecurityKey + internal class RsaSecurityKey : SecurityKey { - #region Variables - - private RsaKeyInformation _securityKeyInformation; - - #endregion - #region Constructors public RsaSecurityKey(RsaKeyInformation rsaKeyInformation) + : base(rsaKeyInformation) { - _securityKeyInformation = rsaKeyInformation ?? throw new ArgumentNullException(nameof(rsaKeyInformation)); } #endregion #region ISecurityKey - public SecurityKeyInformation KeyInformation => _securityKeyInformation; - - public Task EncryptAsync(byte[] data, CancellationToken cancellationToken = default) + public override Task EncryptAsync(byte[] data, CancellationToken cancellationToken = default) { if (data == null) { throw new ArgumentNullException(nameof(data)); } - using var rsa = new RSACryptoServiceProvider(new CspParameters() - { - Flags = CspProviderFlags.UseMachineKeyStore - }) - { - PersistKeyInCsp = false - }; - rsa.ImportCspBlob(_securityKeyInformation.PublicKey); - - var encryptedBytes = rsa.Encrypt(data, _securityKeyInformation.EncryptionPadding); - rsa.Clear(); - return Task.FromResult(encryptedBytes); + var cipher = GetCipher(SecurityKeyInformation.EncryptionPadding); + cipher.Init(true, SecurityKeyInformation.PublicKey); + return Task.FromResult(cipher.ProcessBlock(data, 0, data.Length)); } - public Task DecryptAsync(byte[] data, CancellationToken cancellationToken = default) + public override Task DecryptAsync(byte[] data, CancellationToken cancellationToken = default) { if (data == null) { throw new ArgumentNullException(nameof(data)); } - using var rsa = new RSACryptoServiceProvider(new CspParameters() - { - Flags = CspProviderFlags.UseMachineKeyStore - }) - { - PersistKeyInCsp = false - }; - rsa.ImportCspBlob(_securityKeyInformation.PrivateKey); - - var encryptedBytes = rsa.Decrypt(data, _securityKeyInformation.EncryptionPadding); - rsa.Clear(); - return Task.FromResult(encryptedBytes); + var cipher = GetCipher(SecurityKeyInformation.EncryptionPadding); + cipher.Init(false, SecurityKeyInformation.PrivateKey); + return Task.FromResult(cipher.ProcessBlock(data, 0, data.Length)); } - public Task SignAsync(byte[] data, HashAlgorithmName hashAlgorithmName, CancellationToken cancellationToken = default) + public override Task SignAsync(byte[] data, HashAlgorithmName hashAlgorithmName, CancellationToken cancellationToken = default) { if (data == null) { throw new ArgumentNullException(nameof(data)); } - using var rsa = new RSACryptoServiceProvider(new CspParameters() - { - Flags = CspProviderFlags.UseMachineKeyStore - }) - { - PersistKeyInCsp = false - }; - rsa.ImportCspBlob(_securityKeyInformation.PrivateKey); - - var signedData = rsa.SignData(data, hashAlgorithmName, _securityKeyInformation.SignaturePadding); - rsa.Clear(); - return Task.FromResult(signedData); + var signer = GetSigner(SecurityKeyInformation.SignaturePadding, hashAlgorithmName); + signer.Init(true, SecurityKeyInformation.PrivateKey); + signer.BlockUpdate(data, 0, data.Length); + return Task.FromResult(signer.GenerateSignature()); } - public Task ValidateSignatureAsync(byte[] data, byte[] signedData, HashAlgorithmName hashAlgorithmName, CancellationToken cancellationToken = default) + public override Task ValidateSignatureAsync(byte[] data, byte[] signedData, HashAlgorithmName hashAlgorithmName, CancellationToken cancellationToken = default) { if (data == null) { @@ -103,32 +72,40 @@ public Task ValidateSignatureAsync(byte[] data, byte[] signedData, HashAlg throw new ArgumentNullException(nameof(signedData)); } - using var rsa = new RSACryptoServiceProvider(new CspParameters() - { - Flags = CspProviderFlags.UseMachineKeyStore - }) - { - PersistKeyInCsp = false - }; - rsa.ImportCspBlob(_securityKeyInformation.PublicKey); - - var isValid = rsa.VerifyData(data, signedData, hashAlgorithmName, _securityKeyInformation.SignaturePadding); - rsa.Clear(); - return Task.FromResult(isValid); + var signer = GetSigner(SecurityKeyInformation.SignaturePadding, hashAlgorithmName); + signer.Init(false, SecurityKeyInformation.PublicKey); + signer.BlockUpdate(data, 0, data.Length); + return Task.FromResult(signer.VerifySignature(signedData)); } - public void Dispose() + public override void Dispose() { - if (_securityKeyInformation == null) + if (SecurityKeyInformation == null) { throw new ObjectDisposedException(nameof(RsaSecurityKey)); } - Array.Clear(_securityKeyInformation.PublicKey, 0, _securityKeyInformation.PublicKey.Length); - Array.Clear(_securityKeyInformation.PrivateKey, 0, _securityKeyInformation.PrivateKey.Length); - _securityKeyInformation = null; + SecurityKeyInformation = null; } #endregion + + #region Helpers + + private ISigner GetSigner(RSASignaturePadding signaturePadding, HashAlgorithmName hashAlgorithmName) => signaturePadding.Mode switch + { + RSASignaturePaddingMode.Pss => new PssSigner(new RsaBlindedEngine(), DigestUtilities.GetDigest(hashAlgorithmName.Name)), + RSASignaturePaddingMode.Pkcs1 => new RsaDigestSigner(DigestUtilities.GetDigest(hashAlgorithmName.Name)), + _ => throw new NotSupportedException($"Signature Padding mode {signaturePadding} is not currently supported for RSA security key.") + }; + + private IAsymmetricBlockCipher GetCipher(RSAEncryptionPadding padding) => padding.Mode switch + { + RSAEncryptionPaddingMode.Pkcs1 => new Pkcs1Encoding(new RsaEngine()), + RSAEncryptionPaddingMode.Oaep => new OaepEncoding(new RsaEngine(), DigestUtilities.GetDigest(padding.OaepHashAlgorithm.Name)), + _ => throw new NotSupportedException($"Encryption Padding mode {padding} is not currently supported for RSA security key.") + }; + + #endregion } } diff --git a/src/Common.Security.Cryptography/Keys/Rsa/Models/RsaKeyGenerationParameters.cs b/src/Common.Security.Cryptography/Keys/Rsa/Models/RsaKeyGenerationParameters.cs index f4fb260..e7a7e31 100644 --- a/src/Common.Security.Cryptography/Keys/Rsa/Models/RsaKeyGenerationParameters.cs +++ b/src/Common.Security.Cryptography/Keys/Rsa/Models/RsaKeyGenerationParameters.cs @@ -5,7 +5,7 @@ namespace Common.Security.Cryptography.Keys.Rsa.Models { public class RsaKeyGenerationParameters: SecurityKeyGenerationParameters { - public RSAEncryptionPadding EncryptionPadding { get; set; } = RSAEncryptionPadding.Pkcs1; + public RSAEncryptionPadding EncryptionPadding { get; set; } = RSAEncryptionPadding.OaepSHA256; public RSASignaturePadding SignaturePadding { get; set; } = RSASignaturePadding.Pkcs1; } diff --git a/src/Common.Security.Cryptography/Keys/Rsa/Models/RsaKeyInformation.cs b/src/Common.Security.Cryptography/Keys/Rsa/Models/RsaKeyInformation.cs index a1a690e..cb7fd09 100644 --- a/src/Common.Security.Cryptography/Keys/Rsa/Models/RsaKeyInformation.cs +++ b/src/Common.Security.Cryptography/Keys/Rsa/Models/RsaKeyInformation.cs @@ -1,17 +1,17 @@ -using Common.Security.Cryptography.Keys.Rsa.Models; -using Common.Security.Cryptography.Model; +using Common.Security.Cryptography.Model; +using Org.BouncyCastle.Crypto; using System; using System.Security.Cryptography; -namespace Common.Security.Cryptography.SecurityKeys.Rsa.Models +namespace Common.Security.Cryptography.Keys.Rsa.Models { - public class RsaKeyInformation: SecurityKeyInformation + public class RsaKeyInformation : SecurityKeyInformation { #region Variables - public byte[] PublicKey { get; } + public AsymmetricKeyParameter PublicKey { get; } - public byte[] PrivateKey { get; } + public AsymmetricKeyParameter PrivateKey { get; } public RSAEncryptionPadding EncryptionPadding { get; } @@ -21,7 +21,7 @@ public class RsaKeyInformation: SecurityKeyInformation #region Constructors - public RsaKeyInformation(byte[] publicKey, byte[] privateKey, RSAEncryptionPadding encryptionPadding, + public RsaKeyInformation(AsymmetricKeyParameter publicKey, AsymmetricKeyParameter privateKey, RSAEncryptionPadding encryptionPadding, RSASignaturePadding signaturePadding) : base(SecurityKeyUsageType.Personal) { @@ -35,7 +35,7 @@ public RsaKeyInformation(byte[] publicKey, byte[] privateKey, RSAEncryptionPaddi #region SecurityKeyInformation Overrides - public override byte[] RawKey => PrivateKey; + public override byte[] RawKey => PrivateKey.ToArray(); public override SecurityKeyExchangeInformation GetKeyExhangeInformation() { @@ -43,7 +43,7 @@ public override SecurityKeyExchangeInformation GetKeyExhangeInformation() { EncryptionPadding = EncryptionPadding, SignaturePadding = SignaturePadding, - PublicKey = PublicKey + PublicKey = PublicKey.ToArray() }; } diff --git a/src/Common.Security.Cryptography/Keys/Rsa/Ports/IPrimeNumberGenerator.cs b/src/Common.Security.Cryptography/Keys/Rsa/Ports/IPrimeNumberGenerator.cs new file mode 100644 index 0000000..cf0155e --- /dev/null +++ b/src/Common.Security.Cryptography/Keys/Rsa/Ports/IPrimeNumberGenerator.cs @@ -0,0 +1,11 @@ +using System.Numerics; +using System.Threading; +using System.Threading.Tasks; + +namespace Common.Security.Cryptography.Keys.Rsa.Ports +{ + public interface IPrimeNumberGenerator + { + public Task NextPrimeAsync(int bitLength, CancellationToken cancellationToken = default); + } +} diff --git a/src/Common.Security.Cryptography/Keys/Rsa/ServiceCollectionExtensions.cs b/src/Common.Security.Cryptography/Keys/Rsa/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..94aa83f --- /dev/null +++ b/src/Common.Security.Cryptography/Keys/Rsa/ServiceCollectionExtensions.cs @@ -0,0 +1,15 @@ +using Common.Security.Cryptography.Keys.Rsa.Internal.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace Common.Security.Cryptography.Keys.Rsa +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection AddRsa(this IServiceCollection services) + { + services.AddSecurityKeyDescriptor(); + + return services; + } + } +} diff --git a/src/Common.Security.Cryptography/Keys/SecurityKey.cs b/src/Common.Security.Cryptography/Keys/SecurityKey.cs new file mode 100644 index 0000000..f994722 --- /dev/null +++ b/src/Common.Security.Cryptography/Keys/SecurityKey.cs @@ -0,0 +1,79 @@ +using Common.Security.Cryptography.Model; +using Common.Security.Cryptography.Ports; +using System.Linq; +using System; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; + +namespace Common.Security.Cryptography.Keys +{ + public abstract class SecurityKey : ISecurityKey + where TKeyInformation: SecurityKeyInformation + { + #region Variables + + protected TKeyInformation SecurityKeyInformation; + + #endregion + + #region Constructors + + public SecurityKey(TKeyInformation keyInformation) + { + SecurityKeyInformation = keyInformation ?? throw new ArgumentNullException(nameof(keyInformation)); + } + + #endregion + + #region ISecurityKey + + public SecurityKeyInformation KeyInformation => SecurityKeyInformation; + + public abstract Task DecryptAsync(byte[] data, CancellationToken cancellationToken = default); + + public abstract Task EncryptAsync(byte[] data, CancellationToken cancellationToken = default); + + public abstract void Dispose(); + + public virtual Task SignAsync(byte[] data, HashAlgorithmName hashAlgorithmName, CancellationToken cancellationToken = default) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + + var hashAlgorithm = HashAlgorithm.Create(hashAlgorithmName.Name); + if (hashAlgorithm == null) + { + throw new InvalidOperationException($"The hash algorithm {hashAlgorithmName.Name} is not supported."); + } + + var dataHash = hashAlgorithm.ComputeHash(data); + return EncryptAsync(dataHash, cancellationToken); + } + + public virtual async Task ValidateSignatureAsync(byte[] data, byte[] signedData, HashAlgorithmName hashAlgorithmName, CancellationToken cancellationToken = default) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + if (signedData == null) + { + throw new ArgumentNullException(nameof(signedData)); + } + + var hashAlgorithm = HashAlgorithm.Create(hashAlgorithmName.Name); + if (hashAlgorithm == null) + { + throw new InvalidOperationException($"The hash algorithm {hashAlgorithmName.Name} is not supported."); + } + + var decryptedDataHash = await DecryptAsync(signedData, cancellationToken); + return decryptedDataHash.SequenceEqual(hashAlgorithm.ComputeHash(data)); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Common.Security.Cryptography/Keys/SecurityKeyGenerator.cs b/src/Common.Security.Cryptography/Keys/SecurityKeyGenerator.cs index 62191c9..69756da 100644 --- a/src/Common.Security.Cryptography/Keys/SecurityKeyGenerator.cs +++ b/src/Common.Security.Cryptography/Keys/SecurityKeyGenerator.cs @@ -1,7 +1,6 @@ using Common.Security.Cryptography.Model; using Common.Security.Cryptography.Ports; using System; -using System.Threading; namespace Common.Security.Cryptography.Keys { diff --git a/src/Common.Security.Cryptography/Keys/ServiceCollectionExtensions.cs b/src/Common.Security.Cryptography/Keys/ServiceCollectionExtensions.cs index 7b1f331..45cf6da 100644 --- a/src/Common.Security.Cryptography/Keys/ServiceCollectionExtensions.cs +++ b/src/Common.Security.Cryptography/Keys/ServiceCollectionExtensions.cs @@ -1,5 +1,5 @@ -using Common.Security.Cryptography.Keys.Aes.Internal.Services; -using Common.Security.Cryptography.Keys.Rsa.Internal.Services; +using Common.Security.Cryptography.Keys.Aes; +using Common.Security.Cryptography.Keys.Rsa; using Microsoft.Extensions.DependencyInjection; namespace Common.Security.Cryptography.Keys @@ -8,8 +8,8 @@ public static class ServiceCollectionExtensions { public static IServiceCollection AddSecurityKeys(this IServiceCollection services) { - services.AddSecurityKeyDescriptor(); - services.AddSecurityKeyDescriptor(); + services.AddAes(); + services.AddRsa(); return services; } diff --git a/src/Common.Security.Cryptography/SecurityKeyHelper.cs b/src/Common.Security.Cryptography/SecurityKeyHelper.cs index 84df4e4..fa299ac 100644 --- a/src/Common.Security.Cryptography/SecurityKeyHelper.cs +++ b/src/Common.Security.Cryptography/SecurityKeyHelper.cs @@ -8,7 +8,7 @@ namespace Common.Security.Cryptography { public static class SecurityKeyHelper { - public static void ValidateKeySize(int keySize, KeySizes[] keySizes) + public static void ValidateKeySize(int keySize, params KeySizes[] keySizes) { if (keySizes.All(keySizeLimit => keySize < keySizeLimit.MinSize || keySize > keySizeLimit.MaxSize || keySize % keySizeLimit.SkipSize != 0))