Skip to content

Commit 8b9bd88

Browse files
authored
Merge pull request #17 from Advanced-Systems/rsa-certificate-service
Certificate Service, Certificate Store and Hashing Facilities
2 parents cfe6824 + 87af8c1 commit 8b9bd88

File tree

72 files changed

+2194
-765
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+2194
-765
lines changed

.config/dotnet-tools.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@
1515
"husky"
1616
],
1717
"rollForward": false
18+
},
19+
"dotnet-certificate-tool": {
20+
"version": "2.0.9",
21+
"commands": [
22+
"certificate-tool"
23+
],
24+
"rollForward": false
1825
}
1926
}
2027
}

.github/workflows/dotnet-publish-abstractions.yml

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,36 @@ jobs:
3838
shell: powershell
3939

4040
- name: Restore Dependencies
41-
run: dotnet restore ${{ env.PROJECT_PATH }} --configfile nuget.config
41+
run: >
42+
dotnet restore ${{ env.PROJECT_PATH }}
43+
--configfile nuget.config
4244
4345
- name: Build Project
44-
run: dotnet build ${{ env.PROJECT_PATH }} --nologo --no-restore --configuration Release
46+
run: >
47+
dotnet build ${{ env.PROJECT_PATH }}
48+
--configuration Release
49+
--no-restore
50+
--nologo
4551
4652
- name: Pack Project
47-
run: dotnet pack ${{ env.PROJECT_PATH }} --nologo --no-restore --no-build --configuration Release --output ${{ env.RELEASE_DIRECTORY }}
53+
run: >
54+
dotnet pack ${{ env.PROJECT_PATH }}
55+
--configuration Release
56+
--output ${{ env.RELEASE_DIRECTORY }}
57+
--no-restore
58+
--no-build
59+
--nologo
4860
4961
- name: Deploy Package to GitHub
50-
run: dotnet nuget push '${{ env.RELEASE_DIRECTORY }}\*.nupkg' --skip-duplicate --api-key ${{ secrets.GH_AUTH_TOKEN_PUSH }} --source ${{ env.GITHUB_SOURCE }}
62+
run: >
63+
dotnet nuget push '${{ env.RELEASE_DIRECTORY }}\*.nupkg'
64+
--api-key ${{ secrets.GH_AUTH_TOKEN_PUSH }}
65+
--source ${{ env.GITHUB_SOURCE }}
66+
--skip-duplicate
5167
5268
- name: Deploy Package to NuGet
53-
run: dotnet nuget push '${{ env.RELEASE_DIRECTORY }}\*.nupkg' --skip-duplicate --api-key ${{ secrets.NUGET_AUTH_TOKEN_PUSH }} --source ${{ env.NUGET_SOURCE }}
69+
run: >
70+
dotnet nuget push '${{ env.RELEASE_DIRECTORY }}\*.nupkg'
71+
--api-key ${{ secrets.NUGET_AUTH_TOKEN_PUSH }}
72+
--source ${{ env.NUGET_SOURCE }}
73+
--skip-duplicate

.github/workflows/dotnet-publish-core.yml

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,36 @@ jobs:
3838
shell: powershell
3939

