Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a811d7b
Make CertificateOptions more permissive: allow empty configurations
StefanGreve Nov 22, 2024
58e021f
Implement CertificateOptionsValidator and refactor service registration
StefanGreve Jan 8, 2025
364a576
Import test certificate in GitHub Action runners
StefanGreve Jan 8, 2025
e00b956
Add validOnly (default = true) search parameter to ICertificateServic…
StefanGreve Jan 8, 2025
1ae444a
Implement validOnly flag (and turn it off for testing purposes only)
StefanGreve Jan 8, 2025
891007c
Import certificate on Windows via powershell
StefanGreve Jan 8, 2025
c4ecd1b
Rename CertificateExtensions to CertificateStoreExtensions
StefanGreve Jan 11, 2025
84b6702
Use certificate-tool for importing certificates in Linux
StefanGreve Jan 11, 2025
749cc25
Use certificate-tool for importing certificates in MacOS
StefanGreve Jan 11, 2025
12f2027
Refactor certificate code and start extending implementation of Certi…
StefanGreve Jan 11, 2025
bfcebfd
Rewrite CertificateService and DI logic from scratch and extand inter…
StefanGreve Jan 12, 2025
6f845c0
Fix parsing from appsettings.json
StefanGreve Jan 12, 2025
1158521
Add more tests to CertificateServiceTests
StefanGreve Jan 13, 2025
d06e2ca
Add doc strings to ICertificateService
StefanGreve Jan 15, 2025
603978b
Configure CA and password certificate for testing purposes
StefanGreve Jan 15, 2025
042d83d
Run test in configuration mode Release
StefanGreve Jan 15, 2025
fdda6b4
Format workflow files
StefanGreve Jan 15, 2025
77f4b31
Add unit test to CertificateExtensions
StefanGreve Jan 16, 2025
12d5f79
Test TryImportPfxCertificate
StefanGreve Jan 16, 2025
89b6059
Add placeholder test functions
StefanGreve Jan 16, 2025
fe70b27
Improve doc strings
StefanGreve Jan 17, 2025
70ab335
Refactor and re-organize extension methods
StefanGreve Jan 17, 2025
bf2406f
More doc improvements
StefanGreve Jan 17, 2025
659ec79
Implement TryComputeSecure for password-based key derivation
StefanGreve Jan 17, 2025
b2a5741
Implement GetSecureHash methods in IHashService
StefanGreve Jan 17, 2025
eba0791
Add unit tests for computing secure hashes in service and provider
StefanGreve Jan 18, 2025
012144d
Refactor hash computation code
StefanGreve Jan 26, 2025
6497026
Revamp implementation of HMACProvider, and update unit tests
StefanGreve Feb 1, 2025
9bf920f
Fix off-by-one error
StefanGreve Feb 1, 2025
8c04e55
Fix unit test for hash function support, accounting for different pla…
StefanGreve Feb 1, 2025
037cb6a
Update documentation for IHashService
StefanGreve Feb 1, 2025
073a76e
Change signature: HashFunction as first parameter
StefanGreve Feb 1, 2025
e048aaf
Remove dead validator code
StefanGreve Feb 2, 2025
dd263f6
Rename password certificate file and add encrypted private key to tes…
StefanGreve Feb 2, 2025
c04ff8f
Fix implementation of TryImportPemCertificate and implement missing u…
StefanGreve Feb 2, 2025
32addf2
Don't log full paths of certificates or keys in log
StefanGreve Feb 2, 2025
4c1ab47
Implement KDFProvider, KDFService, IKDFService and add unit tests.
StefanGreve Feb 5, 2025
079dd81
Use IFileSystem abstraction in CertificateService
StefanGreve Feb 5, 2025
42a2bd1
Add libsodium to this project via interop
StefanGreve Feb 5, 2025
5980bc3
Refactor interop code and use LibraryImport instead of DllImport
StefanGreve Feb 5, 2025
87af8c1
Restructure libsodium code
StefanGreve Feb 5, 2025
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
7 changes: 7 additions & 0 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@
"husky"
],
"rollForward": false
},
"dotnet-certificate-tool": {
"version": "2.0.9",
"commands": [
"certificate-tool"
],
"rollForward": false
}
}
}
30 changes: 25 additions & 5 deletions .github/workflows/dotnet-publish-abstractions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,36 @@ jobs:
shell: powershell

