1+ using System . Security . Cryptography ;
2+ using System . Security . Cryptography . X509Certificates ;
3+
4+ namespace Openlysis . Authentication . API . Infrastructure . Services ;
5+
6+ /// <summary>
7+ /// Provides a X509 certificate for authentication purposes in development environments.
8+ /// </summary>
9+ internal sealed class DevelopmentCertificateProvider : ICertificateProvider , IHostedService , IDisposable
10+ {
11+ private const string EcCurveFriendlyName = "nistP256" ;
12+ private const string X500DistinguishedName = "CN=OpenlysisAuth" ;
13+ private const int CertificateYearsValidity = 1 ;
14+ private const string CertFileName = "dev_x509_certificate.pem" ;
15+ private const string PrivateKeyFileName = "dev_ecdsa_key.pem" ;
16+
17+ /// <inheritdoc/>
18+ public X509Certificate2 Certificate
19+ {
20+ get
21+ {
22+ return _selfSignedCertificate ?? throw new InvalidOperationException ( "Certificate has not been set." ) ;
23+ }
24+ }
25+
26+ private readonly ILogger < DevelopmentCertificateProvider > _logger ;
27+ private X509Certificate2 ? _selfSignedCertificate ;
28+
29+ /// <summary>
30+ /// Initializes a new instance of the <see cref="DevelopmentCertificateProvider"/> class.
31+ /// </summary>
32+ /// <param name="logger">The logger instance for logging certificate provider events.</param>
33+ public DevelopmentCertificateProvider ( ILogger < DevelopmentCertificateProvider > logger )
34+ {
35+ _logger = logger ;
36+ }
37+
38+ /// <inheritdoc/>
39+ public void Dispose ( )
40+ {
41+ _selfSignedCertificate ? . Dispose ( ) ;
42+ }
43+
44+ /// <inheritdoc/>
45+ public async Task StartAsync ( CancellationToken cancellationToken )
46+ {
47+ string certFilePath = Path . Combine ( AppDomain . CurrentDomain . BaseDirectory , CertFileName ) ;
48+ string privateKeyFilePath = Path . Combine ( AppDomain . CurrentDomain . BaseDirectory , PrivateKeyFileName ) ;
49+
50+ if ( File . Exists ( certFilePath ) && File . Exists ( privateKeyFilePath ) )
51+ {
52+ _selfSignedCertificate = X509Certificate2 . CreateFromPemFile ( certFilePath , privateKeyFilePath ) ;
53+ if ( _selfSignedCertificate . NotAfter . ToUniversalTime ( ) > DateTime . UtcNow )
54+ {
55+ _logger . LogInformation ( "X509 certificate for development has been loaded successfully from existing certificate." ) ;
56+ return ;
57+ }
58+ }
59+
60+ using var ecDsaKeys = ECDsa . Create ( ECCurve . CreateFromFriendlyName ( EcCurveFriendlyName ) ) ;
61+ _selfSignedCertificate = CreateCertificate ( ecDsaKeys ) ;
62+
63+ string certPem = _selfSignedCertificate . ExportCertificatePem ( ) ;
64+ string privateKeyPem = ecDsaKeys . ExportECPrivateKeyPem ( ) ;
65+
66+ await File . WriteAllTextAsync ( certFilePath , certPem , cancellationToken ) ;
67+ await File . WriteAllTextAsync ( privateKeyFilePath , privateKeyPem , cancellationToken ) ;
68+
69+ _logger . LogInformation ( "X509 certificate for development has been created and loaded successfully." ) ;
70+ }
71+
72+ /// <inheritdoc/>
73+ public Task StopAsync ( CancellationToken cancellationToken )
74+ {
75+ return Task . CompletedTask ;
76+ }
77+
78+ private static X509Certificate2 CreateCertificate ( ECDsa ecDsaKeys )
79+ {
80+ var subjectName = new X500DistinguishedName ( X500DistinguishedName ) ;
81+ var request = new CertificateRequest ( subjectName , ecDsaKeys , HashAlgorithmName . SHA256 ) ;
82+
83+ request . CertificateExtensions . Add ( new X509KeyUsageExtension ( X509KeyUsageFlags . DigitalSignature , critical : true ) ) ;
84+
85+ DateTimeOffset notBefore = DateTimeOffset . UtcNow ;
86+ DateTimeOffset notAfter = notBefore . AddYears ( CertificateYearsValidity ) ;
87+ return request . CreateSelfSigned ( notBefore , notAfter ) ;
88+ }
89+ }
0 commit comments