4040
- name: Restore Dependencies
41-
run: dotnet restore ${{ env.PROJECT_PATH }} --configfile nuget.config
41+
run: >
42+
dotnet restore ${{ env.PROJECT_PATH }}
43+
--configfile nuget.config
4244
4345
- name: Build Project
44-
run: dotnet build ${{ env.PROJECT_PATH }} --nologo --no-restore --configuration Release
46+
run: >
47+
dotnet build ${{ env.PROJECT_PATH }}
48+
--configuration Release
49+
--no-restore
50+
--nologo
4551
4652
- name: Pack Project
47-
run: dotnet pack ${{ env.PROJECT_PATH }} --nologo --no-restore --no-build --configuration Release --output ${{ env.RELEASE_DIRECTORY }}
53+
run: >
54+
dotnet pack ${{ env.PROJECT_PATH }}
55+
--configuration Release
56+
--output ${{ env.RELEASE_DIRECTORY }}
57+
--no-restore
58+
--no-build
59+
--nologo
4860
4961
- name: Deploy Package to GitHub
50-
run: dotnet nuget push '${{ env.RELEASE_DIRECTORY }}\*.nupkg' --skip-duplicate --api-key ${{ secrets.GH_AUTH_TOKEN_PUSH }} --source ${{ env.GITHUB_SOURCE }}
62+
run: >
63+
dotnet nuget push '${{ env.RELEASE_DIRECTORY }}\*.nupkg'
64+
--api-key ${{ secrets.GH_AUTH_TOKEN_PUSH }}
65+
--source ${{ env.GITHUB_SOURCE }}
66+
--skip-duplicate
5167
5268
- name: Deploy Package to NuGet
53-
run: dotnet nuget push '${{ env.RELEASE_DIRECTORY }}\*.nupkg' --skip-duplicate --api-key ${{ secrets.NUGET_AUTH_TOKEN_PUSH }} --source ${{ env.NUGET_SOURCE }}
69+
run: >
70+
dotnet nuget push '${{ env.RELEASE_DIRECTORY }}\*.nupkg'
71+
--api-key ${{ secrets.NUGET_AUTH_TOKEN_PUSH }}
72+
--source ${{ env.NUGET_SOURCE }}
73+
--skip-duplicate

.github/workflows/dotnet-tests.yml

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,54 @@ jobs:
2828
- name: Restore Dependencies
2929
run: dotnet restore
3030

31-
- name: Build
32-
run: dotnet build --no-restore
31+
- name: Restore Dotnet Tools
32+
run: dotnet tool restore
3333

34-
- name: Test
35-
run: dotnet test --no-build --verbosity normal
34+
- name: Build Project
35+
run: >
36+
dotnet build ./AdvancedSystems.Security
37+
--configuration Release
38+
--no-restore
39+
/warnAsError
40+
41+
- name: Import AdvancedSystems Certificate Authority
42+
run: >
43+
dotnet certificate-tool add --file ./development/AdvancedSystems-CA.pfx
44+
--store-name My
45+
--store-location CurrentUser
46+
--password '${{ secrets.ADV_CA_SECRET }}'
47+
48+
- name: Import Password Certificate (Windows)
49+
if: runner.os == 'Windows'
50+
run: |
51+
$AppSettings = Get-Content '.\AdvancedSystems.Security.Tests\appsettings.json' -Raw | ConvertFrom-Json
52+
$Name = $AppSettings.CertificateStore.Name
53+
$Location = $AppSettings.CertificateStore.Location
54+
Import-Certificate -FilePath .\development\AdvancedSystems-PasswordCertificate.pem -CertStoreLocation "Cert:\$Location\$Name"
55+
shell: powershell
56+
57+
- name: Import Password Certificate (Ubuntu)
58+
if: runner.os == 'Linux'
59+
run: |
60+
appSettings='./AdvancedSystems.Security.Tests/appsettings.json'
61+
name=$(jq -r '.CertificateStore.Name' $appSettings)
62+
location=$(jq -r '.CertificateStore.Location' $appSettings)
63+
dotnet certificate-tool add --file ./development/AdvancedSystems-PasswordCertificate.pem --store-name $name --store-location $location
64+
65+
- name: Import Password Certificate (MacOS)
66+
if: runner.os == 'macOS'
67+
run: |
68+
appSettings='./AdvancedSystems.Security.Tests/appsettings.json'
69+
name=$(jq -r '.CertificateStore.Name' $appSettings)
70+
location=$(jq -r '.CertificateStore.Location' $appSettings)
71+
dotnet certificate-tool add --file ./development/AdvancedSystems-PasswordCertificate.pem --store-name $name --store-location $location
72+
73+
- name: Configure DotNet User Secrets
74+
run: |
75+
dotnet user-secrets set CertificatePassword '${{ secrets.ADV_CA_SECRET }}' --project ./AdvancedSystems.Security.Tests
76+
77+
- name: Run Unit Tests
78+
run: >
79+
dotnet test ./AdvancedSystems.Security.Tests
80+
--configuration Release
81+
--verbosity normal

