diff --git a/.github/workflows/keyfactor-bootstrap-workflow.yml b/.github/workflows/keyfactor-bootstrap-workflow.yml index 6d8de53..4487162 100644 --- a/.github/workflows/keyfactor-bootstrap-workflow.yml +++ b/.github/workflows/keyfactor-bootstrap-workflow.yml @@ -14,6 +14,6 @@ jobs: uses: keyfactor/actions/.github/workflows/starter.yml@v2 secrets: token: ${{ secrets.V2BUILDTOKEN}} - APPROVE_README_PUSH: ${{ secrets.APPROVE_README_PUSH}} + APPROVE_README_PUSH: ${{ secrets.V2BUILDTOKEN }} gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }} gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }} diff --git a/CHANGELOG.md b/CHANGELOG.md index ceebe07..f8a7151 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,4 +28,6 @@ # 1.2.3 * Fix for JSON serialization for revocation # 1.2.4 -* Fix for null reference exception on sync with 0 records \ No newline at end of file +* Fix for null reference exception on sync with 0 records +# 1.2.5 +* Allow for manually specifying lifetime length for enrollment \ No newline at end of file diff --git a/README.md b/README.md index 9b4db6a..66ec11e 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,8 @@ REQUIRED. This flag lets Keyfactor know if the certificate can contain multiple OPTIONAL. If the organization name is provided as a parameter here, the Sectigo gateway will use that organization name in requests, instead of the O field in the subject. * ```Department``` OPTIONAL. If your Sectigo account is using department-level products, put the appropriate department name here. Previous versions of the Sectigo gateway read this value from the OU field of the subject, 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. ```json "Templates": { @@ -114,7 +116,8 @@ OPTIONAL. If your Sectigo account is using department-level products, put the ap "Parameters": { "MultiDomain": "false", "Organization": "Organization Name", - "Department": "Department Name" + "Department": "Department Name", + "Lifetime": "199" } } } diff --git a/readme_source.md b/readme_source.md index 462d352..0aaaa26 100644 --- a/readme_source.md +++ b/readme_source.md @@ -64,6 +64,8 @@ REQUIRED. This flag lets Keyfactor know if the certificate can contain multiple OPTIONAL. If the organization name is provided as a parameter here, the Sectigo gateway will use that organization name in requests, instead of the O field in the subject. * ```Department``` OPTIONAL. If your Sectigo account is using department-level products, put the appropriate department name here. Previous versions of the Sectigo gateway read this value from the OU field of the subject, 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. ```json "Templates": { @@ -72,7 +74,8 @@ OPTIONAL. If your Sectigo account is using department-level products, put the ap "Parameters": { "MultiDomain": "false", "Organization": "Organization Name", - "Department": "Department Name" + "Department": "Department Name", + "Lifetime": "199" } } } diff --git a/src/SectigoCAProxy/SectigoCAProxy.cs b/src/SectigoCAProxy/SectigoCAProxy.cs index ee3ab08..5aaddeb 100644 --- a/src/SectigoCAProxy/SectigoCAProxy.cs +++ b/src/SectigoCAProxy/SectigoCAProxy.cs @@ -89,6 +89,7 @@ public override void Synchronize(ICertificateDataReader certificateDataReader, throw producerTask.Exception.Flatten(); } + Logger.Trace($"SYNC TRACE ({certToAdd.Id}): Processing record {certToAdd.Id}"); CAConnectorCertificate dbCert = null; //serial number is blank on certs that have not been issued (awaiting approval) if (!String.IsNullOrEmpty(certToAdd.SerialNumber)) @@ -127,7 +128,7 @@ public override void Synchronize(ICertificateDataReader certificateDataReader, else { //No certificate in the DB by SN. Need to download to get full certdata required for sync process - Logger.Trace($"Attempt to Pickup Certificate {certToAdd.CommonName} (ID: {certToAdd.Id})"); + Logger.Trace($"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()); @@ -138,12 +139,14 @@ public override void Synchronize(ICertificateDataReader certificateDataReader, Logger.Debug($"Certificate Data unavailable for {certToAdd.CommonName} (ID: {certToAdd.Id}). Skipping "); continue; } + Logger.Trace($"SYNC TRACE ({certToAdd.Id}): Retrieved cert data: {certData}"); string prodId = ""; try { - Logger.Trace($"Cert ID: {certToAdd.Id.ToString()}"); - Logger.Trace($"Sync ID: {syncReqId.ToString()}"); - Logger.Trace($"Product ID: {certToAdd.CertType.id.ToString()}"); + Logger.Trace($"SYNC TRACE ({certToAdd.Id}): Cert ID: {certToAdd.Id.ToString()}"); + Logger.Trace($"SYNC TRACE ({certToAdd.Id}): Sync ID: {syncReqId.ToString()}"); + Logger.Trace($"SYNC TRACE ({certToAdd.Id}): Product ID: {certToAdd.CertType.id.ToString()}"); + Logger.Trace($"SYNC TRACE ({certToAdd.Id}): Status: {certToAdd.status}"); prodId = certToAdd.CertType.id.ToString(); } catch { } @@ -392,6 +395,26 @@ public override EnrollmentResult Enroll(ICertificateDataReader certificateDataRe Logger.Trace($"Found {enrollmentProfile.name} profile for enroll request"); } + int termLength; + var profileTerms = Task.Run(async () => await GetProfileTerms(int.Parse(productInfo.ProductID))).Result; + if (productInfo.ProductParameters.ContainsKey("Lifetime") && !string.IsNullOrEmpty(productInfo.ProductParameters["Lifetime"])) + { + var tempTerm = int.Parse(productInfo.ProductParameters["Lifetime"]); + if (profileTerms.Contains(tempTerm)) + { + termLength = tempTerm; + } + else + { + Logger.Error($"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; @@ -410,7 +433,7 @@ public override EnrollmentResult Enroll(ICertificateDataReader certificateDataRe { 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. @@ -642,10 +665,10 @@ 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 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)