- name: Restore Dependencies
run: dotnet restore ${{ env.PROJECT_PATH }} --configfile nuget.config
run: >
dotnet restore ${{ env.PROJECT_PATH }}
--configfile nuget.config

- name: Build Project
run: dotnet build ${{ env.PROJECT_PATH }} --nologo --no-restore --configuration Release
run: >
dotnet build ${{ env.PROJECT_PATH }}
--configuration Release
--no-restore
--nologo

- name: Pack Project
run: dotnet pack ${{ env.PROJECT_PATH }} --nologo --no-restore --no-build --configuration Release --output ${{ env.RELEASE_DIRECTORY }}
run: >
dotnet pack ${{ env.PROJECT_PATH }}
--configuration Release
--output ${{ env.RELEASE_DIRECTORY }}
--no-restore
--no-build
--nologo

- name: Deploy Package to GitHub
run: dotnet nuget push '${{ env.RELEASE_DIRECTORY }}\*.nupkg' --skip-duplicate --api-key ${{ secrets.GH_AUTH_TOKEN_PUSH }} --source ${{ env.GITHUB_SOURCE }}
run: >
dotnet nuget push '${{ env.RELEASE_DIRECTORY }}\*.nupkg'
--api-key ${{ secrets.GH_AUTH_TOKEN_PUSH }}
--source ${{ env.GITHUB_SOURCE }}
--skip-duplicate

- name: Deploy Package to NuGet
run: dotnet nuget push '${{ env.RELEASE_DIRECTORY }}\*.nupkg' --skip-duplicate --api-key ${{ secrets.NUGET_AUTH_TOKEN_PUSH }} --source ${{ env.NUGET_SOURCE }}
run: >
dotnet nuget push '${{ env.RELEASE_DIRECTORY }}\*.nupkg'
--api-key ${{ secrets.NUGET_AUTH_TOKEN_PUSH }}
--source ${{ env.NUGET_SOURCE }}
--skip-duplicate
30 changes: 25 additions & 5 deletions .github/workflows/dotnet-publish-core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,36 @@ jobs:
shell: powershell

- name: Restore Dependencies
run: dotnet restore ${{ env.PROJECT_PATH }} --configfile nuget.config
run: >
dotnet restore ${{ env.PROJECT_PATH }}
--configfile nuget.config

- name: Build Project
run: dotnet build ${{ env.PROJECT_PATH }} --nologo --no-restore --configuration Release
run: >
dotnet build ${{ env.PROJECT_PATH }}
--configuration Release
--no-restore
--nologo

- name: Pack Project
run: dotnet pack ${{ env.PROJECT_PATH }} --nologo --no-restore --no-build --configuration Release --output ${{ env.RELEASE_DIRECTORY }}
run: >
dotnet pack ${{ env.PROJECT_PATH }}
--configuration Release
--output ${{ env.RELEASE_DIRECTORY }}
--no-restore
--no-build
--nologo

- name: Deploy Package to GitHub
run: dotnet nuget push '${{ env.RELEASE_DIRECTORY }}\*.nupkg' --skip-duplicate --api-key ${{ secrets.GH_AUTH_TOKEN_PUSH }} --source ${{ env.GITHUB_SOURCE }}
run: >
dotnet nuget push '${{ env.RELEASE_DIRECTORY }}\*.nupkg'
--api-key ${{ secrets.GH_AUTH_TOKEN_PUSH }}
--source ${{ env.GITHUB_SOURCE }}
--skip-duplicate