.husky/task-runner.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@
2323
"command": "dotnet",
2424
"args": [
2525
"build",
26-
"/warnaserror"
26+
"./AdvancedSystems.Security",
27+
"--configuration",
28+
"Release",
29+
"/warnAsError"
2730
]
2831
},
2932
{
@@ -32,7 +35,11 @@
3235
"command": "dotnet",
3336
"args": [
3437
"test",
35-
"--nologo"
38+
"./AdvancedSystems.Security.Tests",
39+
"--configuration",
40+
"Release",
41+
"--verbosity",
42+
"minimal"
3643
]
3744
}
3845
]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace AdvancedSystems.Security.Abstractions;
2+
3+
/// <summary>
4+
/// Identifies a mathematical function that maps a string of arbitrary length
5+
/// (up to a pre-determined maximum size) to a fixed-length string.
6+
/// </summary>
7+
public enum HashFunction
8+
{
9+
MD5 = 0,
10+
SHA1 = 1,
11+
SHA256 = 2,
12+
SHA384 = 3,
13+
SHA512 = 4,
14+
SHA3_256 = 5,
15+
SHA3_384 = 6,
16+
SHA3_512 = 7,
17+
}
Lines changed: 148 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,172 @@
1-
using System.Security.Cryptography.X509Certificates;
2-
3-
using AdvancedSystems.Security.Abstractions.Exceptions;
1+
using System.Collections.Generic;
2+
using System.Security.Cryptography.X509Certificates;
43

54
namespace AdvancedSystems.Security.Abstractions;
65

