Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
a1c23f6
200 day renewal fixes
bhillkeyfactor Apr 7, 2026
ea18abf
Update generated docs
Apr 7, 2026
a742e03
Improved logging .net 10 support
bhillkeyfactor Apr 8, 2026
fba58ac
Removed Template Sync Logic
bhillkeyfactor Apr 8, 2026
58177df
Update generated docs
Apr 8, 2026
b8d74db
fixed template mapping issue
bhillkeyfactor Apr 8, 2026
ddf1330
Merge branch '200dayfixes' of https://github.com/Keyfactor/cscglobal-…
bhillkeyfactor Apr 8, 2026
0b6f8a5
product fixes
bhillkeyfactor Apr 8, 2026
445eb57
Update generated docs
Apr 8, 2026
8f098a9
fixed renewal issue
bhillkeyfactor Apr 8, 2026
77b42cd
documentation fixes
bhillkeyfactor Apr 8, 2026
204367d
Update generated docs
Apr 8, 2026
ff2f4c5
DNS Changes
bhillkeyfactor May 22, 2026
b138670
Update generated docs
May 22, 2026
84d48a8
dns code updates
bhillkeyfactor May 26, 2026
448f167
Update generated docs
May 26, 2026
eb39fcf
change type
bhillkeyfactor May 26, 2026
fe49f01
Merge branch 'feature/dns-plugins' of https://github.com/Keyfactor/cs…
bhillkeyfactor May 26, 2026
3ba367b
Update generated docs
May 26, 2026
65454d0
fixed mismatch
bhillkeyfactor May 26, 2026
0127331
Use 'cname' validation type for CSC CNAME DCV
bhillkeyfactor May 26, 2026
5ea38b4
Update generated docs
May 26, 2026
73f7ddd
added polling to grab cert
bhillkeyfactor May 27, 2026
95e9337
Update generated docs
May 27, 2026
cfcc069
Merge branch 'release-2.0' into feature/dns-plugins
bhillkeyfactor Jul 14, 2026
b016c6c
Update integration-manifest.json
bhillkeyfactor Jul 14, 2026
cc97504
Update CSCGlobalCAPlugin.csproj
bhillkeyfactor Jul 14, 2026
4d9ff86
Update CSCGlobalCAPlugin.csproj
bhillkeyfactor Jul 14, 2026
78f95a3
Update keyfactor-bootstrap-workflow-v3.yml
bhillkeyfactor Jul 14, 2026
979379b
docs: auto-generate README and documentation [skip ci]
github-actions[bot] Jul 14, 2026
a1275f6
Add Enabled CA connection flag
bhillkeyfactor Jul 14, 2026
c034180
docs: auto-generate README and documentation [skip ci]
github-actions[bot] Jul 14, 2026
07cfb6f
Port product rename, new certificate types, and bug fixes from featur…
bhillkeyfactor Sep 21, 2026
ef36792
docs: auto-generate README and documentation [skip ci]
github-actions[bot] Sep 21, 2026
ed6b3ad
Raise unit test coverage to ~95% line coverage
bhillkeyfactor Sep 21, 2026
82d3228
Raise unit test branch coverage from ~75% to ~80%
bhillkeyfactor Sep 21, 2026
1123ee7
Sync ProductID directly from CSC's certificateType, drop reverse-mapp…
bhillkeyfactor Sep 21, 2026
1ee7a73
Fix Enroll failures reporting INPROCESS instead of FAILED, surface fl…
bhillkeyfactor Sep 22, 2026
d6fdefa
Attach flow summary to EnrollmentContext on success, not just StatusM…
bhillkeyfactor Sep 22, 2026
32a132c
Render flow summary as one bullet per step instead of one blob
bhillkeyfactor Sep 22, 2026
44bc7ff
Rename PublishCnameDcv step and explain why it's a no-op for non-CNAM…
bhillkeyfactor Sep 22, 2026
37d17cc
Merge release-2.0 into feature/port-1.2-content-to-2.0
bhillkeyfactor Oct 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions cscglobal-caplugin.Tests/CSCGlobalCAPluginTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -758,10 +758,19 @@ public void GetTemplateParameterAnnotations_ReturnsExpectedKeys()
}

