@@ -22,6 +22,7 @@ namespace Sisk.Core.Helpers;
2222/// Provides a set of useful functions to issue self-signed development certificates.
2323/// </summary>
2424public static class CertificateHelper {
25+ private const string PfxPassword = "sisk" ;
2526
2627 // -> https://github.com/dotnet/corefx/blob/a10890f4ffe0fadf090c922578ba0e606ebdd16c/src/Common/src/System/Text/StringOrCharArray.cs#L140
2728 // we need to make sure each hashcode for each string is the same.
@@ -58,20 +59,60 @@ static int ComputeArrayHash ( string [] array ) {
5859 /// <param name="dnsNames">The certificate DNS names.</param>
5960 public static X509Certificate2 CreateTrustedDevelopmentCertificate ( params string [ ] dnsNames ) {
6061 int dnsHash = ComputeArrayHash ( dnsNames ) ;
61- X509Certificate2 x509Certificate2 ;
62- using ( var store = new X509Store ( StoreName . Root , StoreLocation . CurrentUser ) ) {
63- store . Open ( OpenFlags . ReadWrite ) ;
64-
65- var siskCert = store . Certificates . FirstOrDefault ( c => c . Issuer . Contains ( $ "Sisk Development CA { dnsHash } " ) ) ;
66- if ( siskCert is null ) {
67- x509Certificate2 = CreateDevelopmentCertificate ( dnsNames ) ;
68- store . Add ( x509Certificate2 ) ;
69- }
70- else {
71- x509Certificate2 = siskCert ;
72- }
62+ string basePath = Path . Combine (
63+ Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) ,
64+ ".sisk" , "development-certs" ) ;
65+
66+ Directory . CreateDirectory ( basePath ) ;
67+
68+ string fileName = $ "SiskDevelopment_{ dnsHash } .pfx";
69+ string pfxPath = Path . Combine ( basePath , fileName ) ;
70+
71+ X509Certificate2 certificate ;
72+
73+ if ( File . Exists ( pfxPath ) ) {
74+ certificate = LoadPfxFromDisk ( pfxPath ) ;
75+ }
76+ else {
77+ using var fresh = CreateDevelopmentCertificate ( dnsNames ) ;
78+
79+ File . WriteAllBytes (
80+ pfxPath ,
81+ fresh . Export ( X509ContentType . Pfx , PfxPassword ) ) ;
82+
83+ certificate = LoadPfxFromDisk ( pfxPath ) ;
84+ }
85+
86+ EnsureTrusted ( certificate ) ;
87+
88+ return certificate ;
89+ }
90+
91+ private static X509Certificate2 LoadPfxFromDisk ( string path ) {
92+ #if NET9_0_OR_GREATER
93+ return X509CertificateLoader . LoadPkcs12 (
94+ File . ReadAllBytes ( path ) ,
95+ PfxPassword ,
96+ X509KeyStorageFlags . Exportable |
97+ X509KeyStorageFlags . PersistKeySet |
98+ X509KeyStorageFlags . UserKeySet ) ;
99+ #else
100+ return new X509Certificate2 (
101+ path ,
102+ PfxPassword ,
103+ X509KeyStorageFlags . Exportable |
104+ X509KeyStorageFlags . PersistKeySet |
105+ X509KeyStorageFlags . UserKeySet ) ;
106+ #endif
107+ }
108+
109+ private static void EnsureTrusted ( X509Certificate2 certificate ) {
110+ // deixa a confiança local (TrustedPeople) ou raiz (Root)
111+ using var trusted = new X509Store ( StoreName . Root , StoreLocation . CurrentUser ) ;
112+ trusted . Open ( OpenFlags . ReadWrite ) ;
113+ if ( trusted . Certificates . Cast < X509Certificate2 > ( ) . FirstOrDefault ( c => c . Thumbprint == certificate . Thumbprint ) is null ) {
114+ trusted . Add ( certificate ) ;
73115 }
74- return x509Certificate2 ;
75116 }
76117
77118 /// <summary>
@@ -82,31 +123,43 @@ public static X509Certificate2 CreateDevelopmentCertificate ( params string [] d
82123 if ( dnsNames . Length == 0 )
83124 throw new ArgumentException ( "At least one DNS name must be specified." , nameof ( dnsNames ) ) ;
84125
85- SubjectAlternativeNameBuilder sanBuilder = new SubjectAlternativeNameBuilder ( ) ;
126+ var sanBuilder = new SubjectAlternativeNameBuilder ( ) ;
86127 sanBuilder . AddIpAddress ( IPAddress . Loopback ) ;
87128 sanBuilder . AddIpAddress ( IPAddress . IPv6Loopback ) ;
88129
89- foreach ( string dnsName in dnsNames . Distinct ( StringComparer . OrdinalIgnoreCase ) ) {
130+ foreach ( string dnsName in dnsNames . Distinct ( StringComparer . OrdinalIgnoreCase ) )
90131 sanBuilder . AddDnsName ( dnsName . ToLowerInvariant ( ) ) ;
91- }
92132
93- X500DistinguishedName distinguishedName = new X500DistinguishedName ( $ "CN = Sisk Development CA { ComputeArrayHash ( dnsNames ) } ,OU = IT,O = Sao Paulo,L = Brazil,S = Sao Paulo,C = Brazil" ) ;
133+ var distinguishedName = new X500DistinguishedName (
134+ $ "CN = Sisk Development CA #{ ComputeArrayHash ( dnsNames ) } ,OU = IT,O = Sao Paulo,L = Brazil,S = Sao Paulo,C = Brazil" ) ;
94135
95- using ( RSA rsa = RSA . Create ( 2048 ) ) {
96- var request = new CertificateRequest ( distinguishedName , rsa , HashAlgorithmName . SHA256 , RSASignaturePadding . Pkcs1 ) ;
136+ using RSA rsa = RSA . Create ( 2048 ) ;
137+ var request = new CertificateRequest ( distinguishedName , rsa , HashAlgorithmName . SHA256 , RSASignaturePadding . Pkcs1 ) ;
97138
98- request . CertificateExtensions . Add (
99- new X509EnhancedKeyUsageExtension ( new OidCollection { new Oid ( "1.3.6.1.5.5.7.3.1" ) } , false ) ) ;
100- request . CertificateExtensions . Add (
101- sanBuilder . Build ( ) ) ;
139+ request . CertificateExtensions . Add (
140+ new X509EnhancedKeyUsageExtension ( new OidCollection { new Oid ( "1.3.6.1.5.5.7.3.1" ) } , false ) ) ;
141+ request . CertificateExtensions . Add ( sanBuilder . Build ( ) ) ;
102142
103- var certificate = request . CreateSelfSigned ( new DateTimeOffset ( DateTime . UtcNow . AddDays ( - 1 ) ) , new DateTimeOffset ( DateTime . UtcNow . AddDays ( 3650 ) ) ) ;
143+ using var certificate = request . CreateSelfSigned (
144+ DateTimeOffset . UtcNow . AddDays ( - 1 ) ,
145+ DateTimeOffset . UtcNow . AddYears ( 10 ) ) ;
146+
147+ var pfxBytes = certificate . Export ( X509ContentType . Pfx , PfxPassword ) ;
104148
105149#if NET9_0_OR_GREATER
106- return X509CertificateLoader . LoadPkcs12 ( certificate . Export ( X509ContentType . Pfx , "sisk" ) , "sisk" , X509KeyStorageFlags . DefaultKeySet ) ;
150+ return X509CertificateLoader . LoadPkcs12 (
151+ pfxBytes ,
152+ PfxPassword ,
153+ X509KeyStorageFlags . Exportable |
154+ X509KeyStorageFlags . PersistKeySet |
155+ X509KeyStorageFlags . UserKeySet ) ;
107156#else
108- return new X509Certificate2 ( certificate . Export ( X509ContentType . Pfx , "sisk" ) , "sisk" , X509KeyStorageFlags . DefaultKeySet ) ;
157+ return new X509Certificate2 (
158+ pfxBytes ,
159+ PfxPassword ,
160+ X509KeyStorageFlags . Exportable |
161+ X509KeyStorageFlags . PersistKeySet |
162+ X509KeyStorageFlags . UserKeySet ) ;
109163#endif
110- }
111164 }
112165}
0 commit comments