Skip to content

Commit dfd3655

Browse files
dns issue
1 parent 7cfac23 commit dfd3655

1 file changed

Lines changed: 70 additions & 25 deletions

File tree

AcmeCaPlugin/AcmeCaPlugin.cs

Lines changed: 70 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
using System.Text;
1919
using Keyfactor.Extensions.CAPlugin.Acme.Clients.DNS;
2020
using System.Text.RegularExpressions;
21+
using Org.BouncyCastle.Asn1;
22+
using Org.BouncyCastle.Asn1.Pkcs;
23+
using Org.BouncyCastle.Asn1.X509;
24+
using Org.BouncyCastle.Pkcs;
2125

2226
namespace Keyfactor.Extensions.CAPlugin.Acme
2327
{
@@ -248,8 +252,12 @@ public async Task<EnrollmentResult> Enroll(
248252
var acmeClient = new AcmeClient(_logger, config, httpClient, protocolClient.Directory,
249253
new Clients.Acme.Account(accountDetails, signer));
250254

251-
// Extract all domains (CN + SANs) for the ACME order
252-
var identifiers = BuildIdentifiersFromSubjectAndSan(subject, san);
255+
// Decode CSR first so we can extract all domains from it
256+
var csrBytes = Convert.FromBase64String(csr);
257+
258+
// Extract all domains directly from CSR (CN + SANs) for the ACME order
259+
// This ensures we authorize exactly what's in the CSR
260+
var identifiers = ExtractDomainsFromCsr(csrBytes);
253261

254262
// Create order
255263
var order = await acmeClient.CreateOrderAsync(identifiers, null);
@@ -260,8 +268,7 @@ public async Task<EnrollmentResult> Enroll(
260268
// Process challenges
261269
await ProcessAuthorizations(acmeClient, order, config);
262270

263-
// Finalize
264-
var csrBytes = Convert.FromBase64String(csr);
271+
// Finalize with original CSR bytes
265272
order = await acmeClient.FinalizeOrderAsync(order, csrBytes);
266273

267274
// If order is valid immediately, download cert
@@ -328,43 +335,81 @@ private static string ExtractDomainFromSubject(string subject)
328335
}
329336

330337
/// <summary>
331-
/// Builds ACME identifiers from subject CN and SANs.
332-
/// ACME orders must include all domains that will be in the CSR.
338+
/// Extracts all DNS names (CN + SANs) directly from the CSR.
339+
/// This ensures the ACME order authorizes exactly what's in the CSR.
333340
/// </summary>
334-
/// <param name="subject">Subject string containing CN</param>
335-
/// <param name="san">Dictionary of SANs (key: type like "dns", value: array of names)</param>
336-
/// <returns>List of unique ACME identifiers for all domains</returns>
337-
private List<Identifier> BuildIdentifiersFromSubjectAndSan(string subject, Dictionary<string, string[]> san)
341+
/// <param name="csrBytes">DER-encoded CSR bytes</param>
342+
/// <returns>List of ACME identifiers for all domains in the CSR</returns>
343+
private List<Identifier> ExtractDomainsFromCsr(byte[] csrBytes)
338344
{
339345
var domains = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
340346

341-
// Add the CN from subject
342-
var cnDomain = ExtractDomainFromSubject(subject);
343-
domains.Add(cnDomain);
344-
_logger.LogDebug("Added CN domain to identifiers: {Domain}", cnDomain);
345-
346-
// Add DNS SANs if present
347-
if (san != null)
347+
try
348348
{
349-
// Check for "dns" key (case-insensitive)
350-
foreach (var kvp in san)
349+
// Parse the CSR using BouncyCastle
350+
var pkcs10 = new Pkcs10CertificationRequest(csrBytes);
351+
var csrInfo = pkcs10.GetCertificationRequestInfo();
352+
353+
// Extract CN from subject
354+
var subject = csrInfo.Subject;
355+
var cnValues = subject.GetValueList(X509Name.CN);
356+
if (cnValues != null && cnValues.Count > 0)
351357
{
352-
if (kvp.Key.Equals("dns", StringComparison.OrdinalIgnoreCase) && kvp.Value != null)
358+
var cn = cnValues[0]?.ToString();
359+
if (!string.IsNullOrWhiteSpace(cn))
353360
{
354-
foreach (var dnsName in kvp.Value)
361+
domains.Add(cn);
362+
_logger.LogDebug("Extracted CN from CSR: {Domain}", cn);
363+
}
364+
}
365+
366+
// Extract SANs from CSR attributes
367+
var attributes = csrInfo.Attributes;
368+
if (attributes != null)
369+
{
370+
foreach (var attr in attributes)
371+
{
372+
var attribute = Org.BouncyCastle.Asn1.Pkcs.AttributePkcs.GetInstance(attr);
373+
if (attribute.AttrType.Equals(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest))
355374
{
356-
if (!string.IsNullOrWhiteSpace(dnsName))
375+
// This attribute contains extension requests
376+
var extensions = X509Extensions.GetInstance(attribute.AttrValues[0]);
377+
var sanExtension = extensions.GetExtension(X509Extensions.SubjectAlternativeName);
378+
379+
if (sanExtension != null)
357380
{
358-
domains.Add(dnsName.Trim());
359-
_logger.LogDebug("Added SAN domain to identifiers: {Domain}", dnsName.Trim());
381+
var sanNames = GeneralNames.GetInstance(sanExtension.GetParsedValue());
382+
foreach (var name in sanNames.GetNames())
383+
{
384+
// TagNo 2 = dNSName
385+
if (name.TagNo == GeneralName.DnsName)
386+
{
387+
var dnsName = name.Name.ToString();
388+
if (!string.IsNullOrWhiteSpace(dnsName))
389+
{
390+
domains.Add(dnsName);
391+
_logger.LogDebug("Extracted SAN from CSR: {Domain}", dnsName);
392+
}
393+
}
394+
}
360395
}
361396
}
362397
}
363398
}
364399
}
400+
catch (Exception ex)
401+
{
402+
_logger.LogError(ex, "Failed to parse CSR for domain extraction");
403+
throw new InvalidOperationException("Failed to parse CSR to extract domains", ex);
404+
}
405+
406+
if (domains.Count == 0)
407+
{
408+
throw new InvalidOperationException("No DNS names found in CSR (neither CN nor SANs)");
409+
}
365410

366411
var identifiers = domains.Select(d => new Identifier { Type = "dns", Value = d }).ToList();
367-
_logger.LogInformation("Created ACME order with {Count} identifier(s): {Domains}",
412+
_logger.LogInformation("Extracted {Count} domain(s) from CSR: {Domains}",
368413
identifiers.Count, string.Join(", ", domains));
369414

370415
return identifiers;

0 commit comments

Comments
 (0)