[Fact]
public void GetProductIds_ReturnsCanonicalTenProducts()
public void GetProductIds_ReturnsCanonicalAndLegacyNames()
{
var plugin = MakePlugin();
Assert.Equal(10, plugin.GetProductIds().Count);
var ids = plugin.GetProductIds();

// Command uses this list to auto-resolve a CA for a template when none is explicitly
// selected, so pre-1.2.0 legacy-named templates need their names advertised here too,
// not just the 10 canonical names.
Assert.Equal(17, ids.Count);
Assert.Contains("CSC TrustedSecure OV", ids);
Assert.Contains("CSC TrustedSecure DV Wildcard, Multiple Names", ids);
Assert.Contains("CSC TrustedSecure EV Certificate", ids);
Assert.Contains("CSC TrustedSecure Domain Validated SSL", ids);
}

// ---------------------------------------------------------------------
Expand Down
173 changes: 96 additions & 77 deletions cscglobal-caplugin/CSCGlobalCAPlugin.cs

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion cscglobal-caplugin/Client/CscGlobalClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ 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<CscGlobalClient>();

Expand Down Expand Up @@ -68,7 +74,7 @@ public CscGlobalClient(IAnyCAPluginConfigProvider config) : this(config, null)
}
Logger.LogTrace("CscGlobalClient: BearerToken is present (length={Length}).", Authorization.Length);

RestClient = ConfigureRestClient();
RestClient = ConfigureRestClient(handler);
Logger.LogTrace("CscGlobalClient: RestClient configured successfully.");
}
else
Expand Down
31 changes: 24 additions & 7 deletions cscglobal-caplugin/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,31 @@ public class ProductIDs
{
public static List<String> productIds = new List<string>()
{
"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"
"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"
};

// Pre-1.2.0 template names. Existing Certificate Templates in Command may still
// reference these, so they're accepted as aliases for their canonical replacement.
public static Dictionary<string, string> legacyProductIdAliases =
new(StringComparer.InvariantCultureIgnoreCase)
{
["CSC TrustedSecure Premium Certificate"] = "CSC TrustedSecure OV",
["CSC TrustedSecure Premium Wildcard Certificate"] = "CSC TrustedSecure OV Wildcard",
["CSC TrustedSecure UC Certificate"] = "CSC TrustedSecure OV, Multiple Names",
["CSC TrustedSecure EV Certificate"] = "CSC TrustedSecure EV",
["CSC TrustedSecure Domain Validated SSL"] = "CSC TrustedSecure DV",
["CSC Trusted Secure Domain Validated Wildcard SSL"] = "CSC TrustedSecure DV Wildcard",
["CSC Trusted Secure Domain Validated UC Certificate"] = "CSC TrustedSecure DV, Multiple Names"
};
}

public class EnrollmentConfigConstants
Expand Down
116 changes: 116 additions & 0 deletions cscglobal-caplugin/FlowLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
}

/// <summary>Record a completed step.</summary>
public FlowLogger Step(string name, string detail = null)

Check warning on line 58 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 58 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 58 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 58 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.
{
var step = new FlowStep { Name = name, Status = FlowStepStatus.Success, Detail = detail };
AddStep(step);
Expand All @@ -65,7 +65,7 @@
}

/// <summary>Record a step that executes an action and times it.</summary>
public FlowLogger Step(string name, Action action, string detail = null)

Check warning on line 68 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 68 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 68 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 68 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.
{
var sw = Stopwatch.StartNew();
var step = new FlowStep { Name = name, Detail = detail };
Expand Down Expand Up @@ -95,7 +95,7 @@
}

/// <summary>Record an async step that executes and times it.</summary>
public async Task<FlowLogger> StepAsync(string name, Func<Task> action, string detail = null)

Check warning on line 98 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 98 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 98 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 98 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.
{
var sw = Stopwatch.StartNew();
var step = new FlowStep { Name = name, Detail = detail };
Expand Down Expand Up @@ -124,8 +124,44 @@
return this;
}

/// <summary>
/// Record an async step whose own return value becomes the step's detail - unlike the
/// <paramref name="detail" /> 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).
/// </summary>
public async Task<FlowLogger> StepAsync(string name, Func<Task<string>> 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;
}

/// <summary>Record a failed step without throwing.</summary>
public FlowLogger Fail(string name, string reason = null)

Check warning on line 164 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 164 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 164 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 164 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.
{
var step = new FlowStep { Name = name, Status = FlowStepStatus.Failed, Detail = reason };
AddStep(step);
Expand All @@ -135,7 +171,7 @@
}

/// <summary>Record a skipped step.</summary>
public FlowLogger Skip(string name, string reason = null)

Check warning on line 174 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 174 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 174 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.

Check warning on line 174 in cscglobal-caplugin/FlowLogger.cs

View workflow job for this annotation

