diff --git a/README.md b/README.md index c185bec..3079352 100644 --- a/README.md +++ b/README.md @@ -96,16 +96,16 @@ If a field value is specified as both an Enrollment Field in Command and in the CONFIG ELEMENT | DESCRIPTION ----------------------------|------------------ -Template Short Name | CSC TrustedSecure OV -Template Display Name | CSC TrustedSecure OV -Friendly Name | CSC TrustedSecure OV +Template Short Name | CSC TrustedSecure Premium Certificate +Template Display Name | CSC TrustedSecure Premium Certificate +Friendly Name | CSC TrustedSecure Premium Certificate Keys Size | 2048 Enforce RFC 2818 Compliance | True CSR Enrollment | True Pfx Enrollment | True -**CSC TrustedSecure OV - Enrollment Fields** +**CSC TrustedSecure Premium Certificate - Enrollment Fields** NAME | DATA TYPE | VALUES -----|--------------|----------------- @@ -120,20 +120,20 @@ Business Unit | Multiple Choice | Get From CSC Differs For Clients Notification Email(s) Comma Separated | String | N/A CN DCV Email | String | N/A -**CSC TrustedSecure EV - Details Tab** +**CSC TrustedSecure EV Certificate - Details Tab** CONFIG ELEMENT | DESCRIPTION ----------------------------|------------------ -Template Short Name | CSC TrustedSecure EV -Template Display Name | CSC TrustedSecure EV -Friendly Name | CSC TrustedSecure EV +Template Short Name | CSC TrustedSecure EV Certificate +Template Display Name | CSC TrustedSecure EV Certificate +Friendly Name | CSC TrustedSecure EV Certificate Keys Size | 2048 Enforce RFC 2818 Compliance | True CSR Enrollment | True Pfx Enrollment | True -**CSC TrustedSecure EV - Enrollment Fields** +**CSC TrustedSecure EV Certificate - Enrollment Fields** NAME | DATA TYPE | VALUES -----|--------------|----------------- @@ -149,20 +149,20 @@ Notification Email(s) Comma Separated | String | N/A CN DCV Email | String | N/A Organization Country | String | N/A -**CSC TrustedSecure OV, Multiple Names - Details Tab** +**CSC TrustedSecure UC Certificate - Details Tab** CONFIG ELEMENT | DESCRIPTION ----------------------------|------------------ -Template Short Name | CSC TrustedSecure OV, Multiple Names -Template Display Name | CSC TrustedSecure OV, Multiple Names -Friendly Name | CSC TrustedSecure OV, Multiple Names +Template Short Name | CSC TrustedSecure UC Certificate +Template Display Name | CSC TrustedSecure UC Certificate +Friendly Name | CSC TrustedSecure UC Certificate Keys Size | 2048 Enforce RFC 2818 Compliance | True CSR Enrollment | True Pfx Enrollment | True -**CSC TrustedSecure OV, Multiple Names - Enrollment Fields** +**CSC TrustedSecure UC Certificate - Enrollment Fields** NAME | DATA TYPE | VALUES -----|--------------|----------------- @@ -179,20 +179,20 @@ CN DCV Email | String | N/A Addtl Sans Comma Separated DCV Emails | String | N/A -**CSC TrustedSecure OV Wildcard - Details Tab** +**CSC TrustedSecure Premium Wildcard Certificate - Details Tab** CONFIG ELEMENT | DESCRIPTION ----------------------------|------------------ -Template Short Name | CSC TrustedSecure OV Wildcard -Template Display Name | CSC TrustedSecure OV Wildcard -Friendly Name | CSC TrustedSecure OV Wildcard +Template Short Name | CSC TrustedSecure Premium Wildcard Certificate +Template Display Name | CSC TrustedSecure Premium Wildcard Certificate +Friendly Name | CSC TrustedSecure Premium Wildcard Certificate Keys Size | 2048 Enforce RFC 2818 Compliance | True CSR Enrollment | True Pfx Enrollment | True -**CSC TrustedSecure OV Wildcard - Enrollment Fields** +**CSC TrustedSecure Premium Wildcard Certificate - Enrollment Fields** NAME | DATA TYPE | VALUES -----|--------------|----------------- @@ -207,20 +207,20 @@ Business Unit | Multiple Choice | Get From CSC Differs For Clients Notification Email(s) Comma Separated | String | N/A CN DCV Email | String | N/A -**CSC TrustedSecure DV - Details Tab** +**CSC TrustedSecure Domain Validated SSL - Details Tab** CONFIG ELEMENT | DESCRIPTION ----------------------------|------------------ -Template Short Name | CSC TrustedSecure DV -Template Display Name | CSC TrustedSecure DV -Friendly Name | CSC TrustedSecure DV +Template Short Name | CSC TrustedSecure Domain Validated SSL +Template Display Name | CSC TrustedSecure Domain Validated SSL +Friendly Name | CSC TrustedSecure Domain Validated SSL Keys Size | 2048 Enforce RFC 2818 Compliance | True CSR Enrollment | True Pfx Enrollment | True -**CSC TrustedSecure DV - Enrollment Fields** + **CSC TrustedSecure Domain Validated SSL - Enrollment Fields** NAME | DATA TYPE | VALUES -----|--------------|----------------- @@ -235,20 +235,20 @@ Business Unit | Multiple Choice | Get From CSC Differs For Clients Notification Email(s) Comma Separated | String | N/A CN DCV Email | String | N/A -**CSC TrustedSecure DV Wildcard - Details Tab** +**CSC TrustedSecure Domain Validated Wildcard SSL - Details Tab** CONFIG ELEMENT | DESCRIPTION ----------------------------|------------------ -Template Short Name | CSC TrustedSecure DV Wildcard -Template Display Name | CSC TrustedSecure DV Wildcard -Friendly Name | CSC TrustedSecure DV Wildcard +Template Short Name | CSC TrustedSecure Domain Validated Wildcard SSL +Template Display Name | CSC TrustedSecure Domain Validated Wildcard SSL +Friendly Name | CSC TrustedSecure Domain Validated Wildcard SSL Keys Size | 2048 Enforce RFC 2818 Compliance | True CSR Enrollment | True Pfx Enrollment | True -**CSC TrustedSecure DV Wildcard - Enrollment Fields** +**CSC TrustedSecure Domain Validated Wildcard SSL - Enrollment Fields** NAME | DATA TYPE | VALUES -----|--------------|----------------- @@ -263,108 +263,20 @@ Business Unit | Multiple Choice | Get From CSC Differs For Clients Notification Email(s) Comma Separated | String | N/A CN DCV Email | String | N/A -**CSC TrustedSecure DV, Multiple Names - Details Tab** +**CSC TrustedSecure Domain Validated UC Certificate - Details Tab** CONFIG ELEMENT | DESCRIPTION ----------------------------|------------------ -Template Short Name | CSC TrustedSecure DV, Multiple Names -Template Display Name | CSC TrustedSecure DV, Multiple Names -Friendly Name | CSC TrustedSecure DV, Multiple Names +Template Short Name | CSC TrustedSecure Domain Validated UC Certificate +Template Display Name | CSC TrustedSecure Domain Validated UC Certificate +Friendly Name | CSC TrustedSecure Domain Validated UC Certificate Keys Size | 2048 Enforce RFC 2818 Compliance | True CSR Enrollment | True Pfx Enrollment | True -**CSC TrustedSecure DV, Multiple Names - Enrollment Fields** - -NAME | DATA TYPE | VALUES ------|--------------|----------------- -Term | Multiple Choice | 12,24 -Applicant First Name | String | N/A -Applicant Last Name | String | N/A -Applicant Email Address | String | N/A -Applicant Phone | String | N/A -Domain Control Validation Method | Multiple Choice | EMAIL -Organization Contact | Multiple Choice | Get From CSC Differs For Clients -Business Unit | Multiple Choice | Get From CSC Differs For Clients -Notification Email(s) Comma Separated | String | N/A -CN DCV Email | String | N/A -Addtl Sans Comma Separated DCV Emails | String | N/A - -**CSC TrustedSecure EV, Multiple Names - Details Tab** - -CONFIG ELEMENT | DESCRIPTION -----------------------------|------------------ -Template Short Name | CSC TrustedSecure EV, Multiple Names -Template Display Name | CSC TrustedSecure EV, Multiple Names -Friendly Name | CSC TrustedSecure EV, Multiple Names -Keys Size | 2048 -Enforce RFC 2818 Compliance | True -CSR Enrollment | True -Pfx Enrollment | True - - -**CSC TrustedSecure EV, Multiple Names - Enrollment Fields** - -NAME | DATA TYPE | VALUES ------|--------------|----------------- -Term | Multiple Choice | 12,24 -Applicant First Name | String | N/A -Applicant Last Name | String | N/A -Applicant Email Address | String | N/A -Applicant Phone | String | N/A -Domain Control Validation Method | Multiple Choice | EMAIL -Organization Contact | Multiple Choice | Get From CSC Differs For Clients -Business Unit | Multiple Choice | Get From CSC Differs For Clients -Notification Email(s) Comma Separated | String | N/A -CN DCV Email | String | N/A -Addtl Sans Comma Separated DCV Emails | String | N/A -Organization Country | String | N/A - -**CSC TrustedSecure OV Wildcard, Multiple Names - Details Tab** - -CONFIG ELEMENT | DESCRIPTION -----------------------------|------------------ -Template Short Name | CSC TrustedSecure OV Wildcard, Multiple Names -Template Display Name | CSC TrustedSecure OV Wildcard, Multiple Names -Friendly Name | CSC TrustedSecure OV Wildcard, Multiple Names -Keys Size | 2048 -Enforce RFC 2818 Compliance | True -CSR Enrollment | True -Pfx Enrollment | True - - -**CSC TrustedSecure OV Wildcard, Multiple Names - Enrollment Fields** - -NAME | DATA TYPE | VALUES ------|--------------|----------------- -Term | Multiple Choice | 12,24 -Applicant First Name | String | N/A -Applicant Last Name | String | N/A -Applicant Email Address | String | N/A -Applicant Phone | String | N/A -Domain Control Validation Method | Multiple Choice | EMAIL -Organization Contact | Multiple Choice | Get From CSC Differs For Clients -Business Unit | Multiple Choice | Get From CSC Differs For Clients -Notification Email(s) Comma Separated | String | N/A -CN DCV Email | String | N/A -Addtl Sans Comma Separated DCV Emails | String | N/A - -**CSC TrustedSecure DV Wildcard, Multiple Names - Details Tab** - -CONFIG ELEMENT | DESCRIPTION -----------------------------|------------------ -Template Short Name | CSC TrustedSecure DV Wildcard, Multiple Names -Template Display Name | CSC TrustedSecure DV Wildcard, Multiple Names -Friendly Name | CSC TrustedSecure DV Wildcard, Multiple Names -Keys Size | 2048 -Enforce RFC 2818 Compliance | True -CSR Enrollment | True -Pfx Enrollment | True - - -**CSC TrustedSecure DV Wildcard, Multiple Names - Enrollment Fields** +**CSC TrustedSecure Domain Validated UC Certificate - Enrollment Fields** NAME | DATA TYPE | VALUES -----|--------------|----------------- diff --git a/cscglobal-caplugin/CSCGlobalCAPlugin.cs b/cscglobal-caplugin/CSCGlobalCAPlugin.cs index e42fc86..6480bb4 100644 --- a/cscglobal-caplugin/CSCGlobalCAPlugin.cs +++ b/cscglobal-caplugin/CSCGlobalCAPlugin.cs @@ -434,11 +434,7 @@ private async Task SyncCertificates(BlockingCollection b if (certStatus == Convert.ToInt32(EndEntityStatus.GENERATED) || certStatus == Convert.ToInt32(EndEntityStatus.REVOKED)) { - // CSC's list/sync API returns the certificate's current product name directly - // (e.g. "CSC TrustedSecure DV"), which already matches the canonical Product ID - // used for enrollment - no reverse lookup needed, same as the CSC-name-is-truth - // approach taken on feature/ev-ov-dv-multiname-certs. - var productId = currentResponseItem.CertificateType ?? "CscGlobal"; + var productId = _requestManager.MapCertificateTypeToProductId(currentResponseItem.CertificateType); Logger.LogTrace("SyncCertificates: UUID={Uuid} qualifies for sync. CertificateType='{CertType}' -> ProductId='{ProductId}'", currentResponseItem.Uuid, currentResponseItem.CertificateType ?? "(null)", productId); @@ -499,6 +495,16 @@ private async Task SyncCertificates(BlockingCollection b Logger.LogTrace("SyncCertificates: fileContent was empty for UUID={Uuid}, skipping.", currentResponseItem.Uuid); skippedCount++; } + else + { + Logger.LogTrace("SyncCertificates: fileContent was empty for UUID={Uuid}, skipping.", currentResponseItem.Uuid); + skippedCount++; + } + } + else + { + Logger.LogTrace("SyncCertificates: UUID={Uuid} status {Status} not eligible for sync, skipping.", currentResponseItem.Uuid, certStatus); + skippedCount++; } else { @@ -637,13 +643,17 @@ public async Task Enroll(string csr, string subject, Dictionar flow.Step("CheckPriorCertSN", () => { - // Command sends this key as "PriorCertSN" (proper case) - a prior version of this - // check gated on "priorcertsn" (lowercase) instead, which Command never actually - // sends, so this block silently never ran and PriorCertSN was never populated. - if (productInfo.ProductParameters.ContainsKey("PriorCertSN")) + if (productInfo.ProductParameters.ContainsKey("priorcertsn")) { - priorSn = productInfo.ProductParameters["PriorCertSN"]; - Logger.LogDebug("Enroll: Prior cert SN: '{PriorSn}'", priorSn ?? "(null)"); + if (productInfo.ProductParameters.ContainsKey("PriorCertSN")) + { + priorSn = productInfo.ProductParameters["PriorCertSN"]; + Logger.LogDebug("Enroll: Prior cert SN: '{PriorSn}'", priorSn ?? "(null)"); + } + else + { + Logger.LogWarning("Enroll: 'priorcertsn' key exists but 'PriorCertSN' (case-sensitive) not found."); + } } }, string.IsNullOrEmpty(priorSn) ? "none" : $"SN={priorSn}"); @@ -688,8 +698,8 @@ await flow.StepAsync("SubmitRegistrationToCSC", async () => flow.Fail("ParseResponse", "API returned null"); return new EnrollmentResult { - Status = (int)EndEntityStatus.FAILED, - StatusMessage = $"{flow.GetSummary()}\n\nEnrollment failed: CSC API returned a null response." + Status = 30, + StatusMessage = "Enrollment failed: CSC API returned a null response." }; } flow.Step("ParseResponse", $"error={enrollmentResponse.RegistrationError != null}"); @@ -700,15 +710,18 @@ await flow.StepAsync("SubmitRegistrationToCSC", async () => flow.Fail("RejectExpiredRenew", "PriorCertSN present on New enrollment"); return new EnrollmentResult { - Status = (int)EndEntityStatus.FAILED, - StatusMessage = $"{flow.GetSummary()}\n\nYou cannot renew an expired cert please perform an new enrollment." + Status = 30, + StatusMessage = "You cannot renew an expired cert please perform an new enrollment." }; } var enrollResult = _requestManager.GetEnrollmentResult(enrollmentResponse); flow.Step("MapResult", $"Status={enrollResult?.Status}, ID={enrollResult?.CARequestID ?? "(null)"}"); - await flow.StepAsync("DcvAutoPublish", () => TryPublishCnameDcvAsync(productInfo, enrollResult)); + await flow.StepAsync("PublishCnameDcv", async () => + { + await TryPublishCnameDcvAsync(productInfo, enrollResult); + }); EnrollmentResult? newPolled = null; await flow.StepAsync("PollForIssuance", async () => @@ -718,12 +731,10 @@ await flow.StepAsync("PollForIssuance", async () => if (newPolled != null) { flow.Step("PollResult", "issued during poll window"); - AttachFlowSummary(newPolled, flow); Logger.MethodExit(LogLevel.Debug); return newPolled; } - AttachFlowSummary(enrollResult, flow); Logger.MethodExit(LogLevel.Debug); return enrollResult; @@ -735,8 +746,8 @@ await flow.StepAsync("PollForIssuance", async () => flow.Fail("ValidatePriorSN", "PriorCertSN is empty"); return new EnrollmentResult { - Status = (int)EndEntityStatus.FAILED, - StatusMessage = $"{flow.GetSummary()}\n\nRenewOrReissue failed: PriorCertSN is required but was not provided." + Status = 30, + StatusMessage = "RenewOrReissue failed: PriorCertSN is required but was not provided." }; } @@ -751,8 +762,8 @@ await flow.StepAsync("LookupOrderId", async () => flow.Fail("ValidateOrderId", $"no order found for SN={priorSn}"); return new EnrollmentResult { - Status = (int)EndEntityStatus.FAILED, - StatusMessage = $"{flow.GetSummary()}\n\nRenewOrReissue failed: could not find order ID for serial number '{priorSn}'." + Status = 30, + StatusMessage = $"RenewOrReissue failed: could not find order ID for serial number '{priorSn}'." }; } @@ -761,8 +772,8 @@ await flow.StepAsync("LookupOrderId", async () => flow.Fail("ValidateOrderId", $"order_id too short ({order_id.Length} chars)"); return new EnrollmentResult { - Status = (int)EndEntityStatus.FAILED, - StatusMessage = $"{flow.GetSummary()}\n\nRenewOrReissue failed: order ID '{order_id}' is too short to extract a UUID." + Status = 30, + StatusMessage = $"RenewOrReissue failed: order ID '{order_id}' is too short to extract a UUID." }; } flow.Step("ValidateOrderId", $"orderId={order_id}"); @@ -810,8 +821,8 @@ await flow.StepAsync("FetchLiveCertForDecision", async () => flow.Fail("FallbackExpiryCheck", fallbackEx.Message); return new EnrollmentResult { - Status = (int)EndEntityStatus.FAILED, - StatusMessage = $"{flow.GetSummary()}\n\nRenewOrReissue failed: unable to determine renewal status for order '{order_id}'. {fallbackEx.Message}" + Status = 30, + StatusMessage = $"RenewOrReissue failed: unable to determine renewal status for order '{order_id}'. {fallbackEx.Message}" }; } } @@ -834,8 +845,8 @@ await flow.StepAsync("LookupRenewalUUID", async () => flow.Fail("ValidateRenewalUUID", "could not resolve PriorCertSN"); return new EnrollmentResult { - Status = (int)EndEntityStatus.FAILED, - StatusMessage = $"{flow.GetSummary()}\n\nRenewal failed: could not resolve prior certificate serial number to a request ID." + Status = 30, + StatusMessage = "Renewal failed: could not resolve prior certificate serial number to a request ID." }; } flow.Step("ValidateRenewalUUID", $"uuid={uUId}"); @@ -859,8 +870,8 @@ await flow.StepAsync("SubmitRenewalToCSC", async () => flow.Fail("ParseRenewalResponse", "API returned null"); return new EnrollmentResult { - Status = (int)EndEntityStatus.FAILED, - StatusMessage = $"{flow.GetSummary()}\n\nRenewal failed: CSC API returned a null response." + Status = 30, + StatusMessage = "Renewal failed: CSC API returned a null response." }; } @@ -873,7 +884,6 @@ await flow.StepAsync("PollForIssuance", async () => { renewPolled = await TryPollForIssuedCertAsync(renewResult?.CARequestID); }); - AttachFlowSummary(renewPolled ?? renewResult, flow); Logger.MethodExit(LogLevel.Debug); return renewPolled ?? renewResult; } @@ -881,9 +891,9 @@ await flow.StepAsync("PollForIssuance", async () => flow.Fail("MissingEnrollmentParams", "Applicant Last Name not present — one-click renew unavailable"); return new EnrollmentResult { - Status = (int)EndEntityStatus.FAILED, + Status = 30, StatusMessage = - $"{flow.GetSummary()}\n\nOne click Renew Is Not Available for this Certificate Type. Use the configure button instead." + "One click Renew Is Not Available for this Certificate Type. Use the configure button instead." }; } @@ -902,8 +912,8 @@ await flow.StepAsync("LookupReissueRequestId", async () => flow.Fail("ValidateReissueRequestId", "could not resolve PriorCertSN"); return new EnrollmentResult { - Status = (int)EndEntityStatus.FAILED, - StatusMessage = $"{flow.GetSummary()}\n\nReissue failed: could not resolve prior certificate serial number to a request ID." + Status = 30, + StatusMessage = "Reissue failed: could not resolve prior certificate serial number to a request ID." }; } @@ -912,8 +922,8 @@ await flow.StepAsync("LookupReissueRequestId", async () => flow.Fail("ValidateReissueRequestId", $"requestid too short ({requestid.Length} chars)"); return new EnrollmentResult { - Status = (int)EndEntityStatus.FAILED, - StatusMessage = $"{flow.GetSummary()}\n\nReissue failed: request ID '{requestid}' is too short to extract a UUID." + Status = 30, + StatusMessage = $"Reissue failed: request ID '{requestid}' is too short to extract a UUID." }; } @@ -939,8 +949,8 @@ await flow.StepAsync("SubmitReissueToCSC", async () => flow.Fail("ParseReissueResponse", "API returned null"); return new EnrollmentResult { - Status = (int)EndEntityStatus.FAILED, - StatusMessage = $"{flow.GetSummary()}\n\nReissue failed: CSC API returned a null response." + Status = 30, + StatusMessage = "Reissue failed: CSC API returned a null response." }; } @@ -953,7 +963,6 @@ await flow.StepAsync("PollForIssuance", async () => { reissuePolled = await TryPollForIssuedCertAsync(reissueResult?.CARequestID); }); - AttachFlowSummary(reissuePolled ?? reissueResult, flow); Logger.MethodExit(LogLevel.Debug); return reissuePolled ?? reissueResult; } @@ -961,7 +970,7 @@ await flow.StepAsync("PollForIssuance", async () => flow.Fail("MissingEnrollmentParams", "Applicant Last Name not present — one-click reissue unavailable"); return new EnrollmentResult { - Status = (int)EndEntityStatus.FAILED, + Status = 30, StatusMessage = $"{flow.GetSummary()}\n\nOne click Reissue Is Not Available for this Certificate Type. Use the configure button instead." }; @@ -970,8 +979,8 @@ await flow.StepAsync("PollForIssuance", async () => flow.Fail("UnhandledType", $"enrollmentType={enrollmentType}"); return new EnrollmentResult { - Status = (int)EndEntityStatus.FAILED, - StatusMessage = $"{flow.GetSummary()}\n\nEnroll failed: unhandled enrollment type '{enrollmentType}'." + Status = 30, + StatusMessage = $"Enroll failed: unhandled enrollment type '{enrollmentType}'." }; } } @@ -982,8 +991,8 @@ await flow.StepAsync("PollForIssuance", async () => Logger.LogError(inner, "Enroll: AggregateException during {EnrollmentType}: {Message}", enrollmentType, inner?.Message ?? ae.Message); return new EnrollmentResult { - Status = (int)EndEntityStatus.FAILED, - StatusMessage = $"{flow.GetSummary()}\n\nEnrollment failed with error: {inner?.Message ?? ae.Message}" + Status = 30, + StatusMessage = $"Enrollment failed with error: {inner?.Message ?? ae.Message}" }; } catch (Exception ex) @@ -992,40 +1001,12 @@ await flow.StepAsync("PollForIssuance", async () => Logger.LogError(ex, "Enroll: unhandled exception during {EnrollmentType}: {Message}", enrollmentType, ex.Message); return new EnrollmentResult { - Status = (int)EndEntityStatus.FAILED, - StatusMessage = $"{flow.GetSummary()}\n\nEnrollment failed with error: {ex.Message}" + Status = 30, + StatusMessage = $"Enrollment failed with error: {ex.Message}" }; } } - // CSC Global business-level failures (e.g. "Open order in progress") come back from - // RequestManager as a terse StatusMessage with no context on what the plugin actually did - // before hitting that error - prepend the flow's step-by-step summary so the message shown - // to the requester in Command explains what ran, not just how it ended. Command's enrollment - // UI does not surface StatusMessage on a successful/pending result at all - only - // EnrollmentContext is - so attach the summary there instead, as its own entry alongside - // whatever DCV instructions came back. Must be called after TryPublishCnameDcvAsync, which - // treats every EnrollmentContext entry as a candidate DNS record to publish - calling this - // first would make it try to publish "Flow Summary" as a CNAME. - private static void AttachFlowSummary(EnrollmentResult? result, FlowLogger flow) - { - if (result == null) - return; - - if (result.Status == (int)EndEntityStatus.FAILED) - { - result.StatusMessage = $"{flow.GetSummary()}\n\n{result.StatusMessage}"; - return; - } - - // One EnrollmentContext entry per step (rather than one entry holding the whole - // multi-line summary) so Command's bulleted rendering shows a readable line per step - // instead of a single run-on blob. - result.EnrollmentContext ??= new Dictionary(); - foreach (var entry in flow.GetSummaryEntries()) - result.EnrollmentContext[entry.Key] = entry.Value; - } - //done public async Task Ping() { @@ -1120,14 +1101,17 @@ public async Task ValidateProductInfo(EnrollmentProductInfo productInfo, throw new ArgumentException("ProductID cannot be null or empty.", nameof(productInfo)); } - if (!_requestManager.IsKnownProductId(productInfo.ProductID)) + var certType = ProductIDs.productIds.Find(x => + x.Equals(productInfo.ProductID, StringComparison.InvariantCultureIgnoreCase)); + + if (certType == null) { Logger.LogError("ValidateProductInfo: cannot find product ID '{ProductId}'. Known IDs: [{KnownIds}]", productInfo.ProductID, string.Join(", ", ProductIDs.productIds)); throw new ArgumentException($"Cannot find {productInfo.ProductID}", "ProductId"); } - Logger.LogInformation("Validated {ProductId} configured for AnyGateway", productInfo.ProductID); + Logger.LogInformation("Validated {CertType} configured for AnyGateway", certType); Logger.MethodExit(LogLevel.Debug); } @@ -1395,21 +1379,19 @@ record = null; /// resolves for its domain. No-op if the factory wasn't injected, the cert isn't using CNAME /// validation, or the response contains no CNAME details. Failures are logged but never thrown — /// manual publishing remains a fallback so the enrollment result is still returned to Keyfactor. - /// Returns a short description of what happened (published/skipped/why), surfaced as the - /// flow step's detail so a no-op for non-CNAME methods doesn't look unexplained. /// - private async Task TryPublishCnameDcvAsync(EnrollmentProductInfo productInfo, EnrollmentResult? enrollResult) + private async Task TryPublishCnameDcvAsync(EnrollmentProductInfo productInfo, EnrollmentResult? enrollResult) { if (_validatorFactory == null) { Logger.LogTrace("TryPublishCnameDcvAsync: no IDomainValidatorFactory was injected, skipping auto-publish."); - return "skipped - no DNS validator factory injected"; + return; } if (enrollResult?.EnrollmentContext == null || enrollResult.EnrollmentContext.Count == 0) { Logger.LogTrace("TryPublishCnameDcvAsync: no CNAME entries in EnrollmentContext, skipping."); - return "skipped - no DCV entries returned by CSC"; + return; } var dcvMethod = productInfo?.ProductParameters != null @@ -1421,7 +1403,7 @@ private async Task TryPublishCnameDcvAsync(EnrollmentProductInfo product !string.Equals(dcvMethod, "CNAME", StringComparison.OrdinalIgnoreCase)) { Logger.LogTrace("TryPublishCnameDcvAsync: DCV method '{Method}' is not CNAME, skipping auto-publish.", dcvMethod ?? "(null)"); - return $"skipped - DCV method is '{dcvMethod ?? "(none)"}', not CNAME"; + return; } Logger.LogInformation( @@ -1507,9 +1489,6 @@ private async Task TryPublishCnameDcvAsync(EnrollmentProductInfo product Logger.LogInformation( "TryPublishCnameDcvAsync: complete. Published={Published}, Failed={Failed}, Unresolved={Unresolved}", successCount, failCount, unresolvedCount); - - return $"published {successCount}, failed {failCount}, unresolved {unresolvedCount} " + - $"of {enrollResult.EnrollmentContext.Count} CNAME record(s)"; } //Trying to fix leaf extraction diff --git a/cscglobal-caplugin/Client/CscGlobalClient.cs b/cscglobal-caplugin/Client/CscGlobalClient.cs index 7a5b722..6ce5c3e 100644 --- a/cscglobal-caplugin/Client/CscGlobalClient.cs +++ b/cscglobal-caplugin/Client/CscGlobalClient.cs @@ -22,12 +22,6 @@ public sealed class CscGlobalClient : ICscGlobalClient private readonly ILogger Logger; public CscGlobalClient(IAnyCAPluginConfigProvider config) : this(config, null) - { - } - - // internal so the test project can supply a fake HttpMessageHandler via - // InternalsVisibleTo, instead of the client making real HTTP calls in unit tests. - internal CscGlobalClient(IAnyCAPluginConfigProvider config, HttpMessageHandler? handler) { Logger = LogHandler.GetClassLogger(); @@ -74,7 +68,7 @@ internal CscGlobalClient(IAnyCAPluginConfigProvider config, HttpMessageHandler? } Logger.LogTrace("CscGlobalClient: BearerToken is present (length={Length}).", Authorization.Length); - RestClient = ConfigureRestClient(handler); + RestClient = ConfigureRestClient(); Logger.LogTrace("CscGlobalClient: RestClient configured successfully."); } else diff --git a/cscglobal-caplugin/Constants.cs b/cscglobal-caplugin/Constants.cs index be33065..dc10866 100644 --- a/cscglobal-caplugin/Constants.cs +++ b/cscglobal-caplugin/Constants.cs @@ -23,16 +23,13 @@ public class ProductIDs { public static List productIds = new List() { - "CSC TrustedSecure OV", - "CSC TrustedSecure OV Wildcard", - "CSC TrustedSecure OV, Multiple Names", - "CSC TrustedSecure EV", - "CSC TrustedSecure DV", - "CSC TrustedSecure DV Wildcard", - "CSC TrustedSecure DV, Multiple Names", - "CSC TrustedSecure EV, Multiple Names", - "CSC TrustedSecure OV Wildcard, Multiple Names", - "CSC TrustedSecure DV Wildcard, Multiple Names" + "CSC TrustedSecure Premium Certificate", + "CSC TrustedSecure EV Certificate", + "CSC TrustedSecure UC Certificate", + "CSC TrustedSecure Premium Wildcard Certificate", + "CSC TrustedSecure Domain Validated SSL", + "CSC Trusted Secure Domain Validated Wildcard SSL", + "CSC Trusted Secure Domain Validated UC Certificate" }; } diff --git a/cscglobal-caplugin/FlowLogger.cs b/cscglobal-caplugin/FlowLogger.cs index fb90961..5696fcd 100644 --- a/cscglobal-caplugin/FlowLogger.cs +++ b/cscglobal-caplugin/FlowLogger.cs @@ -124,42 +124,6 @@ public async Task StepAsync(string name, Func action, string d return this; } - /// - /// Record an async step whose own return value becomes the step's detail - unlike the - /// parameter on the other overload (which is evaluated before - /// the action runs and so can't reflect anything the action decided), this reflects what - /// actually happened during execution (e.g. why a conditional step was a no-op). - /// - public async Task StepAsync(string name, Func> action) - { - var sw = Stopwatch.StartNew(); - var step = new FlowStep { Name = name }; - try - { - _logger.LogTrace(" [{FlowName}] {StepName} ...", _flowName, name); - var detail = await action(); - sw.Stop(); - step.Status = FlowStepStatus.Success; - step.ElapsedMs = sw.ElapsedMilliseconds; - step.Detail = detail; - AddStep(step); - _logger.LogTrace(" [{FlowName}] {StepName} ... OK ({Elapsed}ms){Detail}", - _flowName, name, sw.ElapsedMilliseconds, detail != null ? $" {detail}" : ""); - } - catch (Exception ex) - { - sw.Stop(); - step.Status = FlowStepStatus.Failed; - step.ElapsedMs = sw.ElapsedMilliseconds; - step.Detail = ex.Message; - AddStep(step); - _logger.LogTrace(" [{FlowName}] {StepName} ... FAILED ({Elapsed}ms): {Error}", - _flowName, name, sw.ElapsedMilliseconds, ex.Message); - throw; - } - return this; - } - /// Record a failed step without throwing. public FlowLogger Fail(string name, string reason = null) { @@ -255,86 +219,6 @@ private string RenderFlow() return sb.ToString(); } - /// - /// Concise step-by-step summary suitable for surfacing in a user-facing failure message - /// (unlike 's ASCII-art tree, which is meant for Trace logs only). - /// - public string GetSummary() - { - var hasFailures = _steps.Any(s => s.Status == FlowStepStatus.Failed) || - _steps.SelectMany(s => s.Children).Any(c => c.Status == FlowStepStatus.Failed); - var overallStatus = hasFailures ? "FAILED" : "OK"; - - var sb = new StringBuilder(); - sb.AppendLine($"Flow: {_flowName} [{overallStatus}] Total: {_totalTimer.ElapsedMilliseconds}ms"); - sb.AppendLine("----------------------------------------"); - - foreach (var step in _steps) - { - AppendSummaryLine(sb, step, 0); - foreach (var child in step.Children) - AppendSummaryLine(sb, child, 1); - } - - return sb.ToString(); - } - - private static void AppendSummaryLine(StringBuilder sb, FlowStep step, int indentLevel) - { - var indent = new string(' ', indentLevel * 2); - var icon = GetStatusIcon(step.Status); - var elapsed = step.ElapsedMs > 0 ? $" ({step.ElapsedMs}ms)" : ""; - var detail = !string.IsNullOrEmpty(step.Detail) ? $" - {step.Detail}" : ""; - sb.AppendLine($"{indent}{icon} {step.Name}{elapsed}{detail}"); - } - - /// - /// Same information as , but as one entry per step instead of a - /// single multi-line block. Intended for callers (e.g. EnrollmentResult.EnrollmentContext) - /// whose rendering surface displays a dictionary as a bulleted list and doesn't respect - /// embedded newlines - each step becomes its own bullet instead of one run-on line. - /// - public Dictionary GetSummaryEntries() - { - var allSteps = _steps.Concat(_steps.SelectMany(s => s.Children)).ToList(); - var hasFailures = allSteps.Any(s => s.Status == FlowStepStatus.Failed); - var overallStatus = hasFailures ? "FAILED" : "OK"; - var succeeded = allSteps.Count(s => s.Status == FlowStepStatus.Success); - var failed = allSteps.Count(s => s.Status == FlowStepStatus.Failed); - var skipped = allSteps.Count(s => s.Status == FlowStepStatus.Skipped); - - var entries = new Dictionary - { - [$"Flow: {_flowName}"] = - $"[{overallStatus}] {_totalTimer.ElapsedMilliseconds}ms total - " + - $"{allSteps.Count} steps ({succeeded} ok, {failed} failed, {skipped} skipped)" - }; - - var stepNumber = 0; - foreach (var step in _steps) - { - stepNumber++; - AddSummaryEntry(entries, step, stepNumber, false); - - foreach (var child in step.Children) - { - stepNumber++; - AddSummaryEntry(entries, child, stepNumber, true); - } - } - - return entries; - } - - private static void AddSummaryEntry(Dictionary entries, FlowStep step, int stepNumber, bool indent) - { - var icon = GetStatusIcon(step.Status); - var time = step.ElapsedMs > 0 ? $" ({step.ElapsedMs}ms)" : ""; - var detail = !string.IsNullOrEmpty(step.Detail) ? $" - {step.Detail}" : ""; - var prefix = indent ? " " : ""; - entries[$"Flow Step {stepNumber:00}: {prefix}{step.Name}"] = $"{icon}{time}{detail}"; - } - private static string GetStatusIcon(FlowStepStatus status) { return status switch diff --git a/cscglobal-caplugin/RequestManager.cs b/cscglobal-caplugin/RequestManager.cs index 281e317..f65d589 100644 --- a/cscglobal-caplugin/RequestManager.cs +++ b/cscglobal-caplugin/RequestManager.cs @@ -388,39 +388,55 @@ public RegistrationRequest GetRegistrationRequest(EnrollmentProductInfo productI }; } - // Maps Keyfactor product ID -> CSC API certificate type code (used for enrollment requests). - // Each product has an entry for its current (1.2.0+) canonical name and its pre-1.2.0 legacy - // name, so existing Certificate Templates in Command using the old names keep working. - // Types 7/8/9 are new in 1.2.0 and have no legacy name. + // Maps Keyfactor product ID -> CSC API certificate type code (used for enrollment requests) private static readonly Dictionary ProductIdToCodeMap = new(StringComparer.OrdinalIgnoreCase) { - ["CSC TrustedSecure OV"] = "0", ["CSC TrustedSecure Premium Certificate"] = "0", - ["CSC TrustedSecure OV Wildcard"] = "1", ["CSC TrustedSecure Premium Wildcard Certificate"] = "1", - ["CSC TrustedSecure OV, Multiple Names"] = "2", ["CSC TrustedSecure UC Certificate"] = "2", - ["CSC TrustedSecure EV"] = "3", ["CSC TrustedSecure EV Certificate"] = "3", - ["CSC TrustedSecure DV"] = "4", ["CSC TrustedSecure Domain Validated SSL"] = "4", ["CSC Trusted Secure Domain Validated SSL"] = "4", - ["CSC TrustedSecure DV Wildcard"] = "5", ["CSC Trusted Secure Domain Validated Wildcard SSL"] = "5", - ["CSC TrustedSecure DV, Multiple Names"] = "6", ["CSC Trusted Secure Domain Validated UC Certificate"] = "6", - ["CSC TrustedSecure EV, Multiple Names"] = "7", - ["CSC TrustedSecure OV Wildcard, Multiple Names"] = "8", - ["CSC TrustedSecure DV Wildcard, Multiple Names"] = "9", }; - /// - /// True if productId resolves to a known CSC certificate type - either its canonical - /// (1.2.0+) name or a pre-1.2.0 legacy name. Used by ValidateProductInfo so the list of - /// accepted names can't drift out of sync with what GetCertificateType actually resolves. - /// - public bool IsKnownProductId(string productId) => - !string.IsNullOrEmpty(productId) && ProductIdToCodeMap.ContainsKey(productId); + // Reverse map: CSC API certificateType string -> Keyfactor product ID (used during sync) + // Note: CSC naming is inconsistent — first 4 types use "TrustedSecure" (no space), + // DV Wildcard and DV UC use "Trusted Secure" (with space), + // but CSC API returns DV SSL as "CSC Trusted Secure Domain Validated SSL" (with space) + // while the product ID is "CSC TrustedSecure Domain Validated SSL" (no space). + private static readonly Dictionary CodeToProductIdMap = new(StringComparer.OrdinalIgnoreCase) + { + // Premium + ["0"] = "CSC TrustedSecure Premium Certificate", + ["CSC TrustedSecure Premium Certificate"] = "CSC TrustedSecure Premium Certificate", + ["CSC Trusted Secure Premium Certificate"] = "CSC TrustedSecure Premium Certificate", + // Premium Wildcard + ["1"] = "CSC TrustedSecure Premium Wildcard Certificate", + ["CSC TrustedSecure Premium Wildcard Certificate"] = "CSC TrustedSecure Premium Wildcard Certificate", + ["CSC Trusted Secure Premium Wildcard Certificate"] = "CSC TrustedSecure Premium Wildcard Certificate", + // UC + ["2"] = "CSC TrustedSecure UC Certificate", + ["CSC TrustedSecure UC Certificate"] = "CSC TrustedSecure UC Certificate", + ["CSC Trusted Secure UC Certificate"] = "CSC TrustedSecure UC Certificate", + // EV + ["3"] = "CSC TrustedSecure EV Certificate", + ["CSC TrustedSecure EV Certificate"] = "CSC TrustedSecure EV Certificate", + ["CSC Trusted Secure EV Certificate"] = "CSC TrustedSecure EV Certificate", + // DV SSL — product ID has no space, but CSC API returns with space + ["4"] = "CSC TrustedSecure Domain Validated SSL", + ["CSC TrustedSecure Domain Validated SSL"] = "CSC TrustedSecure Domain Validated SSL", + ["CSC Trusted Secure Domain Validated SSL"] = "CSC TrustedSecure Domain Validated SSL", + // DV Wildcard — product ID has space (matches CSC API) + ["5"] = "CSC Trusted Secure Domain Validated Wildcard SSL", + ["CSC Trusted Secure Domain Validated Wildcard SSL"] = "CSC Trusted Secure Domain Validated Wildcard SSL", + ["CSC TrustedSecure Domain Validated Wildcard SSL"] = "CSC Trusted Secure Domain Validated Wildcard SSL", + // DV UC — product ID has space (matches CSC API) + ["6"] = "CSC Trusted Secure Domain Validated UC Certificate", + ["CSC Trusted Secure Domain Validated UC Certificate"] = "CSC Trusted Secure Domain Validated UC Certificate", + ["CSC TrustedSecure Domain Validated UC Certificate"] = "CSC Trusted Secure Domain Validated UC Certificate", + }; private string GetCertificateType(string productId) { @@ -545,14 +561,6 @@ private List GetSubjectAlternativeNames(EnrollmentProduc ? productInfo.ProductParameters["Domain Control Validation Method"] : null; - // CSC Global rejects the request if any subjectAlternativeNames entry is missing - // domainControlValidation, so every SAN below must resolve to a non-null value - falling - // back to the primary CN's DCV email when no per-domain override matches. - var commonNameValidationEmail = productInfo?.ProductParameters != null - && productInfo.ProductParameters.ContainsKey(EnrollmentConfigConstants.CnDcvEmail) - ? productInfo.ProductParameters[EnrollmentConfigConstants.CnDcvEmail] - : null; - Logger.LogTrace("GetSubjectAlternativeNames: processing {Count} DNS names, methodType='{MethodType}'", dnsNames.Length, methodType ?? "(null)"); @@ -571,19 +579,18 @@ private List GetSubjectAlternativeNames(EnrollmentProduc if (!string.IsNullOrEmpty(methodType) && methodType.ToUpper() == "EMAIL") { - var emailsRaw = productInfo.ProductParameters.ContainsKey(EnrollmentConfigConstants.AdditionalSansCommaSeparatedDcvEmails) - ? productInfo.ProductParameters[EnrollmentConfigConstants.AdditionalSansCommaSeparatedDcvEmails] + var emailsRaw = productInfo.ProductParameters.ContainsKey("Addtl Sans Comma Separated DVC Emails") + ? productInfo.ProductParameters["Addtl Sans Comma Separated DVC Emails"] : null; var emailAddresses = !string.IsNullOrEmpty(emailsRaw) ? emailsRaw.Split(',') : Array.Empty(); Logger.LogTrace("GetSubjectAlternativeNames: EMAIL validation, {Count} email addresses for domain='{Domain}'", emailAddresses.Length, domainName); - san.DomainControlValidation = GetDomainControlValidation(methodType, emailAddresses, domainName) - ?? GetDomainControlValidation(methodType, commonNameValidationEmail); + san.DomainControlValidation = GetDomainControlValidation(methodType, emailAddresses, domainName); } else { Logger.LogTrace("GetSubjectAlternativeNames: CNAME/other validation for domain='{Domain}'", domainName); - san.DomainControlValidation = GetDomainControlValidation(methodType, commonNameValidationEmail); + san.DomainControlValidation = GetDomainControlValidation(methodType, ""); } subjectNameList.Add(san); diff --git a/integration-manifest.json b/integration-manifest.json index 978dacc..e58e50f 100644 --- a/integration-manifest.json +++ b/integration-manifest.json @@ -5,6 +5,7 @@ "status": "production", "support_level": "kf-supported", "link_github": true, + "status": "production", "update_catalog": true, "description": "CSCGlobal CAPlugin for the AnyCA REST Gateway framework", "gateway_framework": "26.2.0", @@ -93,16 +94,13 @@ } ], "product_ids": [ - "CSC TrustedSecure OV", - "CSC TrustedSecure OV Wildcard", - "CSC TrustedSecure OV, Multiple Names", - "CSC TrustedSecure EV", - "CSC TrustedSecure DV", - "CSC TrustedSecure DV Wildcard", - "CSC TrustedSecure DV, Multiple Names", - "CSC TrustedSecure EV, Multiple Names", - "CSC TrustedSecure OV Wildcard, Multiple Names", - "CSC TrustedSecure DV Wildcard, Multiple Names" + "CSC TrustedSecure Premium Certificate", + "CSC TrustedSecure EV Certificate", + "CSC TrustedSecure UC Certificate", + "CSC TrustedSecure Premium Wildcard Certificate", + "CSC TrustedSecure Domain Validated SSL", + "CSC Trusted Secure Domain Validated Wildcard SSL", + "CSC Trusted Secure Domain Validated UC Certificate" ] } }