Skip to content

Commit 457e7e2

Browse files
Skip certs with gateway-unparseable subjects during sync
During Synchronize, mirror the subject parsing the AnyCA Gateway performs when building its /v2/certificate/search response (new X509Name(true, netCert.Subject)). That call throws on subjects BouncyCastle cannot re-parse from .NET's string representation, which returns a 500 for the entire search page and aborts Command's CA sync. GatewayCanParseSubject runs the same parse on each certificate before it is added to the sync buffer. Certificates that would throw are skipped with a [SYNC-SKIP] warning and counted, so a single unparseable subject never lands in the gateway database and can never break the downstream Command sync. The gateway-side fix (try/catch or reading the subject from DER) will be handled separately.
1 parent 977c973 commit 457e7e2

1 file changed

Lines changed: 47 additions & 2 deletions

File tree

GCPCAS/Client/GCPCASClient.cs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ public async Task<int> DownloadAllIssuedCertificates(BlockingCollection<AnyCAPlu
248248

249249
int pageNumber = 0;
250250
int numberOfCertificates = 0;
251+
int skippedCertificates = 0;
251252

252253
try
253254
{
@@ -270,7 +271,21 @@ public async Task<int> DownloadAllIssuedCertificates(BlockingCollection<AnyCAPlu
270271
continue;
271272
}
272273
}
273-
certificatesBuffer.Add(AnyCAPluginCertificateFromGCPCertificate(certificate));
274+
AnyCAPluginCertificate pluginCertificate = AnyCAPluginCertificateFromGCPCertificate(certificate);
275+
276+
// Mirror the subject handling the AnyCA Gateway performs when it builds the
277+
// /v2/certificate/search response: `new X509Name(true, netCert.Subject)`. That call throws
278+
// on subjects BouncyCastle cannot re-parse from .NET's string form, which 500s the entire
279+
// gateway search page and aborts Command's CA sync. Skip such certs here so they never enter
280+
// the gateway database and can never break the downstream sync.
281+
if (!GatewayCanParseSubject(pluginCertificate.Certificate, out string subject, out string skipReason))
282+
{
283+
skippedCertificates++;
284+
_logger.LogWarning($"[SYNC-SKIP] Skipping certificate {pluginCertificate.CARequestID} - its subject would fail the AnyCA Gateway X509Name parse and abort the sync. Subject='{subject}', reason: {skipReason}");
285+
continue;
286+
}
287+
288+
certificatesBuffer.Add(pluginCertificate);
274289
numberOfCertificates++;
275290
_logger.LogDebug($"Found Certificate with name {certificate.CertificateName.CertificateId} {this.ToString()}");
276291
}
@@ -298,7 +313,7 @@ public async Task<int> DownloadAllIssuedCertificates(BlockingCollection<AnyCAPlu
298313
{
299314
certificatesBuffer.CompleteAdding();
300315
_logger.LogDebug($"Fetched {certificatesBuffer.Count} certificates from GCP over {pageNumber} pages.");
301-
_logger.LogInformation($"[SYNC-DIAG] Handed {numberOfCertificates} certificate(s) to the AnyCA Gateway buffer. Review the per-record [SYNC-DIAG] lines above to confirm each carries a parseable fingerprint and NotBefore - these are the values the Gateway must surface to Command on /v2/certificate/search.");
316+
_logger.LogInformation($"[SYNC-DIAG] Handed {numberOfCertificates} certificate(s) to the AnyCA Gateway buffer; skipped {skippedCertificates} certificate(s) with subjects the gateway cannot parse. Review the per-record [SYNC-DIAG]/[SYNC-SKIP] lines above for details.");
302317
}
303318
_logger.MethodExit();
304319
return numberOfCertificates;
@@ -416,6 +431,36 @@ private void LogCertificateContentDiagnostics(string caRequestId, string pem, En
416431
_logger.LogWarning($"[SYNC-DIAG] CARequestID={caRequestId}: FAILED to parse PemCertificate into an X509Certificate2 - the Gateway will likely store an empty fingerprint / notBefore=0 for this record. Error: {ex.Message}");
417432
}
418433
}
434+
435+
/// <summary>
436+
/// Mirrors the subject parsing the AnyCA Gateway performs when it builds the /v2/certificate/search
437+
/// response: <c>new Org.BouncyCastle.Asn1.X509.X509Name(true, netCert.Subject)</c>. That call throws on
438+
/// subjects BouncyCastle cannot re-parse from .NET's string representation, which 500s the entire gateway
439+
/// search page and aborts Command's CA sync. Returning <see langword="false"/> lets the sync skip the
440+
/// certificate so it never enters the gateway database and can never break the downstream Command sync.
441+
/// </summary>
442+
/// <param name="pem">The PEM certificate content that will be handed to the gateway.</param>
443+
/// <param name="subject">The parsed .NET subject string, when available (for logging).</param>
444+
/// <param name="failureReason">The exception message when parsing fails.</param>
445+
/// <returns><see langword="true"/> if the gateway can parse the subject; otherwise <see langword="false"/>.</returns>
446+
private bool GatewayCanParseSubject(string pem, out string subject, out string failureReason)
447+
{
448+
subject = null;
449+
failureReason = null;
450+
try
451+
{
452+
using X509Certificate2 netCert = X509Certificate2.CreateFromPem(pem);
453+
subject = netCert.Subject;
454+
// This is the exact operation the gateway performs and that throws on problematic subjects.
455+
_ = new Org.BouncyCastle.Asn1.X509.X509Name(true, subject);
456+
return true;
457+
}
458+
catch (Exception ex)
459+
{
460+
failureReason = ex.Message;
461+
return false;
462+
}
463+
}
419464
/// <summary>
420465
/// Enrolls a certificate using a configured <see cref="ICreateCertificateRequestBuilder"/> and returns the result.
421466
/// </summary>

0 commit comments

Comments
 (0)