GitHub Actions / call-starter-workflow / call-dotnet-build-and-release-workflow / dotnet-build-and-release

Cannot convert null literal to non-nullable reference type.
{
var step = new FlowStep { Name = name, Status = FlowStepStatus.Skipped, Detail = reason };
AddStep(step);
Expand Down Expand Up @@ -219,6 +255,86 @@
return sb.ToString();
}

/// <summary>
/// Concise step-by-step summary suitable for surfacing in a user-facing failure message
/// (unlike <see cref="RenderFlow" />'s ASCII-art tree, which is meant for Trace logs only).
/// </summary>
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}");
}

/// <summary>
/// Same information as <see cref="GetSummary" />, 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.
/// </summary>
public Dictionary<string, string> 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<string, string>
{
[$"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<string, string> 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
Expand Down
91 changes: 34 additions & 57 deletions cscglobal-caplugin/RequestManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -388,55 +388,39 @@ public RegistrationRequest GetRegistrationRequest(EnrollmentProductInfo productI
};
}

// Maps Keyfactor product ID -> CSC API certificate type code (used for enrollment requests)
// 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.
private static readonly Dictionary<string, string> 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",
};

// 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<string, string> 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",
};
/// <summary>
/// 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.
/// </summary>
public bool IsKnownProductId(string productId) =>
!string.IsNullOrEmpty(productId) && ProductIdToCodeMap.ContainsKey(productId);

private string GetCertificateType(string productId)
{
Expand All @@ -450,22 +434,6 @@ private string GetCertificateType(string productId)
return "-1";
}

/// <summary>
/// Maps a CSC API certificateType value back to a Keyfactor product ID.
/// Handles numeric codes, descriptive strings, and passthrough of already-correct values.
/// </summary>
public string MapCertificateTypeToProductId(string cscCertificateType)
{
Logger.LogTrace("MapCertificateTypeToProductId: input='{CscCertType}'", cscCertificateType ?? "(null)");
if (!string.IsNullOrEmpty(cscCertificateType) && CodeToProductIdMap.TryGetValue(cscCertificateType, out var productId))
{
Logger.LogTrace("MapCertificateTypeToProductId: mapped '{CscCertType}' -> '{ProductId}'", cscCertificateType, productId);
return productId;
}
Logger.LogWarning("MapCertificateTypeToProductId: no mapping for '{CscCertType}', passing through as-is.", cscCertificateType);
return cscCertificateType ?? "CscGlobal";
}

public Notifications GetNotifications(EnrollmentProductInfo productInfo)
{
Logger.LogTrace("GetNotifications: building notifications.");
Expand Down Expand Up @@ -561,6 +529,14 @@ private List<SubjectAlternativeName> 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)");

Expand All @@ -579,18 +555,19 @@ private List<SubjectAlternativeName> GetSubjectAlternativeNames(EnrollmentProduc

if (!string.IsNullOrEmpty(methodType) && methodType.ToUpper() == "EMAIL")
{
var emailsRaw = productInfo.ProductParameters.ContainsKey("Addtl Sans Comma Separated DVC Emails")
? productInfo.ProductParameters["Addtl Sans Comma Separated DVC Emails"]
var emailsRaw = productInfo.ProductParameters.ContainsKey(EnrollmentConfigConstants.AdditionalSansCommaSeparatedDcvEmails)
? productInfo.ProductParameters[EnrollmentConfigConstants.AdditionalSansCommaSeparatedDcvEmails]
: null;
var emailAddresses = !string.IsNullOrEmpty(emailsRaw) ? emailsRaw.Split(',') : Array.Empty<string>();
Logger.LogTrace("GetSubjectAlternativeNames: EMAIL validation, {Count} email addresses for domain='{Domain}'",
emailAddresses.Length, domainName);
san.DomainControlValidation = GetDomainControlValidation(methodType, emailAddresses, domainName);
san.DomainControlValidation = GetDomainControlValidation(methodType, emailAddresses, domainName)
?? GetDomainControlValidation(methodType, commonNameValidationEmail);
}
else
{
Logger.LogTrace("GetSubjectAlternativeNames: CNAME/other validation for domain='{Domain}'", domainName);
san.DomainControlValidation = GetDomainControlValidation(methodType, "");
san.DomainControlValidation = GetDomainControlValidation(methodType, commonNameValidationEmail);
}

subjectNameList.Add(san);
Expand Down
12 changes: 11 additions & 1 deletion integration-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@
}
],
"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",
Expand All @@ -104,4 +114,4 @@
]
}
}
}
}
Loading