76
/// <summary>
8-
/// Defines a service for managing and retrieving X.509 certificates.
7+
/// Defines a contract for managing and retrieving X.509 certificates.
98
/// </summary>
9+
/// <remarks>
10+
/// See also: <seealso href="https://datatracker.ietf.org/doc/rfc5280/"/>.
11+
/// </remarks>
12+
/// <seealso cref="ICertificateStore"/>
1013
public interface ICertificateService
1114
{
1215
#region Methods
1316

1417
/// <summary>
15-
/// Retrieves an X.509 certificate from the specified store using the provided
16-
/// <paramref name="thumbprint"/>.
18+
/// Adds a certificate to a certificate store.
1719
/// </summary>
18-
/// <param name="thumbprint">
19-
/// The thumbprint of the certificate to locate.
20+
/// <param name="storeService">
21+
/// The name of the keyed <seealso cref="ICertificateStore"/> service to use.
22+
/// </param>
23+
/// <param name="certificate">
24+
/// The certificate to add.
25+
/// </param>
26+
/// <returns>
27+
/// Returns <see langword="true"/> if the <paramref name="certificate"/> was added
28+
/// successfully to the certificate store, else <see langword="false"/>.
29+
/// </returns>
30+
bool AddCertificate(string storeService, X509Certificate2 certificate);
31+
32+
/// <summary>
33+
/// <inheritdoc cref="TryImportPemCertificate(string, string, string, string, out X509Certificate2?)" path="/summary"/>
34+
/// </summary>
35+
/// <param name="storeService">
36+
/// <inheritdoc cref="TryImportPemCertificate(string, string, string, string, out X509Certificate2?)" path="/param[@name='storeService']"/>
37+
/// </param>
38+
/// <param name="certificatePath">
39+
/// <inheritdoc cref="TryImportPemCertificate(string, string, string, string, out X509Certificate2?)" path="/param[@name='certificatePath']"/>
40+
/// </param>
41+
/// <param name="privateKeyPath">
42+
/// <inheritdoc cref="TryImportPemCertificate(string, string, string, string, out X509Certificate2?)" path="/param[@name='privateKeyPath']"/>
43+
/// </param>
44+
/// <param name="certificate">
45+
/// <inheritdoc cref="TryImportPemCertificate(string, string, string, string, out X509Certificate2?)" path="/param[@name='certificate']"/>
46+
/// </param>
47+
/// <returns>
48+
/// <inheritdoc cref="TryImportPemCertificate(string, string, string, string, out X509Certificate2?)" path="/returns"/>
49+
/// </returns>
50+
/// <remarks>
51+
/// <inheritdoc cref="TryImportPemCertificate(string, string, string, string, out X509Certificate2?)" path="/remarks"/>
52+
/// </remarks>
53+
bool TryImportPemCertificate(string storeService, string certificatePath, string privateKeyPath, out X509Certificate2? certificate);
54+
55+
/// <summary>
56+
/// Tries to import a PEM certificate file into a certificate store.
57+
/// </summary>
58+
/// <param name="storeService">
59+
/// The name of the keyed <seealso cref="ICertificateStore"/> service to use.
60+
/// </param>
61+
/// <param name="certificatePath">
62+
/// The file path to the PEM certificate.
63+
/// </param>
64+
/// <param name="privateKeyPath">
65+
/// The file path to the PKCS#8 (encrypted) private key associated with the specified certificate.
66+
/// </param>
67+
/// <param name="password">
68+
/// The password required to decrypt the private key (if specified).
69+
/// </param>
70+
/// <param name="certificate">
71+
/// An output parameter that will contain the imported <see cref="X509Certificate2"/> instance if the operation succeeds;
72+
/// otherwise, it will be <see langword="null"/>.
73+
/// </param>
74+
/// <returns>
75+
/// <see langword="true"/> if the certificate was imported to the certificate store successfully, else <see langword="false"/>.
76+
/// </returns>
77+
/// <remarks>
78+
/// See also: <seealso href="https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail"/>.
79+
/// </remarks>
80+
bool TryImportPemCertificate(string storeService, string certificatePath, string privateKeyPath, string password, out X509Certificate2? certificate);
81+
82+
/// <summary>
83+
/// <inheritdoc cref="TryImportPfxCertificate(string, string, string, out X509Certificate2?)" path="/summary"/>
84+
/// </summary>
85+
/// <param name="storeService">
86+
/// <inheritdoc cref="TryImportPfxCertificate(string, string, string, out X509Certificate2?)" path="/param[@name='storeService']"/>
2087
/// </param>
21-
/// <param name="storeName">
22-
/// The certificate store from which to retrieve the certificate.
88+
/// <param name="certificatePath">
89+
/// <inheritdoc cref="TryImportPfxCertificate(string, string, string, out X509Certificate2?)" path="/param[@name='certificatePath']"/>
2390
/// </param>
24-
/// <param name="storeLocation">
25-
/// The location of the certificate store, such as <see cref="StoreLocation.CurrentUser"/>
26-
/// or <see cref="StoreLocation.LocalMachine"/>.
91+
/// <param name="certificate">
92+
/// <inheritdoc cref="TryImportPfxCertificate(string, string, string, out X509Certificate2?)" path="/param[@name='certificate']"/>
2793
/// </param>
2894
/// <returns>
29-
/// The <see cref="X509Certificate2"/> object if the certificate is found, else <c>null</c>.
95+
/// <inheritdoc cref="TryImportPfxCertificate(string, string, string, out X509Certificate2?)" path="/returns"/>
3096
/// </returns>
31-
/// <exception cref="CertificateNotFoundException">
32-
/// Thrown when no certificate with the specified thumbprint is found in the store.
33-
/// </exception>
34-
X509Certificate2? GetStoreCertificate(string thumbprint, StoreName storeName, StoreLocation storeLocation);
97+
/// <remarks>
98+
/// <inheritdoc cref="TryImportPfxCertificate(string, string, string, out X509Certificate2?)" path="/remarks"/>
99+
/// </remarks>
100+
bool TryImportPfxCertificate(string storeService, string certificatePath, out X509Certificate2? certificate);
35101

36102
/// <summary>
37-
/// Retrieves an application-configured X.509 certificate.
103+
/// Tries to import a PFX certificate file into a certificate store.
38104
/// </summary>
105+
/// <param name="storeService">
106+
/// The name of the keyed <seealso cref="ICertificateStore"/> service to use.
107+
/// </param>
108+
/// <param name="certificatePath">
109+
/// The file path to the PFX certificate file that needs to be imported.
110+
/// </param>
111+
/// <param name="password">
112+
/// The password required to access the PFX file's private key.
113+
/// </param>
114+
/// <param name="certificate">
115+
/// An output parameter that will contain the imported <see cref="X509Certificate2"/> instance if the operation succeeds;
116+
/// otherwise, it will be <see langword="null"/>.
117+
/// </param>
118+
/// <returns>
119+
/// <see langword="true"/> if the certificate was imported to the certificate store successfully, else <see langword="false"/>.
120+
/// </returns>
121+
/// <remarks>
122+
/// See also: <seealso href="https://en.wikipedia.org/wiki/PKCS_12"/>.
123+
/// </remarks>
124+
bool TryImportPfxCertificate(string storeService, string certificatePath, string password, out X509Certificate2? certificate);
125+
126+
/// <summary>
127+
/// Retrieves all certificates from the certificate store.
128+
/// </summary>
129+
/// <param name="storeService">
130+
/// The name of the keyed <seealso cref="ICertificateStore"/> service to use.
131+
/// </param>
132+
/// <returns>
133+
/// Returns a collection of <seealso cref="X509Certificate2"/> certificates.
134+
/// </returns>
135+
IEnumerable<X509Certificate2> GetCertificate(string storeService);
136+
137+
/// <summary>
138+
/// Retrieves a certificate from the certificate store by using the <paramref name="thumbprint"/>.
139+
/// </summary>
140+
/// <param name="storeService">
141+
/// The name of the keyed <seealso cref="ICertificateStore"/> service to use.
142+
/// </param>
143+
/// <param name="thumbprint">
144+
/// The string representing the thumbprint of the certificate to retrieve.
145+
/// </param>
146+
/// <param name="validOnly">
147+
/// <see langword="true"/> to allow only valid certificates to be returned from the search;
148+
/// otherwise, <see langword="false"/>.
149+
/// </param>
150+
/// <returns>
151+
/// A <seealso cref="X509Certificate2"/> object if a certificate in the certificate store
152+
/// matches the search criteria, else <see langword="null"/>.
153+
/// </returns>
154+
X509Certificate2? GetCertificate(string storeService, string thumbprint, bool validOnly = true);
155+
156+
/// <summary>
157+
/// Removes a certificate from the certificate store by using the <paramref name="thumbprint"/>.
158+
/// </summary>
159+
/// <param name="storeService">
160+
/// The name of the keyed <seealso cref="ICertificateStore"/> service to use.
161+
/// </param>
162+
/// <param name="thumbprint">
163+
/// The string representing the thumbprint of the certificate to remove.
164+
/// </param>
39165
/// <returns>
40-
/// The <see cref="X509Certificate2"/> object if the certificate is found, else <c>null</c>.
166+
/// Returns <see langword="true"/> if a certificate with the specified <paramref name="thumbprint"/>
167+
/// was removed from the certificate store, else <see langword="false"/>.
41168
/// </returns>
42-
X509Certificate2? GetConfiguredCertificate();
169+
bool RemoveCertificate(string storeService, string thumbprint);
43170

44171
#endregion
45172
}

0 commit comments

Comments
 (0)