Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions .github/workflows/dotnet-tests.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
# This workflow will build a .NET project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net

name: .NET
name: "Unit Tests"

on:
push:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;

namespace AdvancedSystems.Security.Abstractions.Exceptions
{
/// <summary>
/// Represents errors that occur because a specified certificate could not be located.
/// </summary>
/// <remarks>
/// This exception is typically thrown when an attempt to retrieve a certificate by its
/// identifier, such as a thumbprint or subject name, fails. It indicates that the required
/// certificate is not present in the specified certificate store or location.
/// </remarks>
public class CertificateNotFoundException : Exception
{
/// <summary>
/// Initializes a new instance of the <seealso cref="CertificateNotFoundException"/> class.
/// </summary>
public CertificateNotFoundException()
{

}

/// <summary>
/// Initializes a new instance of the <seealso cref="CertificateNotFoundException"/> class with a specified error <paramref name="message"/>.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
public CertificateNotFoundException(string message) : base(message)
{

}

/// <summary>
/// Initializes a new instance of the <seealso cref="CertificateNotFoundException"/> class with a specified error
/// <paramref name="message"/> a reference to the <paramref name="inner"/> exception that is the cause of this exception.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="inner">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
public CertificateNotFoundException(string message, Exception inner) : base(message, inner)
{

}
}
}
21 changes: 19 additions & 2 deletions AdvancedSystems.Security.Abstractions/ICertificateService.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
using System.Security.Cryptography.X509Certificates;
using AdvancedSystems.Security.Abstractions.Exceptions;

using System.Security.Cryptography.X509Certificates;

