diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index e2a98a3..d039a87 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -22,6 +22,13 @@
"certificate-tool"
],
"rollForward": false
+ },
+ "docfx": {
+ "version": "2.78.2",
+ "commands": [
+ "docfx"
+ ],
+ "rollForward": false
}
}
}
\ No newline at end of file
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index d554ec1..e2414cc 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -34,8 +34,10 @@ jobs:
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- - run: dotnet tool update -g docfx
- - run: docfx docs/docfx.json
+ - name: Build Documentation
+ run: >
+ dotnet tool restore --configfile nuget.config
+ dotnet docfx docs/docfx.json
- name: Upload Artifact
uses: actions/upload-pages-artifact@v3.0.1
diff --git a/.husky/pre-commit b/.husky/pre-commit
old mode 100644
new mode 100755
diff --git a/.husky/pre-push b/.husky/pre-push
old mode 100644
new mode 100755
diff --git a/AdvancedSystems.Security.Abstractions/IHashService.cs b/AdvancedSystems.Security.Abstractions/IHashService.cs
index cab1229..d1b87fd 100644
--- a/AdvancedSystems.Security.Abstractions/IHashService.cs
+++ b/AdvancedSystems.Security.Abstractions/IHashService.cs
@@ -28,7 +28,7 @@ public interface IHashService
///
/// Raised if the specified is not implemented.
///
- byte[] Compute(HashFunction hashFunction, byte[] buffer);
+ Span Compute(HashFunction hashFunction, Span buffer);
#endregion
}
\ No newline at end of file
diff --git a/AdvancedSystems.Security.Abstractions/IKDFService.cs b/AdvancedSystems.Security.Abstractions/IKDFService.cs
index 0804187..a566d73 100644
--- a/AdvancedSystems.Security.Abstractions/IKDFService.cs
+++ b/AdvancedSystems.Security.Abstractions/IKDFService.cs
@@ -1,4 +1,6 @@
-namespace AdvancedSystems.Security.Abstractions;
+using System;
+
+namespace AdvancedSystems.Security.Abstractions;
///
/// Represents a contract employing for key derivation functions.
@@ -51,7 +53,7 @@ public interface IKDFService
///
/// Additionally, some platforms may support SHA3-equivalent hash functions.
///
- bool TryComputePBKDF2(HashFunction hashFunction, byte[] password, byte[] salt, int hashSize, int iterations, out byte[]? pbkdf2);
+ bool TryComputePBKDF2(HashFunction hashFunction, Span password, Span salt, int hashSize, int iterations, out byte[]? pbkdf2);
#endregion
}
\ No newline at end of file
diff --git a/AdvancedSystems.Security.Abstractions/IRSACryptoService.cs b/AdvancedSystems.Security.Abstractions/IRSACryptoService.cs
deleted file mode 100644
index 28bda07..0000000
--- a/AdvancedSystems.Security.Abstractions/IRSACryptoService.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System;
-using System.Security.Cryptography;
-using System.Security.Cryptography.X509Certificates;
-using System.Text;
-
-namespace AdvancedSystems.Security.Abstractions;
-
-///
-/// Represents a contract for performing RSA-based asymmetric operations.
-///
-public interface IRSACryptoService : IDisposable
-{
- #region Properties
-
- X509Certificate2 Certificate { get; }
-
- HashAlgorithmName HashAlgorithmName { get; }
-
- RSAEncryptionPadding EncryptionPadding { get; }
-
- RSASignaturePadding SignaturePadding { get; }
-
- Encoding Encoding { get; }
-
- #endregion
-
- #region Methods
-
- byte[] Encrypt(byte[] message);
-
- byte[] Decrypt(byte[] cipher);
-
- byte[] SignData(byte[] data);
-
- bool VerifyData(byte[] data, byte[] signature);
-
- #endregion
-}
\ No newline at end of file
diff --git a/AdvancedSystems.Security.Abstractions/RSACryptoContract.cs b/AdvancedSystems.Security.Abstractions/RSACryptoContract.cs
new file mode 100644
index 0000000..56b2f3b
--- /dev/null
+++ b/AdvancedSystems.Security.Abstractions/RSACryptoContract.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+
+namespace AdvancedSystems.Security.Abstractions;
+
+///
+/// Represents a contract for performing RSA-based asymmetric operations.
+///
+public abstract class RSACryptoContract
+{
+ #region Properties
+
+ ///
+ public abstract X509Certificate2 Certificate { get; }
+
+ ///
+ public abstract HashFunction HashFunction { get; set; }
+
+ ///
+ public abstract RSAEncryptionPadding EncryptionPadding { get; set; }
+
+ ///
+ public abstract RSASignaturePadding SignaturePadding { get; set; }
+
+ #endregion
+
+ #region Methods
+
+ ///
+ /// Encrypts the input .
+ ///
+ ///
+ /// The data to encrypt.
+ ///
+ ///
+ /// The encrypted data.
+ ///
+ ///
+ /// Raised if this object has already been disposed.
+ ///
+ ///
+ /// Raised if the public key of the specified certificate is null.
+ ///
+ public abstract Span Encrypt(Span data);
+
+ ///
+ /// Decrypts the .
+ ///
+ ///
+ /// The data to decrypt.
+ ///
+ ///
+ /// The decrypted data.
+ ///
+ ///
+ /// Raised if this object has already been disposed.
+ ///
+ ///
+ /// Raised if the private key of the specified certificate is null.
+ ///
+ public abstract Span Decrypt(Span cipher);
+
+ ///
+ /// Computes the hash value of the specified data and signs it.
+ ///
+ ///
+ /// The input data to hash and sign.
+ ///
+ ///
+ /// The RSA signature for the specified data.
+ ///
+ ///
+ /// Raised if this object has already been disposed.
+ ///
+ ///
+ /// Raised if the private key of the specified certificate is null.
+ ///
+ public abstract Span SignData(Span data);
+
+ ///
+ /// Verifies that a digital signature is valid by calculating the
+ /// hash value of the specified data using the specified hash algorithm
+ /// and padding, and comparing it to the provided signature.
+ ///
+ ///
+ /// The signed data.
+ ///
+ ///
+ /// The signature data to be verified.
+ ///
+ ///
+ /// if the signature is valid; otherwise, .
+ ///
+ ///
+ /// Raised if this object has already been disposed.
+ ///
+ ///
+ /// Raised if the public key of the specified certificate is null.
+ ///
+ public abstract bool VerifyData(Span data, Span signature);
+
+ #endregion
+}
\ No newline at end of file
diff --git a/AdvancedSystems.Security.Tests/AdvancedSystems.Security.Tests.csproj b/AdvancedSystems.Security.Tests/AdvancedSystems.Security.Tests.csproj
index c652eeb..f13a7c1 100644
--- a/AdvancedSystems.Security.Tests/AdvancedSystems.Security.Tests.csproj
+++ b/AdvancedSystems.Security.Tests/AdvancedSystems.Security.Tests.csproj
@@ -10,15 +10,15 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -26,7 +26,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/AdvancedSystems.Security.Tests/Cryptography/HMACTests.cs b/AdvancedSystems.Security.Tests/Cryptography/HMACTests.cs
index 5fd78c9..37d98f3 100644
--- a/AdvancedSystems.Security.Tests/Cryptography/HMACTests.cs
+++ b/AdvancedSystems.Security.Tests/Cryptography/HMACTests.cs
@@ -44,11 +44,11 @@ public sealed class HMACTests
public void TestHMAC_Value(HashFunction hashFunction, string text, string expectedMac)
{
// Arrange
- byte[] key = "secret".GetBytes(Format.String);
- byte[] buffer = text.GetBytes(Format.String);
+ Span key = "secret".GetBytes(Format.String);
+ Span buffer = text.GetBytes(Format.String);
// Act
- byte[] actualMac = HMACProvider.Compute(hashFunction, key, buffer);
+ Span actualMac = HMACProvider.Compute(hashFunction, key, buffer);
// Assert
Assert.Equal(expectedMac.GetBytes(Format.Hex), actualMac);
@@ -79,7 +79,7 @@ public void TestHMAC_Size(HashFunction hashFunction, string text)
{
// Arrange
int keySize = 32;
- byte[] buffer = text.GetBytes(Format.String);
+ Span buffer = text.GetBytes(Format.String);
int expectedMacSize = hashFunction.GetSize();
// Act
diff --git a/AdvancedSystems.Security.Tests/Cryptography/HashTests.cs b/AdvancedSystems.Security.Tests/Cryptography/HashTests.cs
index 292508e..2874968 100644
--- a/AdvancedSystems.Security.Tests/Cryptography/HashTests.cs
+++ b/AdvancedSystems.Security.Tests/Cryptography/HashTests.cs
@@ -1,4 +1,5 @@
-using System.Security.Cryptography;
+using System;
+using System.Security.Cryptography;
using System.Text;
using AdvancedSystems.Security.Abstractions;
@@ -37,10 +38,10 @@ public void TestMD5Hash(string input, string expected, Format format)
{
// Arrange
Encoding encoding = Encoding.UTF8;
- byte[] buffer = encoding.GetBytes(input);
+ Span buffer = encoding.GetBytes(input);
// Act
- byte[] hash = HashProvider.Compute(HashFunction.MD5, buffer);
+ Span hash = HashProvider.Compute(HashFunction.MD5, buffer);
string md5 = hash.ToString(format);
// Assert
@@ -68,10 +69,10 @@ public void TestSHA1Hash(string input, string expected, Format format)
{
// Arrange
Encoding encoding = Encoding.UTF8;
- byte[] buffer = encoding.GetBytes(input);
+ Span buffer = encoding.GetBytes(input);
// Act
- byte[] hash = HashProvider.Compute(HashFunction.SHA1, buffer);
+ Span hash = HashProvider.Compute(HashFunction.SHA1, buffer);
string sha1 = hash.ToString(format);
// Assert
@@ -99,10 +100,10 @@ public void TestSHA256Hash(string input, string expected, Format format)
{
// Arrange
Encoding encoding = Encoding.UTF8;
- byte[] buffer = encoding.GetBytes(input);
+ Span buffer = encoding.GetBytes(input);
// Act
- byte[] hash = HashProvider.Compute(HashFunction.SHA256, buffer);
+ Span hash = HashProvider.Compute(HashFunction.SHA256, buffer);
string sha256 = hash.ToString(format);
// Assert
@@ -130,10 +131,10 @@ public void TestSHA384Hash(string input, string expected, Format format)
{
// Arrange
Encoding encoding = Encoding.UTF8;
- byte[] buffer = encoding.GetBytes(input);
+ Span buffer = encoding.GetBytes(input);
// Act
- byte[] hash = HashProvider.Compute(HashFunction.SHA384, buffer);
+ Span hash = HashProvider.Compute(HashFunction.SHA384, buffer);
string sha384 = hash.ToString(format);
// Assert
@@ -161,10 +162,10 @@ public void TestSHA512Hash(string input, string expected, Format format)
{
// Arrange
Encoding encoding = Encoding.UTF8;
- byte[] buffer = encoding.GetBytes(input);
+ Span buffer = encoding.GetBytes(input);
// Act
- byte[] hash = HashProvider.Compute(HashFunction.SHA512, buffer);
+ Span hash = HashProvider.Compute(HashFunction.SHA512, buffer);
string sha512 = hash.ToString(format);
// Assert
diff --git a/AdvancedSystems.Security.Tests/Cryptography/RSACryptoProviderTests.cs b/AdvancedSystems.Security.Tests/Cryptography/RSACryptoProviderTests.cs
new file mode 100644
index 0000000..af846ef
--- /dev/null
+++ b/AdvancedSystems.Security.Tests/Cryptography/RSACryptoProviderTests.cs
@@ -0,0 +1,67 @@
+using System;
+
+using AdvancedSystems.Security.Abstractions;
+using AdvancedSystems.Security.Cryptography;
+using AdvancedSystems.Security.Extensions;
+using AdvancedSystems.Security.Tests.Fixtures;
+
+using Xunit;
+
+namespace AdvancedSystems.Security.Tests.Cryptography;
+
+///
+/// Tests the default implementation of
+/// as a provider class ().
+///
+public sealed class RSACryptoProviderTests : IClassFixture
+{
+ private readonly RSACryptoProviderFixture _sut;
+
+ public RSACryptoProviderTests(RSACryptoProviderFixture rsaCryptoProviderFixture)
+ {
+ this._sut = rsaCryptoProviderFixture;
+ }
+
+ #region Tests
+
+ ///
+ /// Tests that encrypts an array of bytes correctly
+ /// by using a pre-configured certificate.
+ ///
+ [Fact]
+ public void TestEncryptionDecryption_Roundtrip()
+ {
+ // Arrange
+ string message = "Hello, World!";
+ Span buffer = message.GetBytes(Format.String);
+
+ // Act
+ Span cipher = this._sut.RSACryptoProvider.Encrypt(buffer);
+ Span source = this._sut.RSACryptoProvider.Decrypt(cipher);
+ string decryptedMessage = source.ToString(Format.String);
+
+ // Assert
+ Assert.Equal(message, decryptedMessage);
+ }
+
+ ///
+ /// Tests that signs and verifies an array of bytes
+ /// correctly by using a pre-configured certificate.
+ ///
+ [Fact]
+ public void TestSigningVerification_Roundtrip()
+ {
+ // Arrange
+ string message = "Hello, World!";
+ Span buffer = message.GetBytes(Format.String);
+
+ // Act
+ Span signature = this._sut.RSACryptoProvider.SignData(buffer);
+ bool verified = this._sut.RSACryptoProvider.VerifyData(buffer, signature);
+
+ // Assert
+ Assert.True(verified);
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/AdvancedSystems.Security.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs b/AdvancedSystems.Security.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs
index 3332569..8e2fd81 100644
--- a/AdvancedSystems.Security.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs
+++ b/AdvancedSystems.Security.Tests/DependencyInjection/ServiceCollectionExtensionsTests.cs
@@ -1,8 +1,10 @@
-using System.Security.Cryptography.X509Certificates;
+using System.Collections.Generic;
+using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using AdvancedSystems.Security.Abstractions;
using AdvancedSystems.Security.DependencyInjection;
+using AdvancedSystems.Security.Options;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
@@ -24,23 +26,20 @@ public sealed class ServiceCollectionExtensionsTests
///
/// Tests that can be initialized through dependency injection.
///
+ ///
+ /// Although of little practical value, this test verifies that
+ /// can be registered without having a strong dependency on during
+ /// the initialization phase.
+ ///
[Fact]
public async Task TestAddCertificateService_FromOptions()
{
// Arrange
- string storeService = "my/CurrentUser";
-
using var hostBuilder = await new HostBuilder()
.ConfigureWebHost(builder => builder
.UseTestServer()
.ConfigureServices(services =>
{
- services.AddCertificateStore(storeService, options =>
- {
- options.Location = StoreLocation.CurrentUser;
- options.Name = StoreName.My;
- });
-
services.AddCertificateService();
})
.Configure(app =>
@@ -62,7 +61,8 @@ public async Task TestAddCertificateService_FromOptions()
#region AddCertificateStore Tests
///
- /// Tests that can be initialized through dependency injection from configuration options.
+ /// Tests that can be initialized through dependency injection
+ /// from configuration options.
///
[Fact]
public async Task TestAddCertificateStore_FromOptions()
@@ -96,24 +96,34 @@ public async Task TestAddCertificateStore_FromOptions()
}
///
- /// Tests that can be initialized through dependency injection from configuration sections.
+ /// Tests that can be initialized through dependency injection
+ /// from configuration sections.
///
[Fact]
public async Task TestAddCertificateStore_FromAppSettings()
{
// Arrange
- string storeService = "my/CurrentUser";
+ string section = Sections.CERTIFICATE_STORE;
+ string storeService = "MyStoreService";
+ string storeLocation = "CurrentUser";
+ string storeName = "My";
+
+ var appSettings = new Dictionary
+ {
+ { $"{section}:{nameof(CertificateStoreOptions.Location)}", storeLocation },
+ { $"{section}:{nameof(CertificateStoreOptions.Name)}", storeName },
+ };
+
+ var configurationRoot = new ConfigurationBuilder()
+ .AddInMemoryCollection(appSettings)
+ .Build();
using var hostBuilder = await new HostBuilder()
.ConfigureWebHost(builder => builder
.UseTestServer()
- .ConfigureAppConfiguration(config =>
- {
- config.AddJsonFile("appsettings.json", optional: false);
- })
.ConfigureServices((context, services) =>
{
- var storeSettings = context.Configuration.GetSection(Sections.CERTIFICATE_STORE);
+ var storeSettings = configurationRoot.GetRequiredSection(section);
services.AddCertificateStore(storeService, storeSettings);
})
.Configure(app =>
diff --git a/AdvancedSystems.Security.Tests/Extensions/CertificateExtensionsTests.cs b/AdvancedSystems.Security.Tests/Extensions/CertificateExtensionsTests.cs
index 09f42e6..fc2001a 100644
--- a/AdvancedSystems.Security.Tests/Extensions/CertificateExtensionsTests.cs
+++ b/AdvancedSystems.Security.Tests/Extensions/CertificateExtensionsTests.cs
@@ -15,11 +15,11 @@ namespace AdvancedSystems.Security.Tests.Extensions;
///
/// Tests the public methods in .
///
-public sealed class CertificateExtensionsTests : IClassFixture
+public sealed class CertificateExtensionsTests : IClassFixture
{
- private readonly CertificateFixture _certificateFixture;
+ private readonly HostFixture _certificateFixture;
- public CertificateExtensionsTests(CertificateFixture certificateFixture)
+ public CertificateExtensionsTests(HostFixture certificateFixture)
{
this._certificateFixture = certificateFixture;
}
diff --git a/AdvancedSystems.Security.Tests/Extensions/CoreExtensionsTests.cs b/AdvancedSystems.Security.Tests/Extensions/CoreExtensionsTests.cs
index d3905f0..b7178da 100644
--- a/AdvancedSystems.Security.Tests/Extensions/CoreExtensionsTests.cs
+++ b/AdvancedSystems.Security.Tests/Extensions/CoreExtensionsTests.cs
@@ -1,4 +1,5 @@
-using System.Text;
+using System;
+using System.Text;
using AdvancedSystems.Security.Cryptography;
using AdvancedSystems.Security.Extensions;
@@ -18,11 +19,11 @@ public sealed class CoreExtensionsTests
public void TestStringFormatting(string input)
{
// Arrange
- byte[] buffer = input.GetBytes(Format.String);
+ Span buffer = input.GetBytes(Format.String);
// Act
string fromBytes = buffer.ToString(Format.String);
- byte[] @string = fromBytes.GetBytes(Format.String);
+ Span @string = fromBytes.GetBytes(Format.String);
// Assert
Assert.Equal(buffer, @string);
@@ -35,11 +36,11 @@ public void TestStringFormatting(string input)
public void TestBase64Formatting(int size)
{
// Arrange
- byte[] buffer = CryptoRandomProvider.GetBytes(size).ToArray();
+ Span buffer = CryptoRandomProvider.GetBytes(size).ToArray();
// Act
string base64 = buffer.ToString(Format.Base64);
- byte[] fromBytes = base64.GetBytes(Format.Base64);
+ Span fromBytes = base64.GetBytes(Format.Base64);
// Assert
Assert.Equal(buffer, fromBytes);
@@ -52,11 +53,11 @@ public void TestBase64Formatting(int size)
public void TestHexFormatting(int size)
{
// Arrange
- byte[] buffer = CryptoRandomProvider.GetBytes(size).ToArray();
+ Span buffer = CryptoRandomProvider.GetBytes(size).ToArray();
// Act
string hexadecimal = buffer.ToString(Format.Hex);
- byte[] fromBytes = hexadecimal.GetBytes(Format.Hex);
+ Span fromBytes = hexadecimal.GetBytes(Format.Hex);
// Assert
Assert.Equal(buffer, fromBytes);
diff --git a/AdvancedSystems.Security.Tests/Fixtures/CertificateStoreFixture.cs b/AdvancedSystems.Security.Tests/Fixtures/CertificateStoreFixture.cs
index d64cd4e..918b201 100644
--- a/AdvancedSystems.Security.Tests/Fixtures/CertificateStoreFixture.cs
+++ b/AdvancedSystems.Security.Tests/Fixtures/CertificateStoreFixture.cs
@@ -1,11 +1,14 @@
-using System.Security.Cryptography.X509Certificates;
+using System;
+using System.Security.Cryptography.X509Certificates;
using AdvancedSystems.Security.Services;
namespace AdvancedSystems.Security.Tests.Fixtures;
-public sealed class CertificateStoreFixture
+public sealed class CertificateStoreFixture : IDisposable
{
+ private bool _isDisposed = false;
+
public CertificateStoreFixture()
{
this.CertificateStore = new CertificateStore(StoreName.My, StoreLocation.CurrentUser);
@@ -16,4 +19,26 @@ public CertificateStoreFixture()
public CertificateStore CertificateStore { get; set; }
#endregion
+
+ #region Methods
+
+ public void Dispose()
+ {
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ public void Dispose(bool disposing)
+ {
+ if (this._isDisposed) return;
+
+ if (disposing)
+ {
+ this.CertificateStore.Dispose();
+ }
+
+ this._isDisposed = true;
+ }
+
+ #endregion
}
\ No newline at end of file
diff --git a/AdvancedSystems.Security.Tests/Fixtures/CryptoRandomFixture.cs b/AdvancedSystems.Security.Tests/Fixtures/CryptoRandomServiceFixture.cs
similarity index 79%
rename from AdvancedSystems.Security.Tests/Fixtures/CryptoRandomFixture.cs
rename to AdvancedSystems.Security.Tests/Fixtures/CryptoRandomServiceFixture.cs
index 9540026..3d5a766 100644
--- a/AdvancedSystems.Security.Tests/Fixtures/CryptoRandomFixture.cs
+++ b/AdvancedSystems.Security.Tests/Fixtures/CryptoRandomServiceFixture.cs
@@ -3,9 +3,9 @@
namespace AdvancedSystems.Security.Tests.Fixtures;
-public sealed class CryptoRandomFixture
+public sealed class CryptoRandomServiceFixture
{
- public CryptoRandomFixture()
+ public CryptoRandomServiceFixture()
{
this.CryptoRandomService = new CryptoRandomService();
}
diff --git a/AdvancedSystems.Security.Tests/Fixtures/HMACFixture.cs b/AdvancedSystems.Security.Tests/Fixtures/HMACServiceFixture.cs
similarity index 80%
rename from AdvancedSystems.Security.Tests/Fixtures/HMACFixture.cs
rename to AdvancedSystems.Security.Tests/Fixtures/HMACServiceFixture.cs
index 461f097..a2904d2 100644
--- a/AdvancedSystems.Security.Tests/Fixtures/HMACFixture.cs
+++ b/AdvancedSystems.Security.Tests/Fixtures/HMACServiceFixture.cs
@@ -3,9 +3,9 @@
namespace AdvancedSystems.Security.Tests.Fixtures;
-public sealed class HMACFixture
+public sealed class HMACServiceFixture
{
- public HMACFixture()
+ public HMACServiceFixture()
{
this.HMACService = new HMACService();
}
diff --git a/AdvancedSystems.Security.Tests/Fixtures/HashFixture.cs b/AdvancedSystems.Security.Tests/Fixtures/HashServiceFixture.cs
similarity index 100%
rename from AdvancedSystems.Security.Tests/Fixtures/HashFixture.cs
rename to AdvancedSystems.Security.Tests/Fixtures/HashServiceFixture.cs
diff --git a/AdvancedSystems.Security.Tests/Fixtures/CertificateFixture.cs b/AdvancedSystems.Security.Tests/Fixtures/HostFixture.cs
similarity index 96%
rename from AdvancedSystems.Security.Tests/Fixtures/CertificateFixture.cs
rename to AdvancedSystems.Security.Tests/Fixtures/HostFixture.cs
index a7717d9..788abda 100644
--- a/AdvancedSystems.Security.Tests/Fixtures/CertificateFixture.cs
+++ b/AdvancedSystems.Security.Tests/Fixtures/HostFixture.cs
@@ -13,7 +13,7 @@
namespace AdvancedSystems.Security.Tests.Fixtures;
-public sealed class CertificateFixture : IAsyncLifetime
+public sealed class HostFixture : IAsyncLifetime
{
#region Properties
diff --git a/AdvancedSystems.Security.Tests/Fixtures/RSACryptoProviderFixture.cs b/AdvancedSystems.Security.Tests/Fixtures/RSACryptoProviderFixture.cs
new file mode 100644
index 0000000..c4ff2fe
--- /dev/null
+++ b/AdvancedSystems.Security.Tests/Fixtures/RSACryptoProviderFixture.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Linq;
+using System.Security.Cryptography.X509Certificates;
+
+using AdvancedSystems.Security.Cryptography;
+using AdvancedSystems.Security.Services;
+using AdvancedSystems.Security.Tests.Helpers;
+
+namespace AdvancedSystems.Security.Tests.Fixtures;
+
+public sealed class RSACryptoProviderFixture : IDisposable
+{
+ public RSACryptoProviderFixture()
+ {
+ using var store = new CertificateStore(StoreName.My, StoreLocation.CurrentUser);
+ store.Open(OpenFlags.ReadOnly);
+
+ this.PasswordCertificate = store.Certificates
+ .Find(X509FindType.FindByThumbprint, Certificates.PasswordCertificateThumbprint, validOnly: false)
+ .OfType()
+ .First();
+
+ this.RSACryptoProvider = new RSACryptoProvider(this.PasswordCertificate);
+ }
+
+ #region Properties
+
+ public X509Certificate2 PasswordCertificate { get; private set; }
+
+ public RSACryptoProvider RSACryptoProvider { get; private set; }
+
+ #endregion
+
+ #region Methods
+
+ public void Dispose()
+ {
+ this.PasswordCertificate.Dispose();
+ this.RSACryptoProvider.Dispose();
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/AdvancedSystems.Security.Tests/Helpers/Certificates.cs b/AdvancedSystems.Security.Tests/Helpers/Certificates.cs
new file mode 100644
index 0000000..ace7269
--- /dev/null
+++ b/AdvancedSystems.Security.Tests/Helpers/Certificates.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+
+namespace AdvancedSystems.Security.Tests.Helpers;
+
+internal static class Certificates
+{
+ internal const string PasswordCertificateThumbprint = "2BFC1C18AC1A99E4284D07F1D2F0312C8EAB33FC";
+
+ internal static X509Certificate2 CreateSelfSignedCertificate(string subject)
+ {
+ using var csdsa = ECDsa.Create();
+ var request = new CertificateRequest(subject, csdsa, HashAlgorithmName.SHA256);
+ var validFrom = DateTimeOffset.UtcNow;
+ X509Certificate2 certificate = request.CreateSelfSigned(validFrom, validFrom.AddHours(1));
+ return certificate;
+ }
+}
\ No newline at end of file
diff --git a/AdvancedSystems.Security.Tests/Helpers/UserSecrets.cs b/AdvancedSystems.Security.Tests/Helpers/UserSecrets.cs
index 6c7605f..cc1dc23 100644
--- a/AdvancedSystems.Security.Tests/Helpers/UserSecrets.cs
+++ b/AdvancedSystems.Security.Tests/Helpers/UserSecrets.cs
@@ -5,5 +5,5 @@ internal static class UserSecrets
///
/// Retrieves the certificate password from the secrets store.
///
- public const string CERTIFICATE_PASSWORD = "CertificatePassword";
+ internal const string CERTIFICATE_PASSWORD = "CertificatePassword";
}
\ No newline at end of file
diff --git a/AdvancedSystems.Security.Tests/Services/CertificateServiceTests.cs b/AdvancedSystems.Security.Tests/Services/CertificateServiceTests.cs
index e4ad01d..80caf63 100644
--- a/AdvancedSystems.Security.Tests/Services/CertificateServiceTests.cs
+++ b/AdvancedSystems.Security.Tests/Services/CertificateServiceTests.cs
@@ -1,7 +1,6 @@
using System;
using System.IO;
using System.Linq;
-using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using AdvancedSystems.Security.Abstractions;
@@ -18,11 +17,11 @@ namespace AdvancedSystems.Security.Tests.Services;
///
/// Tests the public methods in .
///
-public sealed class CertificateServiceTests : IClassFixture
+public sealed class CertificateServiceTests : IClassFixture
{
- private readonly CertificateFixture _sut;
+ private readonly HostFixture _sut;
- public CertificateServiceTests(CertificateFixture certificateFixture)
+ public CertificateServiceTests(HostFixture certificateFixture)
{
this._sut = certificateFixture;
}
@@ -123,7 +122,7 @@ public void TestGetCertificate_ByThumbprint()
{
// Arrange
string storeService = this._sut.ConfiguredStoreService;
- string thumbprint = "2BFC1C18AC1A99E4284D07F1D2F0312C8EAB33FC";
+ string thumbprint = Certificates.PasswordCertificateThumbprint;
// Act
var certificateService = this._sut.Host?.Services.GetService();
@@ -148,7 +147,7 @@ public void TestAddRemoveCertificate()
{
// Arrange
string storeService = this._sut.ConfiguredStoreService;
- var certificate1 = CreateSelfSignedCertificate("O=AdvancedSystems");
+ var certificate1 = Certificates.CreateSelfSignedCertificate("O=AdvancedSystems");
string thumbprint = certificate1.Thumbprint;
// Act
@@ -168,17 +167,4 @@ public void TestAddRemoveCertificate()
}
#endregion
-
- #region Helpers
-
- private static X509Certificate2 CreateSelfSignedCertificate(string subject)
- {
- using var csdsa = ECDsa.Create();
- var request = new CertificateRequest(subject, csdsa, HashAlgorithmName.SHA256);
- var validFrom = DateTimeOffset.UtcNow;
- X509Certificate2 certificate = request.CreateSelfSigned(validFrom, validFrom.AddHours(1));
- return certificate;
- }
-
- #endregion
}
\ No newline at end of file
diff --git a/AdvancedSystems.Security.Tests/Services/CryptoRandomServiceTests.cs b/AdvancedSystems.Security.Tests/Services/CryptoRandomServiceTests.cs
index df2f796..e10f2a1 100644
--- a/AdvancedSystems.Security.Tests/Services/CryptoRandomServiceTests.cs
+++ b/AdvancedSystems.Security.Tests/Services/CryptoRandomServiceTests.cs
@@ -14,11 +14,11 @@ namespace AdvancedSystems.Security.Tests.Services;
///
/// These methods are more exhaustively tested by the underlying provider class.
///
-public sealed class CryptoRandomServiceTests : IClassFixture
+public sealed class CryptoRandomServiceTests : IClassFixture
{
- private readonly CryptoRandomFixture _sut;
+ private readonly CryptoRandomServiceFixture _sut;
- public CryptoRandomServiceTests(CryptoRandomFixture fixture)
+ public CryptoRandomServiceTests(CryptoRandomServiceFixture fixture)
{
this._sut = fixture;
}
diff --git a/AdvancedSystems.Security.Tests/Services/HMACServiceTests.cs b/AdvancedSystems.Security.Tests/Services/HMACServiceTests.cs
index 7c773ca..37c2bc5 100644
--- a/AdvancedSystems.Security.Tests/Services/HMACServiceTests.cs
+++ b/AdvancedSystems.Security.Tests/Services/HMACServiceTests.cs
@@ -12,12 +12,12 @@ namespace AdvancedSystems.Security.Tests.Services;
///
/// Tests the public methods in .
///
-public sealed class HMACServiceTests : IClassFixture, IClassFixture
+public sealed class HMACServiceTests : IClassFixture, IClassFixture
{
private readonly IHMACService _sut;
private readonly ICryptoRandomService _cryptoRandomService;
- public HMACServiceTests(HMACFixture fixture, CryptoRandomFixture cryptoRandomFixture)
+ public HMACServiceTests(HMACServiceFixture fixture, CryptoRandomServiceFixture cryptoRandomFixture)
{
this._sut = fixture.HMACService;
this._cryptoRandomService = cryptoRandomFixture.CryptoRandomService;
@@ -35,13 +35,13 @@ public void TestCompute()
// Arrange
var sha256 = HashFunction.SHA256;
Span key = this._cryptoRandomService.GetBytes(32);
- byte[] data = "Hello, World".GetBytes(Format.String);
+ Span data = "Hello, World".GetBytes(Format.String);
// Act
- byte[] mac = this._sut.Compute(sha256, key, data);
+ Span mac = this._sut.Compute(sha256, key, data);
// Assert
- Assert.NotEmpty(mac);
+ Assert.NotEmpty(mac.ToArray());
}
#endregion
diff --git a/AdvancedSystems.Security.Tests/Services/HashServiceTests.cs b/AdvancedSystems.Security.Tests/Services/HashServiceTests.cs
index 3984d09..156f033 100644
--- a/AdvancedSystems.Security.Tests/Services/HashServiceTests.cs
+++ b/AdvancedSystems.Security.Tests/Services/HashServiceTests.cs
@@ -32,7 +32,7 @@ public HashServiceTests(HashServiceFixture fixture)
#region Tests
///
- /// Tests that returns the expected hash,
+ /// Tests that returns the expected hash,
/// and that the log warning message is called on or .
///
///
diff --git a/AdvancedSystems.Security.Tests/Services/KDFServiceTests.cs b/AdvancedSystems.Security.Tests/Services/KDFServiceTests.cs
index 2683601..c630e82 100644
--- a/AdvancedSystems.Security.Tests/Services/KDFServiceTests.cs
+++ b/AdvancedSystems.Security.Tests/Services/KDFServiceTests.cs
@@ -1,4 +1,6 @@
-using AdvancedSystems.Security.Abstractions;
+using System;
+
+using AdvancedSystems.Security.Abstractions;
using AdvancedSystems.Security.Cryptography;
using AdvancedSystems.Security.Extensions;
using AdvancedSystems.Security.Tests.Fixtures;
@@ -22,7 +24,7 @@ public KDFServiceTests(KDFServiceFixture kdfServiceFixture)
#region Tests
///
- /// Tests that
+ /// Tests that
/// returns a non-empty hash with success state .
///
[Fact]
@@ -32,8 +34,8 @@ public void TestTryComputePBKDF2()
var sha256 = HashFunction.SHA256;
int iterations = 30_000;
int saltSize = 128;
- byte[] password = "REDACTED".GetBytes(Format.String);
- byte[] salt = CryptoRandomProvider.GetBytes(saltSize).ToArray();
+ Span password = "REDACTED".GetBytes(Format.String);
+ Span salt = CryptoRandomProvider.GetBytes(saltSize).ToArray();
// Act
bool success = this._sut.TryComputePBKDF2(sha256, password, salt, sha256.GetSize(), iterations, out byte[]? pbkdf2);
diff --git a/AdvancedSystems.Security.Tests/Services/RSACryptoServiceTests.cs b/AdvancedSystems.Security.Tests/Services/RSACryptoServiceTests.cs
new file mode 100644
index 0000000..d2e7fac
--- /dev/null
+++ b/AdvancedSystems.Security.Tests/Services/RSACryptoServiceTests.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Security.Cryptography;
+
+using AdvancedSystems.Security.Abstractions;
+using AdvancedSystems.Security.Cryptography;
+using AdvancedSystems.Security.Extensions;
+using AdvancedSystems.Security.Options;
+using AdvancedSystems.Security.Services;
+using AdvancedSystems.Security.Tests.Fixtures;
+using AdvancedSystems.Security.Tests.Helpers;
+
+using Microsoft.Extensions.DependencyInjection;
+
+using Moq;
+
+using Xunit;
+
+namespace AdvancedSystems.Security.Tests.Services;
+
+///
+/// Tests the default implementation of
+/// as a service class ().
+///
+public sealed class RSACryptoServiceTests : IClassFixture
+{
+ private readonly HostFixture _certificateFixture;
+ private readonly Mock _certificateService = new();
+ private readonly RSACryptoService _sut;
+
+ public RSACryptoServiceTests(HostFixture certificateFixture)
+ {
+ this._certificateFixture = certificateFixture;
+
+ var rsaOptions = new RSACryptoOptions
+ {
+ HashFunction = HashFunction.SHA256,
+ EncryptionPadding = RSAEncryptionPadding.OaepSHA256,
+ SignaturePadding = RSASignaturePadding.Pss,
+ Thumbprint = Certificates.PasswordCertificateThumbprint,
+ StoreService = this._certificateFixture.ConfiguredStoreService,
+ };
+
+ ICertificateService certificateService = this._certificateFixture.Host?.Services.GetService()
+ ?? throw new InvalidOperationException($"Failed to retrieve {nameof(ICertificateService)} from DI container.");
+
+ // NOTE: Use invalid certificates for testing purposes only
+ this._certificateService.Setup(x => x.GetCertificate(rsaOptions.StoreService, rsaOptions.Thumbprint, true))
+ .Returns(certificateService.GetCertificate(rsaOptions.StoreService, rsaOptions.Thumbprint, false));
+
+ this._sut = new RSACryptoService(
+ this._certificateService.Object,
+ Microsoft.Extensions.Options.Options.Create(rsaOptions)
+ );
+
+ this._certificateService.VerifyAll();
+ }
+
+ #region Tests
+
+ ///
+ /// Tests that encrypts an array of bytes correctly
+ /// by using a pre-configured certificate.
+ ///
+ [Fact]
+ public void TestEncryptionDecryption_Roundtrip()
+ {
+ // Arrange
+ string message = "Hello, World!";
+ Span buffer = message.GetBytes(Format.String);
+
+ // Act
+ Span cipher = this._sut.Encrypt(buffer);
+ Span source = this._sut.Decrypt(cipher);
+ string decryptedMessage = source.ToString(Format.String);
+
+ // Assert
+ Assert.Equal(message, decryptedMessage);
+ }
+
+ ///
+ /// Tests that signs and verifies an array of bytes
+ /// correctly by using a pre-configured certificate.
+ ///
+ [Fact]
+ public void TestSigningVerification_Roundtrip()
+ {
+ // Arrange
+ string message = "Hello, World!";
+ Span buffer = message.GetBytes(Format.String);
+
+ // Act
+ Span signature = this._sut.SignData(buffer);
+ bool verified = this._sut.VerifyData(buffer, signature);
+
+ // Assert
+ Assert.True(verified);
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/AdvancedSystems.Security/AdvancedSystems.Security.csproj b/AdvancedSystems.Security/AdvancedSystems.Security.csproj
index 5c765a1..857521f 100644
--- a/AdvancedSystems.Security/AdvancedSystems.Security.csproj
+++ b/AdvancedSystems.Security/AdvancedSystems.Security.csproj
@@ -13,12 +13,12 @@
-
+
-
-
+
+
diff --git a/AdvancedSystems.Security/Cryptography/HashProvider.cs b/AdvancedSystems.Security/Cryptography/HashProvider.cs
index a96fa9b..b88638d 100644
--- a/AdvancedSystems.Security/Cryptography/HashProvider.cs
+++ b/AdvancedSystems.Security/Cryptography/HashProvider.cs
@@ -10,8 +10,8 @@ namespace AdvancedSystems.Security.Cryptography;
///
public static class HashProvider
{
- ///
- public static byte[] Compute(HashFunction hashFunction, byte[] buffer)
+ ///
+ public static Span Compute(HashFunction hashFunction, Span buffer)
{
return hashFunction switch
{
diff --git a/AdvancedSystems.Security/Cryptography/KDFProvider.cs b/AdvancedSystems.Security/Cryptography/KDFProvider.cs
index 41dbc5f..c809bfa 100644
--- a/AdvancedSystems.Security/Cryptography/KDFProvider.cs
+++ b/AdvancedSystems.Security/Cryptography/KDFProvider.cs
@@ -12,7 +12,7 @@ namespace AdvancedSystems.Security.Cryptography;
///
public static class KDFProvider
{
- ///
+ ///
public static bool TryComputePBKDF2(HashFunction hashFunction, byte[] password, byte[] salt, int hashSize, int iterations, [NotNullWhen(true)] out byte[]? pbkdf2)
{
try
diff --git a/AdvancedSystems.Security/Cryptography/RSACryptoProvider.cs b/AdvancedSystems.Security/Cryptography/RSACryptoProvider.cs
index 656910e..a4cf147 100644
--- a/AdvancedSystems.Security/Cryptography/RSACryptoProvider.cs
+++ b/AdvancedSystems.Security/Cryptography/RSACryptoProvider.cs
@@ -1,112 +1,116 @@
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
-using System.Text;
-using AdvancedSystems.Core.Extensions;
+using AdvancedSystems.Security.Abstractions;
+using AdvancedSystems.Security.Extensions;
namespace AdvancedSystems.Security.Cryptography;
///
/// Represents a class for performing RSA-based asymmetric operations.
///
-public sealed class RSACryptoProvider
+public sealed class RSACryptoProvider : RSACryptoContract, IDisposable
{
- private static readonly HashAlgorithmName DEFAULT_HASH_ALGORITHM_NAME = HashAlgorithmName.SHA256;
- private static readonly RSAEncryptionPadding DEFAULT_RSA_ENCRYPTION_PADDING = RSAEncryptionPadding.OaepSHA256;
- private static readonly RSASignaturePadding DEFAULT_RSA_SIGNATURE_PADDING = RSASignaturePadding.Pss;
- private static readonly Encoding DEFAULT_ENCODING = Encoding.UTF8;
-
- public RSACryptoProvider(X509Certificate2 certificate, HashAlgorithmName hashAlgorithm, RSAEncryptionPadding encryptionPadding, RSASignaturePadding signaturePadding, Encoding encoding)
- {
- this.Certificate = certificate;
- this.HashAlgorithmName = hashAlgorithm;
- this.EncryptionPadding = encryptionPadding;
- this.SignaturePadding = signaturePadding;
- this.Encoding = encoding;
- }
+ private bool _isDisposed = false;
public RSACryptoProvider(X509Certificate2 certificate)
{
this.Certificate = certificate;
- this.HashAlgorithmName = RSACryptoProvider.DEFAULT_HASH_ALGORITHM_NAME;
- this.EncryptionPadding = RSACryptoProvider.DEFAULT_RSA_ENCRYPTION_PADDING;
- this.SignaturePadding = RSACryptoProvider.DEFAULT_RSA_SIGNATURE_PADDING;
- this.Encoding = RSACryptoProvider.DEFAULT_ENCODING;
}
#region Properties
- public X509Certificate2 Certificate { get; private set; }
-
- public HashAlgorithmName HashAlgorithmName { get; set; }
+ ///
+ public override X509Certificate2 Certificate { get; }
- public RSAEncryptionPadding EncryptionPadding { get; set; }
+ ///
+ public override HashFunction HashFunction { get; set; } = HashFunction.SHA256;
- public RSASignaturePadding SignaturePadding { get; set; }
+ ///
+ public override RSAEncryptionPadding EncryptionPadding { get; set; } = RSAEncryptionPadding.OaepSHA256;
- public Encoding Encoding { get; set; }
+ ///
+ public override RSASignaturePadding SignaturePadding { get; set; } = RSASignaturePadding.Pss;
#endregion
- #region Public Methods
+ #region Methods
+
+ ///
+ public void Dispose()
+ {
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (this._isDisposed) return;
+
+ if (disposing)
+ {
+ this.Certificate.Dispose();
+ }
+
+ this._isDisposed = true;
+ }
- public string Encrypt(string message, Encoding? encoding = null)
+ ///
+ public override Span Encrypt(Span data)
{
- encoding ??= this.Encoding;
+ ObjectDisposedException.ThrowIf(this._isDisposed, nameof(this.Certificate));
- using RSA? publicKey = this.Certificate.GetRSAPublicKey();
- ArgumentNullException.ThrowIfNull(publicKey, nameof(publicKey));
+ using RSA publicKey = this.Certificate.GetRSAPublicKey()
+ ?? throw new CryptographicException("Public Key is null.");
- byte[] buffer = encoding.GetBytes(message);
- byte[] cipher = publicKey.Encrypt(buffer, this.EncryptionPadding);
- return Convert.ToBase64String(cipher);
+ Span cipher = publicKey.Encrypt(data, this.EncryptionPadding);
+ return cipher;
}
- public string Decrypt(string cipher, Encoding? encoding = null)
+ ///
+ public override Span Decrypt(Span cipher)
{
+ ObjectDisposedException.ThrowIf(this._isDisposed, nameof(this.Certificate));
+
if (!this.Certificate.HasPrivateKey)
{
- throw new CryptographicException($"Certificate with thumbprint '{this.Certificate.Thumbprint}' has no private key.");
+ throw new CryptographicException($"Certificate with thumbprint \"{this.Certificate.Thumbprint}\" has no private key.");
}
- encoding ??= this.Encoding;
-
- using RSA? privateKey = this.Certificate.GetRSAPrivateKey();
- ArgumentNullException.ThrowIfNull(privateKey, nameof(privateKey));
+ using RSA privateKey = this.Certificate.GetRSAPrivateKey()
+ ?? throw new CryptographicException("Private Key is null.");
- byte[] buffer = Convert.FromBase64String(cipher);
- byte[] source = privateKey.Decrypt(buffer, this.EncryptionPadding);
- return encoding.GetString(source);
+ Span source = privateKey.Decrypt(cipher, this.EncryptionPadding);
+ return source;
}
- public string SignData(string data, Encoding? encoding = null)
+ ///
+ public override Span SignData(Span data)
{
- if (data.IsNullOrEmpty())
+ ObjectDisposedException.ThrowIf(this._isDisposed, nameof(this.Certificate));
+
+ if (!this.Certificate.HasPrivateKey)
{
- throw new ArgumentNullException(nameof(data));
+ throw new CryptographicException($"Certificate with thumbprint \"{this.Certificate.Thumbprint}\" has no private key.");
}
- using RSA? privateKey = this.Certificate.GetRSAPrivateKey();
- ArgumentNullException.ThrowIfNull(privateKey, nameof(privateKey));
-
- encoding ??= this.Encoding;
- byte[] buffer = encoding.GetBytes(data);
+ using RSA privateKey = this.Certificate.GetRSAPrivateKey()
+ ?? throw new CryptographicException("Private Key is null.");
- byte[] signature = privateKey.SignData(buffer, this.HashAlgorithmName, this.SignaturePadding);
- return Convert.ToBase64String(signature);
+ Span signature = privateKey.SignData(data, this.HashFunction.ToHashAlgorithmName(), this.SignaturePadding);
+ return signature;
}
- public bool VerifyData(string data, string signature, Encoding? encoding = null)
+ ///
+ public override bool VerifyData(Span data, Span signature)
{
- using RSA? publicKey = this.Certificate.GetRSAPublicKey();
- ArgumentNullException.ThrowIfNull(publicKey, nameof(publicKey));
+ ObjectDisposedException.ThrowIf(this._isDisposed, nameof(this.Certificate));
- encoding ??= this.Encoding;
- byte[] buffer = encoding.GetBytes(data);
- byte[] signedBuffer = Convert.FromBase64String(signature);
+ using RSA publicKey = this.Certificate.GetRSAPublicKey()
+ ?? throw new CryptographicException("Public Key is null.");
- bool isVerified = publicKey.VerifyData(buffer, signedBuffer, this.HashAlgorithmName, this.SignaturePadding);
+ bool isVerified = publicKey.VerifyData(data, signature, this.HashFunction.ToHashAlgorithmName(), this.SignaturePadding);
return isVerified;
}
diff --git a/AdvancedSystems.Security/DependencyInjection/ServiceCollectionExtensions.cs b/AdvancedSystems.Security/DependencyInjection/ServiceCollectionExtensions.cs
index 714f54c..7687d31 100644
--- a/AdvancedSystems.Security/DependencyInjection/ServiceCollectionExtensions.cs
+++ b/AdvancedSystems.Security/DependencyInjection/ServiceCollectionExtensions.cs
@@ -177,11 +177,13 @@ public static IServiceCollection AddKDFService(this IServiceCollection services)
private static IServiceCollection AddRSACryptoService(this IServiceCollection services)
{
- throw new NotImplementedException();
+ services.TryAdd(ServiceDescriptor.Transient());
+
+ return services;
}
///
- /// Adds the default implementation of to .
+ /// Adds the default implementation of to .
///
///
/// The service collection containing the service.
@@ -200,7 +202,7 @@ public static IServiceCollection AddRSACryptoService(this IServiceCollection ser
}
///
- /// Adds the default implementation of to .
+ /// Adds the default implementation of to .
///
///
/// The service collection containing the service.
diff --git a/AdvancedSystems.Security/Extensions/CertificateExtensions.cs b/AdvancedSystems.Security/Extensions/CertificateExtensions.cs
index c0dc448..04c43a6 100644
--- a/AdvancedSystems.Security/Extensions/CertificateExtensions.cs
+++ b/AdvancedSystems.Security/Extensions/CertificateExtensions.cs
@@ -11,7 +11,7 @@ namespace AdvancedSystems.Security.Extensions;
/// Defines functions for interacting with X.509 certificates.
///
///
-public static partial class CertificateExtensions
+public static class CertificateExtensions
{
///
/// Attempts to parse the specified distinguished name (DN) string into a object.
diff --git a/AdvancedSystems.Security/Extensions/CoreExtensions.cs b/AdvancedSystems.Security/Extensions/CoreExtensions.cs
index 86327d8..e8cb9de 100644
--- a/AdvancedSystems.Security/Extensions/CoreExtensions.cs
+++ b/AdvancedSystems.Security/Extensions/CoreExtensions.cs
@@ -11,7 +11,7 @@ namespace AdvancedSystems.Security.Extensions;
///
public static class CoreExtensions
{
- public static string ToString(this byte[] array, Format format)
+ public static string ToString(this Span array, Format format)
{
return format switch
{
@@ -22,7 +22,7 @@ public static string ToString(this byte[] array, Format format)
};
}
- public static byte[] GetBytes(this string @string, Format format)
+ public static Span GetBytes(this string @string, Format format)
{
return format switch
{
diff --git a/AdvancedSystems.Security/Extensions/StringExtensions.cs b/AdvancedSystems.Security/Extensions/StringExtensions.cs
deleted file mode 100644
index 1aba33b..0000000
--- a/AdvancedSystems.Security/Extensions/StringExtensions.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System;
-using System.Runtime.CompilerServices;
-using System.Text;
-
-using AdvancedSystems.Security.Cryptography;
-
-namespace AdvancedSystems.Security.Extensions;
-
-public static class StringExtensions
-{
-
-}
\ No newline at end of file
diff --git a/AdvancedSystems.Security/Options/RSACryptoOptions.cs b/AdvancedSystems.Security/Options/RSACryptoOptions.cs
index 6cbea4a..6f7c985 100644
--- a/AdvancedSystems.Security/Options/RSACryptoOptions.cs
+++ b/AdvancedSystems.Security/Options/RSACryptoOptions.cs
@@ -1,23 +1,43 @@
using System.ComponentModel.DataAnnotations;
using System.Security.Cryptography;
-using System.Text;
+
+using AdvancedSystems.Security.Abstractions;
namespace AdvancedSystems.Security.Options;
+///
+/// Configures options for the .
+///
public sealed record RSACryptoOptions
{
+ ///
+ ///
+ ///
[Required]
- public required HashAlgorithmName HashAlgorithmName { get; set; }
+ [EnumDataType(typeof(HashFunction))]
+ public required HashFunction HashFunction { get; set; }
+ ///
+ ///
+ ///
[Required]
public required RSAEncryptionPadding EncryptionPadding { get; set; }
+ ///
+ ///
+ ///
[Required]
public required RSASignaturePadding SignaturePadding { get; set; }
+ ///
+ /// The string representing the thumbprint of the encryption certificate to retrieve.
+ ///
[Required]
- public required Encoding Encoding { get; set; }
+ public required string Thumbprint { get; set; }
+ ///
+ ///
+ ///
[Required]
- public required string Thumbprint { get; set; }
+ public required string StoreService { get; set; }
}
\ No newline at end of file
diff --git a/AdvancedSystems.Security/Services/CertificateService.cs b/AdvancedSystems.Security/Services/CertificateService.cs
index e570f05..73651af 100644
--- a/AdvancedSystems.Security/Services/CertificateService.cs
+++ b/AdvancedSystems.Security/Services/CertificateService.cs
@@ -160,7 +160,7 @@ public IEnumerable GetCertificate(string storeService)
}
catch (ArgumentNullException)
{
- return Enumerable.Empty();
+ return [];
}
finally
{
diff --git a/AdvancedSystems.Security/Services/HashService.cs b/AdvancedSystems.Security/Services/HashService.cs
index 52e35f8..c468478 100644
--- a/AdvancedSystems.Security/Services/HashService.cs
+++ b/AdvancedSystems.Security/Services/HashService.cs
@@ -1,4 +1,6 @@
-using AdvancedSystems.Security.Abstractions;
+using System;
+
+using AdvancedSystems.Security.Abstractions;
using AdvancedSystems.Security.Cryptography;
using AdvancedSystems.Security.Extensions;
@@ -21,7 +23,7 @@ public HashService(ILogger logger)
#region Methods
///
- public byte[] Compute(HashFunction hashFunction, byte[] buffer)
+ public Span Compute(HashFunction hashFunction, Span buffer)
{
if (hashFunction is HashFunction.MD5 or HashFunction.SHA1)
{
diff --git a/AdvancedSystems.Security/Services/KDFService.cs b/AdvancedSystems.Security/Services/KDFService.cs
index e445215..05d77d5 100644
--- a/AdvancedSystems.Security/Services/KDFService.cs
+++ b/AdvancedSystems.Security/Services/KDFService.cs
@@ -1,4 +1,5 @@
-using System.Diagnostics.CodeAnalysis;
+using System;
+using System.Diagnostics.CodeAnalysis;
using AdvancedSystems.Security.Abstractions;
using AdvancedSystems.Security.Cryptography;
@@ -13,9 +14,9 @@ public sealed class KDFService : IKDFService
#region Methods
///
- public bool TryComputePBKDF2(HashFunction hashFunction, byte[] password, byte[] salt, int hashSize, int iterations, [NotNullWhen(true)] out byte[]? pbkdf2)
+ public bool TryComputePBKDF2(HashFunction hashFunction, Span password, Span salt, int hashSize, int iterations, [NotNullWhen(true)] out byte[]? pbkdf2)
{
- return KDFProvider.TryComputePBKDF2(hashFunction, password, salt, hashSize, iterations, out pbkdf2);
+ return KDFProvider.TryComputePBKDF2(hashFunction, password.ToArray(), salt.ToArray(), hashSize, iterations, out pbkdf2);
}
#endregion
diff --git a/AdvancedSystems.Security/Services/RSACryptoService.cs b/AdvancedSystems.Security/Services/RSACryptoService.cs
index 2fb4368..8ce38c2 100644
--- a/AdvancedSystems.Security/Services/RSACryptoService.cs
+++ b/AdvancedSystems.Security/Services/RSACryptoService.cs
@@ -1,13 +1,12 @@
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
-using System.Text;
using AdvancedSystems.Security.Abstractions;
+using AdvancedSystems.Security.Abstractions.Exceptions;
using AdvancedSystems.Security.Cryptography;
using AdvancedSystems.Security.Options;
-using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace AdvancedSystems.Security.Services;
@@ -15,78 +14,67 @@ namespace AdvancedSystems.Security.Services;
///
/// Represents a service for performing RSA-based asymmetric operations.
///
-public sealed class RSACryptoService
+public sealed class RSACryptoService : RSACryptoContract, IDisposable
{
- private readonly ILogger _logger;
- private readonly ICertificateService _certificateService;
- private readonly RSACryptoOptions _rsaOptions;
-
- private bool _disposed = false;
- private readonly X509Certificate2 _certificate;
+ private bool _isDisposed = false;
private readonly RSACryptoProvider _provider;
- public RSACryptoService(ILogger logger, ICertificateService certificateService, IOptions rsaOptions)
+ public RSACryptoService(ICertificateService certificateService, IOptions options)
{
- this._logger = logger;
- this._certificateService = certificateService;
- this._rsaOptions = rsaOptions.Value;
-
- this._certificate = this._certificateService.GetCertificate("default", this._rsaOptions.Thumbprint, validOnly: true)
- ?? throw new ArgumentNullException();
-
- this._provider = new RSACryptoProvider(
- this._certificate,
- this._rsaOptions.HashAlgorithmName,
- this._rsaOptions.EncryptionPadding,
- this._rsaOptions.SignaturePadding,
- this._rsaOptions.Encoding
- );
+ RSACryptoOptions rsaOptions = options.Value;
+
+ this.Certificate = certificateService.GetCertificate(rsaOptions.StoreService, rsaOptions.Thumbprint, validOnly: true)
+ ?? throw new CertificateNotFoundException($"Failed to retrieve certificate with options {nameof(RSACryptoOptions.StoreService)}=\"{rsaOptions.StoreService}\" and {nameof(RSACryptoOptions.Thumbprint)}=\"{rsaOptions.Thumbprint}\".");
+
+ this._provider = new RSACryptoProvider(this.Certificate)
+ {
+ HashFunction = rsaOptions.HashFunction,
+ EncryptionPadding = rsaOptions.EncryptionPadding,
+ SignaturePadding = rsaOptions.SignaturePadding
+ };
}
#region Properties
- ///
- public X509Certificate2 Certificate
+ ///
+ public override X509Certificate2 Certificate { get; }
+
+ ///
+ public override HashFunction HashFunction
{
get
{
- return this._certificate;
+ return this._provider.HashFunction;
}
- }
-
- ///
- public HashAlgorithmName HashAlgorithmName
- {
- get
+ set
{
- return this._provider.HashAlgorithmName;
+ this._provider.HashFunction = value;
}
}
- ///
- public RSAEncryptionPadding EncryptionPadding
+ ///
+ public override RSAEncryptionPadding EncryptionPadding
{
get
{
return this._provider.EncryptionPadding;
}
+ set
+ {
+ this._provider.EncryptionPadding = value;
+ }
}
- ///
- public RSASignaturePadding SignaturePadding
+ ///
+ public override RSASignaturePadding SignaturePadding
{
get
{
return this._provider.SignaturePadding;
}
- }
-
- ///
- public Encoding Encoding
- {
- get
+ set
{
- return this._provider.Encoding;
+ this._provider.SignaturePadding = value;
}
}
@@ -94,45 +82,56 @@ public Encoding Encoding
#region Methods
- ///
-
+ ///
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
- ///
- public void Dispose(bool disposing)
+ private void Dispose(bool disposing)
{
- if (this._disposed || !disposing) return;
+ if (this._isDisposed) return;
- this._certificate.Dispose();
- this._disposed = true;
+ if (disposing)
+ {
+ this.Certificate.Dispose();
+ this._provider.Dispose();
+ }
+
+ this._isDisposed = true;
}
- ///
- public string Encrypt(string message, Encoding? encoding = null)
+ ///
+ public override Span Encrypt(Span buffer)
{
- return this._provider.Encrypt(message, encoding);
+ ObjectDisposedException.ThrowIf(this._isDisposed, nameof(this.Certificate));
+
+ return this._provider.Encrypt(buffer);
}
- ///
- public string Decrypt(string cipher, Encoding? encoding = null)
+ ///
+ public override Span Decrypt(Span cipher)
{
- return this._provider.Decrypt(cipher, encoding);
+ ObjectDisposedException.ThrowIf(this._isDisposed, nameof(this.Certificate));
+
+ return this._provider.Decrypt(cipher);
}
- ///
- public string SignData(string data, Encoding? encoding = null)
+ ///
+ public override Span SignData(Span data)
{
- return this._provider.SignData(data, encoding);
+ ObjectDisposedException.ThrowIf(this._isDisposed, nameof(this.Certificate));
+
+ return this._provider.SignData(data);
}
- ///
- public bool VerifyData(string data, string signature, Encoding? encoding = null)
+ ///
+ public override bool VerifyData(Span data, Span signature)
{
- return this._provider.VerifyData(data, signature, encoding);
+ ObjectDisposedException.ThrowIf(this._isDisposed, nameof(this.Certificate));
+
+ return this._provider.VerifyData(data, signature);
}
#endregion
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..8d7194e
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,50 @@
+MAIN_PROJECT = ./AdvancedSystems.Security
+TEST_PROJECT = ./AdvancedSystems.Security.Tests
+
+import-certificate:
+ # password certificate authority
+ dotnet certificate-tool add --file ./development/AdvancedSystems-CA.pfx \
+ --store-name My \
+ --store-location CurrentUser \
+ --password $(password);
+
+ # password certificate
+ # TODO: read values from appsettings.json
+ dotnet certificate-tool add --file ./development/AdvancedSystems-PasswordCertificate.pem \
+ --store-name My \
+ --store-location CurrentUser;
+
+install: import-certificate
+ # main project
+ dotnet restore --configfile nuget.config
+ dotnet tool restore --configfile nuget.config
+ dotnet husky install
+
+ # unit test project
+ dotnet user-secrets init --project $(TEST_PROJECT)
+ dotnet user-secrets set CertificatePassword $(password) --project $(TEST_PROJECT)
+
+build:
+ dotnet build $(MAIN_PROJECT) --configuration $(configuration) --no-restore /warnAsError
+
+test: build
+ dotnet test $(TEST_PROJECT) --configuration $(configuration) --verbosity normal
+
+lint:
+ dotnet format
+
+documentation:
+ dotnet tool restore --configfile nuget.config
+
+ if [ "$(serve)" = "true" ]; then \
+ dotnet docfx ./docs/docfx.json --serve --open-browser; \
+ else \
+ dotnet docfx ./docs/docfx.json; \
+ fi
+
+clean:
+ dotnet clean
+ dotnet clean --configuration Release
+ @clear
+
+$(V).SILENT:
\ No newline at end of file
diff --git a/readme.md b/readme.md
index 764c837..ffd74d5 100644
--- a/readme.md
+++ b/readme.md
@@ -34,21 +34,17 @@ for debugging .NET assemblies.
Configure local user secrets for the test suite (optional):
```powershell
-$Password = Read-Host -Prompt "AdvancedSystems-CA.pfx Password"
-dotnet user-secrets set CertificatePassword $Password --project ./AdvancedSystems.Tests
+$Password = Read-Host "Password" -MaskInput
+make install password=$Password
```
Run test suite:
```powershell
-dotnet test ./AdvancedSystems.Core.Tests --configuration Release
+make test configuration=Release
```
-In addition to unit testing, this project also uses stryker for mutation testing, which is setup to be installed with
-
-```powershell
-dotnet tool restore --configfile nuget.config
-```
+In addition to unit testing, this project also uses stryker for mutation testing, which is set up to be installed with
Run stryker locally:
@@ -56,8 +52,8 @@ Run stryker locally:
dotnet stryker
```
-Build and serve documentation locally (`http://localhost:8080`):
+Build and serve documentation locally:
```powershell
-docfx ./docs/docfx.json --serve
+make documentation serve=true
```