From cea20a4a6bd976c87e1edd83c73c9064fe9ba90b Mon Sep 17 00:00:00 2001 From: David Galey Date: Thu, 18 Sep 2025 13:23:43 -0400 Subject: [PATCH 1/6] add some sync logging --- sectigo-scm-caplugin/SectigoCAPlugin.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/sectigo-scm-caplugin/SectigoCAPlugin.cs b/sectigo-scm-caplugin/SectigoCAPlugin.cs index c88038b..e5cdd8c 100644 --- a/sectigo-scm-caplugin/SectigoCAPlugin.cs +++ b/sectigo-scm-caplugin/SectigoCAPlugin.cs @@ -520,7 +520,7 @@ public async Task Synchronize(BlockingCollection blockin _logger.LogError($"Synchronize task failed with the following message: {producerTask.Exception.Flatten().Message}"); throw producerTask.Exception.Flatten(); } - + _logger.LogTrace($"SYNC TRACE ({certToAdd.Id}): Processing record {certToAdd.Id}"); string dbCertId = null; int dbCertStatus = -1; //serial number is blank on certs that have not been issued (awaiting approval) @@ -566,23 +566,26 @@ public async Task Synchronize(BlockingCollection blockin } //Download to get full certdata required for sync process - _logger.LogTrace($"Attempt to Pickup Certificate {certToAdd.CommonName} (ID: {certToAdd.Id})"); + _logger.LogTrace($"SYNC TRACE ({certToAdd.Id}): Attempt to Pickup Certificate {certToAdd.CommonName}"); var certdataApi = Task.Run(async () => await client.PickupCertificate(certToAdd.Id, certToAdd.CommonName)).Result; if (certdataApi != null) certData = Convert.ToBase64String(certdataApi.GetRawCertData()); + if (certToAdd == null || String.IsNullOrEmpty(certToAdd.SerialNumber) || String.IsNullOrEmpty(certToAdd.CommonName) || String.IsNullOrEmpty(certData)) { _logger.LogDebug($"Certificate Data unavailable for {certToAdd.CommonName} (ID: {certToAdd.Id}). Skipping "); continue; } + _logger.LogTrace($"SYNC TRACE ({certToAdd.Id}): Retrieved cert data: {certData}"); string prodId = ""; try { - _logger.LogTrace($"Cert ID: {certToAdd.Id.ToString()}"); - _logger.LogTrace($"Sync ID: {syncReqId.ToString()}"); - _logger.LogTrace($"Product ID: {certToAdd.CertType.id.ToString()}"); + _logger.LogTrace($"SYNC TRACE ({certToAdd.Id}): Cert ID: {certToAdd.Id.ToString()}"); + _logger.LogTrace($"SYNC TRACE ({certToAdd.Id}): Sync ID: {syncReqId.ToString()}"); + _logger.LogTrace($"SYNC TRACE ({certToAdd.Id}): Product ID: {certToAdd.CertType.id.ToString()}"); + _logger.LogTrace($"SYNC TRACE ({certToAdd.Id}): Status: {certToAdd.status}"); prodId = certToAdd.CertType.id.ToString(); } catch { } From 731592248ed311dc253f00522fefe211e8d657af Mon Sep 17 00:00:00 2001 From: David Galey Date: Wed, 11 Feb 2026 02:56:42 -0500 Subject: [PATCH 2/6] allow manual lifetime specification --- CHANGELOG.md | 3 +++ sectigo-scm-caplugin/Constants.cs | 1 + sectigo-scm-caplugin/SectigoCAPlugin.cs | 33 ++++++++++++++++++++++--- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b8f82c..093894b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,3 +14,6 @@ Fix for JSON serialization of revocation 1.1.0 Add support for using the cert upload feature to upload auth certs Switch to .NET 8 + +1.1.1 +Allow for manual specification of enrollment term length \ No newline at end of file diff --git a/sectigo-scm-caplugin/Constants.cs b/sectigo-scm-caplugin/Constants.cs index 014be4c..47f7af0 100644 --- a/sectigo-scm-caplugin/Constants.cs +++ b/sectigo-scm-caplugin/Constants.cs @@ -27,6 +27,7 @@ public class Config public const string MULTIDOMAIN = "MultiDomain"; public const string ORGANIZATION = "Organization"; public const string DEPARTMENT = "Department"; + public const string LIFETIME = "Lifetime"; } //headers for API client diff --git a/sectigo-scm-caplugin/SectigoCAPlugin.cs b/sectigo-scm-caplugin/SectigoCAPlugin.cs index c88038b..ac056cf 100644 --- a/sectigo-scm-caplugin/SectigoCAPlugin.cs +++ b/sectigo-scm-caplugin/SectigoCAPlugin.cs @@ -196,6 +196,26 @@ public async Task Enroll(string csr, string subject, Dictionar _logger.LogTrace($"Found {enrollmentProfile.name} profile for enroll request"); } + int termLength; + var profileTerms = Task.Run(async () => await GetProfileTerms(int.Parse(productInfo.ProductID))).Result; + if (!string.IsNullOrEmpty(productInfo.ProductParameters[Constants.Config.LIFETIME])) + { + var tempTerm = int.Parse(productInfo.ProductParameters[Constants.Config.LIFETIME]); + if (profileTerms.Contains(tempTerm)) + { + termLength = tempTerm; + } + else + { + _logger.LogError($"Specified term length of {tempTerm} does not match available terms for product ID {productInfo.ProductID}. Available terms are {string.Join(",", profileTerms)}"); + throw new Exception($"Specified term length of {tempTerm} does not match available terms for product ID {productInfo.ProductID}"); + } + } + else + { + termLength = profileTerms[0]; + } + int sslId; string priorSn = string.Empty; Certificate newCert = null; @@ -216,7 +236,7 @@ public async Task Enroll(string csr, string subject, Dictionar { csr = csr, orgId = requestOrgId, - term = Task.Run(async () => await GetProfileTerm(int.Parse(productInfo.ProductID))).Result, + term = termLength, certType = enrollmentProfile.id, //External requestor is expected to be an email. Use config to pull the enrollment field or send blank //sectigo will default to the account (API account) making the request. @@ -431,6 +451,13 @@ public Dictionary GetTemplateParameterAnnotations() Hidden = false, DefaultValue = "", Type = "String" + }, + [Constants.Config.LIFETIME] = new PropertyConfigInfo() + { + Comments = "OPTIONAL: The term length (in days) to use for enrollment. If not provided, the default is the first value available in the profile definition in your Sectigo account.", + Hidden = false, + DefaultValue = "", + Type = "String" } }; } @@ -674,11 +701,11 @@ private async Task GetOrganizationAsync(string orgName) return orgList.Organizations.Where(x => x.name.ToLower().Equals(orgName.ToLower())).FirstOrDefault(); } - private async Task GetProfileTerm(int profileId) + private async Task> GetProfileTerms(int profileId) { var client = SectigoClient.InitializeClient(_config, _certificateResolver); var profileList = await client.ListSslProfiles(); - return profileList.SslProfiles.Where(x => x.id == profileId).FirstOrDefault().terms[0]; + return profileList.SslProfiles.Where(x => x.id == profileId).FirstOrDefault().terms.ToList(); } private async Task GetProfile(int profileId) From 60686a2bf31164f3dd76f59d047c6f2262724c47 Mon Sep 17 00:00:00 2001 From: Keyfactor Date: Wed, 11 Feb 2026 07:58:37 +0000 Subject: [PATCH 3/6] Update generated docs --- README.md | 1 + integration-manifest.json | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/README.md b/README.md index 4b14544..1a4ca46 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ In addition, for the admin account you plan to use, make sure it has the API adm * **MultiDomain** - This flag lets Keyfactor know if the certificate can contain multiple domain names. Depending on the setting, the SAN entries of the request will change to support Sectigo requirements. * **Organization** - If the organization name is provided here, the Sectigo gateway will use that organization name in requests instead of whatever is in the O= field in the request subject. * **Department** - If your Sectigo account is using department-level products, put the appropriate department name here. Previously, this was alternatively supplied in the OU= subject field, which is now deprecated. + * **Lifetime** - OPTIONAL: The term length (in days) to use for enrollment. If not provided, the default is the first value available in the profile definition in your Sectigo account. diff --git a/integration-manifest.json b/integration-manifest.json index aba2711..8665460 100644 --- a/integration-manifest.json +++ b/integration-manifest.json @@ -78,6 +78,10 @@ { "name": "Department", "description": "If your Sectigo account is using department-level products, put the appropriate department name here. Previously, this was alternatively supplied in the OU= subject field, which is now deprecated." + }, + { + "name": "Lifetime", + "description": "OPTIONAL: The term length (in days) to use for enrollment. If not provided, the default is the first value available in the profile definition in your Sectigo account." } ] } From d474db2e1be824d063c2d7163050ecba61b62934 Mon Sep 17 00:00:00 2001 From: David Galey Date: Mon, 2 Mar 2026 13:25:10 -0500 Subject: [PATCH 4/6] rethrow existing exceptions --- sectigo-scm-caplugin/SectigoCAPlugin.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sectigo-scm-caplugin/SectigoCAPlugin.cs b/sectigo-scm-caplugin/SectigoCAPlugin.cs index 0ce86de..c21ea3d 100644 --- a/sectigo-scm-caplugin/SectigoCAPlugin.cs +++ b/sectigo-scm-caplugin/SectigoCAPlugin.cs @@ -265,7 +265,7 @@ public async Task Enroll(string csr, string subject, Dictionar catch (HttpRequestException httpEx) { _logger.LogError($"Enrollment Failed due to a HTTP error: {httpEx.Message}"); - throw new Exception(httpEx.Message); + throw; } catch (Exception ex) { @@ -277,7 +277,7 @@ public async Task Enroll(string csr, string subject, Dictionar retError = ex.InnerException.Message; } - throw new Exception(retError); + throw; } } From 51a46de3c59382afde21cc7bfccef4517dca2b92 Mon Sep 17 00:00:00 2001 From: David Galey Date: Mon, 2 Mar 2026 13:30:14 -0500 Subject: [PATCH 5/6] handle empty sync --- sectigo-scm-caplugin/Client/SectigoClient.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sectigo-scm-caplugin/Client/SectigoClient.cs b/sectigo-scm-caplugin/Client/SectigoClient.cs index f7da84f..66e7caf 100644 --- a/sectigo-scm-caplugin/Client/SectigoClient.cs +++ b/sectigo-scm-caplugin/Client/SectigoClient.cs @@ -61,7 +61,10 @@ public async Task CertificateListProducer(BlockingCollection certs, Logger.LogInformation($"Request Certificates at Position {certIndex} with Page Size {pageSize}"); certificatePageToProcess = await PageCertificates(certIndex, pageSize, filter); Logger.LogDebug($"Found {certificatePageToProcess.Count} certificate to process"); - + if (certificatePageToProcess.Count == 0) + { + return; + } //Processing Loop will add and retry adding to queue until all certificates have been processed for a page batchCount = 0; blockedCount = 0; From e318ae628dfd42845bb0e703fc1a75212ac01005 Mon Sep 17 00:00:00 2001 From: David Galey Date: Mon, 2 Mar 2026 13:33:14 -0500 Subject: [PATCH 6/6] changelog --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 093894b..509317c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,4 +16,8 @@ Add support for using the cert upload feature to upload auth certs Switch to .NET 8 1.1.1 -Allow for manual specification of enrollment term length \ No newline at end of file +Allow for manual specification of enrollment term length + +1.1.2 +Add Lifetime parameter to allow for manual specification of cert validity +Bugfix - Properly handle syncs of 0 records \ No newline at end of file