namespace AdvancedSystems.Security.Abstractions
{
/// <summary>
/// Defines a service for managing and retrieving X.509 certificates.
/// </summary>
public interface ICertificateService
{
#region Methods

X509Certificate2? GetStoreCertificate(StoreName storeName, StoreLocation storeLocation, string thumbprint);
/// <summary>
/// Retrieves an X.509 certificate from the specified store using the provided <paramref name="thumbprint"/>.
/// </summary>
/// <param name="thumbprint">The thumbprint of the certificate to locate.</param>
/// <param name="storeName">The certificate store from which to retrieve the certificate.</param>
/// <param name="storeLocation">The location of the certificate store, such as <see cref="StoreLocation.CurrentUser"/> or <see cref="StoreLocation.LocalMachine"/>.</param>
/// <returns>The <see cref="X509Certificate2"/> object if the certificate is found, else <c>null</c>.</returns>
/// <exception cref="CertificateNotFoundException">Thrown when no certificate with the specified thumbprint is found in the store.</exception>
X509Certificate2? GetStoreCertificate(string thumbprint, StoreName storeName, StoreLocation storeLocation);

/// <summary>
/// Retrieves an application-configured X.509 certificate.
/// </summary>
/// <returns>The <see cref="X509Certificate2"/> object if the certificate is found, else <c>null</c>.</returns>
X509Certificate2? GetConfiguredCertificate();

#endregion
Expand Down
110 changes: 110 additions & 0 deletions AdvancedSystems.Security.Abstractions/ICertificateStore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using System;
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace AdvancedSystems.Security.Abstractions
{
/// <summary>
/// Represents an X.509 store, which is a physical store where certificates are persisted and managed.
/// </summary>
public interface ICertificateStore : IDisposable
{
#region Properties

/// <summary>
/// Gets an <seealso cref="IntPtr"/> handle to an <c>HCERTSTORE</c> store.
/// </summary>
IntPtr StoreHandle { get; }

/// <summary>
/// Gets the location of the X.509 certificate store.
/// </summary>
StoreLocation Location { get; }

/// <summary>
/// Gets the name of the X.509 certificate store.
/// </summary>
string? Name { get; }

/// <summary>
/// Returns a collection of certificates located in an X.509 certificate store.
/// </summary>
X509Certificate2Collection Certificates { get; }

/// <summary>
/// Gets a value that indicates whether the instance is connected to an open certificate store.
/// </summary>
bool IsOpen { get; }

#endregion

#region Methods

/// <summary>
/// Opens an X.509 certificate store or creates a new store, depending on <seealso cref="OpenFlags"/> flag settings.
/// </summary>
/// <param name="flags">A bitwise combination of enumeration values that specifies the way to open the X.509 certificate store.</param>
/// <exception cref="CryptographicException">The store cannot be opened as requested.</exception>
/// <exception cref="SecurityException">The caller does not have the required permission.</exception>
/// <exception cref="ArgumentException">The store contains invalid values.</exception>
/// <remarks>
/// Use this method to open an existing X.509 store. Note that you must have additional permissions, specified by
/// <c>StorePermissionFlags</c>, to enumerate the certificates in the store. You can create a new store
/// by passing a store name that does not exist to the class constructor, and then using any of the <seealso cref="OpenFlags"/>
/// flags except <seealso cref="OpenFlags.OpenExistingOnly"/>.
/// </remarks>
void Open(OpenFlags flags);

/// <summary>
/// Closes an X.509 certificate store.
/// </summary>
/// <remarks>
/// This method releases all resources associated with the store. You should always
/// close an X.509 certificate store after use.
/// </remarks>
void Close();

/// <summary>
/// Adds a certificate to an X.509 certificate store.
/// </summary>
/// <param name="certificate">The certificate to add.</param>
/// <exception cref="ArgumentNullException"><paramref name="certificate"/> is <c>null</c>.</exception>
/// <exception cref="CryptographicException">The certificate could not be added to the store.</exception>
void Add(X509Certificate2 certificate);

/// <summary>
/// Adds a collection of certificates to an X.509 certificate store.
/// </summary>
/// <param name="certificates">The collection of certificates to add.</param>
/// <exception cref="ArgumentNullException"><paramref name="certificates"/> is <c>null</c>.</exception>
/// <exception cref="SecurityException">The caller does not have the required permission.</exception>
/// <remarks>
/// This method adds more than one certificate to an X.509 certificate store; if one certificate
/// addition fails, the operation is reverted and no certificates are added.
/// </remarks>
void AddRange(X509Certificate2Collection certificates);

/// <summary>
/// Removes a certificate from an X.509 certificate store.
/// </summary>
/// <param name="certificate">The certificate to remove.</param>
/// <exception cref="ArgumentNullException"><paramref name="certificate"/> is <c>null</c>.</exception>
/// <exception cref="SecurityException">The caller does not have the required permission.</exception>
void Remove(X509Certificate2 certificate);

/// <summary>
/// Removes a range of certificates from an X.509 certificate store.
/// </summary>
/// <param name="certificates">A range of certificates to remove.</param>
/// <exception cref="ArgumentNullException"><paramref name="certificates"/> is <c>null</c>.</exception>
/// <exception cref="SecurityException">The caller does not have the required permission.</exception>
/// <remarks>
/// This method removes more than one certificate from an X.509 certificate store; if one certificate
/// removal fails, the operation is reverted and no certificates are removed.
/// </remarks>
void RemoveRange(X509Certificate2Collection certificates);

#endregion
}
}
2 changes: 1 addition & 1 deletion AdvancedSystems.Security.Abstractions/IHashService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace AdvancedSystems.Security.Abstractions
{
/// <summary>
/// Defines a methods for computing hash codes.
/// Defines a service for computing hash codes.
/// </summary>
public interface IHashService
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,19 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="8.0.7" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.analyzers" Version="1.14.0">
<PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit.analyzers" Version="1.15.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit.runner.console" Version="2.8.1">
<PackageReference Include="xunit.runner.console" Version="2.9.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1">
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand All @@ -36,4 +37,10 @@
<ProjectReference Include="..\AdvancedSystems.Security\AdvancedSystems.Security.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
4 changes: 4 additions & 0 deletions AdvancedSystems.Security.Tests/Cryptography/HashTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ namespace AdvancedSystems.Security.Tests.Cryptography;

public class HashTests
{
#region Tests

[Theory]
[InlineData("Hello, World!", "65a8e27d8879283831b664bd8b7f0ad4", Format.Hex)]
[InlineData("Hello, World!", "ZajifYh5KDgxtmS9i38K1A==", Format.Base64)]
Expand Down Expand Up @@ -104,4 +106,6 @@ public void TestSHA512Hash(string input, string expected, Format format)
// Assert
Assert.Equal(expected, sha512);
}

#endregion
}
59 changes: 59 additions & 0 deletions AdvancedSystems.Security.Tests/Fixtures/CertificateFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

using AdvancedSystems.Security.Abstractions;
using AdvancedSystems.Security.Options;
using AdvancedSystems.Security.Services;

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

using Moq;

namespace AdvancedSystems.Security.Tests.Fixtures;

public class CertificateFixture
{
public CertificateFixture()
{
this.Logger = new Mock<ILogger<CertificateService>>();
this.Options = new Mock<IOptions<CertificateOptions>>();
this.Store = new Mock<ICertificateStore>();
this.CertificateService = new CertificateService(this.Logger.Object, this.Options.Object, this.Store.Object);
}

#region Properties

public Mock<ILogger<CertificateService>> Logger { get; private set; }

public ICertificateService CertificateService { get; private set; }

public Mock<IOptions<CertificateOptions>> Options { get; private set; }

public Mock<ICertificateStore> Store { get; private set; }

#endregion

#region Helper Methods

public static X509Certificate2 CreateCertificate(string subjectName)
{
using var ecdsa = ECDsa.Create();
var request = new CertificateRequest(subjectName, ecdsa, HashAlgorithmName.SHA256);
var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddHours(1));
return certificate;
}

public static X509Certificate2Collection CreateCertificateCollection(int length)
{
var certificates = Enumerable.Range(0, length)
.Select(_ => CreateCertificate("O=AdvancedSystems"))
.ToArray();

return new X509Certificate2Collection(certificates);
}

#endregion
}
19 changes: 19 additions & 0 deletions AdvancedSystems.Security.Tests/Fixtures/CertificateStoreFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Security.Cryptography.X509Certificates;

using AdvancedSystems.Security.Services;

namespace AdvancedSystems.Security.Tests.Fixtures;

public class CertificateStoreFixture
{
public CertificateStoreFixture()
{
this.CertificateStore = new CertificateStore(StoreName.My, StoreLocation.CurrentUser);
}

#region Properties

public CertificateStore CertificateStore { get; set; }

#endregion
}
12 changes: 8 additions & 4 deletions AdvancedSystems.Security.Tests/Fixtures/HashFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@ namespace AdvancedSystems.Security.Tests.Fixtures;

public class HashServiceFixture
{
public IHashService HashService { get; set; }

public Mock<ILogger<HashService>> Logger { get; set; }

public HashServiceFixture()
{
this.Logger = new Mock<ILogger<HashService>>();
this.HashService = new HashService(this.Logger.Object);
}

#region Properties

public Mock<ILogger<HashService>> Logger { get; private set; }

public IHashService HashService { get; private set; }

#endregion
}
Loading