- name: Deploy Package to NuGet
run: dotnet nuget push '${{ env.RELEASE_DIRECTORY }}\*.nupkg' --skip-duplicate --api-key ${{ secrets.NUGET_AUTH_TOKEN_PUSH }} --source ${{ env.NUGET_SOURCE }}
run: >
dotnet nuget push '${{ env.RELEASE_DIRECTORY }}\*.nupkg'
--api-key ${{ secrets.NUGET_AUTH_TOKEN_PUSH }}
--source ${{ env.NUGET_SOURCE }}
--skip-duplicate
54 changes: 50 additions & 4 deletions .github/workflows/dotnet-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,54 @@ jobs:
- name: Restore Dependencies
run: dotnet restore

- name: Build
run: dotnet build --no-restore
- name: Restore Dotnet Tools
run: dotnet tool restore

- name: Test
run: dotnet test --no-build --verbosity normal
- name: Build Project
run: >
dotnet build ./AdvancedSystems.Security
--configuration Release
--no-restore
/warnAsError

- name: Import AdvancedSystems Certificate Authority
run: >
dotnet certificate-tool add --file ./development/AdvancedSystems-CA.pfx
--store-name My
--store-location CurrentUser
--password '${{ secrets.ADV_CA_SECRET }}'

- name: Import Password Certificate (Windows)
if: runner.os == 'Windows'
run: |
$AppSettings = Get-Content '.\AdvancedSystems.Security.Tests\appsettings.json' -Raw | ConvertFrom-Json
$Name = $AppSettings.CertificateStore.Name
$Location = $AppSettings.CertificateStore.Location
Import-Certificate -FilePath .\development\AdvancedSystems-PasswordCertificate.pem -CertStoreLocation "Cert:\$Location\$Name"
shell: powershell

- name: Import Password Certificate (Ubuntu)
if: runner.os == 'Linux'
run: |
appSettings='./AdvancedSystems.Security.Tests/appsettings.json'
name=$(jq -r '.CertificateStore.Name' $appSettings)
location=$(jq -r '.CertificateStore.Location' $appSettings)
dotnet certificate-tool add --file ./development/AdvancedSystems-PasswordCertificate.pem --store-name $name --store-location $location

- name: Import Password Certificate (MacOS)
if: runner.os == 'macOS'
run: |
appSettings='./AdvancedSystems.Security.Tests/appsettings.json'
name=$(jq -r '.CertificateStore.Name' $appSettings)
location=$(jq -r '.CertificateStore.Location' $appSettings)
dotnet certificate-tool add --file ./development/AdvancedSystems-PasswordCertificate.pem --store-name $name --store-location $location

- name: Configure DotNet User Secrets
run: |
dotnet user-secrets set CertificatePassword '${{ secrets.ADV_CA_SECRET }}' --project ./AdvancedSystems.Security.Tests

- name: Run Unit Tests
run: >
dotnet test ./AdvancedSystems.Security.Tests
--configuration Release
--verbosity normal
11 changes: 9 additions & 2 deletions .husky/task-runner.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@
"command": "dotnet",
"args": [
"build",
"/warnaserror"
"./AdvancedSystems.Security",
"--configuration",
"Release",
"/warnAsError"
]
},
{
Expand All @@ -32,7 +35,11 @@
"command": "dotnet",
"args": [
"test",
"--nologo"
"./AdvancedSystems.Security.Tests",
"--configuration",
"Release",
"--verbosity",
"minimal"
]
}
]
Expand Down
17 changes: 17 additions & 0 deletions AdvancedSystems.Security.Abstractions/HashFunction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace AdvancedSystems.Security.Abstractions;

/// <summary>
/// Identifies a mathematical function that maps a string of arbitrary length
/// (up to a pre-determined maximum size) to a fixed-length string.
/// </summary>
public enum HashFunction
{
MD5 = 0,
SHA1 = 1,
SHA256 = 2,
SHA384 = 3,
SHA512 = 4,
SHA3_256 = 5,
SHA3_384 = 6,
SHA3_512 = 7,
}
169 changes: 148 additions & 21 deletions AdvancedSystems.Security.Abstractions/ICertificateService.cs
Original file line number Diff line number Diff line change
@@ -1,45 +1,172 @@
using System.Security.Cryptography.X509Certificates;

using AdvancedSystems.Security.Abstractions.Exceptions;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;

