diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 0000000..66b2d4d --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,35 @@ +name: .NET +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Clone 📥 + uses: actions/checkout@v2 + - name: Setup .NET 5 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 5.0.x + - name: Restore dependencies 📦 + run: dotnet restore + - name: Build Library 🔧 + run: dotnet build src/Org.Security.Cryptography.X509Extensions/Org.Security.Cryptography.X509Extensions.csproj --no-restore + - name: Build Tests 🔧 + run: dotnet build src/UnitTests/UnitTests.csproj --no-restore + - name: Test With Code Coverage 🧪 + run: dotnet test src/UnitTests/UnitTests.csproj --no-build --framework Net5.0 /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Threshold=80 /p:ThresholdType=line /p:Exclude=\"[*]X509.EnduranceTest.Shared*,[*]UnitTests*\" --verbosity normal + - name: Upload Coverage To CodeCov.io ⇪ + uses: codecov/codecov-action@v2 + with: + files: src/UnitTests/coverage.Net5.0.opencover.xml + verbose: true + - name: Deploy Docs to GitHug Pages 🚀 + uses: JamesIves/github-pages-deploy-action@releases/v3 + with: + GITHUB_TOKEN: $ + BRANCH: gh-pages + FOLDER: src/Org.Security.Cryptography.X509Extensions/_site diff --git a/.gitignore b/.gitignore index dfcfd56..d334f24 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,9 @@ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs +#logs +.log + # Mono auto generated files mono_crash.* @@ -61,6 +64,10 @@ project.lock.json project.fragment.lock.json artifacts/ +#donet tools + +tools +.config # StyleCop StyleCopReport.xml @@ -140,7 +147,7 @@ _TeamCity* # Visual Studio code coverage results *.coverage *.coveragexml - +*.opencover.xml # NCrunch _NCrunch_* .*crunch*.local.xml @@ -166,6 +173,10 @@ DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html +#DocFx files + +/**/_site + # Click-Once directory publish/ @@ -226,7 +237,6 @@ ClientBin/ *.dbmdl *.dbproj.schemaview *.jfm -*.pfx *.publishsettings orleans.codegen.cs diff --git a/README.md b/README.md new file mode 100644 index 0000000..45965e6 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ + +| Area | Badges | +|:--------------|:-------------| +| Build | ![.Net workflow](https://github.com/dotnet-demos/Org.Security.Cryptography.X509Extensions/actions/workflows/dotnet.yml/badge.svg) | +| Code | ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/dotnet-demos/Org.Security.Cryptography.X509Extensions) ![GitHub repo size](https://img.shields.io/github/repo-size/dotnet-demos/Org.Security.Cryptography.X509Extensions) [![](https://tokei.rs/b1/github/dotnet-demos/Org.Security.Cryptography.X509Extensions)](https://github.com/dotnet-demos/Org.Security.Cryptography.X509Extensions) | +| Code Quality | [![Maintainability](https://api.codeclimate.com/v1/badges/b64e91057b6c905e0347/maintainability)](https://codeclimate.com/github/dotnet-demos/Org.Security.Cryptography.X509Extensions/maintainability) | +| Test | [![codecov](https://codecov.io/gh/dotnet-demos/Org.Security.Cryptography.X509Extensions/branch/master/graph/badge.svg?token=AS2FV3ACUI)](https://codecov.io/gh/dotnet-demos/Org.Security.Cryptography.X509Extensions) | + +# Org.Security.Cryptography.X509Extensions +`X509Certificate2` Extensions and classes for Encrypting and Signing using X509 certs. + +# Getting started + +- Clone or download the repo +- Compile Org.Security.Cryptography.X509Extensions.csproj to get the assembly. +- Refer the assembly in your project. + +## Usage (Encryption) + + ```C# + var x509Certificate = GetCertificateUsingYourWay(); // This certificate doesn't need to have private key. + Stream yourStreamToEncrypt = GetYourStreamToEncrypt(); + var encryptedStream = new MemoryStream(); + x509Certificate.EncryptStream(yourStreamToEncrypt,encryptedStream); + ``` + +## Usage (Decryption) + + ```C# + var x509Certificate = GetCertificateWithPrivateKeyUsingYourWay(); + Stream yourStreamToDecrypt = GetYourStreamToDecrypt(); + var decryptedStream = new MemoryStream(); + x509Certificate.DecryptStream(yourStreamToDecrypt, decryptedStream); + ``` +For other APIs, please refer the unit tests or the [API documentation](https://dotnet-demos.github.io/Org.Security.Cryptography.X509Extensions/api/index.html) + +# Documentation + +[Documentation site](https://dotnet-demos.github.io/Org.Security.Cryptography.X509Extensions/) has [articles](https://dotnet-demos.github.io/Org.Security.Cryptography.X509Extensions/articles/intro.html) as well as [API documentation](https://dotnet-demos.github.io/Org.Security.Cryptography.X509Extensions/api/index.html). + +# Running tests + +Use `dotnet test` command or use the "Test Explorer" windows of Visual Studio. + +In order to view coverage, use any of the below methods. + +## Commandline + +Below command has codecoverage threshold 100. It will fail as of now. + +`dotnet test "src/UnitTests/UnitTests.csproj" --framework Net5.0 /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Threshold=100 /p:ThresholdType=line /p:Exclude="[*]X509.EnduranceTest.Shared*"` + +It is excluding the shared test library. + +## Visual Studio + +Use the "Run Coverlet Report" extension as mentioned [here](https://www.code4it.dev/blog/code-coverage-vs-2019-coverlet). diff --git a/src/ReadMe-FIPS.md b/ReadMe-FIPS.md similarity index 100% rename from src/ReadMe-FIPS.md rename to ReadMe-FIPS.md diff --git a/X509Extensions.sln b/X509Extensions.sln new file mode 100644 index 0000000..3e5d5ac --- /dev/null +++ b/X509Extensions.sln @@ -0,0 +1,54 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33723.286 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{EAA2E090-9112-4E79-B25C-030FF4B6D25D}" + ProjectSection(SolutionItems) = preProject + .github\workflows\dotnet.yml = .github\workflows\dotnet.yml + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Org.Security.Cryptography.X509Extensions", "src\Org.Security.Cryptography.X509Extensions\Org.Security.Cryptography.X509Extensions.csproj", "{1EEFA765-F0B2-4C93-A543-F3DC6410F60E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "src\UnitTests\UnitTests.csproj", "{870A984C-F0FB-438C-8FAF-E5ECD1E1A680}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "X509.EnduranceTest.Shared", "src\X509.EnduranceTest.Shared\X509.EnduranceTest.Shared.csproj", "{F2511529-D3CA-4050-AD04-1605EA0AF5B0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X509.EnduranceTest.NetFramework", "src\X509.EnduranceTest.NetFramework\X509.EnduranceTest.NetFramework.csproj", "{9389DAC3-E2E1-41BA-BE5C-34F3F8930DFC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "X509.EnduranceTest.Net6", "src\X509.EnduranceTest.Net6\X509.EnduranceTest.Net6.csproj", "{B96A2F2E-7E13-4C7E-B6ED-40C7D4BD6149}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1EEFA765-F0B2-4C93-A543-F3DC6410F60E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1EEFA765-F0B2-4C93-A543-F3DC6410F60E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1EEFA765-F0B2-4C93-A543-F3DC6410F60E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1EEFA765-F0B2-4C93-A543-F3DC6410F60E}.Release|Any CPU.Build.0 = Release|Any CPU + {870A984C-F0FB-438C-8FAF-E5ECD1E1A680}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {870A984C-F0FB-438C-8FAF-E5ECD1E1A680}.Debug|Any CPU.Build.0 = Debug|Any CPU + {870A984C-F0FB-438C-8FAF-E5ECD1E1A680}.Release|Any CPU.ActiveCfg = Release|Any CPU + {870A984C-F0FB-438C-8FAF-E5ECD1E1A680}.Release|Any CPU.Build.0 = Release|Any CPU + {F2511529-D3CA-4050-AD04-1605EA0AF5B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2511529-D3CA-4050-AD04-1605EA0AF5B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2511529-D3CA-4050-AD04-1605EA0AF5B0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2511529-D3CA-4050-AD04-1605EA0AF5B0}.Release|Any CPU.Build.0 = Release|Any CPU + {9389DAC3-E2E1-41BA-BE5C-34F3F8930DFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9389DAC3-E2E1-41BA-BE5C-34F3F8930DFC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9389DAC3-E2E1-41BA-BE5C-34F3F8930DFC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9389DAC3-E2E1-41BA-BE5C-34F3F8930DFC}.Release|Any CPU.Build.0 = Release|Any CPU + {B96A2F2E-7E13-4C7E-B6ED-40C7D4BD6149}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B96A2F2E-7E13-4C7E-B6ED-40C7D4BD6149}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B96A2F2E-7E13-4C7E-B6ED-40C7D4BD6149}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B96A2F2E-7E13-4C7E-B6ED-40C7D4BD6149}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {854A5078-7E41-474A-827E-7FA6204BF5CA} + EndGlobalSection +EndGlobal diff --git a/diagrams/simple.puml b/diagrams/simple.puml new file mode 100644 index 0000000..1bbb692 --- /dev/null +++ b/diagrams/simple.puml @@ -0,0 +1,19 @@ +@startuml +object Sender { + Encrypt data +} +package payload { + object Contents{ + Length Of encrypted DEK (Int32) + Asymmemetric Encrypted DEK(Data Encryption Key) + Length Of encrypted IV (Int32) + Asymmemetric Encrypted IV (Initialization Vector) + Symmetric Encrypted Data using DEK + } +} +object Receiver { + Decrypt data +} +Sender -right-> Contents +Contents -right-> Receiver +@enduml diff --git a/diagrams/thumbprint.puml b/diagrams/thumbprint.puml new file mode 100644 index 0000000..439adfd --- /dev/null +++ b/diagrams/thumbprint.puml @@ -0,0 +1,21 @@ +@startuml +object Sender { + Encrypt data +} +package payload { + object Contents{ + Length Of certiticate thumbprint (Int32) + Unencrypted certificate thumbprint + Length Of encrypted DEK (Int32) + Asymmemetric Encrypted DEK(Data Encryption Key) + Length Of encrypted IV (Int32) + Asymmemetric Encrypted IV (Initialization Vector) + Symmetric Encrypted Data using DEK + } +} +object Receiver { + Decrypt data +} +Sender -right-> Contents +Contents -right-> Receiver +@enduml diff --git a/diagrams/timestamp.puml b/diagrams/timestamp.puml new file mode 100644 index 0000000..d505605 --- /dev/null +++ b/diagrams/timestamp.puml @@ -0,0 +1,23 @@ +@startuml +object Sender { + Encrypt data +} +package payload { + object Contents{ + Length Of certiticate thumbprint (Int32) + Unencrypted certificate thumbprint + Length Of encrypted timestamp in utc (Int32) + Asymmemetric Encrypted DEK(Data Encryption Key) + Length Of encrypted DEK (Int32) + Asymmemetric Encrypted DEK(Data Encryption Key) + Length Of encrypted IV (Int32) + Asymmemetric Encrypted IV (Initialization Vector) + Symmetric Encrypted Data using DEK + } +} +object Receiver { + Decrypt data +} +Sender -right-> Contents +Contents -right-> Receiver +@enduml diff --git a/src/Org.Security.Cryptography.X509Extensions/.gitignore b/src/Org.Security.Cryptography.X509Extensions/.gitignore new file mode 100644 index 0000000..4378419 --- /dev/null +++ b/src/Org.Security.Cryptography.X509Extensions/.gitignore @@ -0,0 +1,9 @@ +############### +# folder # +############### +/**/DROP/ +/**/TEMP/ +/**/packages/ +/**/bin/ +/**/obj/ +_site diff --git a/src/Org.Security.Cryptography.X509Extensions/CacheManager.cs b/src/Org.Security.Cryptography.X509Extensions/CacheManager.cs new file mode 100644 index 0000000..7c8daf9 --- /dev/null +++ b/src/Org.Security.Cryptography.X509Extensions/CacheManager.cs @@ -0,0 +1,31 @@ + +using Microsoft.Extensions.Caching.Memory; +using System; +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("UnitTests")] +namespace Org.Security.Cryptography +{ + internal static class CacheManager + { + static MemoryCache algorithmCache = new MemoryCache(new MemoryCacheOptions()); + + //2021-08-27 - Joy George Kunjikkuru - Hack of the day - Just for unit testing exposing this ClearCache(); Should never be exposing in real scenario. + internal static void ClearCache() + { + algorithmCache = new MemoryCache(new MemoryCacheOptions()); + } + internal static TOut GetOrAdd(object key, Func valueFunction) + { + TOut outValue; + algorithmCache.TryGetValue(key, out outValue); + if (null == outValue) + { + outValue = valueFunction(key); + algorithmCache.Set(key, outValue); + + } + return outValue; + } + } +} diff --git a/src/Org.Security.Cryptography.X509Extensions/Defaults.cs b/src/Org.Security.Cryptography.X509Extensions/Defaults.cs new file mode 100644 index 0000000..2ee5df1 --- /dev/null +++ b/src/Org.Security.Cryptography.X509Extensions/Defaults.cs @@ -0,0 +1,12 @@ +using System; + +namespace Org.Security.Cryptography +{ + internal static class Defaults + { + internal const string DEF_DataEncryptionAlgorithmName = "Aes"; + internal const int DEF_KeySize = 256; + internal const int DEF_BlockSize = 128; + internal static readonly TimeSpan EncyptedPayloadTimeSpan = TimeSpan.FromMinutes(1); + } +} diff --git a/src/Org.Security.Cryptography.X509Extensions/Org.Security.Cryptography.X509Extensions.csproj b/src/Org.Security.Cryptography.X509Extensions/Org.Security.Cryptography.X509Extensions.csproj index 7e8dd40..aa863a3 100644 --- a/src/Org.Security.Cryptography.X509Extensions/Org.Security.Cryptography.X509Extensions.csproj +++ b/src/Org.Security.Cryptography.X509Extensions/Org.Security.Cryptography.X509Extensions.csproj @@ -16,5 +16,16 @@ MIT + + Docfx-$(TargetFramework).log + Warning + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/src/Org.Security.Cryptography.X509Extensions/StreamExtensions.cs b/src/Org.Security.Cryptography.X509Extensions/StreamExtensions.cs new file mode 100644 index 0000000..beb7c87 --- /dev/null +++ b/src/Org.Security.Cryptography.X509Extensions/StreamExtensions.cs @@ -0,0 +1,48 @@ + +using System; +using System.IO; + +namespace Org.Security.Cryptography +{ + internal static class StreamExtensions + { + //............................................................................... + #region Utils: WriteLengthAndBytes(), ReadLengthAndBytes() + //............................................................................... + internal static void WriteLengthAndBytes(this Stream outputStream, byte[] bytes) + { + if (null == outputStream) throw new ArgumentNullException(nameof(outputStream)); + if (null == bytes) throw new ArgumentNullException(nameof(bytes)); + + // Int32 length to exactly-four-bytes array. + var length = BitConverter.GetBytes((Int32)bytes.Length); + + // Write the four-byte-length followed by the data. + outputStream.Write(length, 0, length.Length); + outputStream.Write(bytes, 0, bytes.Length); + } + + internal static byte[] ReadLengthAndBytes(this Stream inputStream, int maxBytes) + { + if (null == inputStream) throw new ArgumentNullException(nameof(inputStream)); + + // Read an Int32, exactly four bytes. + var arrLength = new byte[4]; + var bytesRead = inputStream.Read(arrLength, 0, 4); + if (bytesRead != 4) throw new Exception("Unexpected end of InputStream. Expecting 4 bytes."); + + // Length of data to read. + var length = BitConverter.ToInt32(arrLength, 0); + if (length > maxBytes) throw new Exception($"Unexpected data size {length:#,0} bytes. Expecting NOT more than {maxBytes:#,0} bytes."); + + // Read suggested no of bytes... + var bytes = new byte[length]; + bytesRead = inputStream.Read(bytes, 0, bytes.Length); + if (bytesRead != bytes.Length) throw new Exception($"Unexpected end of input stream. Expecting {bytes.Length:#,0} bytes."); + + return bytes; + } + + #endregion + } +} diff --git a/src/Org.Security.Cryptography.X509Extensions/Validator.cs b/src/Org.Security.Cryptography.X509Extensions/Validator.cs new file mode 100644 index 0000000..24c7b44 --- /dev/null +++ b/src/Org.Security.Cryptography.X509Extensions/Validator.cs @@ -0,0 +1,19 @@ +using System; +using System.IO; +using System.Security.Cryptography.X509Certificates; + +namespace Org.Security.Cryptography +{ + + class Validator + { + internal static void ValidateParametersAndThrowException(X509Certificate2 x509Cert, Stream inputStream, Stream outputStream, string dataEncryptionAlgorithmName) + { + if (null == x509Cert) throw new ArgumentNullException(nameof(x509Cert)); + if (null == inputStream) throw new ArgumentNullException(nameof(inputStream)); + if (null == outputStream) throw new ArgumentNullException(nameof(outputStream)); + if (string.IsNullOrWhiteSpace(dataEncryptionAlgorithmName)) throw new ArgumentNullException(nameof(dataEncryptionAlgorithmName)); + } + + } +} \ No newline at end of file diff --git a/src/Org.Security.Cryptography.X509Extensions/X509AsymmetricAlgorithmExtensions.cs b/src/Org.Security.Cryptography.X509Extensions/X509AsymmetricAlgorithmExtensions.cs index 7795c2a..facc292 100644 --- a/src/Org.Security.Cryptography.X509Extensions/X509AsymmetricAlgorithmExtensions.cs +++ b/src/Org.Security.Cryptography.X509Extensions/X509AsymmetricAlgorithmExtensions.cs @@ -1,5 +1,4 @@ - -using System; +using System; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; @@ -7,60 +6,71 @@ namespace Org.Security.Cryptography { internal static class X509AsymmetricAlgorithmExtensions { + /// /// Returns an AsymmetricAlgorithm, representing the PublicKey /// internal static AsymmetricAlgorithm GetPublicKeyAsymmetricAlgorithm(this X509Certificate2 x509Cert) { - if (null == x509Cert) throw new ArgumentNullException(nameof(x509Cert)); + // Not sure what scenario the thumnprint will be null. If the cert is loaded it would have thumbprint if (null == x509Cert.Thumbprint) throw new ArgumentNullException("X509Certificate2.Thumbprint was NULL."); - - try + return CacheManager.GetOrAdd($"{nameof(GetPublicKeyAsymmetricAlgorithm)}{x509Cert.Thumbprint}", (key) => { try { - // [FASTER] - return x509Cert.PublicKey?.Key ?? throw new Exception($"X509Certificate2.PublicKey?.Key was NULL."); + try + { + // [FASTER] + return x509Cert.PublicKey?.Key ?? throw new Exception($"X509Certificate2.PublicKey?.Key was NULL."); + } + catch (CryptographicException) + { + // [SLOWER] - Seems rare scenario + return x509Cert.GetRSAPublicKey() ?? throw new Exception($"X509Certificate2.GetRSAPublicKey() returned NULL"); + } } - catch (CryptographicException) + catch (Exception err) { - // [SLOWER] - return x509Cert.GetRSAPublicKey() ?? throw new Exception($"X509Certificate2.GetRSAPublicKey() returned NULL"); + var msg = $"Error accessing PublicKey of the X509 Certificate. Cert: {x509Cert.Thumbprint}"; + throw new Exception(msg, err); } - } - catch (Exception err) - { - var msg = $"Error accessing PublicKey of the X509 Certificate. Cert: {x509Cert.Thumbprint}"; - throw new Exception(msg, err); - } + }); } /// /// Returns an AsymmetricAlgorithm, representing the PrivateKey /// + /// + /// Why the below complications? The PrivateKey caching and faster. Other one is not. + /// https://github.com/dotnet/runtime/issues/17269#issuecomment-218932128 + /// internal static AsymmetricAlgorithm GetPrivateKeyAsymmetricAlgorithm(this X509Certificate2 x509Cert) { - if (null == x509Cert) throw new ArgumentNullException(nameof(x509Cert)); if (null == x509Cert.Thumbprint) throw new ArgumentNullException("X509Certificate2.Thumbprint was NULL."); - try - { - try - { - // [FASTER] - return x509Cert.PrivateKey ?? throw new Exception($"X509Certificate2.PrivateKey was NULL."); - } - catch (CryptographicException) - { - // [SLOWER] - return x509Cert.GetRSAPrivateKey() ?? throw new Exception($"X509Certificate2.GetRSAPrivateKey() returned NULL."); - } - } - catch (Exception err) - { - var msg = $"Error accessing PrivateKey of the X509 Certificate. Cert: {x509Cert.Thumbprint}"; - throw new Exception(msg, err); - } + return CacheManager.GetOrAdd($"{nameof(GetPrivateKeyAsymmetricAlgorithm)}{x509Cert.Thumbprint}", (key) => + { + try + { + try + { + // [FASTER works in .Net Core 3.1] + return x509Cert.PrivateKey ?? throw new Exception($"X509Certificate2.PrivateKey was NULL."); + } + catch (CryptographicException) + { + // [SLOWER works in .Net Framework 4.8] + // Someone did analysis https://docs.microsoft.com/en-us/archive/blogs/alejacma/invalid-provider-type-specified-error-when-accessing-x509certificate2-privatekey-on-cng-certificates + return x509Cert.GetRSAPrivateKey() ?? throw new Exception($"X509Certificate2.GetRSAPrivateKey() returned NULL."); + } + } + catch (Exception err) + { + var msg = $"Error accessing PrivateKey of the X509 Certificate. Cert: {x509Cert.Thumbprint}"; + throw new Exception(msg, err); + } + + }); } } } diff --git a/src/Org.Security.Cryptography.X509Extensions/X509Certificate2StreamDecryptionExtensions.cs b/src/Org.Security.Cryptography.X509Extensions/X509Certificate2StreamDecryptionExtensions.cs new file mode 100644 index 0000000..ef387a3 --- /dev/null +++ b/src/Org.Security.Cryptography.X509Extensions/X509Certificate2StreamDecryptionExtensions.cs @@ -0,0 +1,103 @@ + +using System; +using System.IO; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; + +namespace Org.Security.Cryptography +{ + public static class X509Certificate2StreamDecryptionExtensions + { + #region Public + /// + /// The X509 Certificate private key serves as KeyEncryptionKey (KEK). + /// Reads and decrypts the Encrypted DataEncryptionKey and Encrypted IV using the KEK. + /// Decrypts the data using the DataEncryptionKey and IV. + /// NOTE: The InputStream will be disposed at the end of this call. + /// + public static void DecryptStream(this X509Certificate2 x509Cert, + Stream inputStream, + Stream outputStream, + string dataEncryptionAlgorithmName = Defaults.DEF_DataEncryptionAlgorithmName) + { + x509Cert.DecryptStreamWithTimestampValidation(inputStream, outputStream, false, dataEncryptionAlgorithmName); + } + public static void DecryptStreamWithTimestampValidation(this X509Certificate2 x509Cert, + Stream inputStream, + Stream outputStream, + bool validateTimestamp, + string dataEncryptionAlgorithmName = Defaults.DEF_DataEncryptionAlgorithmName) + { + Validator.ValidateParametersAndThrowException(x509Cert, inputStream, outputStream, dataEncryptionAlgorithmName); + // Decrypt using Private key. + // DO NOT Dispose this; Doing so will render the X509Certificate use-less, if the caller had cached the cert. + // We didn't acquire the X509 Certificate; Caller is responsible for disposing X509Certificate2. + // Did endurance test of 1 mil cycles, found NO HANDLE leak. + var keyEncryption = x509Cert.GetPrivateKeyAsymmetricAlgorithm(); + + using (var dataEncryption = SymmetricAlgorithm.Create(dataEncryptionAlgorithmName)) + { + if (null == dataEncryption) throw new Exception($"SymmetricAlgorithm.Create('{dataEncryptionAlgorithmName}') returned null."); + // KeySize/blockSize will be selected when we assign key/IV later. + DecryptStream(inputStream, outputStream, validateTimestamp, Defaults.EncyptedPayloadTimeSpan, keyEncryption, dataEncryption); + } + } + public static void DecryptStreamWithTimestampValidation(this X509Certificate2 x509Cert, + Stream inputStream, + Stream outputStream, + TimeSpan lifeSpan, + string dataEncryptionAlgorithmName = Defaults.DEF_DataEncryptionAlgorithmName) + { + Validator.ValidateParametersAndThrowException(x509Cert, inputStream, outputStream, dataEncryptionAlgorithmName); + // Decrypt using Private key. + // DO NOT Dispose this; Doing so will render the X509Certificate use-less, if the caller had cached the cert. + // We didn't acquire the X509 Certificate; Caller is responsible for disposing X509Certificate2. + // Did endurance test of 1 mil cycles, found NO HANDLE leak. + var keyEncryption = x509Cert.GetPrivateKeyAsymmetricAlgorithm(); + + using (var dataEncryption = SymmetricAlgorithm.Create(dataEncryptionAlgorithmName)) + { + if (null == dataEncryption) throw new Exception($"SymmetricAlgorithm.Create('{dataEncryptionAlgorithmName}') returned null."); + // KeySize/blockSize will be selected when we assign key/IV later. + DecryptStream(inputStream, outputStream, true, lifeSpan, keyEncryption, dataEncryption); + } + } + #endregion + + #region Private helpers + static void DecryptStream(Stream inputStream, Stream outputStream, bool validateTimestamp, TimeSpan lifeSpanOfInput, AsymmetricAlgorithm keyEncryption, SymmetricAlgorithm dataEncryption) + { + //Some validations are done by callers and this is not public. + if (null == keyEncryption) throw new ArgumentNullException(nameof(keyEncryption)); + + var keyDeformatter = new RSAPKCS1KeyExchangeDeformatter(keyEncryption); + if (true == validateTimestamp) + { + var encryptionUTCTimestampbytes = inputStream.ReadLengthAndBytes(maxBytes: 2048); + var decryptionUTCTimestampBytes = keyDeformatter.DecryptKeyExchange(encryptionUTCTimestampbytes); + var encryptionUTCTimeStampTicks = BitConverter.ToInt64(decryptionUTCTimestampBytes, 0); + var encryptionDataTime = new DateTime(encryptionUTCTimeStampTicks); + if ((DateTime.UtcNow - encryptionDataTime) > lifeSpanOfInput) throw new TimeoutException($"The encrypted message timed out as it was created {lifeSpanOfInput} ago"); + } + var encryptedDEK = inputStream.ReadLengthAndBytes(maxBytes: 2048); + var encryptedIV = inputStream.ReadLengthAndBytes(maxBytes: 2048); + + dataEncryption.Key = keyDeformatter.DecryptKeyExchange(encryptedDEK); + dataEncryption.IV = keyDeformatter.DecryptKeyExchange(encryptedIV); + + // About... + // Trace.WriteLine($"Decrypting. KEK: {keyEncryption.GetType().Name} / {keyEncryption.KeySize} bits"); + // Trace.WriteLine($"Decrypting. DEK: {dataEncryption.GetType().Name} / {dataEncryption.KeySize} bits / BlockSize: {dataEncryption.BlockSize} bits"); + + // Read the encrypted data. + // Note: Disposing the CryptoStream also disposes the inputStream. There is no keepOpen option. + using (var transform = dataEncryption.CreateDecryptor()) + using (var cryptoStream = new CryptoStream(inputStream, transform, CryptoStreamMode.Read)) + { + cryptoStream.CopyTo(outputStream, bufferSize: dataEncryption.BlockSize * 4); + } + } + + #endregion + } +} diff --git a/src/Org.Security.Cryptography.X509Extensions/X509Certificate2StreamEncryptionExtensions.cs b/src/Org.Security.Cryptography.X509Extensions/X509Certificate2StreamEncryptionExtensions.cs new file mode 100644 index 0000000..199c831 --- /dev/null +++ b/src/Org.Security.Cryptography.X509Extensions/X509Certificate2StreamEncryptionExtensions.cs @@ -0,0 +1,101 @@ + +using System; +using System.IO; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace Org.Security.Cryptography +{ + /// + /// Extensions to encrypt/decrypt Streams using X509 Certificates. + /// DEFAULT: AES-256/128 for Data encryption. + /// + public static class X509Certificate2StreamEncryptionExtensions + { + #region Public + /// + /// The X509 Certificate public key serves as KeyEncryptionKey (KEK). + /// Data is encrypted using a randomly generated DataEncryptionKey and IV. + /// Writes encrypted DataEncryptionKey, encrypted IV and encrypted data to the output stream. + /// NOTE: The OutputStream will be disposed at the end of this call. + /// + public static void EncryptStream(this X509Certificate2 x509Cert, + Stream inputStream, + Stream outputStream, + bool includeUTCTimeStamp = false, + string dataEncryptionAlgorithmName = Defaults.DEF_DataEncryptionAlgorithmName, + int keySize = Defaults.DEF_KeySize, + int blockSize = Defaults.DEF_BlockSize) + { + Validator.ValidateParametersAndThrowException(x509Cert, inputStream, outputStream, dataEncryptionAlgorithmName); + + // Encrypt using Public key. + // DO NOT Dispose this; Doing so will render the X509Certificate use-less, if the caller had cached the cert. + // We didn't acquire the X509 Certificate; Caller is responsible for disposing X509Certificate2. + // Did endurance test of 1 mil cycles, found NO HANDLE leak. + var keyEncryption = x509Cert.GetPublicKeyAsymmetricAlgorithm(); + + using (var dataEncryption = SymmetricAlgorithm.Create(dataEncryptionAlgorithmName)) + { + if (null == dataEncryption) throw new CryptographicException($"Not able to create Symmetric Data Encryption Algorithm using {dataEncryptionAlgorithmName}"); + + // Select suggested keySize/blockSize. + dataEncryption.KeySize = keySize; + dataEncryption.BlockSize = blockSize; + EncryptStream(inputStream, outputStream, includeUTCTimeStamp, keyEncryption, dataEncryption); + } + } + + + #endregion + + #region Private Helpers + //............................................................................... + //Encrypt/Decrypt the Key (Asymmetric) and the Data (Symmetric) + //............................................................................... + static void EncryptStream( + Stream inputStream, + Stream outputStream, + bool includeUTCTimeStamp, + AsymmetricAlgorithm keyEncryption, + SymmetricAlgorithm dataEncryption) + { + if (null == keyEncryption) throw new ArgumentNullException(nameof(keyEncryption)); + if (null == dataEncryption) throw new ArgumentNullException(nameof(dataEncryption)); + + // About... + // Trace.WriteLine($"Encrypting. KEK: {keyEncryption.GetType().Name} / {keyEncryption.KeySize} bits"); + // Trace.WriteLine($"Encrypting. DEK: {dataEncryption.GetType().Name} / {dataEncryption.KeySize} bits / BlockSize: {dataEncryption.BlockSize} bits"); + + var keyFormatter = new RSAPKCS1KeyExchangeFormatter(keyEncryption); + if (true == includeUTCTimeStamp) + { + var currentUTCTimeTicks = DateTime.UtcNow.Ticks; + var currentUTCTimeBytes = BitConverter.GetBytes(currentUTCTimeTicks); + var encryptedUTCTimeBytes = keyFormatter.CreateKeyExchange(currentUTCTimeBytes); + outputStream.WriteLengthAndBytes(encryptedUTCTimeBytes); + } + // The DataEncryptionKey and the IV. + var DEK = dataEncryption.Key ?? throw new Exception("SymmetricAlgorithm.Key was NULL"); + var IV = dataEncryption.IV ?? throw new Exception("SymmetricAlgorithm.IV was NULL"); + + // Encrypt the DataEncryptionKey and the IV + var encryptedDEK = keyFormatter.CreateKeyExchange(DEK); + var encryptedIV = keyFormatter.CreateKeyExchange(IV); + + // Write the Encrypted DEK and IV + outputStream.WriteLengthAndBytes(encryptedDEK); + outputStream.WriteLengthAndBytes(encryptedIV); + + // Write the encrypted data. + // Note: Disposing the CryptoStream also disposes the outputStream. There is no keepOpen option. + using (var transform = dataEncryption.CreateEncryptor()) + using (var cryptoStream = new CryptoStream(outputStream, transform, CryptoStreamMode.Write)) + { + inputStream.CopyTo(cryptoStream, bufferSize: dataEncryption.BlockSize * 4); + } + } + #endregion + } +} diff --git a/src/Org.Security.Cryptography.X509Extensions/X509CertificateBasedDecryptor.cs b/src/Org.Security.Cryptography.X509Extensions/X509CertificateBasedDecryptor.cs new file mode 100644 index 0000000..d2a5d89 --- /dev/null +++ b/src/Org.Security.Cryptography.X509Extensions/X509CertificateBasedDecryptor.cs @@ -0,0 +1,100 @@ +using System; +using System.IO; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace Org.Security.Cryptography +{ + public class X509CertificateBasedDecryptor + { + + #region public + public void DecryptStream( + Stream inputStream, + Stream outputStream, + Func certificateSelector, + string dataEncryptionAlgorithmName = Defaults.DEF_DataEncryptionAlgorithmName) + { + var certificateForKeyEncryption = GetCertificateFromStreamAfterValidations(inputStream, outputStream, dataEncryptionAlgorithmName, certificateSelector); + certificateForKeyEncryption.DecryptStreamWithTimestampValidation(inputStream, outputStream, false, dataEncryptionAlgorithmName); + } + + public string DecryptBase64EncodedString( + string valueToDecode, + Func certificateSelector) + { + var inputData = Convert.FromBase64String(valueToDecode); + using (var input = new MemoryStream(inputData)) + using (var output = new MemoryStream(inputData.Length)) + { + this.DecryptStream(input, output, certificateSelector); + output.Flush(); + var outputArray = output.ToArray(); + return Encoding.UTF8.GetString(outputArray); + } + } + public void DecryptStreamWithTimestampValidation( + Stream inputStream, + Stream outputStream, + Func certificateSelector, + string dataEncryptionAlgorithmName = Defaults.DEF_DataEncryptionAlgorithmName + ) + { + this.DecryptStreamWithTimestampValidation( + inputStream, + outputStream, + certificateSelector, + TimeSpan.FromMinutes(1), + dataEncryptionAlgorithmName); + } + public void DecryptStreamWithTimestampValidation( + Stream inputStream, + Stream outputStream, + Func certificateSelector, + TimeSpan lifeSpanOfInput, + string dataEncryptionAlgorithmName = Defaults.DEF_DataEncryptionAlgorithmName + ) + { + X509Certificate2 certificateForKeyEncryption = GetCertificateFromStreamAfterValidations(inputStream, outputStream, dataEncryptionAlgorithmName, certificateSelector); + + certificateForKeyEncryption.DecryptStreamWithTimestampValidation(inputStream, outputStream, lifeSpanOfInput, dataEncryptionAlgorithmName); + } + + public string DecryptBase64EncodedStringWithTimestampValidation( + string valueToDecode, + Func certificateSelector) + { + return this.DecryptBase64EncodedStringWithTimestampValidation(valueToDecode, certificateSelector, Defaults.EncyptedPayloadTimeSpan); + } + public string DecryptBase64EncodedStringWithTimestampValidation( + string valueToDecode, + Func certificateSelector, + TimeSpan lifeSpanOfInput, + string dataEncryptionAlgorithmName = Defaults.DEF_DataEncryptionAlgorithmName) + { + var inputData = Convert.FromBase64String(valueToDecode); + using (var input = new MemoryStream(inputData)) + using (var output = new MemoryStream(inputData.Length)) + { + this.DecryptStreamWithTimestampValidation(input, output, certificateSelector, lifeSpanOfInput, dataEncryptionAlgorithmName); + output.Flush(); + var outputArray = output.ToArray(); + return Encoding.UTF8.GetString(outputArray); + } + } + #endregion + + #region Private helpers + private static X509Certificate2 GetCertificateFromStreamAfterValidations(Stream inputStream, Stream outputStream, string dataEncryptionAlgorithmName, Func certificateSelector) + { + if (null == certificateSelector) throw new ArgumentNullException(nameof(certificateSelector)); + var thumprintArray = inputStream.ReadLengthAndBytes(maxBytes: 2048); + var certificateThumbprint = Encoding.UTF8.GetString(thumprintArray); + var certificateForKeyEncryption = certificateSelector(certificateThumbprint); + Validator.ValidateParametersAndThrowException(certificateForKeyEncryption, inputStream, outputStream, dataEncryptionAlgorithmName); + return certificateForKeyEncryption; + } + #endregion + } + +} \ No newline at end of file diff --git a/src/Org.Security.Cryptography.X509Extensions/X509CertificateBasedEncryptor.cs b/src/Org.Security.Cryptography.X509Extensions/X509CertificateBasedEncryptor.cs new file mode 100644 index 0000000..c49dd83 --- /dev/null +++ b/src/Org.Security.Cryptography.X509Extensions/X509CertificateBasedEncryptor.cs @@ -0,0 +1,136 @@ +using System; +using System.IO; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace Org.Security.Cryptography +{ + /// + /// Provides capabilities to encrypt data using X509 certificate. + /// + public class X509CertificateBasedEncryptor + { + #region public + /// + /// Encrypt input stream using the symmetric algorithm provided in + /// The key of symmetric algorithm is encrypted using the Asymmetric algorithm available on the given + /// Certificate to encrypt + /// + /// + /// Name of symmetric algorithm. Defaults to 'Aes' + /// + /// + /// The thumbprint of the certificate is attached as first thing in the encrypted data. + /// Use this if decryptor doesn't know what certificate encryptor used. Mainly in internet/web/distributed systems scenarios where the certificate rotated out of sync. + /// + public void EncryptStreamWithTimestamp( + X509Certificate2 x509Cert, + Stream inputStream, + Stream outputStream, + string dataEncryptionAlgorithmName = Defaults.DEF_DataEncryptionAlgorithmName, + int keySize = Defaults.DEF_KeySize, + int blockSize = Defaults.DEF_BlockSize) + { + ValidateAndWritePlainThumprintToOutputStream(x509Cert, outputStream); + x509Cert.EncryptStream(inputStream, outputStream, true, dataEncryptionAlgorithmName, keySize, blockSize); + } + + /// + /// Encrypt input stream using the symmetric algorithm provided in . + /// The key of symmetric algorithm is encrypted using the Asymmetric algorithm available on the given + /// Certificate to encrypt + /// + /// + /// Name of symmetric algorithm. Defaults to 'Aes' + /// + /// + /// The thumbprint of the certificate is attached as first thing in the encrypted data. + /// Use this if decryptor doesn't know what certificate encryptor used. Mainly in internet/web/distributed systems scenarios where the certificate rotated out of sync. + /// + public void EncryptStream(X509Certificate2 x509Cert, + Stream inputStream, + Stream outputStream, + string dataEncryptionAlgorithmName = Defaults.DEF_DataEncryptionAlgorithmName, + int keySize = Defaults.DEF_KeySize, + int blockSize = Defaults.DEF_BlockSize) + { + ValidateAndWritePlainThumprintToOutputStream(x509Cert, outputStream); + x509Cert.EncryptStream(inputStream, outputStream, false, dataEncryptionAlgorithmName, keySize, blockSize); + } + /// + /// Encrypt using the symmetric algorithm provided in . + /// The key of symmetric algorithm is encrypted using the Asymmetric algorithm available on the given . + /// + /// Certificate to encrypt + /// The input string to encode + /// Name of symmetric algorithm. Defaults to 'Aes' + /// + /// + /// The encrypted content in base64 format. + /// + /// The thumbprint of the certificate is attached as first thing in the encrypted data. + /// Use this if decryptor doesn't know what certificate encryptor used. Mainly in internet/web/distributed systems scenarios where the certificate rotated out of sync. + /// + public string EncryptStringToBase64(X509Certificate2 x509Cert, + string valueToEncode, + string dataEncryptionAlgorithmName = Defaults.DEF_DataEncryptionAlgorithmName, + int keySize = Defaults.DEF_KeySize, + int blockSize = Defaults.DEF_BlockSize) + { + var inputData = Encoding.UTF8.GetBytes(valueToEncode); + using (var input = new MemoryStream(inputData)) + using (var output = new MemoryStream(inputData.Length)) + { + this.EncryptStream(x509Cert, input, output, dataEncryptionAlgorithmName, keySize, blockSize); + output.Flush(); + var outputArray = output.ToArray(); + //TODO: Consider using CryptoStream to convert into base64 https://stackoverflow.com/questions/19134062/encode-a-filestream-to-base64-with-c-sharp + return Convert.ToBase64String(outputArray); + } + } + /// + /// Encrypt using the symmetric algorithm provided including the timestamp of emcryption. + /// The key of symmetric algorithm is encrypted using the Asymmetric algorithm available on the given . + /// + /// Certificate to encrypt + /// The input string to encode + /// Name of symmetric algorithm. Defaults to 'Aes' + /// + /// + /// The encrypted content in base64 format. + /// The thumbprint of the certificate is attached as first thing in the encrypted data. + /// Use this if decryptor doesn't know what certificate encryptor used. Mainly in internet/web/distributed systems scenarios where the certificate rotated out of sync. + /// The timestamped encrypted payload is good in client-server or internet scenarios to avoid re-play attacks. + /// + public string EncryptStringToBase64WithTimestamp(X509Certificate2 x509Cert, + string valueToEncode, + string dataEncryptionAlgorithmName = Defaults.DEF_DataEncryptionAlgorithmName, + int keySize = Defaults.DEF_KeySize, + int blockSize = Defaults.DEF_BlockSize) + { + var inputData = Encoding.UTF8.GetBytes(valueToEncode); + using (var input = new MemoryStream(inputData)) + using (var output = new MemoryStream(inputData.Length)) + { + this.EncryptStreamWithTimestamp(x509Cert, input, output, dataEncryptionAlgorithmName, keySize, blockSize); + output.Flush(); + var outputArray = output.ToArray(); + return Convert.ToBase64String(outputArray); + } + } + + #endregion + + #region Private helpers + private static void ValidateAndWritePlainThumprintToOutputStream(X509Certificate2 x509Cert, Stream outputStream) + { + if (null == x509Cert) throw new ArgumentNullException(nameof(x509Cert)); + var thumbprintArray = Encoding.UTF8.GetBytes(x509Cert.Thumbprint); + outputStream.WriteLengthAndBytes(thumbprintArray); + } + #endregion + } +} \ No newline at end of file diff --git a/src/Org.Security.Cryptography.X509Extensions/X509SignatureExtensions.cs b/src/Org.Security.Cryptography.X509Extensions/X509SignatureExtensions.cs index 9e24893..0d8da95 100644 --- a/src/Org.Security.Cryptography.X509Extensions/X509SignatureExtensions.cs +++ b/src/Org.Security.Cryptography.X509Extensions/X509SignatureExtensions.cs @@ -19,8 +19,9 @@ public static byte[] CreateSignature(this X509Certificate2 x509Cert, byte[] mess var hashAlgorithmName = InferHashAlgorithm(messageDigest); var formatter = new RSAPKCS1SignatureFormatter(asymmetricAlgorithm); + formatter.SetHashAlgorithm(hashAlgorithmName); - + // The below code will throw exception when running in .Net Framework, if the algorithm is MD5 return formatter.CreateSignature(messageDigest); } @@ -45,15 +46,12 @@ public static bool VerifySignature(this X509Certificate2 x509Cert, byte[] hash, static string InferHashAlgorithm(byte[] hash) { - if (null == hash) throw new ArgumentNullException(nameof(hash)); - - // MD5 128 bit / 16 bytes - // SHA1 160 bit / 20 bytes - // SHA224 224 bit / 28 bytes - // SHA265 256 bit / 32 bytes - // SHA384 384 bit / 48 bytes - // SHA512 512 bit / 64 bytes - + // MD5 128 bit /8 = 16 bytes + // SHA1 160 bit /8 = 20 bytes + // SHA224 224 bit /8 = 28 bytes + // SHA265 256 bit /8 = 32 bytes + // SHA384 384 bit /8 = 48 bytes + // SHA512 512 bit /8 = 64 bytes switch (hash.Length) { case 16: return HashAlgorithmName.MD5.Name; @@ -66,4 +64,4 @@ static string InferHashAlgorithm(byte[] hash) } } } -} +} \ No newline at end of file diff --git a/src/Org.Security.Cryptography.X509Extensions/X509StreamEncryptionExtensions.cs b/src/Org.Security.Cryptography.X509Extensions/X509StreamEncryptionExtensions.cs deleted file mode 100644 index fb6b6fd..0000000 --- a/src/Org.Security.Cryptography.X509Extensions/X509StreamEncryptionExtensions.cs +++ /dev/null @@ -1,184 +0,0 @@ - -using System; -using System.IO; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; - -namespace Org.Security.Cryptography -{ - /// - /// Extensions to encrypt/decrypt Streams using X509 Certificates. - /// DEFAULT: AES-256/128 for Data encryption. - /// - public static class X509StreamEncryptionExtensions - { - // Defaults - const string DEF_DataEncryptionAlgorithmName = "Aes"; - const int DEF_KeySize = 256; - const int DEF_BlockSize = 128; - - /// - /// The X509 Certificate public key serves as KeyEncryptionKey (KEK). - /// Data is encrypted using a randomly generated DataEncryptionKey and IV. - /// Writes encrypted DataEncryptionKey, encrypted IV and encrypted data to the output stream. - /// NOTE: The OutputStream will be disposed at the end of this call. - /// - public static void EncryptStream(this X509Certificate2 x509Cert, Stream inputStream, Stream outputStream, string dataEncryptionAlgorithmName = DEF_DataEncryptionAlgorithmName, int keySize = DEF_KeySize, int blockSize = DEF_BlockSize) - { - if (null == x509Cert) throw new ArgumentNullException(nameof(x509Cert)); - if (null == inputStream) throw new ArgumentNullException(nameof(inputStream)); - if (null == outputStream) throw new ArgumentNullException(nameof(outputStream)); - if (null == dataEncryptionAlgorithmName) throw new ArgumentNullException(nameof(dataEncryptionAlgorithmName)); - - // Encrypt using Public key. - // DO NOT Dispose this; Doing so will render the X509Certificate use-less, if the caller had cached the cert. - // We didn't acquire the X509 Certificate; Caller is responsible for disposing X509Certificate2. - // Did endurance test of 1 mil cycles, found NO HANDLE leak. - var keyEncryption = x509Cert.GetPublicKeyAsymmetricAlgorithm(); - - using (var dataEncryption = SymmetricAlgorithm.Create(dataEncryptionAlgorithmName)) - { - if (null == dataEncryption) throw new Exception($"SymmetricAlgorithm.Create('{dataEncryptionAlgorithmName}') returned null."); - - // Select suggested keySize/blockSize. - dataEncryption.KeySize = keySize; - dataEncryption.BlockSize = blockSize; - EncryptStream(inputStream, outputStream, keyEncryption, dataEncryption); - } - } - - /// - /// The X509 Certificate private key serves as KeyEncryptionKey (KEK). - /// Reads and decrypts the Encrypted DataEncryptionKey and Encrypted IV using the KEK. - /// Decrypts the data using the DataEncryptionKey and IV. - /// NOTE: The InputStream will be disposed at the end of this call. - /// - public static void DecryptStream(this X509Certificate2 x509Cert, Stream inputStream, Stream outputStream, string dataEncryptionAlgorithmName = DEF_DataEncryptionAlgorithmName) - { - if (null == x509Cert) throw new ArgumentNullException(nameof(x509Cert)); - if (null == inputStream) throw new ArgumentNullException(nameof(inputStream)); - if (null == outputStream) throw new ArgumentNullException(nameof(outputStream)); - if (null == dataEncryptionAlgorithmName) throw new ArgumentNullException(nameof(dataEncryptionAlgorithmName)); - - // Decrypt using Private key. - // DO NOT Dispose this; Doing so will render the X509Certificate use-less, if the caller had cached the cert. - // We didn't acquire the X509 Certificate; Caller is responsible for disposing X509Certificate2. - // Did endurance test of 1 mil cycles, found NO HANDLE leak. - var keyEncryption = x509Cert.GetPrivateKeyAsymmetricAlgorithm(); - - using (var dataEncryption = SymmetricAlgorithm.Create(dataEncryptionAlgorithmName)) - { - if (null == dataEncryption) throw new Exception($"SymmetricAlgorithm.Create('{dataEncryptionAlgorithmName}') returned null."); - - // KeySize/blockSize will be selected when we assign key/IV later. - DecryptStream(inputStream, outputStream, keyEncryption, dataEncryption); - } - } - - //............................................................................... - #region Encrypt/Decrypt the Key (Asymmetric) and the Data (Symmetric) - //............................................................................... - - static void EncryptStream(Stream inputStream, Stream outputStream, AsymmetricAlgorithm keyEncryption, SymmetricAlgorithm dataEncryption) - { - if (null == inputStream) throw new ArgumentNullException(nameof(inputStream)); - if (null == outputStream) throw new ArgumentNullException(nameof(outputStream)); - if (null == keyEncryption) throw new ArgumentNullException(nameof(keyEncryption)); - if (null == dataEncryption) throw new ArgumentNullException(nameof(dataEncryption)); - - // About... - // Trace.WriteLine($"Encrypting. KEK: {keyEncryption.GetType().Name} / {keyEncryption.KeySize} bits"); - // Trace.WriteLine($"Encrypting. DEK: {dataEncryption.GetType().Name} / {dataEncryption.KeySize} bits / BlockSize: {dataEncryption.BlockSize} bits"); - - // The DataEncryptionKey and the IV. - var DEK = dataEncryption.Key ?? throw new Exception("SymmetricAlgorithm.Key was NULL"); - var IV = dataEncryption.IV ?? throw new Exception("SymmetricAlgorithm.IV was NULL"); - - // Encrypt the DataEncryptionKey and the IV - var keyFormatter = new RSAPKCS1KeyExchangeFormatter(keyEncryption); - var encryptedDEK = keyFormatter.CreateKeyExchange(DEK); - var encryptedIV = keyFormatter.CreateKeyExchange(IV); - - // Write the Encrypted DEK and IV - outputStream.WriteLengthAndBytes(encryptedDEK); - outputStream.WriteLengthAndBytes(encryptedIV); - - // Write the encrypted data. - // Note: Disposing the CryptoStream also disposes the outputStream. There is no keepOpen option. - using (var transform = dataEncryption.CreateEncryptor()) - using (var cryptoStream = new CryptoStream(outputStream, transform, CryptoStreamMode.Write)) - { - inputStream.CopyTo(cryptoStream, bufferSize: dataEncryption.BlockSize * 4); - } - } - - static void DecryptStream(Stream inputStream, Stream outputStream, AsymmetricAlgorithm keyEncryption, SymmetricAlgorithm dataEncryption) - { - if (null == inputStream) throw new ArgumentNullException(nameof(inputStream)); - if (null == outputStream) throw new ArgumentNullException(nameof(outputStream)); - if (null == keyEncryption) throw new ArgumentNullException(nameof(keyEncryption)); - if (null == dataEncryption) throw new ArgumentNullException(nameof(dataEncryption)); - - var encryptedDEK = inputStream.ReadLengthAndBytes(maxBytes: 2048); - var encryptedIV = inputStream.ReadLengthAndBytes(maxBytes: 2048); - - var keyDeformatter = new RSAPKCS1KeyExchangeDeformatter(keyEncryption); - dataEncryption.Key = keyDeformatter.DecryptKeyExchange(encryptedDEK); - dataEncryption.IV = keyDeformatter.DecryptKeyExchange(encryptedIV); - - // About... - // Trace.WriteLine($"Decrypting. KEK: {keyEncryption.GetType().Name} / {keyEncryption.KeySize} bits"); - // Trace.WriteLine($"Decrypting. DEK: {dataEncryption.GetType().Name} / {dataEncryption.KeySize} bits / BlockSize: {dataEncryption.BlockSize} bits"); - - // Read the encrypted data. - // Note: Disposing the CryptoStream also disposes the inputStream. There is no keepOpen option. - using (var transform = dataEncryption.CreateDecryptor()) - using (var cryptoStream = new CryptoStream(inputStream, transform, CryptoStreamMode.Read)) - { - cryptoStream.CopyTo(outputStream, bufferSize: dataEncryption.BlockSize * 4); - } - } - - #endregion - - //............................................................................... - #region Utils: WriteLengthAndBytes(), ReadLengthAndBytes() - //............................................................................... - static void WriteLengthAndBytes(this Stream outputStream, byte[] bytes) - { - if (null == outputStream) throw new ArgumentNullException(nameof(outputStream)); - if (null == bytes) throw new ArgumentNullException(nameof(bytes)); - - // Int32 length to exactly-four-bytes array. - var length = BitConverter.GetBytes((Int32)bytes.Length); - - // Write the four-byte-length followed by the data. - outputStream.Write(length, 0, length.Length); - outputStream.Write(bytes, 0, bytes.Length); - } - - static byte[] ReadLengthAndBytes(this Stream inputStream, int maxBytes) - { - if (null == inputStream) throw new ArgumentNullException(nameof(inputStream)); - - // Read an Int32, exactly four bytes. - var arrLength = new byte[4]; - var bytesRead = inputStream.Read(arrLength, 0, 4); - if (bytesRead != 4) throw new Exception("Unexpected end of InputStream. Expecting 4 bytes."); - - // Length of data to read. - var length = BitConverter.ToInt32(arrLength, 0); - if (length > maxBytes) throw new Exception($"Unexpected data size {length:#,0} bytes. Expecting NOT more than {maxBytes:#,0} bytes."); - - // Read suggested no of bytes... - var bytes = new byte[length]; - bytesRead = inputStream.Read(bytes, 0, bytes.Length); - if (bytesRead != bytes.Length) throw new Exception($"Unexpected end of input stream. Expecting {bytes.Length:#,0} bytes."); - - return bytes; - } - - #endregion - - } -} diff --git a/src/Org.Security.Cryptography.X509Extensions/api/.gitignore b/src/Org.Security.Cryptography.X509Extensions/api/.gitignore new file mode 100644 index 0000000..e8079a3 --- /dev/null +++ b/src/Org.Security.Cryptography.X509Extensions/api/.gitignore @@ -0,0 +1,5 @@ +############### +# temp file # +############### +*.yml +.manifest diff --git a/src/Org.Security.Cryptography.X509Extensions/api/index.md b/src/Org.Security.Cryptography.X509Extensions/api/index.md new file mode 100644 index 0000000..7e715d8 --- /dev/null +++ b/src/Org.Security.Cryptography.X509Extensions/api/index.md @@ -0,0 +1,3 @@ +# Welcome to Org.Security.Cryptography + +This library contains classes and extension methods to work with cryptography in .Net. Feel free to explore the APIs. \ No newline at end of file diff --git a/src/Org.Security.Cryptography.X509Extensions/articles/FIPS.md b/src/Org.Security.Cryptography.X509Extensions/articles/FIPS.md new file mode 100644 index 0000000..89c0586 --- /dev/null +++ b/src/Org.Security.Cryptography.X509Extensions/articles/FIPS.md @@ -0,0 +1,54 @@ + +# About Federal Information Processing Standard + +Is this package FIPS compliant? Depends. + +### .NET Core Federal Information Processing Standard (FIPS) compliance +* [.NET Core and FIPS](https://docs.microsoft.com/en-us/dotnet/standard/security/fips-compliance) +* Does not enforce the use of FIPS Approved algorithms or key sizes in .NET Core apps. +* The developer is responsible for ensuring that non-compliant FIPS algorithms aren't used. + +### Microsoft’s approach to FIPS 140-2 validation + +* [Microsoft’s approach to FIPS 140-2 validation](https://docs.microsoft.com/en-us/windows/security/threat-protection/fips-140-validation) +* [Using Windows in a FIPS 140-2 approved mode](https://docs.microsoft.com/en-us/windows/security/threat-protection/fips-140-validation#using-windows-in-a-fips-140-2-approved-mode-of-operation) +* [Use FIPS compliant algorithms for encryption, hashing, and signing](https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/system-cryptography-use-fips-compliant-algorithms-for-encryption-hashing-and-signing) +* [Why We’re Not Recommending FIPS Mode Anymore](https://docs.microsoft.com/en-us/archive/blogs/secguide/why-were-not-recommending-fips-mode-anymore) + +### Windows FIPS Mode +> This policy setting determines whether the TLS/SSL security provider supports only the FIPS-compliant strong cipher suite known as TLS_RSA_WITH_3DES_EDE_CBC_SHA, which means that +> * the provider only supports the TLS protocol as a client computer and as a server, if applicable. +> * uses only the Triple Data Encryption Standard (3DES) encryption algorithm for the TLS traffic encryption, +> * only the Rivest-Shamir-Adleman (RSA) public key algorithm for the TLS key exchange and authentication, +> * and only the Secure Hash Algorithm version 1 (SHA-1) hashing algorithm for the TLS hashing requirements. + +## More + +Credit: [AesCryptoServiceProvider and FIPS mode](https://social.msdn.microsoft.com/Forums/vstudio/en-US/521b669d-09d8-46c9-812b-843b611f42e4/aescryptoserviceprovider-and-fips-mode) + +Aes algorithm (as in "the algorithm") is FIPS 140-2 compliant. +Aes algorithm implementation by Microsoft (Enhanced Cryptographic Provider in rsaenh.dll) is also FIPS 140-2 compliant. +System.Security.Cryptography.AesCryptoServiceProvider uses rsaenh.dll CSP, hence is its also FIPS 140-2 compliant. + +As an example, AesManaged DOESN'T use rsaenh.dll CSP. +AesManaged checks for FIPS mode and will throw an exception is FIPS compliance is turned on. + +Strictly speaking it's not the AesCryptoServiceProvider or AesManaged that are FIPS 140-2 compliant. +Its the underlying libraries accessed through the CAPI (like the Enhanced Cryptographic Provider in rsaenh.dll). +All other .NET CSPs, e.g. AesManaged or MD5CryptoServiceProvider, that do not rely on this libraries are not compliant. + +The security policy FIPS mode simply turns on a flag in the registry, nothing more +REF: HKLM\SYSTEM\CurrentControlSet\Control\Lsa\fipsalgorithmpolicy) + +The AesManaged class for example checks the flag in its constructor and +if it's set it simply throws an exception that tells the user that this is not a FIPS compliant algorithm, +because it doesn't call into the compliant libraries. + +Turning the flag in the registry ON suggestes the use of FIPS compliant algorithms. +But does not trigger any other system-side processing. +By enabling this flag, you'll get an exception every single time an application attempts to use a non-compliant algorithm. + +Since rsaenh.dll is FIPS compliant, the AesCryptoServiceProvider will not throw such an exception. +After all, it is only a thin wrapper around the CAPI (advapi32.dll, crypt32.dll). + + diff --git a/src/Org.Security.Cryptography.X509Extensions/articles/intro.md b/src/Org.Security.Cryptography.X509Extensions/articles/intro.md new file mode 100644 index 0000000..19ba50e --- /dev/null +++ b/src/Org.Security.Cryptography.X509Extensions/articles/intro.md @@ -0,0 +1,3 @@ +# X509Certificate2 Extensions and Classes for cryptography + +This is a library to provide cryptographic functions such as signing and encryption. For more details refer [Api documentation](../api/index.md) \ No newline at end of file diff --git a/src/Org.Security.Cryptography.X509Extensions/articles/simple-encryption.md b/src/Org.Security.Cryptography.X509Extensions/articles/simple-encryption.md new file mode 100644 index 0000000..88b2bee --- /dev/null +++ b/src/Org.Security.Cryptography.X509Extensions/articles/simple-encryption.md @@ -0,0 +1,15 @@ +# Simple encryption + +## Use cases +- Same application decrypting the content. +- Not transmitted over wire. +- The certificate is known to the decrypting application. + +## Payload +The basic encryption includes the below contents in the payload. + +![Simple](https://www.plantuml.com/plantuml/proxy?fmt=svg&cache=no&src=https://raw.githubusercontent.com/dotnet-demos/Org.Security.Cryptography.X509Extensions/master/diagrams/simple.puml) + +## Cons +- If the certificate is rotated out of sync between the encrypting and decrypting applications, the decryption will fail. +- If the content is transmitted over wire using encrypted auth information, attackers can intercept and replay using different HTTP or wire payload. \ No newline at end of file diff --git a/src/Org.Security.Cryptography.X509Extensions/articles/toc.yml b/src/Org.Security.Cryptography.X509Extensions/articles/toc.yml new file mode 100644 index 0000000..9a080a4 --- /dev/null +++ b/src/Org.Security.Cryptography.X509Extensions/articles/toc.yml @@ -0,0 +1,10 @@ +- name: Introduction + href: intro.md +- name: Simple encryption + href: simple-encryption.md +- name: Encryption with thumbprint of certificate + href: with-thumbprint.md +- name: Encryption with timestamp of encryption + href: with-timestamp.md +- name: Federal Information Processing Standard + href: FIPS.md \ No newline at end of file diff --git a/src/Org.Security.Cryptography.X509Extensions/articles/with-thumbprint.md b/src/Org.Security.Cryptography.X509Extensions/articles/with-thumbprint.md new file mode 100644 index 0000000..24f018a --- /dev/null +++ b/src/Org.Security.Cryptography.X509Extensions/articles/with-thumbprint.md @@ -0,0 +1,14 @@ +# Encryption with thumbprint of certificate + +## Use cases +- Different application decrypting the content. +- Not transmitted over wire. +- The certificate is rotated out of sync between encrypting and decrypting applications. + +## Payload +The encryption with thumbprint includes the below contents in the payload. + +![Thumbprint](https://www.plantuml.com/plantuml/proxy?fmt=svg&cache=no&src=https://raw.githubusercontent.com/dotnet-demos/Org.Security.Cryptography.X509Extensions/master/diagrams/thumbprint.puml) + +## Cons +- If the content is transmitted over wire using encrypted auth information, attackers can intercept and replay using different HTTP or wire payload. \ No newline at end of file diff --git a/src/Org.Security.Cryptography.X509Extensions/articles/with-timestamp.md b/src/Org.Security.Cryptography.X509Extensions/articles/with-timestamp.md new file mode 100644 index 0000000..44f652c --- /dev/null +++ b/src/Org.Security.Cryptography.X509Extensions/articles/with-timestamp.md @@ -0,0 +1,14 @@ +# Encryption with timestamp of encryption + +## Use cases +- Different application decrypting the content. +- Transmitted over wire as authentication. +- The certificate is rotated out of sync between encrypting and decrypting applications. + +## Payload +The encryption with timestamp includes the below contents in the payload. + +![timestamp](https://www.plantuml.com/plantuml/proxy?fmt=svg&cache=no&src=https://raw.githubusercontent.com/dotnet-demos/Org.Security.Cryptography.X509Extensions/master/diagrams/timestamp.puml) + +## Cons +- The time to decrypt may be little more. \ No newline at end of file diff --git a/src/Org.Security.Cryptography.X509Extensions/docfx.json b/src/Org.Security.Cryptography.X509Extensions/docfx.json new file mode 100644 index 0000000..602f158 --- /dev/null +++ b/src/Org.Security.Cryptography.X509Extensions/docfx.json @@ -0,0 +1,69 @@ +{ + "metadata": [ + { + "src": [ + { + "files": [ + "**.csproj" + ], + "src": "." + } + ], + "exclude": [ "**/bin/**", "**/obj/**" ], + "dest": "api", + "disableGitFeatures": false, + "disableDefaultFilter": false, + "properties": { + "TargetFramework": "netstandard2.0" + } + } + ], + "build": { + "content": [ + { + "files": [ + "api/**.yml", + "api/index.md" + ] + }, + { + "files": [ + "articles/**.md", + "articles/**/toc.yml", + "toc.yml", + "*.md" + ] + } + ], + "resource": [ + { + "files": [ + "images/**" + ] + } + ], + "overwrite": [ + { + "files": [ + "apidoc/**.md" + ], + "exclude": [ + "obj/**", + "_site/**" + ] + } + ], + "dest": "_site", + "globalMetadataFiles": [], + "fileMetadataFiles": [], + "template": [ + "default" + ], + "postProcessors": [], + "markdownEngineName": "markdig", + "noLangKeyword": false, + "keepFileLink": false, + "cleanupCacheHistory": false, + "disableGitFeatures": false + } +} diff --git a/src/Org.Security.Cryptography.X509Extensions/index.md b/src/Org.Security.Cryptography.X509Extensions/index.md new file mode 100644 index 0000000..523462f --- /dev/null +++ b/src/Org.Security.Cryptography.X509Extensions/index.md @@ -0,0 +1,13 @@ +# X509Certificate2 Extensions and Classes for cryptography + +Welcome to the **X509Certificate2 Extensions and Classes for cryptography** + +## Quick Start Notes: + +- Compile the libray +- Refer to your project + +## Documentation + +Refer [Api Documentation](api/index.md) + diff --git a/src/Org.Security.Cryptography.X509Extensions/log.txt b/src/Org.Security.Cryptography.X509Extensions/log.txt new file mode 100644 index 0000000..edff8c5 --- /dev/null +++ b/src/Org.Security.Cryptography.X509Extensions/log.txt @@ -0,0 +1,3 @@ +{"message":"Workspace failed with: [Failure] Msbuild failed when processing the file 'C:\\source\\repos\\dotnet-demos\\Org.Security.Cryptography.X509Extensions\\src\\Org.Security.Cryptography.X509Extensions\\Org.Security.Cryptography.X509Extensions.csproj' with message: Could not load SDK Resolver. A manifest file exists, but the path to the SDK Resolver DLL file could not be found. Manifest file path 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildTools\\MSBuild\\15.0\\Bin\\SdkResolvers\\Microsoft.Build.NuGetSdkResolver\\Microsoft.Build.NuGetSdkResolver.xml'. SDK resolver path: C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildTools\\Common7\\IDE\\CommonExtensions\\Microsoft\\NuGet\\Microsoft.Build.NuGetSdkResolver.dll C:\\source\\repos\\dotnet-demos\\Org.Security.Cryptography.X509Extensions\\src\\Org.Security.Cryptography.X509Extensions\\Org.Security.Cryptography.X509Extensions.csproj","source":"MetadataCommand.ExtractMetadata","file":"C:/source/repos/dotnet-demos/Org.Security.Cryptography.X509Extensions/src/Org.Security.Cryptography.X509Extensions/Org.Security.Cryptography.X509Extensions.csproj","date_time":"2021-08-20T15:24:45.3362903Z","message_severity":"warning","correlation_id":"702BD417-4435-4408-91B4-0A70A2DB163C.1.1.5"} +{"message":"Project 'C:\\source\\repos\\dotnet-demos\\Org.Security.Cryptography.X509Extensions\\src\\Org.Security.Cryptography.X509Extensions\\Org.Security.Cryptography.X509Extensions.csproj' does not contain any documents.","source":"MetadataCommand.ExtractMetadata","date_time":"2021-08-20T15:24:45.7318821Z","message_severity":"warning","correlation_id":"702BD417-4435-4408-91B4-0A70A2DB163C.1.1.8"} +{"message":"No metadata is generated for Org.Security.Cryptography.X509Extensions.","source":"MetadataCommand.ExtractMetadata","date_time":"2021-08-20T15:24:46.0762661Z","message_severity":"warning","correlation_id":"702BD417-4435-4408-91B4-0A70A2DB163C.1.1.13"} diff --git a/src/Org.Security.Cryptography.X509Extensions/toc.yml b/src/Org.Security.Cryptography.X509Extensions/toc.yml new file mode 100644 index 0000000..59f8010 --- /dev/null +++ b/src/Org.Security.Cryptography.X509Extensions/toc.yml @@ -0,0 +1,5 @@ +- name: Articles + href: articles/ +- name: Api Documentation + href: api/ + homepage: api/index.md diff --git a/src/UnitTests/App.config b/src/UnitTests/App.config index d5c2631..a0ca823 100644 --- a/src/UnitTests/App.config +++ b/src/UnitTests/App.config @@ -3,6 +3,9 @@ + + + + - diff --git a/src/UnitTests/MyConfig.cs b/src/UnitTests/MyConfig.cs deleted file mode 100644 index 467d930..0000000 --- a/src/UnitTests/MyConfig.cs +++ /dev/null @@ -1,13 +0,0 @@ - -using System; -using System.Configuration; - -namespace UnitTests -{ - internal static class MyConfig - { - internal static string TestCertThumbPrint => - ConfigurationManager.AppSettings["X509.ThumbPrint"] ?? - throw new Exception($"AppSetting 'X509.ThumbPrint' not defined."); - } -} diff --git a/src/UnitTests/SignAndVerify/X509Certificate2_CreateSignature.cs b/src/UnitTests/SignAndVerify/X509Certificate2_CreateSignature.cs new file mode 100644 index 0000000..54de869 --- /dev/null +++ b/src/UnitTests/SignAndVerify/X509Certificate2_CreateSignature.cs @@ -0,0 +1,40 @@ + +using System; +using Org.Security.Cryptography; +using System.Security.Cryptography; +using System.Text; + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Security.Cryptography.X509Certificates; + +namespace UnitTests.SignAndVerify +{ + [TestClass] + public class X509Certificate2_CreateSignature + { + #region Validation tests + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void WhenCertificateIsNull_ThrowArgumentNullException() + { + //arrange + const string TestData = "Hello world"; + var payload = Encoding.UTF8.GetBytes(TestData); + X509Certificate2 certificate2 = null; + //Act + using var hashAlgorithm = HashAlgorithm.Create("MD5"); + var hash = hashAlgorithm.ComputeHash(payload); + certificate2.CreateSignature(hash); + } + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void WhenPayloadIsNull_ThrowArgumentNullException() + { + //arrange + //Act + using var hashAlgorithm = HashAlgorithm.Create("MD5"); + MyConfig.SigningCertificate.CreateSignature(null); + } + #endregion + } +} \ No newline at end of file diff --git a/src/UnitTests/SignAndVerify/X509Certificate2_VerifySignature.cs b/src/UnitTests/SignAndVerify/X509Certificate2_VerifySignature.cs new file mode 100644 index 0000000..8dc3905 --- /dev/null +++ b/src/UnitTests/SignAndVerify/X509Certificate2_VerifySignature.cs @@ -0,0 +1,88 @@ + +using System; +using Org.Security.Cryptography; +using System.Security.Cryptography; +using System.Text; + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Security.Cryptography.X509Certificates; + +namespace UnitTests.SignAndVerify +{ + [TestClass] + public class X509Certificate2_VerifySignature + { + #region Validation tests + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void WhenCertificateIsNull_ThrowArgumentNullException() + { + //Arrange + const string TestData = "Hello world"; + var payload = Encoding.UTF8.GetBytes(TestData); + using var hashAlgorithm = HashAlgorithm.Create("MD5"); + var hash = hashAlgorithm.ComputeHash(payload); + var signature = MyConfig.SigningCertificate.CreateSignature(hash); + X509Certificate2 certificate2 = null; + // Act + var good = certificate2.VerifySignature(hash, signature); + Assert.IsTrue(good); + } + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void WhenSignatureIsNull_ThrowArgumentNullException() + { + //Arrange + const string TestData = "Hello world"; + var payload = Encoding.UTF8.GetBytes(TestData); + using var hashAlgorithm = HashAlgorithm.Create("MD5"); + var hash = hashAlgorithm.ComputeHash(payload); + // Act + var good = MyConfig.VerifyCertificate.VerifySignature(hash, null); + Assert.IsTrue(good); + } + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void WhenHashIsNull_ThrowArgumentNullException() + { + //Arrange + const string TestData = "Hello world"; + var payload = Encoding.UTF8.GetBytes(TestData); + using var hashAlgorithm = HashAlgorithm.Create("MD5"); + var hash = hashAlgorithm.ComputeHash(payload); + var signature = MyConfig.SigningCertificate.CreateSignature(hash); + // Act + var good = MyConfig.VerifyCertificate.VerifySignature(null, signature); + Assert.IsTrue(good); + } + #endregion + + #region Happy scenarios + [TestMethod] + public void UsingMultipleHashAlgorithms_ShouldWork() + { + const string TestData = "Hello world"; + + var payload = Encoding.UTF8.GetBytes(TestData); + + string[] HashAlgorithmNames = { "SHA256", "SHA384", "MD5", "SHA1", "SHA512" }; + + foreach (var name in HashAlgorithmNames) + { + using var hashAlgorithm = HashAlgorithm.Create(name); + // Digest + var hash = hashAlgorithm.ComputeHash(payload); + Console.WriteLine($"Hash: {name} {hash.Length * 8} bits / {hash.Length} BYTES"); + + // Sign + var signature = MyConfig.SigningCertificate.CreateSignature(hash); + Console.WriteLine($"Signature: {signature.Length * 8} bits / {signature.Length} BYTES"); + + // Verify + var good = MyConfig.VerifyCertificate.VerifySignature(hash, signature); + Assert.IsTrue(good); + } + } + #endregion + } +} \ No newline at end of file diff --git a/src/UnitTests/TestCertificates/hello.world.2048.net.cer b/src/UnitTests/TestCertificates/hello.world.2048.net.cer new file mode 100644 index 0000000..0dd3a0d Binary files /dev/null and b/src/UnitTests/TestCertificates/hello.world.2048.net.cer differ diff --git a/src/UnitTests/TestCertificates/hello.world.2048.net.pfx b/src/UnitTests/TestCertificates/hello.world.2048.net.pfx new file mode 100644 index 0000000..3f844f6 Binary files /dev/null and b/src/UnitTests/TestCertificates/hello.world.2048.net.pfx differ diff --git a/src/UnitTests/UnitTests.csproj b/src/UnitTests/UnitTests.csproj index a4bb841..2c22c7e 100644 --- a/src/UnitTests/UnitTests.csproj +++ b/src/UnitTests/UnitTests.csproj @@ -1,12 +1,19 @@  - netcoreapp3.1 + netcoreapp3.1;net6.0; false - + + 9.0 + + + all + runtime; build; native; contentfiles; analyzers + + @@ -16,6 +23,19 @@ + + + + + + PreserveNewest + + + Always + + + Always + + - - - + --> + + + + \ No newline at end of file diff --git a/src/UnitTests/X509Certificate2_DecryptStream.cs b/src/UnitTests/X509Certificate2_DecryptStream.cs new file mode 100644 index 0000000..28fb676 --- /dev/null +++ b/src/UnitTests/X509Certificate2_DecryptStream.cs @@ -0,0 +1,33 @@ + +using System; +using System.Linq; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Org.Security.Cryptography; +using X509.EnduranceTest.Shared; + +namespace UnitTests.Decryption +{ + [TestClass] + public class X509Certificate2_DecryptStream + { + [TestMethod] + [ExpectedException(typeof(Exception))] + public void WhenDecryptStreamIsCalledWithCertThatDontHavePrivateKey_ThrowException() + { + //Arrange + CacheManager.ClearCache(); + const string TEST = "Hello World!"; + var x509CertWithoutPrivateKey = CertificateLoader.LoadFromFile("TestCertificates/hello.world.2048.net.cer"); + byte[] input = Encoding.UTF8.GetBytes(TEST); + //Act + byte[] output1 = EncryptionDecryptionUtils.DecryptBytesUsingExtensionMethod( + x509CertWithoutPrivateKey, + EncryptionDecryptionUtils.EncryptBytesUsingExtensionMethod(x509CertWithoutPrivateKey, input)); + //Assert + Assert.IsTrue(input.SequenceEqual(output1)); + } + + } +} + diff --git a/src/UnitTests/X509Certificate2_DecryptStream_PerfTests.cs b/src/UnitTests/X509Certificate2_DecryptStream_PerfTests.cs new file mode 100644 index 0000000..c6586a6 --- /dev/null +++ b/src/UnitTests/X509Certificate2_DecryptStream_PerfTests.cs @@ -0,0 +1,51 @@ + +using System; +using System.Linq; +using System.Diagnostics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X509.EnduranceTest.Shared; + +namespace UnitTests.Decryption +{ + [TestClass] + public class X509Certificate2_DecryptStream_PerfTests + { + [TestMethod] + public void X509_Benchmark_Decryption() + { + const int SampleDataSizeInKB = 8; + const int BenchmarkLoopCount = 1000; + + // Generate some random data + // Perform a dry run + // Capture Encrypted and Decrypted version + var SampleData = TestDataGenerator.GenerateJunk(SampleDataSizeInKB); + var encryptedBytes = EncryptionDecryptionUtils.EncryptBytesUsingExtensionMethod(MyConfig.EncryptionCertificate, SampleData); + var decryptedBytes = EncryptionDecryptionUtils.DecryptBytesUsingExtensionMethod(MyConfig.DecryptionCertificate, encryptedBytes); + + Assert.IsTrue(decryptedBytes.SequenceEqual(SampleData), "Decrypted bytes doesn't match original data."); + + //Act + var timer = Stopwatch.StartNew(); + + for (int i = 0; i < BenchmarkLoopCount; i++) + { + EncryptionDecryptionUtils.DecryptBytesUsingExtensionMethod(MyConfig.DecryptionCertificate, encryptedBytes); + } + timer.Stop(); + //Assert + var totalMs = timer.Elapsed.TotalMilliseconds; + var totalSec = timer.Elapsed.TotalSeconds; + var avgMs = totalMs / BenchmarkLoopCount; + var ratePerSec = BenchmarkLoopCount / totalSec; + + Console.WriteLine("Decryption Benchmark:"); + Console.WriteLine($"SampleDataSize: {SampleData.Length / 1024:#,0} KB"); + Console.WriteLine($"Elapsed: {timer.Elapsed}"); + Console.WriteLine($"{BenchmarkLoopCount:#,0} iterations @ {ratePerSec:#,0.0} per Sec. / Average: {avgMs:#,0.00} milliSec"); + + } + + } +} + diff --git a/src/UnitTests/X509Certificate2_EncryptStream.cs b/src/UnitTests/X509Certificate2_EncryptStream.cs new file mode 100644 index 0000000..2db0f9b --- /dev/null +++ b/src/UnitTests/X509Certificate2_EncryptStream.cs @@ -0,0 +1,100 @@ +using System.Security.Cryptography.X509Certificates; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X509.EnduranceTest.Shared; +using Org.Security.Cryptography; +using System.IO; +using System; +using System.Security.Cryptography; + +namespace UnitTests.Encryption +{ + [TestClass] + public class X509Certificate2_EncryptStream + { + #region Validation tests + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void WhenEncryptionCertificateIsNull_ThrowsArgumentNullException() + { + //Arrange + X509Certificate2 certificate2 = null; + //Act + certificate2.EncryptStream(new MemoryStream(), new MemoryStream()); + //Assert + } + [TestMethod] + [ExpectedException(typeof(CryptographicException))] + public void WhenEncryptionCertificateIsNotLoaded_ThrowsArgumentNullException() + { + //Arrange + X509Certificate2 certificate2 = new X509Certificate2(); + //Act + certificate2.EncryptStream(new MemoryStream(), new MemoryStream()); + //Assert + } + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void WhenInputStreamIsNull_ThrowsArgumentNullException() + { + //Arrange + //Act + MyConfig.EncryptionCertificate.EncryptStream(null, new MemoryStream()); + //Assert + } + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void WhenOutputStreamIsNull_ThrowsArgumentNullException() + { + //Arrange + //Act + MyConfig.EncryptionCertificate.EncryptStream( new MemoryStream(),null); + //Assert + } + #endregion + + #region Encrypted contentSize tests + [TestMethod] + public void WhenSigleLetterAIsEncrypted_ResultSizeWillBe536() + { + //Arrange + const string sampleData = "A"; + var x509EncryptionCert = CertificateLoader.LoadFromFile("TestCertificates/hello.world.2048.net.cer"); + byte[] input = Encoding.UTF8.GetBytes(sampleData); + //Act + byte[] output1 = EncryptionDecryptionUtils.EncryptBytesUsingExtensionMethod(x509EncryptionCert, input); + //Assert + int expectedEncryptedArraySize = 536; + Assert.AreEqual(expectedEncryptedArraySize, output1.Length, $"Expected encrypted size for letter A is {expectedEncryptedArraySize}, but actual {output1.Length}"); + } + + [TestMethod] + public void WhenPersonFullNameJoyGeorgeKunjikkuruIsEncrypted_ResultSizeWillBe552() + { + //Arrange + const string fullName = "JoyGeorgeKunjikkuru"; + var x509EncryptionCert = CertificateLoader.LoadFromFile("TestCertificates/hello.world.2048.net.cer"); + byte[] input = Encoding.UTF8.GetBytes(fullName); + //Act + byte[] output1 = EncryptionDecryptionUtils.EncryptBytesUsingExtensionMethod(x509EncryptionCert, input); + //Assert + int expectedEncryptedArraySize = 552; + Assert.AreEqual(expectedEncryptedArraySize, output1.Length, $"Expected encrypted size for letter A is {expectedEncryptedArraySize}, but actual {output1.Length}"); + } + [TestMethod] + public void When8KBIsEncrypted_ResultSizeWillBe8728() + { + //Arrange + const int SampleDataSizeInKB = 8; + var x509EncryptionCert = CertificateLoader.LoadFromFile("TestCertificates/hello.world.2048.net.cer"); + var inputArray = TestDataGenerator.GenerateJunk(SampleDataSizeInKB); + //Act + byte[] output1 = EncryptionDecryptionUtils.EncryptBytesUsingExtensionMethod(x509EncryptionCert, inputArray); + //Assert + int expectedEncryptedArraySize = 8728; + Assert.AreEqual(expectedEncryptedArraySize, output1.Length, $"Expected encrypted size for letter A is {expectedEncryptedArraySize}, but actual {output1.Length}"); + } + #endregion + } +} + diff --git a/src/UnitTests/X509Certificate2_EncryptStream_DecryptStream.cs b/src/UnitTests/X509Certificate2_EncryptStream_DecryptStream.cs new file mode 100644 index 0000000..9e74499 --- /dev/null +++ b/src/UnitTests/X509Certificate2_EncryptStream_DecryptStream.cs @@ -0,0 +1,57 @@ + +using System; +using System.Linq; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Org.Security.Cryptography; + +namespace UnitTests +{ + [TestClass] + public class X509Certificate2_EncryptStream_DecryptStream + { + + [TestMethod] + public void WhenEncryptAndDecryptAreCalledWithCertsLoadedFromFiles_ShouldWork() + { + //Arrange + const string TEST = "Hello World!"; + byte[] input = Encoding.UTF8.GetBytes(TEST); + //Act + byte[] output1 = EncryptionDecryptionUtils.DecryptBytesUsingExtensionMethod( + MyConfig.DecryptionCertificate, + EncryptionDecryptionUtils.EncryptBytesUsingExtensionMethod(MyConfig.EncryptionCertificate, input)); + //Assert + Assert.IsTrue(input.SequenceEqual(output1)); + } + // Ideally, it doesn't matter where from the certificate loaded as long as the X509Certificate2 object is available to tests. + [TestMethod] + public void X509_TripleRoundTripTest() + { + const string TEST = "Hello World!"; + + byte[] input = Encoding.UTF8.GetBytes(TEST); + byte[] output1 = EncryptionDecryptionUtils.DecryptBytesUsingExtensionMethod( + MyConfig.DecryptionCertificate, + EncryptionDecryptionUtils.EncryptBytesUsingExtensionMethod(MyConfig.EncryptionCertificate, input)); + byte[] output2 = EncryptionDecryptionUtils.DecryptBytesUsingExtensionMethod( + MyConfig.DecryptionCertificate, + EncryptionDecryptionUtils.EncryptBytesUsingExtensionMethod(MyConfig.EncryptionCertificate, input)); + byte[] output3 = EncryptionDecryptionUtils.DecryptBytesUsingExtensionMethod( + MyConfig.DecryptionCertificate, + EncryptionDecryptionUtils.EncryptBytesUsingExtensionMethod(MyConfig.EncryptionCertificate, input)); + + Assert.IsTrue(input.SequenceEqual(output1)); + Assert.IsTrue(input.SequenceEqual(output2)); + Assert.IsTrue(input.SequenceEqual(output3)); + + // Seeing is believing... + Console.WriteLine($"Original: {TEST}"); + Console.WriteLine($"#1 {Encoding.UTF8.GetString(output1)}"); + Console.WriteLine($"#2 {Encoding.UTF8.GetString(output2)}"); + Console.WriteLine($"#3 {Encoding.UTF8.GetString(output3)}"); + } + } +} + diff --git a/src/UnitTests/X509Certificate2_EncryptStream_PerfTests.cs b/src/UnitTests/X509Certificate2_EncryptStream_PerfTests.cs new file mode 100644 index 0000000..daab0e1 --- /dev/null +++ b/src/UnitTests/X509Certificate2_EncryptStream_PerfTests.cs @@ -0,0 +1,55 @@ + +using System; +using System.Linq; +using System.Diagnostics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X509.EnduranceTest.Shared; + +namespace UnitTests.Encryption +{ + [TestClass] + public class X509Certificate2_EncryptStream_PerfTests + { + [TestMethod] + public void X509_Benchmark_1000TimesEncryption_ShouldBeCompletedWithin1Second() + { + //Arrange + const int SampleDataSizeInKB = 8; + const int BenchmarkLoopCount = 1000; + + // Generate some random data + // Perform a dry run to warm up. + // Capture Encrypted and Decrypted version + var SampleData = TestDataGenerator.GenerateJunk(SampleDataSizeInKB); + var encryptedBytes = EncryptionDecryptionUtils.EncryptBytesUsingExtensionMethod(MyConfig.EncryptionCertificate, SampleData); + var decryptedBytes = EncryptionDecryptionUtils.DecryptBytesUsingExtensionMethod(MyConfig.DecryptionCertificate, encryptedBytes); + + Assert.IsTrue(decryptedBytes.SequenceEqual(SampleData), "Decrypted bytes doesn't match original data."); + + //Act + var timer = Stopwatch.StartNew(); + for (int i = 0; i < BenchmarkLoopCount; i++) + { + EncryptionDecryptionUtils.EncryptBytesUsingExtensionMethod(MyConfig.EncryptionCertificate, decryptedBytes); + } + timer.Stop(); + + //Assert + var totalMs = timer.Elapsed.TotalMilliseconds; + var totalSec = timer.Elapsed.TotalSeconds; + var avgMs = totalMs / BenchmarkLoopCount; + var ratePerSec = BenchmarkLoopCount / totalSec; + + Console.WriteLine("Encryption Benchmark:"); + Console.WriteLine($"SampleDataSize: {SampleData.Length / 1024:#,0} KB"); + Console.WriteLine($"Elapsed: {timer.Elapsed}"); + Console.WriteLine($"{BenchmarkLoopCount:#,0} iterations @ {ratePerSec:#,0.0} per Sec. / Average: {avgMs:#,0.00} milliSec"); + + Assert.IsTrue( + 1 > timer.Elapsed.TotalSeconds, + $"Encrypting {BenchmarkLoopCount} times took more than 1 second. Consider optimizing or check machine configuration"); + } + + } +} + diff --git a/src/UnitTests/X509CertificateBasedDecryptor_DecryptBase64EncodedString.cs b/src/UnitTests/X509CertificateBasedDecryptor_DecryptBase64EncodedString.cs new file mode 100644 index 0000000..2c09131 --- /dev/null +++ b/src/UnitTests/X509CertificateBasedDecryptor_DecryptBase64EncodedString.cs @@ -0,0 +1,55 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Org.Security.Cryptography; +using X509.EnduranceTest.Shared; + +namespace UnitTests.Decryption +{ + [TestClass] + public class X509CertificateBasedDecryptor_DecryptBase64EncodedString + { + [TestMethod] + public void WhenItIsCalledWithProperParameters_ShouldEncrypt() + { + //Arrange + const string TEST = "Hello World!"; + var x509EncryptionCert = MyConfig.EncryptionCertificate; + var x509DecryptionCert = MyConfig.DecryptionCertificate; + //Act + var encryptedBase64 = new X509CertificateBasedEncryptor().EncryptStringToBase64(x509EncryptionCert, TEST); + var decryptedOutput = new X509CertificateBasedDecryptor().DecryptBase64EncodedString( + encryptedBase64, + thumbprint => x509DecryptionCert); + //Assert + Assert.IsTrue(TEST.Equals(decryptedOutput)); + } + #region Encrypted contentSize tests + [TestMethod] + public void WhenSigleLetterAIsEncrypted_ResultSizeWillBe536() + { + //Arrange + const string TEST = "A"; + var x509EncryptionCert = CertificateLoader.LoadFromFile("TestCertificates/hello.world.2048.net.cer"); + //Act + var encryptedBase64 = new X509CertificateBasedEncryptor().EncryptStringToBase64(x509EncryptionCert, TEST); + //Assert + int expectedEncryptedArraySize = 776; + Assert.AreEqual(expectedEncryptedArraySize, encryptedBase64.Length, $"Expected encrypted size for letter A is {expectedEncryptedArraySize}, but actual {encryptedBase64.Length}"); + } + [TestMethod] + public void WhenPersonFullNameJoyGeorgeKunjikkuruIsEncrypted_ResultSizeWillBe552() + { + //Arrange + const string input = "JoyGeorgeKunjikkuru"; + var x509EncryptionCert = CertificateLoader.LoadFromFile("TestCertificates/hello.world.2048.net.cer"); + //Act + var encryptedBase64 = new X509CertificateBasedEncryptor().EncryptStringToBase64(x509EncryptionCert, input); + //Assert + int expectedEncryptedArraySize = 796; + Assert.AreEqual(expectedEncryptedArraySize, encryptedBase64.Length, $"Expected encrypted size for letter A is {expectedEncryptedArraySize}, but actual {encryptedBase64.Length}"); + + } + #endregion + + } +} + diff --git a/src/UnitTests/X509CertificateBasedDecryptor_DecryptBase64EncodedStringWithTimestampValidation.cs b/src/UnitTests/X509CertificateBasedDecryptor_DecryptBase64EncodedStringWithTimestampValidation.cs new file mode 100644 index 0000000..24ed532 --- /dev/null +++ b/src/UnitTests/X509CertificateBasedDecryptor_DecryptBase64EncodedStringWithTimestampValidation.cs @@ -0,0 +1,64 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Org.Security.Cryptography; +using System; +using System.Security.Cryptography; +using X509.EnduranceTest.Shared; + +namespace UnitTests.Decryption +{ + [TestClass] + public class X509CertificateBasedDecryptor_DecryptBase64EncodedStringWithTimestampValidation + { + + #region Validations + + #endregion + + #region Happy scenarios + [TestMethod] + public void WhenItIsCalledWithProperParameters_ShouldWork() + { + //Arrange + const string input = "Hello World!"; + var x509EncryptionCert = MyConfig.EncryptionCertificate; + var x509DecryptionCert = MyConfig.DecryptionCertificate; + //Act + var encryptedBase64 = new X509CertificateBasedEncryptor().EncryptStringToBase64WithTimestamp(x509EncryptionCert, input); + var decryptedOutput = new X509CertificateBasedDecryptor().DecryptBase64EncodedStringWithTimestampValidation( + encryptedBase64, + thumbprint => x509DecryptionCert); + //Assert + Assert.IsTrue(input.Equals(decryptedOutput)); + } + [TestMethod] + public void WhenDESAlgorithmIsUsed_ShouldWork() + { + //Arrange + const string input = "Hello World!"; + var x509EncryptionCert = MyConfig.EncryptionCertificate; + var x509DecryptionCert = MyConfig.DecryptionCertificate; + //Act + var encryptedBase64 = new X509CertificateBasedEncryptor().EncryptStringToBase64WithTimestamp(x509EncryptionCert, input, "DES", 64, 64); + var decryptedOutput = new X509CertificateBasedDecryptor().DecryptBase64EncodedStringWithTimestampValidation( + encryptedBase64, + thumbprint => x509DecryptionCert, TimeSpan.FromMinutes(1), "DES"); + //Assert + Assert.IsTrue(input.Equals(decryptedOutput)); + } + [TestMethod] + public void WhenRijndaelAlgorithmIsUsedWithDefaultKeyAndBlockSizes_ShouldEncryptAndDecryt() + { + //Arrange + string DataEncryptionAlgorithmName = "Rijndael"; + var encryptedBase64 = new X509CertificateBasedEncryptor().EncryptStringToBase64WithTimestamp(MyConfig.EncryptionCertificate, "Hello World!", DataEncryptionAlgorithmName); + + //Act + var decryptedOutput = new X509CertificateBasedDecryptor().DecryptBase64EncodedStringWithTimestampValidation( + encryptedBase64, + thumbprint => MyConfig.DecryptionCertificate, TimeSpan.FromMinutes(1), DataEncryptionAlgorithmName); + //Assert + } + #endregion + } +} + diff --git a/src/UnitTests/X509CertificateBasedDecryptor_DecryptStream.cs b/src/UnitTests/X509CertificateBasedDecryptor_DecryptStream.cs new file mode 100644 index 0000000..c912f81 --- /dev/null +++ b/src/UnitTests/X509CertificateBasedDecryptor_DecryptStream.cs @@ -0,0 +1,136 @@ + +using System; +using System.IO; +using System.Linq; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Org.Security.Cryptography; +using X509.EnduranceTest.Shared; + +namespace UnitTests.Decryption +{ + [TestClass] + public class X509CertificateBasedDecryptor_DecryptStream + { + #region Validation tests + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void WhenTheInputStreamParameterIsNull_ThrowArgumentNullException() + { + //Arrange + const string TEST = "Hello World!"; + var x509EncryptionCert = MyConfig.EncryptionCertificate; + var x509DecryptionCert = MyConfig.DecryptionCertificate; + byte[] input = Encoding.UTF8.GetBytes(TEST); + //Act + new X509CertificateBasedDecryptor().DecryptStream(null, new MemoryStream(), + thumbprint => MyConfig.DecryptionCertificate); + //Assert + } + [TestMethod] + [ExpectedException(typeof(Exception))] + public void WhenTheOutputStreamParameterIsNull_ThrowArgumentNullException() + { + //Arrange + const string TEST = "Hello World!"; + var x509EncryptionCert = MyConfig.EncryptionCertificate; + var x509DecryptionCert = MyConfig.DecryptionCertificate; + byte[] input = Encoding.UTF8.GetBytes(TEST); + //Act + new X509CertificateBasedDecryptor().DecryptStream(new MemoryStream(), null, + thumbprint => MyConfig.DecryptionCertificate); + //Assert + } + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void WhenTheOutputStreamParameterIsNullButInputStreamIsValie_ThrowArgumentNullException() + { + //Arrange + const string TEST = "Hello World!"; + using var input = new MemoryStream(Encoding.UTF8.GetBytes(TEST)); + using var outputStream = new MemoryStream(); + new X509CertificateBasedEncryptor().EncryptStream(MyConfig.EncryptionCertificate, input, outputStream); + outputStream.Flush(); + byte[] encryptedData = outputStream.ToArray(); + Stream encryptedStream = new MemoryStream(encryptedData); + //Act + new X509CertificateBasedDecryptor().DecryptStream(encryptedStream, null, + thumbprint => MyConfig.DecryptionCertificate); + //Assert + } + [TestMethod] + [ExpectedException(typeof(Exception))] + public void WhenTheDataEncryptionAlgorithmNameParameterIsNull_ThrowException() + { + //Arrange + const string TEST = "Hello World!"; + var x509EncryptionCert = MyConfig.EncryptionCertificate; + var x509DecryptionCert = MyConfig.DecryptionCertificate; + byte[] input = Encoding.UTF8.GetBytes(TEST); + //Act + new X509CertificateBasedDecryptor().DecryptStream( + new MemoryStream(), + new MemoryStream(), + thumbprint => MyConfig.DecryptionCertificate, + null); + //Assert + } + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void WhenTheDataEncryptionAlgorithmNameParameterIsNullButInputStreamIsValid_ThrowArgumentNullException() + { + //Arrange + const string TEST = "Hello World!"; + byte[] inputData = Encoding.UTF8.GetBytes(TEST); + var encryptor = new X509CertificateBasedEncryptor(); + using var input = new MemoryStream(inputData); + using var outputStream = new MemoryStream(inputData.Length); + encryptor.EncryptStream(MyConfig.EncryptionCertificate, input, outputStream); + outputStream.Flush(); + byte[] encryptedData = outputStream.ToArray(); + Stream encryptedStream = new MemoryStream(encryptedData); + //Act + new X509CertificateBasedDecryptor().DecryptStream( + encryptedStream, + new MemoryStream(), + thumbprint => MyConfig.DecryptionCertificate, + null); + //Assert + } + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void WhenTheCertificateSelectorParamIsNull_ThrowArgumentNullException() + { + //Arrange + const string TEST = "Hello World!"; + var x509EncryptionCert = MyConfig.EncryptionCertificate; + var x509DecryptionCert = MyConfig.DecryptionCertificate; + byte[] input = Encoding.UTF8.GetBytes(TEST); + //Act + byte[] encryptedArray = EncryptionDecryptionUtils.EncryptBytesUsingX509CertificateBasedEncryptor(x509EncryptionCert, input); + byte[] decryptedOutput = EncryptionDecryptionUtils.DecryptBytesUsingX509CertificateBasedDecryptor( + encryptedArray, + null); + //Assert + Assert.IsTrue(input.SequenceEqual(decryptedOutput)); + } + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void WhenTheDecryptionCertificateIsNull_ThrowArgumentNullException() + { + //Arrange + const string TEST = "Hello World!"; + var x509EncryptionCert = MyConfig.EncryptionCertificate; + var x509DecryptionCert = MyConfig.DecryptionCertificate; + byte[] input = Encoding.UTF8.GetBytes(TEST); + //Act + byte[] encryptedArray = EncryptionDecryptionUtils.EncryptBytesUsingX509CertificateBasedEncryptor(x509EncryptionCert, input); + byte[] decryptedOutput = EncryptionDecryptionUtils.DecryptBytesUsingX509CertificateBasedDecryptor( + encryptedArray, + thumbprint => null); + //Assert + Assert.IsTrue(input.SequenceEqual(decryptedOutput)); + } + #endregion + } +} \ No newline at end of file diff --git a/src/UnitTests/X509CertificateBasedDecryptor_DecryptStreamWithTimestampValidation.cs b/src/UnitTests/X509CertificateBasedDecryptor_DecryptStreamWithTimestampValidation.cs new file mode 100644 index 0000000..56bc912 --- /dev/null +++ b/src/UnitTests/X509CertificateBasedDecryptor_DecryptStreamWithTimestampValidation.cs @@ -0,0 +1,133 @@ + +using System; +using System.Linq; +using System.Text; +using System.Threading; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Org.Security.Cryptography; + +namespace UnitTests.Decryption +{ + [TestClass] + public class X509CertificateBasedDecryptor_DecryptStreamWithTimestampValidation + { + #region Validation tests + + [TestMethod] + [ExpectedException(typeof(Exception))] + public void WhenDecryptionCertificateDontHavePrivateKey_ThrowException() + { + //Arrange + CacheManager.ClearCache(); + const string inputString = "Hello World!"; + byte[] input = Encoding.UTF8.GetBytes(inputString); + //Act + byte[] encryptedArray = EncryptionDecryptionUtils.EncryptBytesWithTimestampUsingX509CertificateBasedEncryptor(MyConfig.EncryptionCertificate, input); + byte[] decryptedOutput = EncryptionDecryptionUtils.DecryptBytesWithTimestampValidationUsingX509CertificateBasedDecryptor( + encryptedArray, + thumbprint => MyConfig.EncryptionCertificate); + //Assert + Assert.IsTrue(input.SequenceEqual(decryptedOutput)); + } + #endregion + + #region Positive and happy scenarios + + [TestMethod] + public void WhenDecryptionHappensWithinDefault1MinPeriod_ShouldDecrypt() + { + //Arrange + const string TEST = "Hello World!"; + byte[] input = Encoding.UTF8.GetBytes(TEST); + //Act + byte[] encryptedArray = EncryptionDecryptionUtils.EncryptBytesWithTimestampUsingX509CertificateBasedEncryptor(MyConfig.EncryptionCertificate, input); + byte[] decryptedOutput = EncryptionDecryptionUtils.DecryptBytesWithTimestampValidationUsingX509CertificateBasedDecryptor( + encryptedArray, + thumbprint => MyConfig.DecryptionCertificate); + //Assert + Assert.IsTrue(input.SequenceEqual(decryptedOutput)); + } + + [TestMethod] + public void WhenDecryptionHappensBefore10SecsOfDefaultExpiry_ShouldDecrypt() + { + //Arrange + const string inputString = "Hello World!"; + byte[] input = Encoding.UTF8.GetBytes(inputString); + //Act + byte[] encryptedArray = EncryptionDecryptionUtils.EncryptBytesWithTimestampUsingX509CertificateBasedEncryptor(MyConfig.EncryptionCertificate, input); + Thread.Sleep(TimeSpan.FromSeconds(2)); + byte[] decryptedOutput = EncryptionDecryptionUtils.DecryptBytesWithTimestampValidationUsingX509CertificateBasedDecryptor( + encryptedArray, + thumbprint => MyConfig.DecryptionCertificate, TimeSpan.FromSeconds(10)); + //Assert + Assert.IsTrue(input.SequenceEqual(decryptedOutput)); + } + #endregion + + #region Negative and sad scenarios + [TestMethod] + [ExpectedException(typeof(TimeoutException))] + public void WhenDecryptionHappensAfter1MinOfDefaultExpiry_ThrowException() + { + //Arrange + const string inputString = "Hello World!"; + byte[] input = Encoding.UTF8.GetBytes(inputString); + //Act + byte[] encryptedArray = EncryptionDecryptionUtils.EncryptBytesWithTimestampUsingX509CertificateBasedEncryptor(MyConfig.EncryptionCertificate, input); + Thread.Sleep(TimeSpan.FromSeconds(65)); + byte[] decryptedOutput = EncryptionDecryptionUtils.DecryptBytesWithTimestampValidationUsingX509CertificateBasedDecryptor( + encryptedArray, + thumbprint => MyConfig.DecryptionCertificate); + //Assert + Assert.IsTrue(input.SequenceEqual(decryptedOutput)); + } + [TestMethod] + [ExpectedException(typeof(TimeoutException))] + public void WhenDecryptionHappensAfter10SecsOfDefaultExpiry_ThrowException() + { + //Arrange + const string inputString = "Hello World!"; + byte[] input = Encoding.UTF8.GetBytes(inputString); + //Act + byte[] encryptedArray = EncryptionDecryptionUtils.EncryptBytesWithTimestampUsingX509CertificateBasedEncryptor(MyConfig.EncryptionCertificate, input); + Thread.Sleep(TimeSpan.FromSeconds(12)); + byte[] decryptedOutput = EncryptionDecryptionUtils.DecryptBytesWithTimestampValidationUsingX509CertificateBasedDecryptor( + encryptedArray, + thumbprint => MyConfig.DecryptionCertificate,TimeSpan.FromSeconds(10)); + //Assert + Assert.IsTrue(input.SequenceEqual(decryptedOutput)); + } + /// + /// TODO: This guy gives different exceptions different times One time got StackOverflowException. + /// Consider having a header to denote the encrypted package. + /// + [TestMethod] + public void WhenDecryptionPayloadDontHaveTimestamp_ThrowException() + { + //Arrange + const string inputString = "Hello World!"; + byte[] input = Encoding.UTF8.GetBytes(inputString); + bool exceptionThrown = false; + //Act + + byte[] encryptedArray = EncryptionDecryptionUtils.EncryptBytesUsingX509CertificateBasedEncryptor(MyConfig.EncryptionCertificate, input); + try + { + byte[] decryptedOutput = EncryptionDecryptionUtils.DecryptBytesWithTimestampValidationUsingX509CertificateBasedDecryptor( + encryptedArray, + thumbprint => MyConfig.DecryptionCertificate); + }catch (ArgumentOutOfRangeException) + { + exceptionThrown = true; + } + catch(InvalidOperationException) + { + exceptionThrown = true; + } + //Assert + Assert.IsTrue(exceptionThrown,$"Expected either {typeof(ArgumentOutOfRangeException)} or {typeof(InvalidOperationException)}"); + } + #endregion + } +} \ No newline at end of file diff --git a/src/UnitTests/X509CertificateBasedEncryptor_EncryptStream.cs b/src/UnitTests/X509CertificateBasedEncryptor_EncryptStream.cs new file mode 100644 index 0000000..0d175ee --- /dev/null +++ b/src/UnitTests/X509CertificateBasedEncryptor_EncryptStream.cs @@ -0,0 +1,95 @@ + +using System; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Org.Security.Cryptography; +using X509.EnduranceTest.Shared; + +namespace UnitTests.Encryption +{ + [TestClass] + public class X509CertificateBasedEncryptor_EncryptStream + { + #region Validations + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void WhenEncryptionCertificateParameterIsNull_ThrowArgumentNullException() + { + //Arrange + //Act + new X509CertificateBasedEncryptor().EncryptStream(null, new MemoryStream(), new MemoryStream()); + //Assert + } + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void WhenTheInputStreamParameterIsNull_ThrowArgumentNullException() + { + //Arrange + //Act + new X509CertificateBasedEncryptor().EncryptStream(MyConfig.EncryptionCertificate, null, new MemoryStream()); + //Assert + } + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void WhenTheOutputStreamParameterIsNull_ThrowArgumentNullException() + { + //Arrange + //Act + new X509CertificateBasedEncryptor().EncryptStream(MyConfig.EncryptionCertificate, new MemoryStream(),null); + //Assert + } + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void WhenTheDataEncryptionAlgorithmNameIsNull_ThrowArgumentNullException() + { + //Arrange + //Act + new X509CertificateBasedEncryptor().EncryptStream(MyConfig.EncryptionCertificate, new MemoryStream(), new MemoryStream(),null); + //Assert + } + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void WhenTheDataEncryptionAlgorithmNameIsWhiteSpace_ThrowArgumentNullException() + { + //Arrange + //Act + new X509CertificateBasedEncryptor().EncryptStream(MyConfig.EncryptionCertificate, new MemoryStream(), new MemoryStream(), " "); + //Assert + } + [TestMethod] + public void WhenTheDataEncryptionAlgorithmNameIsUnknown_ThrowCryptographicException() + { + //Arrange + Action act= () => new X509CertificateBasedEncryptor().EncryptStream(MyConfig.EncryptionCertificate, new MemoryStream(), new MemoryStream(), "MyAlgo"); + //Act + act + .Should() + .Throw() + .WithMessage("Not able to create Symmetric Data Encryption Algorithm using MyAlgo"); + //Assert + } + #endregion + + #region Positive / happy scenarios + [TestMethod] + public void WhenItIsCalledWithProperParameters_ShouldEncryptAndDecrypt() + { + //Arrange + const string TEST = "Hello World!"; + var x509EncryptionCert = MyConfig.EncryptionCertificate; + var x509DecryptionCert = MyConfig.DecryptionCertificate; byte[] input = Encoding.UTF8.GetBytes(TEST); + //Act + byte[] encryptedArray = EncryptionDecryptionUtils.EncryptBytesUsingX509CertificateBasedEncryptor(x509EncryptionCert, input); + byte[] decryptedOutput = EncryptionDecryptionUtils.DecryptBytesUsingX509CertificateBasedDecryptor( + encryptedArray, + thumbprint => x509DecryptionCert); + //Assert + Assert.IsTrue(input.SequenceEqual(decryptedOutput)); + } + #endregion + } +} \ No newline at end of file diff --git a/src/UnitTests/X509CertificateBasedEncryptor_EncryptStreamWithTimestamp.cs b/src/UnitTests/X509CertificateBasedEncryptor_EncryptStreamWithTimestamp.cs new file mode 100644 index 0000000..3c869c5 --- /dev/null +++ b/src/UnitTests/X509CertificateBasedEncryptor_EncryptStreamWithTimestamp.cs @@ -0,0 +1,41 @@ +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Org.Security.Cryptography; +using X509.EnduranceTest.Shared; + +namespace UnitTests.Encryption +{ + [TestClass] + public class X509CertificateBasedEncryptor_EncryptStreamWithTimestamp + { + + #region Encrypted contentSize tests + [TestMethod] + public void WhenSigleLetterAIsEncrypted_ResultSizeWillBe536() + { + //Arrange + const string TEST = "A"; + byte[] input = Encoding.UTF8.GetBytes(TEST); + //Act + byte[] encryptedArray = EncryptionDecryptionUtils.EncryptBytesWithTimestampUsingX509CertificateBasedEncryptor(MyConfig.EncryptionCertificate, input); + //Assert + int expectedEncryptedArraySize = 840; + Assert.AreEqual(expectedEncryptedArraySize, encryptedArray.Length, + $"Expected encrypted size for letter A is {expectedEncryptedArraySize}, but actual {encryptedArray.Length}"); + } + [TestMethod] + public void WhenPersonFullNameJoyGeorgeKunjikkuruIsEncrypted_ResultSizeWillBe552() + { + //Arrange + const string TEST = "JoyGeorgeKunjikkuru"; + byte[] input = Encoding.UTF8.GetBytes(TEST); + //Act + byte[] encryptedArray = EncryptionDecryptionUtils.EncryptBytesWithTimestampUsingX509CertificateBasedEncryptor(MyConfig.EncryptionCertificate, input); + //Assert + int expectedEncryptedArraySize = 856; + Assert.AreEqual(expectedEncryptedArraySize, encryptedArray.Length, + $"Expected encrypted size for letter A is {expectedEncryptedArraySize}, but actual {encryptedArray.Length}"); + } + #endregion + } +} \ No newline at end of file diff --git a/src/UnitTests/X509CertificateBasedEncryptor_EncryptStringToBase64.cs b/src/UnitTests/X509CertificateBasedEncryptor_EncryptStringToBase64.cs new file mode 100644 index 0000000..d285c56 --- /dev/null +++ b/src/UnitTests/X509CertificateBasedEncryptor_EncryptStringToBase64.cs @@ -0,0 +1,55 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Org.Security.Cryptography; +using X509.EnduranceTest.Shared; + +namespace UnitTests.Encryption +{ + [TestClass] + public class X509CertificateBasedEncryptor_EncryptStringToBase64 + { + [TestMethod] + public void WhenItIsCalledWithProperParameters_ShouldEncrypt() + { + //Arrange + const string TEST = "Hello World!"; + var x509EncryptionCert = MyConfig.EncryptionCertificate; + var x509DecryptionCert = MyConfig.DecryptionCertificate; + //Act + var encryptedBase64 = new X509CertificateBasedEncryptor().EncryptStringToBase64(x509EncryptionCert, TEST); + var decryptedOutput = new X509CertificateBasedDecryptor().DecryptBase64EncodedString( + encryptedBase64, + thumbprint => x509DecryptionCert); + //Assert + Assert.IsTrue(TEST.Equals(decryptedOutput)); + } + #region Encrypted contentSize tests + [TestMethod] + public void WhenSigleLetterAIsEncrypted_ResultSizeWillBe536() + { + //Arrange + const string TEST = "A"; + var x509EncryptionCert = CertificateLoader.LoadFromFile("TestCertificates/hello.world.2048.net.cer"); + //Act + var encryptedBase64 = new X509CertificateBasedEncryptor().EncryptStringToBase64(x509EncryptionCert, TEST); + //Assert + int expectedEncryptedArraySize = 776; + Assert.AreEqual(expectedEncryptedArraySize, encryptedBase64.Length, $"Expected encrypted size for letter A is {expectedEncryptedArraySize}, but actual {encryptedBase64.Length}"); + } + [TestMethod] + public void WhenPersonFullNameJoyGeorgeKunjikkuruIsEncrypted_ResultSizeWillBe552() + { + //Arrange + const string input = "JoyGeorgeKunjikkuru"; + var x509EncryptionCert = CertificateLoader.LoadFromFile("TestCertificates/hello.world.2048.net.cer"); + //Act + var encryptedBase64 = new X509CertificateBasedEncryptor().EncryptStringToBase64(x509EncryptionCert, input); + //Assert + int expectedEncryptedArraySize = 796; + Assert.AreEqual(expectedEncryptedArraySize, encryptedBase64.Length, $"Expected encrypted size for letter A is {expectedEncryptedArraySize}, but actual {encryptedBase64.Length}"); + + } + #endregion + + } +} + diff --git a/src/UnitTests/X509CertificateBasedEncryptor_EncryptStringToBase64WithTimestamp.cs b/src/UnitTests/X509CertificateBasedEncryptor_EncryptStringToBase64WithTimestamp.cs new file mode 100644 index 0000000..e8e1f90 --- /dev/null +++ b/src/UnitTests/X509CertificateBasedEncryptor_EncryptStringToBase64WithTimestamp.cs @@ -0,0 +1,69 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Org.Security.Cryptography; +using System; +using System.Security.Cryptography; +using X509.EnduranceTest.Shared; + +namespace UnitTests.Encryption +{ + [TestClass] + public class X509CertificateBasedEncryptor_EncryptStringToBase64WithTimestamp + { + #region Validations + [TestMethod] + [ExpectedException(typeof(CryptographicException))] + public void WhenDESAlgorithmIsUsedWithDefaultKeyAndBlockSizes_ThrowCryptographicException() + { + //Arrange + const string input = "Hello World!"; + var encryptedBase64 = new X509CertificateBasedEncryptor().EncryptStringToBase64WithTimestamp(MyConfig.EncryptionCertificate, input, "DES"); + + //Assert + } + [TestMethod] + public void WhenRC2AlgorithmIsUsedWithDefaultKeyAndBlockSizes_ThrowCryptographicException() + { + //Arrange + //Act + Action act =()=> new X509CertificateBasedEncryptor().EncryptStringToBase64WithTimestamp(MyConfig.EncryptionCertificate, "Hello World!", "RC2"); + act + .Should() + .Throw() + .WithMessage("Specified key is not a valid size for this algorithm."); + //Assert + } + [TestMethod] + public void WhenTripleDESAlgorithmIsUsedWithDefaultKeyAndBlockSizes_ThrowCryptographicException() + { + //Arrange + //Act + Action act = () => new X509CertificateBasedEncryptor().EncryptStringToBase64WithTimestamp(MyConfig.EncryptionCertificate, "Hello World!", "TripleDES"); + act + .Should() + .Throw() + .WithMessage("Specified key is not a valid size for this algorithm."); + //Assert + } + #endregion + + #region Happy scenarios + [TestMethod] + public void WhenItIsCalledWithProperParameters_ShouldEncrypt() + { + //Arrange + const string input = "Hello World!"; + var x509EncryptionCert = MyConfig.EncryptionCertificate; + var x509DecryptionCert = MyConfig.DecryptionCertificate; + //Act + var encryptedBase64 = new X509CertificateBasedEncryptor().EncryptStringToBase64WithTimestamp(x509EncryptionCert, input); + var decryptedOutput = new X509CertificateBasedDecryptor().DecryptBase64EncodedStringWithTimestampValidation( + encryptedBase64, + thumbprint => x509DecryptionCert); + //Assert + Assert.IsTrue(input.Equals(decryptedOutput)); + } + #endregion + } +} + diff --git a/src/UnitTests/X509EncryptionTests.cs b/src/UnitTests/X509EncryptionTests.cs deleted file mode 100644 index 0d732bc..0000000 --- a/src/UnitTests/X509EncryptionTests.cs +++ /dev/null @@ -1,137 +0,0 @@ - -using System; -using System.Linq; -using System.Diagnostics; -using System.IO; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -using Org.Security.Cryptography; - -namespace UnitTests -{ - [TestClass] - public class X509EncryptionTests - { - [TestMethod] - public void X509_TripleRoundTripTest() - { - const string TEST = "Hello World!"; - - var x509Cert = X509CertificateCache.GetCertificate(MyConfig.TestCertThumbPrint); - - byte[] input = Encoding.UTF8.GetBytes(TEST); - byte[] output1 = DecryptBytes(x509Cert, EncryptBytes(x509Cert, input)); - byte[] output2 = DecryptBytes(x509Cert, EncryptBytes(x509Cert, input)); - byte[] output3 = DecryptBytes(x509Cert, EncryptBytes(x509Cert, input)); - - Assert.IsTrue(input.SequenceEqual(output1)); - Assert.IsTrue(input.SequenceEqual(output2)); - Assert.IsTrue(input.SequenceEqual(output3)); - - // Seeing is believing... - Console.WriteLine($"Original: {TEST}"); - Console.WriteLine($"#1 {Encoding.UTF8.GetString(output1)}"); - Console.WriteLine($"#2 {Encoding.UTF8.GetString(output2)}"); - Console.WriteLine($"#3 {Encoding.UTF8.GetString(output3)}"); - } - - [TestMethod] - public void X509_Benchmark_EncryptionAndDecryption() - { - const int SampleDataSizeInKB = 8; - const int BenchmarkLoopCount = 1000; - - var x509Cert = X509CertificateCache.GetCertificate(MyConfig.TestCertThumbPrint); - - // Generate some random data - // Perform a dry run - // Capture Encrypted and Decrypted version - var SampleData = GenerateJunk(SampleDataSizeInKB); - var encryptedBytes = EncryptBytes(x509Cert, SampleData); - var decryptedBytes = DecryptBytes(x509Cert, encryptedBytes); - - Assert.IsTrue(decryptedBytes.SequenceEqual(SampleData), "Decrypted bytes doesn't match original data."); - - var timer = Stopwatch.StartNew(); - for (int i=0; i< BenchmarkLoopCount; i++) - { - EncryptBytes(x509Cert, decryptedBytes); - } - timer.Stop(); - - var totalMs = timer.Elapsed.TotalMilliseconds; - var totalSec = timer.Elapsed.TotalSeconds; - var avgMs = totalMs / BenchmarkLoopCount; - var ratePerSec = BenchmarkLoopCount / totalSec; - - Console.WriteLine("Encryption Benchmark:"); - Console.WriteLine($"SampleDataSize: {SampleData.Length / 1024:#,0} KB"); - Console.WriteLine($"Elapsed: {timer.Elapsed}"); - Console.WriteLine($"{BenchmarkLoopCount:#,0} iterations @ {ratePerSec:#,0.0} per Sec. / Average: {avgMs:#,0.00} milliSec"); - - - timer = Stopwatch.StartNew(); - for (int i = 0; i < BenchmarkLoopCount; i++) - { - DecryptBytes(x509Cert, encryptedBytes); - } - timer.Stop(); - - totalMs = timer.Elapsed.TotalMilliseconds; - totalSec = timer.Elapsed.TotalSeconds; - avgMs = totalMs / BenchmarkLoopCount; - ratePerSec = BenchmarkLoopCount / totalSec; - - Console.WriteLine("Decryption Benchmark:"); - Console.WriteLine($"SampleDataSize: {SampleData.Length / 1024:#,0} KB"); - Console.WriteLine($"Elapsed: {timer.Elapsed}"); - Console.WriteLine($"{BenchmarkLoopCount:#,0} iterations @ {ratePerSec:#,0.0} per Sec. / Average: {avgMs:#,0.00} milliSec"); - - } - - byte[] EncryptBytes(X509Certificate2 x509Cert, byte[] inputData) - { - using (var input = new MemoryStream(inputData)) - using (var output = new MemoryStream(inputData.Length)) - { - x509Cert.EncryptStream(input, output); - output.Flush(); - return output.ToArray(); - } - } - - byte[] DecryptBytes(X509Certificate2 x509Cert, byte[] inputData) - { - using (var input = new MemoryStream(inputData)) - using (var output = new MemoryStream(inputData.Length)) - { - x509Cert.DecryptStream(input, output); - output.Flush(); - return output.ToArray(); - } - } - - static byte[] GenerateJunk(int kiloBytes) - { - int maxBytes = kiloBytes * 1024; - - using (var buffer = new MemoryStream(maxBytes)) - { - var bytesWritten = 0; - - while (bytesWritten < maxBytes) - { - var more = Guid.NewGuid().ToByteArray(); - buffer.Write(more, 0, more.Length); - bytesWritten += more.Length; - } - - buffer.Flush(); - return buffer.ToArray(); - } - } - } -} - diff --git a/src/UnitTests/X509SignatureTests.cs b/src/UnitTests/X509SignatureTests.cs deleted file mode 100644 index 56501e1..0000000 --- a/src/UnitTests/X509SignatureTests.cs +++ /dev/null @@ -1,43 +0,0 @@ - -using System; -using Org.Security.Cryptography; -using System.Security.Cryptography; -using System.Text; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace UnitTests.POCs -{ - [TestClass] - public class X509SignatureTests - { - [TestMethod] - public void TestSignature() - { - const string TestData = "Hello world"; - - var cert = X509CertificateCache.GetCertificate(MyConfig.TestCertThumbPrint); - var payload = Encoding.UTF8.GetBytes(TestData); - - string[] HashAlgorithmNames = { "MD5", "SHA1", "SHA256", "SHA384", "SHA512" }; - - foreach (var name in HashAlgorithmNames) - { - using (var hashAlgorithm = HashAlgorithm.Create(name)) - { - // Digest - var hash = hashAlgorithm.ComputeHash(payload); - Console.WriteLine($"Hash: {name} {hash.Length * 8} bits / {hash.Length} BYTES"); - - // Sign - var signature = cert.CreateSignature(hash); - Console.WriteLine($"Signature: {signature.Length * 8} bits / {signature.Length} BYTES"); - - // Verify - var good = cert.VerifySignature(hash, signature); - Assert.IsTrue(good); - } - } - } - } -} \ No newline at end of file diff --git a/src/UnitTests/test.runsettings b/src/UnitTests/test.runsettings new file mode 100644 index 0000000..39ac4c5 --- /dev/null +++ b/src/UnitTests/test.runsettings @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + .*Org.Security.Cryptography.X509Extensions.dll + + + + True + True + True + False + + + + + + \ No newline at end of file diff --git a/src/X509.EnduranceTest.NetCore/App.config b/src/X509.EnduranceTest.Net6/App.config similarity index 52% rename from src/X509.EnduranceTest.NetCore/App.config rename to src/X509.EnduranceTest.Net6/App.config index fbb712a..9e8ffb1 100644 --- a/src/X509.EnduranceTest.NetCore/App.config +++ b/src/X509.EnduranceTest.Net6/App.config @@ -5,6 +5,10 @@ + + + + diff --git a/src/X509.EnduranceTest.Net6/Program.cs b/src/X509.EnduranceTest.Net6/Program.cs new file mode 100644 index 0000000..311d487 --- /dev/null +++ b/src/X509.EnduranceTest.Net6/Program.cs @@ -0,0 +1,20 @@ + +using System; +using System.Threading; +using System.Threading.Tasks; +using X509.EnduranceTest.Shared; + +namespace X509.EnduranceTest +{ + class Program + { + async static Task Main(string[] args) + { + await new TestProgram().Run(CancellationToken.None); + //await X509.EnduranceTest.Shared.TestMain.Run(); + + Console.WriteLine("Press ENTER to quit..."); + Console.ReadLine(); + } + } +} \ No newline at end of file diff --git a/src/X509.EnduranceTest.NetCore/X509.EnduranceTest.NetCore.csproj b/src/X509.EnduranceTest.Net6/X509.EnduranceTest.Net6.csproj similarity index 89% rename from src/X509.EnduranceTest.NetCore/X509.EnduranceTest.NetCore.csproj rename to src/X509.EnduranceTest.Net6/X509.EnduranceTest.Net6.csproj index 97a509d..78b8b86 100644 --- a/src/X509.EnduranceTest.NetCore/X509.EnduranceTest.NetCore.csproj +++ b/src/X509.EnduranceTest.Net6/X509.EnduranceTest.Net6.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.1 + net6 @@ -13,6 +13,4 @@ - - diff --git a/src/X509.EnduranceTest.NetCore/Program.cs b/src/X509.EnduranceTest.NetCore/Program.cs deleted file mode 100644 index 6ade0c4..0000000 --- a/src/X509.EnduranceTest.NetCore/Program.cs +++ /dev/null @@ -1,16 +0,0 @@ - -using System; - -namespace X509.EnduranceTest -{ - class Program - { - static void Main(string[] args) - { - X509.EnduranceTest.Shared.TestMain.Run(); - - Console.WriteLine("Press ENTER to quit..."); - Console.ReadLine(); - } - } -} diff --git a/src/X509.EnduranceTest.NetFramework/App.config b/src/X509.EnduranceTest.NetFramework/App.config index fbb712a..1a9103f 100644 --- a/src/X509.EnduranceTest.NetFramework/App.config +++ b/src/X509.EnduranceTest.NetFramework/App.config @@ -1,19 +1,30 @@ - + + + + + - + + + + + + + + + - \ No newline at end of file +--> diff --git a/src/X509.EnduranceTest.NetFramework/Program.cs b/src/X509.EnduranceTest.NetFramework/Program.cs index 6ade0c4..0b9a201 100644 --- a/src/X509.EnduranceTest.NetFramework/Program.cs +++ b/src/X509.EnduranceTest.NetFramework/Program.cs @@ -1,16 +1,17 @@  using System; +using System.Threading; +using System.Threading.Tasks; +using X509.EnduranceTest.Shared; namespace X509.EnduranceTest { class Program { - static void Main(string[] args) + static async Task Main(string[] args) { - X509.EnduranceTest.Shared.TestMain.Run(); - - Console.WriteLine("Press ENTER to quit..."); - Console.ReadLine(); + await new TestProgram().Run(CancellationToken.None); + //await X509.EnduranceTest.Shared.TestMain.Run(); } } } diff --git a/src/X509.EnduranceTest.NetFramework/X509.EnduranceTest.NetFramework.csproj b/src/X509.EnduranceTest.NetFramework/X509.EnduranceTest.NetFramework.csproj index be7bd8c..936bf86 100644 --- a/src/X509.EnduranceTest.NetFramework/X509.EnduranceTest.NetFramework.csproj +++ b/src/X509.EnduranceTest.NetFramework/X509.EnduranceTest.NetFramework.csproj @@ -8,10 +8,11 @@ Exe X509.EnduranceTest.NetFramework X509.EnduranceTest.NetFramework - v4.7.2 + v4.8 512 true true + AnyCPU @@ -33,13 +34,52 @@ 4 + + ..\..\packages\Bogus.33.0.2\lib\net40\Bogus.dll + + + ..\..\packages\EasyConsoleStd.2.0.0\lib\netstandard2.0\EasyConsole.dll + + + + ..\..\packages\Microsoft.Extensions.Caching.Abstractions.5.0.0\lib\net461\Microsoft.Extensions.Caching.Abstractions.dll + + + ..\..\packages\Microsoft.Extensions.Caching.Memory.5.0.0\lib\net461\Microsoft.Extensions.Caching.Memory.dll + + + ..\..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.5.0.0\lib\net461\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\..\packages\Microsoft.Extensions.Logging.Abstractions.5.0.0\lib\net461\Microsoft.Extensions.Logging.Abstractions.dll + + + ..\..\packages\Microsoft.Extensions.Options.5.0.0\lib\net461\Microsoft.Extensions.Options.dll + + + ..\..\packages\Microsoft.Extensions.Primitives.5.0.0\lib\net461\Microsoft.Extensions.Primitives.dll + + + ..\..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + - ..\packages\System.Configuration.ConfigurationManager.4.7.0\lib\net461\System.Configuration.ConfigurationManager.dll + ..\..\packages\System.Configuration.ConfigurationManager.4.7.0\lib\net461\System.Configuration.ConfigurationManager.dll + + + ..\..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll + + + + ..\..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\..\packages\System.Runtime.CompilerServices.Unsafe.5.0.0\lib\net45\System.Runtime.CompilerServices.Unsafe.dll + ..\packages\System.Security.AccessControl.4.7.0\lib\net461\System.Security.AccessControl.dll diff --git a/src/X509.EnduranceTest.NetFramework/packages.config b/src/X509.EnduranceTest.NetFramework/packages.config index 8ee555d..6f35f23 100644 --- a/src/X509.EnduranceTest.NetFramework/packages.config +++ b/src/X509.EnduranceTest.NetFramework/packages.config @@ -1,7 +1,19 @@  - - - - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/X509.EnduranceTest.Shared/Common/CertificateLoader.cs b/src/X509.EnduranceTest.Shared/Common/CertificateLoader.cs new file mode 100644 index 0000000..bed205a --- /dev/null +++ b/src/X509.EnduranceTest.Shared/Common/CertificateLoader.cs @@ -0,0 +1,19 @@ +using System; +using System.Security.Cryptography.X509Certificates; + +namespace X509.EnduranceTest.Shared +{ + public class CertificateLoader + { + public static X509Certificate2 LoadFromFile(string filePath) + { + return new X509Certificate2(X509Certificate2.CreateFromCertFile(filePath)); + } + public static X509Certificate2 LoadFromFile(string filePath,string password) + { + var cert = new X509Certificate2(filePath, password, X509KeyStorageFlags.PersistKeySet); + if (null == cert) throw new NullReferenceException($"Certificate not found at {filePath}"); + return cert; + } + } +} diff --git a/src/X509.EnduranceTest.Shared/Common/ConsoleWriter.cs b/src/X509.EnduranceTest.Shared/Common/ConsoleWriter.cs new file mode 100644 index 0000000..dfad719 --- /dev/null +++ b/src/X509.EnduranceTest.Shared/Common/ConsoleWriter.cs @@ -0,0 +1,94 @@ + +using EasyConsole; +using System; +using System.Security.Cryptography.X509Certificates; + +namespace X509.EnduranceTest.Shared +{ + class ConsoleWriter + { + internal static void PrintCSP(X509Certificate2 cert) + { + Console.WriteLine($"Cert: {cert.Subject} / {cert.Thumbprint}"); + Console.WriteLine(); + + try + { + Console.WriteLine("cert.PublicKey.Key"); + var alg = cert.PublicKey.Key; + Console.WriteLine(alg.GetType().FullName); + Console.WriteLine(); + } + catch (Exception err) + { + PrintErrorSummary(err); + } + + try + { + // Fails in .NET Framework if -KeySpec Signature not specified. + // Works in .NET Core + Console.WriteLine("cert.PrivateKey"); + var alg = cert.PrivateKey; + Console.WriteLine(alg.GetType().FullName); + Console.WriteLine(); + } + catch (Exception err) + { + PrintErrorSummary(err); + } + + try + { + Console.WriteLine("cert.GetRSAPublicKey()"); + var alg = cert.GetRSAPublicKey(); + Console.WriteLine(alg.GetType().FullName); + Console.WriteLine(); + } + catch (Exception err) + { + PrintErrorSummary(err); + } + + try + { + Console.WriteLine("cert.GetRSAPrivateKey()"); + var alg = cert.GetRSAPrivateKey(); + Console.WriteLine(alg.GetType().FullName); + Console.WriteLine(); + } + catch (Exception err) + { + PrintErrorSummary(err); + } + + static void PrintErrorSummary(Exception ex) + { + Console.WriteLine("ERROR:"); + while (null != ex) + { + Console.WriteLine($"[{ex.GetType().FullName}]"); + Console.WriteLine(ex.Message); + ex = ex.InnerException; + } + } + } + + internal static void WriteSuccess(string value) + { + Output.WriteLine(ConsoleColor.Green, value); + } + + internal static void WriteRecursively(Exception err) + { + var topError = err; + while (null != err) + { + Console.WriteLine($"[{err.GetType().FullName}]"); + Console.WriteLine(err.Message); + err = err.InnerException; + } + Console.WriteLine(topError.StackTrace); + } + } +} \ No newline at end of file diff --git a/src/X509.EnduranceTest.Shared/Common/EnduranceTestRunner.cs b/src/X509.EnduranceTest.Shared/Common/EnduranceTestRunner.cs new file mode 100644 index 0000000..24092a3 --- /dev/null +++ b/src/X509.EnduranceTest.Shared/Common/EnduranceTestRunner.cs @@ -0,0 +1,38 @@ + +using System; +using System.Diagnostics; + +namespace X509.EnduranceTest.Shared +{ + public record EnduranceTestResult(TimeSpan Elapsed, double IteractionsPerSecond, int IterationsCompleted); + + internal class EnduranceTestRunner + { + internal static EnduranceTestResult Run( int maxIterations, Action actionToTest) + { + Console.WriteLine($"MaxIterations: {maxIterations:#,0}"); + + var counter = 0; + var elapsed = Stopwatch.StartNew(); + var statusUpdateInterval = TimeSpan.FromSeconds(2); + var nextStatusUpdate = DateTime.Now.Add(statusUpdateInterval); + + var rate = 0.0; + + while (counter++ <= maxIterations) + { + actionToTest(); + if (nextStatusUpdate < DateTime.Now) + { + rate = counter / elapsed.Elapsed.TotalSeconds; + Console.WriteLine($"{elapsed.Elapsed:hh\\:mm\\:ss} @ {rate:#,0} per-sec. Iterations: {counter:#,0} (Use Ctrl-C to quit...)"); + nextStatusUpdate = DateTime.Now.Add(statusUpdateInterval); + } + } + rate = counter / elapsed.Elapsed.TotalSeconds; + Console.WriteLine("Finished."); + Console.WriteLine($"{elapsed.Elapsed:hh\\:mm\\:ss} @ {rate:#,0} per-sec. Iterations: {counter:#,0} (Use Ctrl-C to quit...)"); + return new EnduranceTestResult(elapsed.Elapsed,rate,counter); + } + } +} \ No newline at end of file diff --git a/src/X509.EnduranceTest.Shared/Common/MyConfig.cs b/src/X509.EnduranceTest.Shared/Common/MyConfig.cs new file mode 100644 index 0000000..c142557 --- /dev/null +++ b/src/X509.EnduranceTest.Shared/Common/MyConfig.cs @@ -0,0 +1,27 @@ + +using System; +using System.Configuration; +using System.Security.Cryptography.X509Certificates; +using X509.EnduranceTest.Shared; + +namespace UnitTests +{ + public static class MyConfig + { + internal static string TestCertThumbPrint => + ConfigurationManager.AppSettings["X509.ThumbPrint"] ?? + throw new Exception($"AppSetting 'X509.ThumbPrint' not defined."); + /// + /// Hardcoded password of test certificate files that are inside /TestCertificates folder + /// + /// Never hard code passwords in the code like this. This is just test + internal static string TestCertficatePassword => "MyP@ssw0rd"; + /// + /// TODO: Below certificates expire on 2023-08-17. Need to recreate certificate then to continue using this test project + /// + public static X509Certificate2 EncryptionCertificate=> CertificateLoader.LoadFromFile(ConfigurationManager.AppSettings["EncryptionCertificatePath"]); + public static X509Certificate2 DecryptionCertificate => CertificateLoader.LoadFromFile(ConfigurationManager.AppSettings["DecryptionCertificatePath"], MyConfig.TestCertficatePassword); + public static X509Certificate2 VerifyCertificate => CertificateLoader.LoadFromFile(ConfigurationManager.AppSettings["VerifyCertificatePath"]); + public static X509Certificate2 SigningCertificate => CertificateLoader.LoadFromFile(ConfigurationManager.AppSettings["SigningCertificatePath"], MyConfig.TestCertficatePassword); + } +} diff --git a/src/X509.EnduranceTest.Shared/Common/MyConfigForEnduranceTests.cs b/src/X509.EnduranceTest.Shared/Common/MyConfigForEnduranceTests.cs new file mode 100644 index 0000000..5f3b576 --- /dev/null +++ b/src/X509.EnduranceTest.Shared/Common/MyConfigForEnduranceTests.cs @@ -0,0 +1,14 @@ + +using System; +using System.Configuration; + +namespace UnitTests +{ + internal static class MyConfigForEnduranceTests + { + internal static int SampleDataSizeKB => Convert.ToInt32(AppSetting("SampleDataSizeKB")); + + static string AppSetting(string name) => ConfigurationManager.AppSettings[name] ?? throw new Exception($"AppSetting 'name' not defined."); + + } +} diff --git a/src/X509.EnduranceTest.Shared/Common/TestDataGenerator.cs b/src/X509.EnduranceTest.Shared/Common/TestDataGenerator.cs new file mode 100644 index 0000000..d7687e8 --- /dev/null +++ b/src/X509.EnduranceTest.Shared/Common/TestDataGenerator.cs @@ -0,0 +1,26 @@ + +using System; +using System.IO; + +namespace X509.EnduranceTest.Shared +{ + public class TestDataGenerator + { + public static byte[] GenerateJunk(int kiloBytes) + { + int maxBytes = kiloBytes * 1024; + + using var buffer = new MemoryStream(maxBytes); + var bytesWritten = 0; + + while (bytesWritten < maxBytes) + { + var more = Guid.NewGuid().ToByteArray(); + buffer.Write(more, 0, more.Length); + bytesWritten += more.Length; + } + buffer.Flush(); + return buffer.ToArray(); + } + } +} diff --git a/src/Org.Security.Cryptography.X509Extensions/X509CertificateCache.cs b/src/X509.EnduranceTest.Shared/Common/X509CertificateCache.cs similarity index 98% rename from src/Org.Security.Cryptography.X509Extensions/X509CertificateCache.cs rename to src/X509.EnduranceTest.Shared/Common/X509CertificateCache.cs index f67cc87..d0bb07a 100644 --- a/src/Org.Security.Cryptography.X509Extensions/X509CertificateCache.cs +++ b/src/X509.EnduranceTest.Shared/Common/X509CertificateCache.cs @@ -42,7 +42,7 @@ using System.Linq; using System.Security.Cryptography.X509Certificates; -namespace Org.Security.Cryptography +namespace X509.EnduranceTest.Shared { /// /// Per-thread-cache of X509Certificate2 instances, identified by StoreName, StoreLocation, Thumbprint and an optional cache name. @@ -86,9 +86,8 @@ public static X509Certificate2 TryGetCertificate(string x509Thumbprint, StoreNam // Lookup the cache. var found = CertificateCache.TryGetValue(cacheKey, out var certFromCache); if (found && null != certFromCache) return certFromCache; - // Not in cache. Look in the store. - using (X509Store store = new X509Store(storeName, storeLocation)) + using (X509Store store = new(storeName, storeLocation)) { // Open an existing store. store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); diff --git a/src/X509.EnduranceTest.Shared/EncrypAndDecrypt/EncryptionDecryptionMenuPage.cs b/src/X509.EnduranceTest.Shared/EncrypAndDecrypt/EncryptionDecryptionMenuPage.cs new file mode 100644 index 0000000..052d891 --- /dev/null +++ b/src/X509.EnduranceTest.Shared/EncrypAndDecrypt/EncryptionDecryptionMenuPage.cs @@ -0,0 +1,51 @@ + +using System; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; +using EasyConsole; +using UnitTests; + +namespace X509.EnduranceTest.Shared +{ + internal class EncryptionDecryptionMenuPage:MenuPage + { + public EncryptionDecryptionMenuPage(TestProgram program) : base("EncryptionDecryption", program) + { + this.Menu.AddSync("Print AsymmetricAlgorithm provider.", () => ConsoleWriter.PrintCSP(MyConfig.DecryptionCertificate)); + this.Menu.AddSync("Validate Encryption/Decryption, ONCE.", () => ValidateEncryptionAndDecryptionOnce(MyConfig.DecryptionCertificate)); + this.Menu.AddSync("X509Certificate2.EncryptStream - 8KB random data 100,000 times", () => X509Certificate2ExtensionsEnduranceTests.Encryption(MyConfig.DecryptionCertificate, 8, 100000)); + this.Menu.AddSync("X509Certificate2.DecryptStream - 8KB random data 100,000 times", () => X509Certificate2ExtensionsEnduranceTests.Decryption(MyConfig.DecryptionCertificate, 8, 100000)); + this.Menu.AddSync("X509CertificateBasedEncryptor.EncryptStringToBase64WithTimestamp - Random 256 bytes, 100,000", () => X509CertificateBasedEncryptorEnduranceTests.EncryptStringToBase64WithTimestamp(.25, 100000)); + this.Menu.AddSync("X509CertificateBasedDecryptor.DecryptBase64EncodedStringWithTimestampValidation - Random 256 bytes, 100,000", () => X509CertificateBasedDecryptorEnduranceTests.DecryptStringToBase64WithTimestamp(.25, 100000)); + this.Menu.AddSync("X509CertificateBasedEncryptor.EncryptStringToBase64WithTimestamp - Random 1 KB, 100,000", () => X509CertificateBasedEncryptorEnduranceTests.EncryptStringToBase64WithTimestamp(1, 100000)); + this.Menu.AddSync("X509CertificateBasedDecryptor.DecryptBase64EncodedStringWithTimestampValidation - Random 1 KB, 100,000", () => X509CertificateBasedDecryptorEnduranceTests.DecryptStringToBase64WithTimestamp(1, 100000)); + this.Menu.AddSync("X509CertificateBasedEncryptor.EncryptStringToBase64WithTimestamp - Random 8KB, 100,000", () => X509CertificateBasedEncryptorEnduranceTests.EncryptStringToBase64WithTimestamp(8, 100000)); + this.Menu.AddSync("X509CertificateBasedDecryptor.DecryptBase64EncodedStringWithTimestampValidation - Random 8KB, 100,000", () => X509CertificateBasedDecryptorEnduranceTests.DecryptStringToBase64WithTimestamp(8, 100000)); + } + public async override Task Display(CancellationToken cancellationToken) + { + await base.Display(cancellationToken); + Input.ReadString("Press any key to continue"); + await this.Program.NavigateTo(cancellationToken); + } + internal static void ValidateEncryptionAndDecryptionOnce(X509Certificate2 cert) + { + var sampleData = TestDataGenerator.GenerateJunk(MyConfigForEnduranceTests.SampleDataSizeKB); + Console.WriteLine($"Generated {sampleData.Length / 1024} KB random binary data."); + + // Encrypt/Decrypt ONCE... + var encryptedBytes = EncryptionDecryptionUtils.EncryptBytesUsingExtensionMethod(cert, sampleData); + var decryptedBytes = EncryptionDecryptionUtils.DecryptBytesUsingExtensionMethod(cert, encryptedBytes); + Console.WriteLine($"SampleData: {sampleData.Length:#,0} bytes"); + Console.WriteLine($"Encrypted: {encryptedBytes.Length:#,0} bytes"); + Console.WriteLine($"Decrypted: {decryptedBytes.Length:#,0} bytes"); + + // Vallidate + var good = sampleData.SequenceEqual(decryptedBytes); + if (!good) throw new Exception("Decrypted result doesn't match original data."); + } + + } +} \ No newline at end of file diff --git a/src/X509.EnduranceTest.Shared/EncrypAndDecrypt/EncryptionDecryptionUtils.cs b/src/X509.EnduranceTest.Shared/EncrypAndDecrypt/EncryptionDecryptionUtils.cs new file mode 100644 index 0000000..2b1660a --- /dev/null +++ b/src/X509.EnduranceTest.Shared/EncrypAndDecrypt/EncryptionDecryptionUtils.cs @@ -0,0 +1,74 @@ +using System; +using System.IO; +using System.Security.Cryptography.X509Certificates; + +using Org.Security.Cryptography; + +namespace UnitTests +{ + public class EncryptionDecryptionUtils + { + public static byte[] EncryptBytesWithTimestampUsingX509CertificateBasedEncryptor(X509Certificate2 x509Cert, byte[] inputData) + { + var encryptor = new X509CertificateBasedEncryptor(); + using var input = new MemoryStream(inputData); + using var output = new MemoryStream(inputData.Length); + encryptor.EncryptStreamWithTimestamp(x509Cert, input, output); + output.Flush(); + return output.ToArray(); + } + public static byte[] DecryptBytesWithTimestampValidationUsingX509CertificateBasedDecryptor( + byte[] inputData, + Func certificateSelector, + TimeSpan? lifeSpan = null) + { + var decryptor = new X509CertificateBasedDecryptor(); + using var input = new MemoryStream(inputData); + using var output = new MemoryStream(inputData.Length); + if (lifeSpan.HasValue) + { + decryptor.DecryptStreamWithTimestampValidation(input, output, certificateSelector, lifeSpan.Value); + } + else + { + decryptor.DecryptStreamWithTimestampValidation(input, output, certificateSelector); + } + output.Flush(); + return output.ToArray(); + } + public static byte[] EncryptBytesUsingX509CertificateBasedEncryptor(X509Certificate2 x509Cert, byte[] inputData) + { + var encryptor = new X509CertificateBasedEncryptor(); + using var input = new MemoryStream(inputData); + using var output = new MemoryStream(inputData.Length); + encryptor.EncryptStream(x509Cert, input, output); + output.Flush(); + return output.ToArray(); + } + public static byte[] DecryptBytesUsingX509CertificateBasedDecryptor(byte[] inputData, Func certificateSelector) + { + var decryptor = new X509CertificateBasedDecryptor(); + using var input = new MemoryStream(inputData); + using var output = new MemoryStream(inputData.Length); + decryptor.DecryptStream(input, output, certificateSelector); + output.Flush(); + return output.ToArray(); + } + public static byte[] EncryptBytesUsingExtensionMethod(X509Certificate2 x509Cert, byte[] inputData) + { + using var input = new MemoryStream(inputData); + using var output = new MemoryStream(inputData.Length); + x509Cert.EncryptStream(input, output); + output.Flush(); + return output.ToArray(); + } + public static byte[] DecryptBytesUsingExtensionMethod(X509Certificate2 x509Cert, byte[] inputData) + { + using var input = new MemoryStream(inputData); + using var output = new MemoryStream(inputData.Length); + x509Cert.DecryptStream(input, output); + output.Flush(); + return output.ToArray(); + } + } +} \ No newline at end of file diff --git a/src/X509.EnduranceTest.Shared/EncrypAndDecrypt/X509Certificate2ExtensionsEnduranceTests.cs b/src/X509.EnduranceTest.Shared/EncrypAndDecrypt/X509Certificate2ExtensionsEnduranceTests.cs new file mode 100644 index 0000000..f7a742b --- /dev/null +++ b/src/X509.EnduranceTest.Shared/EncrypAndDecrypt/X509Certificate2ExtensionsEnduranceTests.cs @@ -0,0 +1,28 @@ + +using System; +using System.Security.Cryptography.X509Certificates; +using UnitTests; + +namespace X509.EnduranceTest.Shared +{ + internal class X509Certificate2ExtensionsEnduranceTests + { + internal static void Encryption(X509Certificate2 cert, int dataSizeInKB, int maxIterations) + { + var sampleData = TestDataGenerator.GenerateJunk(dataSizeInKB); + Console.WriteLine($"Generated {sampleData.Length / 1024} KB random binary data."); + var encryptedBytes = EncryptionDecryptionUtils.EncryptBytesUsingExtensionMethod(cert, sampleData); + var decryptedBytes = EncryptionDecryptionUtils.DecryptBytesUsingExtensionMethod(cert, encryptedBytes); + + var result = EnduranceTestRunner.Run(maxIterations, () => EncryptionDecryptionUtils.EncryptBytesUsingExtensionMethod(cert, decryptedBytes)); + } + + internal static void Decryption(X509Certificate2 cert, int dataSizeInKB, int maxIterations) + { + var sampleData = TestDataGenerator.GenerateJunk(dataSizeInKB); + Console.WriteLine($"Generated {sampleData.Length / 1024} KB random binary data."); + var encryptedBytes = EncryptionDecryptionUtils.EncryptBytesUsingExtensionMethod(cert, sampleData); + EnduranceTestRunner.Run(maxIterations, () => EncryptionDecryptionUtils.DecryptBytesUsingExtensionMethod(cert, encryptedBytes)); + } + } +} diff --git a/src/X509.EnduranceTest.Shared/EncrypAndDecrypt/X509CertificateBasedDecryptorEnduranceTests.cs b/src/X509.EnduranceTest.Shared/EncrypAndDecrypt/X509CertificateBasedDecryptorEnduranceTests.cs new file mode 100644 index 0000000..7cc1283 --- /dev/null +++ b/src/X509.EnduranceTest.Shared/EncrypAndDecrypt/X509CertificateBasedDecryptorEnduranceTests.cs @@ -0,0 +1,25 @@ +using Bogus.DataSets; +using Org.Security.Cryptography; +using System; +using UnitTests; + +namespace X509.EnduranceTest.Shared +{ + internal class X509CertificateBasedDecryptorEnduranceTests + { + internal static void DecryptStringToBase64WithTimestamp(double dataSizeInKB,int loopCount) + { + var sampleData = new Lorem().Random.String2((int)(dataSizeInKB * 1024)); + Console.WriteLine($"Generated {sampleData.Length:0,#} bytes random text data."); + var encryptor = new X509CertificateBasedEncryptor(); + var encryptonCertificate = MyConfig.EncryptionCertificate; + var decryptonCertificate = MyConfig.DecryptionCertificate; + + var encryptedValue = encryptor.EncryptStringToBase64WithTimestamp(encryptonCertificate, sampleData); + var decryptor = new X509CertificateBasedDecryptor(); + + var result = EnduranceTestRunner.Run(loopCount, + () => decryptor.DecryptBase64EncodedStringWithTimestampValidation(encryptedValue,(thumprint)=> decryptonCertificate, TimeSpan.FromMinutes(5))); + } + } +} \ No newline at end of file diff --git a/src/X509.EnduranceTest.Shared/EncrypAndDecrypt/X509CertificateBasedEncryptorEnduranceTests.cs b/src/X509.EnduranceTest.Shared/EncrypAndDecrypt/X509CertificateBasedEncryptorEnduranceTests.cs new file mode 100644 index 0000000..3d922fb --- /dev/null +++ b/src/X509.EnduranceTest.Shared/EncrypAndDecrypt/X509CertificateBasedEncryptorEnduranceTests.cs @@ -0,0 +1,20 @@ +using Bogus.DataSets; +using Org.Security.Cryptography; +using System; +using UnitTests; + +namespace X509.EnduranceTest.Shared +{ + internal class X509CertificateBasedEncryptorEnduranceTests + { + internal static void EncryptStringToBase64WithTimestamp(double dataSizeInKB, int loopCount) + { + var sampleData = new Lorem().Random.String2((int)(dataSizeInKB * 1024)); + Console.WriteLine($"Generated {sampleData.Length:#,0} bytes random text data."); + var encryptor = new X509CertificateBasedEncryptor(); + var encryptionCertificate = MyConfig.EncryptionCertificate; + var result = EnduranceTestRunner.Run(loopCount, + () => encryptor.EncryptStringToBase64WithTimestamp(encryptionCertificate, sampleData)); + } + } +} \ No newline at end of file diff --git a/src/X509.EnduranceTest.Shared/HackForCSharp9.cs b/src/X509.EnduranceTest.Shared/HackForCSharp9.cs new file mode 100644 index 0000000..18e3496 --- /dev/null +++ b/src/X509.EnduranceTest.Shared/HackForCSharp9.cs @@ -0,0 +1,20 @@ +/// +/// Hack to get C# 9.0 compiled for .Net Framework projects +/// https://blog.ndepend.com/using-c9-record-and-init-property-in-your-net-framework-4-x-net-standard-and-net-core-projects/ +/// +/// +/// In .Net 5 the below class is already present. So better do conditional compilation for below code. +/// +namespace System.Runtime.CompilerServices +{ + using System.ComponentModel; + + /// + /// Reserved to be used by the compiler for tracking metadata. + /// This class should not be used by developers in source code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class IsExternalInit + { + } +} \ No newline at end of file diff --git a/src/X509.EnduranceTest.Shared/MainMenuPage.cs b/src/X509.EnduranceTest.Shared/MainMenuPage.cs new file mode 100644 index 0000000..3f40313 --- /dev/null +++ b/src/X509.EnduranceTest.Shared/MainMenuPage.cs @@ -0,0 +1,12 @@ +using EasyConsole; +namespace X509.EnduranceTest.Shared +{ + internal class MainMenuPage : MenuPage + { + public MainMenuPage(TestProgram program) : base("Main Page", program, + new Option("Encryption/Decryption", (token) => program.NavigateTo(token)), + new Option("Sign&Verify", (token) => program.NavigateTo(token))) + { + } + } +} \ No newline at end of file diff --git a/src/X509.EnduranceTest.Shared/SignAndVerify/SignAndVerifyMenuPage.cs b/src/X509.EnduranceTest.Shared/SignAndVerify/SignAndVerifyMenuPage.cs new file mode 100644 index 0000000..d3e266e --- /dev/null +++ b/src/X509.EnduranceTest.Shared/SignAndVerify/SignAndVerifyMenuPage.cs @@ -0,0 +1,51 @@ +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using EasyConsole; +using Org.Security.Cryptography; +using UnitTests; + +namespace X509.EnduranceTest.Shared +{ + internal class SignAndVerifyMenuPage : MenuPage + { + public SignAndVerifyMenuPage(TestProgram program) : base("SignAndVerify", program) + { + this.Menu.AddSync("Validate Sign/Verify ONCE using MD5. (Fail in .Net Framework)", () => ValidateSignAndVerifyOnce("MD5")); + + this.Menu.AddSync("Validate Sign/Verify ONCE using SHA1.", () => ValidateSignAndVerifyOnce("SHA1")); + this.Menu.AddSync("Validate Sign/Verify ONCE using SHA256.", () => ValidateSignAndVerifyOnce("SHA256")); + + this.Menu.AddSync("X509Certificate2.CreateSignature - SHA256, Random 256 bytes, 100,000 times", () => X509Certificate2_SignAndVerifyEnduranceTests.RunSign("SHA256", .25, 100000)); + this.Menu.AddSync("X509Certificate2.CreateSignature - SHA256, Random 1 KB, 100,000 times", () => X509Certificate2_SignAndVerifyEnduranceTests.RunSign("SHA256", 1, 100000)); + this.Menu.AddSync("X509Certificate2.CreateSignature - SHA256, Random 8 KB, 100,000 times", () => X509Certificate2_SignAndVerifyEnduranceTests.RunSign("SHA256", 8, 100000)); + this.Menu.AddSync("X509Certificate2.CreateSignature - SHA512, Random 8 KB, 100,000 times", () => X509Certificate2_SignAndVerifyEnduranceTests.RunSign("SHA512", 8, 100000)); + + this.Menu.AddSync("X509Certificate2.VerifySignature - SHA256, Random 256 bytes, 100,000 times", () => X509Certificate2_SignAndVerifyEnduranceTests.RunVerify("SHA256", .25, 100000)); + this.Menu.AddSync("X509Certificate2.VerifySignature - SHA256, Random 1 KB, 100,000 times", () => X509Certificate2_SignAndVerifyEnduranceTests.RunVerify("SHA256", 1, 100000)); + this.Menu.AddSync("X509Certificate2.VerifySignature - SHA256, Random 8 KB, 100,000 times", () => X509Certificate2_SignAndVerifyEnduranceTests.RunVerify("SHA256", 8, 100000)); + this.Menu.AddSync("X509Certificate2.VerifySignature - SHA512, Random 8 KB, 100,000 times", () => X509Certificate2_SignAndVerifyEnduranceTests.RunVerify("SHA512", 8, 100000)); + } + private void ValidateSignAndVerifyOnce(string hashName) + { + const string TestData = "Hello world"; + var payload = Encoding.UTF8.GetBytes(TestData); + using var hashAlgorithm = HashAlgorithm.Create(hashName); + var hash = hashAlgorithm.ComputeHash(payload); + var signature = MyConfig.SigningCertificate.CreateSignature(hash); + X509Certificate2 verifyCertificate = MyConfig.VerifyCertificate; + // Act + var good = verifyCertificate.VerifySignature(hash, signature); + ConsoleWriter.WriteSuccess("Successfully signed and verified"); + } + + public async override Task Display(CancellationToken cancellationToken) + { + await base.Display(cancellationToken); + Input.ReadString("Press any key to continue"); + await this.Program.NavigateTo(cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/X509.EnduranceTest.Shared/SignAndVerify/X509Certificate2_SignAndVerifyEnduranceTests.cs b/src/X509.EnduranceTest.Shared/SignAndVerify/X509Certificate2_SignAndVerifyEnduranceTests.cs new file mode 100644 index 0000000..2b512cb --- /dev/null +++ b/src/X509.EnduranceTest.Shared/SignAndVerify/X509Certificate2_SignAndVerifyEnduranceTests.cs @@ -0,0 +1,38 @@ +using System; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using Org.Security.Cryptography; +using UnitTests; + +namespace X509.EnduranceTest.Shared +{ + internal class X509Certificate2_SignAndVerifyEnduranceTests + { + internal static void RunSign( + string hashName , + //Below dataSizeInKB doesn't make sense as the hash is signed and hash is fixed size. + double dataSizeInKB, + int maxIterations) + { + X509Certificate2 cert = MyConfig.SigningCertificate; + var payload = TestDataGenerator.GenerateJunk((int)(dataSizeInKB * 1024)); + Console.WriteLine($"Generated {payload.Length / 1024} KB random binary data."); + using var hashAlgorithm = HashAlgorithm.Create(hashName); + // Digest + var hash = hashAlgorithm.ComputeHash(payload); + Console.WriteLine($"Hash: {hashName} {hash.Length * 8} bits / {hash.Length} BYTES"); + // Act + EnduranceTestRunner.Run(maxIterations, () => cert.CreateSignature(hash)); + } + internal static void RunVerify(string hashName, double dataSizeInKB, int maxIterations) + { + var payload = TestDataGenerator.GenerateJunk((int)(dataSizeInKB * 1024)); + using var hashAlgorithm = HashAlgorithm.Create(hashName); + var hash = hashAlgorithm.ComputeHash(payload); + var signature = MyConfig.SigningCertificate.CreateSignature(hash); + X509Certificate2 verifyCertificate = MyConfig.VerifyCertificate; + // Act + EnduranceTestRunner.Run(maxIterations,()=> verifyCertificate.VerifySignature(hash, signature)); + } + } +} \ No newline at end of file diff --git a/src/X509.EnduranceTest.Shared/TestMain.cs b/src/X509.EnduranceTest.Shared/TestMain.cs index 7ef4061..159de15 100644 --- a/src/X509.EnduranceTest.Shared/TestMain.cs +++ b/src/X509.EnduranceTest.Shared/TestMain.cs @@ -1,266 +1,52 @@  using System; using System.Configuration; -using System.Diagnostics; using System.IO; -using System.Linq; using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; - +using System.Threading; +using System.Threading.Tasks; +using EasyConsole; using Org.Security.Cryptography; +using UnitTests; namespace X509.EnduranceTest.Shared { + public static class TestMain { - //............................................................................... - #region AppSettings - //............................................................................... - - static string X509Thumbprint => AppSetting("X509.Thumbprint"); - static int SampleDataSizeKB => Convert.ToInt32(AppSetting("SampleDataSizeKB")); - static int LoopCount => Convert.ToInt32(AppSetting("LoopCount")); - - static string AppSetting(string name) => ConfigurationManager.AppSettings[name] ?? throw new Exception($"AppSetting 'name' not defined."); - - #endregion - - public static void Run() + async public static Task Run() { try { - PrintOptionsAndRunTest(); + await PrintOptionsAndRunTestAsync(); } catch (Exception err) { - var topError = err; - - while (null != err) - { - Console.WriteLine($"[{err.GetType().FullName}]"); - Console.WriteLine(err.Message); - err = err.InnerException; - } - - Console.WriteLine(topError.StackTrace); - } - } - - static void PrintOptionsAndRunTest() - { - while (true) - { - Console.WriteLine("-------------------------------------------"); - Console.WriteLine("[P] Print AsymmetricAlgorithm provider."); - Console.WriteLine("[V] Validate Encryption/Decryption, ONCE."); - Console.WriteLine("[E] Start ENcryption loop."); - Console.WriteLine("[D] Start DEcryption loop."); - Console.WriteLine("[Q] or Ctrl-C to quit"); - Console.WriteLine("-------------------------------------------"); - - var input = (Console.ReadLine() ?? string.Empty).Trim().ToUpper(); - - var cert = X509CertificateCache.GetCertificate(X509Thumbprint); - - switch (input) - { - case "V": - ValidateEncryptionAndDecryptionOnce(cert); - break; - - case "E": - BeginEncryptionLoop(cert, LoopCount); - break; - - case "D": - BeginDecryptionLoop(cert, LoopCount); - break; - - case "P": - PrintCSP(cert); - break; - - case "Q": - return; - - default: - ValidateEncryptionAndDecryptionOnce(cert); - break; - } - - Console.WriteLine(); + ConsoleWriter.WriteRecursively(err); } + Console.WriteLine("Press ENTER to quit..."); + Console.ReadLine(); } - - public static void ValidateEncryptionAndDecryptionOnce(X509Certificate2 cert) + async static Task PrintOptionsAndRunTestAsync() { - var sampleData = GenerateJunk(SampleDataSizeKB); - Console.WriteLine($"Generated {sampleData.Length / 1024} KB random binary data."); - - // Encrypt/Decrypt ONCE... - var encryptedBytes = EncryptBytes(sampleData, cert); - var decryptedBytes = DecryptBytes(encryptedBytes, cert); - Console.WriteLine($"SampleData: {sampleData.Length:#,0} bytes"); - Console.WriteLine($"Encrypted: {encryptedBytes.Length:#,0} bytes"); - Console.WriteLine($"Decrypted: {decryptedBytes.Length:#,0} bytes"); - - // Vallidate - var good = sampleData.SequenceEqual(decryptedBytes); - if (!good) throw new Exception("Decrypted result doesn't match original data."); - } - - static void PrintCSP(X509Certificate2 cert) - { - Console.WriteLine($"Cert: {cert.Subject} / {cert.Thumbprint}"); - Console.WriteLine(); - - try - { - Console.WriteLine("cert.PublicKey.Key"); - var alg = cert.PublicKey.Key; - Console.WriteLine(alg.GetType().FullName); - Console.WriteLine(); - } - catch (Exception err) - { - PrintErrorSummary(err); - } - - try - { - // Fails in .NET Framework if -KeySpec Signature not specified. - // Works in .NET Core - Console.WriteLine("cert.PrivateKey"); - var alg = cert.PrivateKey; - Console.WriteLine(alg.GetType().FullName); - Console.WriteLine(); - } - catch (Exception err) - { - PrintErrorSummary(err); - } - - try - { - Console.WriteLine("cert.GetRSAPublicKey()"); - var alg = cert.GetRSAPublicKey(); - Console.WriteLine(alg.GetType().FullName); - Console.WriteLine(); - } - catch (Exception err) + bool canContinue= true; + while (canContinue) { - PrintErrorSummary(err); - } - - try - { - Console.WriteLine("cert.GetRSAPrivateKey()"); - var alg = cert.GetRSAPrivateKey(); - Console.WriteLine(alg.GetType().FullName); - Console.WriteLine(); - } - catch (Exception err) - { - PrintErrorSummary(err); - } - - void PrintErrorSummary(Exception ex) - { - Console.WriteLine("ERROR:"); - while(null != ex) - { - Console.WriteLine($"[{ex.GetType().FullName}]"); - Console.WriteLine(ex.Message); - ex = ex.InnerException; - } - } - } - - static void BeginEncryptionLoop(X509Certificate2 cert, int maxIterations) - { - BeginLoop(cert, maxIterations, encrypt: true); - } - - static void BeginDecryptionLoop(X509Certificate2 cert, int maxIterations) - { - BeginLoop(cert, maxIterations, decrypt: true); - } - - static void BeginLoop(X509Certificate2 cert, int maxIterations, bool encrypt = false, bool decrypt = false) - { - var sampleData = GenerateJunk(SampleDataSizeKB); - - Console.WriteLine($"MaxIterations: {maxIterations:#,0}"); - Console.WriteLine($"Generated {sampleData.Length / 1024} KB random binary data."); - - var encryptedBytes = EncryptBytes(sampleData, cert); - var decryptedBytes = DecryptBytes(encryptedBytes, cert); - - var counter = 0; - var elapsed = Stopwatch.StartNew(); - var statusUpdateInterval = TimeSpan.FromSeconds(2); - var nextStatusUpdate = DateTime.Now.Add(statusUpdateInterval); - - var rate = 0.0; - - while (counter++ <= maxIterations) - { - if (encrypt) EncryptBytes(decryptedBytes, cert); - if (decrypt) DecryptBytes(encryptedBytes, cert); - - if (nextStatusUpdate < DateTime.Now) - { - rate = counter / elapsed.Elapsed.TotalSeconds; - Console.WriteLine($"{elapsed.Elapsed:hh\\:mm\\:ss} @ {rate:#,0} per-sec. Iterations: {counter:#,0} (Use Ctrl-C to quit...)"); - nextStatusUpdate = DateTime.Now.Add(statusUpdateInterval); - } - } - - rate = counter / elapsed.Elapsed.TotalSeconds; - Console.WriteLine("Finished."); - Console.WriteLine($"{elapsed.Elapsed:hh\\:mm\\:ss} @ {rate:#,0} per-sec. Iterations: {counter:#,0} (Use Ctrl-C to quit...)"); - } - - static byte[] EncryptBytes(byte[] inputData, X509Certificate2 cert) - { - using (var input = new MemoryStream(inputData)) - using (var output = new MemoryStream()) - { - cert.EncryptStream(input, output); - output.Flush(); - return output.ToArray(); - } - } - - static byte[] DecryptBytes(byte[] inputData, X509Certificate2 cert) - { - using (var input = new MemoryStream(inputData)) - using (var output = new MemoryStream(inputData.Length)) - { - cert.DecryptStream(input, output); - output.Flush(); - return output.ToArray(); - } - } - - static byte[] GenerateJunk(int kiloBytes) - { - int maxBytes = kiloBytes * 1024; - - using (var buffer = new MemoryStream(maxBytes)) - { - var bytesWritten = 0; - - while (bytesWritten < maxBytes) - { - var more = Guid.NewGuid().ToByteArray(); - buffer.Write(more, 0, more.Length); - bytesWritten += more.Length; - } - - buffer.Flush(); - return buffer.ToArray(); + var menu = new Menu() + .AddSync("Print AsymmetricAlgorithm provider.", () => ConsoleWriter.PrintCSP(MyConfig.DecryptionCertificate)) + //.AddSync("Validate Encryption/Decryption, ONCE.", () => ValidateEncryptionAndDecryptionOnce(MyConfig.DecryptionCertificate)) + .AddSync("X509Certificate2.EncryptStream - 8KB random data 100,000 times", () => X509Certificate2ExtensionsEnduranceTests.Encryption(MyConfig.DecryptionCertificate,8, 100000)) + .AddSync("X509Certificate2.DecryptStream - 8KB random data 100,000 times", () => X509Certificate2ExtensionsEnduranceTests.Decryption(MyConfig.DecryptionCertificate,8, 100000)) + .AddSync("X509CertificateBasedEncryptor.EncryptStringToBase64WithTimestamp - Random 256 bytes, 100,000", () => X509CertificateBasedEncryptorEnduranceTests.EncryptStringToBase64WithTimestamp(.25, 100000)) + .AddSync("X509CertificateBasedDecryptor.DecryptBase64EncodedStringWithTimestampValidation - Random 256 bytes, 100,000", () => X509CertificateBasedDecryptorEnduranceTests.DecryptStringToBase64WithTimestamp(.25, 100000)) + .AddSync("X509CertificateBasedEncryptor.EncryptStringToBase64WithTimestamp - Random 1 KB, 100,000", () => X509CertificateBasedEncryptorEnduranceTests.EncryptStringToBase64WithTimestamp(1, 100000)) + .AddSync("X509CertificateBasedDecryptor.DecryptBase64EncodedStringWithTimestampValidation - Random 1 KB, 100,000", () => X509CertificateBasedDecryptorEnduranceTests.DecryptStringToBase64WithTimestamp(1, 100000)) + .AddSync("X509CertificateBasedEncryptor.EncryptStringToBase64WithTimestamp - Random 8KB, 100,000", () => X509CertificateBasedEncryptorEnduranceTests.EncryptStringToBase64WithTimestamp(8, 100000)) + .AddSync("X509CertificateBasedDecryptor.DecryptBase64EncodedStringWithTimestampValidation - Random 8KB, 100,000", () => X509CertificateBasedDecryptorEnduranceTests.DecryptStringToBase64WithTimestamp(8,100000)) + .AddSync("Exit",()=> canContinue = false); + await menu.Display(CancellationToken.None); } + } - } -} + } +} \ No newline at end of file diff --git a/src/X509.EnduranceTest.Shared/TestProgram.cs b/src/X509.EnduranceTest.Shared/TestProgram.cs new file mode 100644 index 0000000..de498c1 --- /dev/null +++ b/src/X509.EnduranceTest.Shared/TestProgram.cs @@ -0,0 +1,15 @@ +using EasyConsole; + +namespace X509.EnduranceTest.Shared +{ + public class TestProgram : Program + { + public TestProgram():base("Org.Security.Cryptography.X509Extensions Endurance Tests", true) + { + AddPage(new MainMenuPage(this)); + AddPage(new EncryptionDecryptionMenuPage(this)); + AddPage(new SignAndVerifyMenuPage(this)); + SetPage(); + } + } +} \ No newline at end of file diff --git a/src/X509.EnduranceTest.Shared/X509.EnduranceTest.Shared.csproj b/src/X509.EnduranceTest.Shared/X509.EnduranceTest.Shared.csproj index 2b4d646..7bb4cb1 100644 --- a/src/X509.EnduranceTest.Shared/X509.EnduranceTest.Shared.csproj +++ b/src/X509.EnduranceTest.Shared/X509.EnduranceTest.Shared.csproj @@ -1,10 +1,14 @@ - + netstandard2.0 - + + 9.0 + + + diff --git a/src/X509Extensions.sln b/src/X509Extensions.sln deleted file mode 100644 index 55431c6..0000000 --- a/src/X509Extensions.sln +++ /dev/null @@ -1,54 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29905.134 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "UnitTests\UnitTests.csproj", "{C1DCEBAC-7A36-482D-BFD7-117FB6E36A4D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Org.Security.Cryptography.X509Extensions", "Org.Security.Cryptography.X509Extensions\Org.Security.Cryptography.X509Extensions.csproj", "{1050B0D5-DFC2-491A-B49D-088DEA765FE2}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{EAA2E090-9112-4E79-B25C-030FF4B6D25D}" - ProjectSection(SolutionItems) = preProject - ReadMe-FIPS.md = ReadMe-FIPS.md - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X509.EnduranceTest.NetFramework", "X509.EnduranceTest.NetFramework\X509.EnduranceTest.NetFramework.csproj", "{9389DAC3-E2E1-41BA-BE5C-34F3F8930DFC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X509.EnduranceTest.Shared", "X509.EnduranceTest.Shared\X509.EnduranceTest.Shared.csproj", "{EFD2F9AB-C5D1-44AF-9BC9-A24D35DF3E8C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X509.EnduranceTest.NetCore", "X509.EnduranceTest.NetCore\X509.EnduranceTest.NetCore.csproj", "{390AEE6C-9000-488C-8087-846FC0C34A78}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {C1DCEBAC-7A36-482D-BFD7-117FB6E36A4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C1DCEBAC-7A36-482D-BFD7-117FB6E36A4D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C1DCEBAC-7A36-482D-BFD7-117FB6E36A4D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C1DCEBAC-7A36-482D-BFD7-117FB6E36A4D}.Release|Any CPU.Build.0 = Release|Any CPU - {1050B0D5-DFC2-491A-B49D-088DEA765FE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1050B0D5-DFC2-491A-B49D-088DEA765FE2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1050B0D5-DFC2-491A-B49D-088DEA765FE2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1050B0D5-DFC2-491A-B49D-088DEA765FE2}.Release|Any CPU.Build.0 = Release|Any CPU - {9389DAC3-E2E1-41BA-BE5C-34F3F8930DFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9389DAC3-E2E1-41BA-BE5C-34F3F8930DFC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9389DAC3-E2E1-41BA-BE5C-34F3F8930DFC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9389DAC3-E2E1-41BA-BE5C-34F3F8930DFC}.Release|Any CPU.Build.0 = Release|Any CPU - {EFD2F9AB-C5D1-44AF-9BC9-A24D35DF3E8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EFD2F9AB-C5D1-44AF-9BC9-A24D35DF3E8C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EFD2F9AB-C5D1-44AF-9BC9-A24D35DF3E8C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EFD2F9AB-C5D1-44AF-9BC9-A24D35DF3E8C}.Release|Any CPU.Build.0 = Release|Any CPU - {390AEE6C-9000-488C-8087-846FC0C34A78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {390AEE6C-9000-488C-8087-846FC0C34A78}.Debug|Any CPU.Build.0 = Debug|Any CPU - {390AEE6C-9000-488C-8087-846FC0C34A78}.Release|Any CPU.ActiveCfg = Release|Any CPU - {390AEE6C-9000-488C-8087-846FC0C34A78}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {854A5078-7E41-474A-827E-7FA6204BF5CA} - EndGlobalSection -EndGlobal