11// Copyright 2025 Keyfactor
22// Licensed under the Apache License, Version 2.0
3- using System ;
4- using System . Collections . Generic ;
5- using System . Threading . Tasks ;
3+ using ACMESharp . Authorizations ;
4+ using ACMESharp . Protocol ;
5+ using ACMESharp . Protocol . Resources ;
66using Keyfactor . AnyGateway . Extensions ;
7+ using Keyfactor . Extensions . CAPlugin . Acme . Clients . Acme ;
8+ using Keyfactor . Extensions . CAPlugin . Acme . Clients . DNS ;
79using Keyfactor . Logging ;
810using Keyfactor . PKI . Enums . EJBCA ;
911using Microsoft . Extensions . Logging ;
1012using Newtonsoft . Json ;
11- using System . Linq ;
12- using System . Net . Http ;
13- using ACMESharp . Authorizations ;
14- using Keyfactor . Extensions . CAPlugin . Acme . Clients . Acme ;
15- using System . Threading ;
16- using ACMESharp . Protocol . Resources ;
17- using ACMESharp . Protocol ;
18- using System . Text ;
19- using Keyfactor . Extensions . CAPlugin . Acme . Clients . DNS ;
20- using System . Text . RegularExpressions ;
2113using Org . BouncyCastle . Asn1 ;
2214using Org . BouncyCastle . Asn1 . Pkcs ;
2315using Org . BouncyCastle . Asn1 . X509 ;
2416using Org . BouncyCastle . Pkcs ;
17+ using System ;
18+ using System . Collections . Generic ;
19+ using System . Linq ;
20+ using System . Net . Http ;
21+ using System . Text ;
22+ using System . Text . RegularExpressions ;
23+ using System . Threading ;
24+ using System . Threading . Tasks ;
25+ using static Org . BouncyCastle . Math . EC . ECCurve ;
2526
2627namespace Keyfactor . Extensions . CAPlugin . Acme
2728{
@@ -62,6 +63,7 @@ public class AcmeCaPlugin : IAnyCAPlugin
6263 {
6364 private static readonly ILogger _logger = LogHandler . GetClassLogger < AcmeCaPlugin > ( ) ;
6465 private IAnyCAPluginConfigProvider Config { get ; set ; }
66+ private IDomainValidator _domainValidator ;
6567
6668 // Constants for better maintainability
6769 private const string DEFAULT_PRODUCT_ID = "default" ;
@@ -76,6 +78,8 @@ public void Initialize(IAnyCAPluginConfigProvider configProvider, ICertificateDa
7678 {
7779 _logger . MethodEntry ( ) ;
7880 Config = configProvider ?? throw new ArgumentNullException ( nameof ( configProvider ) ) ;
81+ _domainValidator = new Dns01DomainValidator ( ) ;
82+ _domainValidator . Initialize ( new DomainValidatorConfigProvider ( configProvider . CAConnectionData ) ) ;
7983 _logger . MethodExit ( ) ;
8084 }
8185
@@ -230,6 +234,7 @@ public async Task<EnrollmentResult> Enroll(
230234 {
231235 _logger . MethodEntry ( ) ;
232236
237+
233238 if ( string . IsNullOrWhiteSpace ( csr ) )
234239 throw new ArgumentException ( "CSR cannot be null or empty" , nameof ( csr ) ) ;
235240 if ( string . IsNullOrWhiteSpace ( subject ) )
@@ -471,74 +476,47 @@ private async Task ProcessAuthorizations(AcmeClient acmeClient, OrderDetails ord
471476 var dnsVerifier = new DnsVerificationHelper ( _logger , config . DnsVerificationServer ) ;
472477 var pendingChallenges = new List < ( Authorization authz , Challenge challenge , Dns01ChallengeValidationDetails validation ) > ( ) ;
473478
474- // First pass: Create all DNS records
479+ // First pass: Create all DNS records using IDomainValidator
475480 foreach ( var authzUrl in payload . Authorizations )
476481 {
477482 var authz = await acmeClient . GetAuthorizationAsync ( authzUrl ) ;
478483
479- // Skip if authorization is already valid (cached)
480484 if ( authz . Status == "valid" )
481485 {
482486 _logger . LogInformation ( "Using cached authorization for {Domain}" , authz . Identifier . Value ) ;
483487 continue ;
484488 }
485489
486- // Find DNS-01 challenge
487490 var challenge = authz . Challenges . FirstOrDefault ( c => c . Type == DNS_CHALLENGE_TYPE ) ;
488491 if ( challenge == null )
489492 throw new InvalidOperationException ( $ "{ DNS_CHALLENGE_TYPE } challenge not available") ;
490493
491- // Decode challenge validation details
492494 var validation = acmeClient . DecodeChallengeValidation ( authz , challenge ) as Dns01ChallengeValidationDetails ;
493495 if ( validation == null )
494496 throw new InvalidOperationException ( $ "Failed to decode { DNS_CHALLENGE_TYPE } challenge validation details") ;
495497
496- // Create DNS record (will throw exception with details if it fails)
497- var dnsProvider = DnsProviderFactory . Create ( config , _logger ) ;
498- await dnsProvider . CreateRecordAsync ( validation . DnsRecordName , validation . DnsRecordValue ) ;
498+ // Use IDomainValidator instead of DnsProviderFactory directly
499+ var result = await _domainValidator . StageValidation (
500+ validation . DnsRecordName ,
501+ validation . DnsRecordValue ,
502+ CancellationToken . None ) ;
503+
504+ if ( ! result . Success )
505+ throw new InvalidOperationException ( $ "Failed to stage DNS validation: { result . ErrorMessage } ") ;
499506
500507 _logger . LogInformation ( "Created DNS record {RecordName} for domain {Domain}" ,
501508 validation . DnsRecordName , authz . Identifier . Value ) ;
502509
503510 pendingChallenges . Add ( ( authz , challenge , validation ) ) ;
504511 }
505512
506- // Second pass: Wait for DNS propagation and submit challenges
513+ // Second pass: Wait for propagation and submit challenges
514+ // ... rest of your existing code ...
515+
516+ // Optional: Cleanup after challenges complete
507517 foreach ( var ( authz , challenge , validation ) in pendingChallenges )
508518 {
509- // Skip external DNS verification for Infoblox since it cannot ping external DNS providers
510- bool isInfoblox = config . DnsProvider ? . Trim ( ) . Equals ( "infoblox" , StringComparison . OrdinalIgnoreCase ) ?? false ;
511-
512- if ( isInfoblox )
513- {
514- _logger . LogInformation ( "Skipping external DNS propagation check for Infoblox provider for {Domain}. Adding short delay..." , authz . Identifier . Value ) ;
515- // Add a short delay to allow Infoblox to process the record internally
516- await Task . Delay ( TimeSpan . FromSeconds ( 5 ) ) ;
517- }
518- else
519- {
520- _logger . LogInformation ( "Waiting for DNS propagation for {Domain}..." , authz . Identifier . Value ) ;
521-
522- // Wait for DNS propagation with verification
523- var propagated = await dnsVerifier . WaitForDnsPropagationAsync (
524- validation . DnsRecordName ,
525- validation . DnsRecordValue ,
526- minimumServers : 3 // Require at least 3 DNS servers to confirm
527- ) ;
528-
529- if ( ! propagated )
530- {
531- _logger . LogWarning ( "DNS record may not have fully propagated for {Domain}. Proceeding anyway..." ,
532- authz . Identifier . Value ) ;
533-
534- // Optional: Add a final delay as fallback
535- await Task . Delay ( TimeSpan . FromSeconds ( 30 ) ) ;
536- }
537- }
538-
539- // Submit challenge response
540- _logger . LogInformation ( "Submitting challenge for {Domain}" , authz . Identifier . Value ) ;
541- await acmeClient . AnswerChallengeAsync ( challenge ) ;
519+ await _domainValidator . CleanupValidation ( validation . DnsRecordName , CancellationToken . None ) ;
542520 }
543521 }
544522
0 commit comments