namespace AdvancedSystems.Security.Abstractions;

/// <summary>
/// Defines a service for managing and retrieving X.509 certificates.
/// Defines a contract for managing and retrieving X.509 certificates.
/// </summary>
/// <remarks>
/// See also: <seealso href="https://datatracker.ietf.org/doc/rfc5280/"/>.
/// </remarks>
/// <seealso cref="ICertificateStore"/>
public interface ICertificateService
{
#region Methods

/// <summary>
/// Retrieves an X.509 certificate from the specified store using the provided
/// <paramref name="thumbprint"/>.
/// Adds a certificate to a certificate store.
/// </summary>
/// <param name="thumbprint">
/// The thumbprint of the certificate to locate.
/// <param name="storeService">
/// The name of the keyed <seealso cref="ICertificateStore"/> service to use.
/// </param>
/// <param name="certificate">
/// The certificate to add.
/// </param>
/// <returns>
/// Returns <see langword="true"/> if the <paramref name="certificate"/> was added
/// successfully to the certificate store, else <see langword="false"/>.
/// </returns>
bool AddCertificate(string storeService, X509Certificate2 certificate);

/// <summary>
/// <inheritdoc cref="TryImportPemCertificate(string, string, string, string, out X509Certificate2?)" path="/summary"/>
/// </summary>
/// <param name="storeService">
/// <inheritdoc cref="TryImportPemCertificate(string, string, string, string, out X509Certificate2?)" path="/param[@name='storeService']"/>
/// </param>
/// <param name="certificatePath">
/// <inheritdoc cref="TryImportPemCertificate(string, string, string, string, out X509Certificate2?)" path="/param[@name='certificatePath']"/>
/// </param>
/// <param name="privateKeyPath">
/// <inheritdoc cref="TryImportPemCertificate(string, string, string, string, out X509Certificate2?)" path="/param[@name='privateKeyPath']"/>
/// </param>
/// <param name="certificate">
/// <inheritdoc cref="TryImportPemCertificate(string, string, string, string, out X509Certificate2?)" path="/param[@name='certificate']"/>
/// </param>
/// <returns>
/// <inheritdoc cref="TryImportPemCertificate(string, string, string, string, out X509Certificate2?)" path="/returns"/>
/// </returns>
/// <remarks>
/// <inheritdoc cref="TryImportPemCertificate(string, string, string, string, out X509Certificate2?)" path="/remarks"/>
/// </remarks>
bool TryImportPemCertificate(string storeService, string certificatePath, string privateKeyPath, out X509Certificate2? certificate);

/// <summary>
/// Tries to import a PEM certificate file into a certificate store.
/// </summary>
/// <param name="storeService">
/// The name of the keyed <seealso cref="ICertificateStore"/> service to use.
/// </param>
/// <param name="certificatePath">
/// The file path to the PEM certificate.
/// </param>
/// <param name="privateKeyPath">
/// The file path to the PKCS#8 (encrypted) private key associated with the specified certificate.
/// </param>
/// <param name="password">
/// The password required to decrypt the private key (if specified).
/// </param>
/// <param name="certificate">
/// An output parameter that will contain the imported <see cref="X509Certificate2"/> instance if the operation succeeds;
/// otherwise, it will be <see langword="null"/>.
/// </param>
/// <returns>
/// <see langword="true"/> if the certificate was imported to the certificate store successfully, else <see langword="false"/>.
/// </returns>
/// <remarks>
/// See also: <seealso href="https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail"/>.
/// </remarks>
bool TryImportPemCertificate(string storeService, string certificatePath, string privateKeyPath, string password, out X509Certificate2? certificate);

/// <summary>
/// <inheritdoc cref="TryImportPfxCertificate(string, string, string, out X509Certificate2?)" path="/summary"/>
/// </summary>
/// <param name="storeService">
/// <inheritdoc cref="TryImportPfxCertificate(string, string, string, out X509Certificate2?)" path="/param[@name='storeService']"/>
/// </param>
/// <param name="storeName">
/// The certificate store from which to retrieve the certificate.
/// <param name="certificatePath">
/// <inheritdoc cref="TryImportPfxCertificate(string, string, string, out X509Certificate2?)" path="/param[@name='certificatePath']"/>
/// </param>
/// <param name="storeLocation">
/// The location of the certificate store, such as <see cref="StoreLocation.CurrentUser"/>
/// or <see cref="StoreLocation.LocalMachine"/>.
/// <param name="certificate">
/// <inheritdoc cref="TryImportPfxCertificate(string, string, string, out X509Certificate2?)" path="/param[@name='certificate']"/>
/// </param>
/// <returns>
/// The <see cref="X509Certificate2"/> object if the certificate is found, else <c>null</c>.
/// <inheritdoc cref="TryImportPfxCertificate(string, string, string, out X509Certificate2?)" path="/returns"/>
/// </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);
/// <remarks>
/// <inheritdoc cref="TryImportPfxCertificate(string, string, string, out X509Certificate2?)" path="/remarks"/>
/// </remarks>
bool TryImportPfxCertificate(string storeService, string certificatePath, out X509Certificate2? certificate);

/// <summary>
/// Retrieves an application-configured X.509 certificate.
/// Tries to import a PFX certificate file into a certificate store.
/// </summary>
/// <param name="storeService">
/// The name of the keyed <seealso cref="ICertificateStore"/> service to use.
/// </param>
/// <param name="certificatePath">
/// The file path to the PFX certificate file that needs to be imported.
/// </param>
/// <param name="password">
/// The password required to access the PFX file's private key.
/// </param>
/// <param name="certificate">
/// An output parameter that will contain the imported <see cref="X509Certificate2"/> instance if the operation succeeds;
/// otherwise, it will be <see langword="null"/>.
/// </param>
/// <returns>
/// <see langword="true"/> if the certificate was imported to the certificate store successfully, else <see langword="false"/>.
/// </returns>
/// <remarks>
/// See also: <seealso href="https://en.wikipedia.org/wiki/PKCS_12"/>.
/// </remarks>
bool TryImportPfxCertificate(string storeService, string certificatePath, string password, out X509Certificate2? certificate);

/// <summary>
/// Retrieves all certificates from the certificate store.
/// </summary>
/// <param name="storeService">
/// The name of the keyed <seealso cref="ICertificateStore"/> service to use.
/// </param>
/// <returns>
/// Returns a collection of <seealso cref="X509Certificate2"/> certificates.
/// </returns>
IEnumerable<X509Certificate2> GetCertificate(string storeService);

/// <summary>
/// Retrieves a certificate from the certificate store by using the <paramref name="thumbprint"/>.
/// </summary>
/// <param name="storeService">
/// The name of the keyed <seealso cref="ICertificateStore"/> service to use.
/// </param>
/// <param name="thumbprint">
/// The string representing the thumbprint of the certificate to retrieve.
/// </param>
/// <param name="validOnly">
/// <see langword="true"/> to allow only valid certificates to be returned from the search;
/// otherwise, <see langword="false"/>.
/// </param>
/// <returns>
/// A <seealso cref="X509Certificate2"/> object if a certificate in the certificate store
/// matches the search criteria, else <see langword="null"/>.
/// </returns>
X509Certificate2? GetCertificate(string storeService, string thumbprint, bool validOnly = true);

/// <summary>
/// Removes a certificate from the certificate store by using the <paramref name="thumbprint"/>.
/// </summary>
/// <param name="storeService">
/// The name of the keyed <seealso cref="ICertificateStore"/> service to use.
/// </param>
/// <param name="thumbprint">
/// The string representing the thumbprint of the certificate to remove.
/// </param>
/// <returns>
/// The <see cref="X509Certificate2"/> object if the certificate is found, else <c>null</c>.
/// Returns <see langword="true"/> if a certificate with the specified <paramref name="thumbprint"/>
/// was removed from the certificate store, else <see langword="false"/>.
/// </returns>
X509Certificate2? GetConfiguredCertificate();
bool RemoveCertificate(string storeService, string thumbprint);

#endregion
}
Loading