diff --git a/.github/workflows/keyfactor-bootstrap-workflow.yml b/.github/workflows/keyfactor-bootstrap-workflow.yml new file mode 100644 index 0000000..6d8de53 --- /dev/null +++ b/.github/workflows/keyfactor-bootstrap-workflow.yml @@ -0,0 +1,19 @@ +name: Keyfactor Bootstrap Workflow + +on: + workflow_dispatch: + pull_request: + types: [opened, closed, synchronize, edited, reopened] + push: + create: + branches: + - 'release-*.*' + +jobs: + call-starter-workflow: + uses: keyfactor/actions/.github/workflows/starter.yml@v2 + secrets: + token: ${{ secrets.V2BUILDTOKEN}} + APPROVE_README_PUSH: ${{ secrets.APPROVE_README_PUSH}} + gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }} + gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }} diff --git a/.github/workflows/keyfactor-starter-workflow.yml b/.github/workflows/keyfactor-starter-workflow.yml deleted file mode 100644 index e781bef..0000000 --- a/.github/workflows/keyfactor-starter-workflow.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Starter Workflow -on: [workflow_dispatch, push, pull_request] - -jobs: - call-create-github-release-workflow: - uses: Keyfactor/actions/.github/workflows/github-release.yml@main - - get-manifest-properties: - runs-on: windows-latest - outputs: - update_catalog: ${{ steps.read-json.outputs.prop }} - steps: - - uses: actions/checkout@v3 - - name: Read json - id: read-json - shell: pwsh - run: | - $json = Get-Content integration-manifest.json | ConvertFrom-Json - echo "::set-output name=prop::$(echo $json.update_catalog)" - - call-dotnet-build-and-release-workflow: - needs: [call-create-github-release-workflow] - uses: Keyfactor/actions/.github/workflows/dotnet-build-and-release.yml@main - with: - release_version: ${{ needs.call-create-github-release-workflow.outputs.release_version }} - release_url: ${{ needs.call-create-github-release-workflow.outputs.release_url }} - release_dir: src/SectigoCAProxy/bin/Release - secrets: - token: ${{ secrets.PRIVATE_PACKAGE_ACCESS }} - - call-generate-readme-workflow: - if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' - uses: Keyfactor/actions/.github/workflows/generate-readme.yml@main - secrets: - token: ${{ secrets.APPROVE_README_PUSH }} - - call-update-catalog-workflow: - needs: get-manifest-properties - if: needs.get-manifest-properties.outputs.update_catalog == 'True' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') - uses: Keyfactor/actions/.github/workflows/update-catalog.yml@main - secrets: - token: ${{ secrets.SDK_SYNC_PAT }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 878e97e..797eb74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,4 +15,7 @@ # 1.0.10 * Handle change to Sectigo API for organization/department lookups # 1.1.0 -* Allow organization name to be provided in the template section of the config \ No newline at end of file +* Allow organization name to be provided in the template section of the config +# 1.2.0 +* Allow for blank CN to be provided +* Fixes for Certificate Authentication \ No newline at end of file diff --git a/README.md b/README.md index c0aa825..fa9efb6 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,22 @@ + # Sectigo Certificate Manager Sectigo Certificate Manager is a private certificate authority designed for enterprise use. The Sectigo Gateway enables the following certificate authority management functions via Keyfactor Command: Enrollment of new certificates, Revocation of existing certificates, and Synchronization of previously issued certificates. #### Integration status: Production - Ready for use in production environments. +## About the Keyfactor AnyCA Gateway DCOM Connector -## About the Keyfactor AnyGateway CA Connector - -This repository contains an AnyGateway CA Connector, which is a plugin to the Keyfactor AnyGateway. AnyGateway CA Connectors allow Keyfactor Command to be used for inventory, issuance, and revocation of certificates from a third-party certificate authority. - +This repository contains an AnyCA Gateway Connector, which is a plugin to the Keyfactor AnyGateway. AnyCA Gateway Connectors allow Keyfactor Command to be used for inventory, issuance, and revocation of certificates from a third-party certificate authority. ## Support for Sectigo Certificate Manager -Sectigo Certificate Manager +Sectigo Certificate Manager is supported by Keyfactor for Keyfactor customers. If you have a support issue, please open a support ticket via the Keyfactor Support Portal at https://support.keyfactor.com ###### To report a problem or suggest a new feature, use the **[Issues](../../issues)** tab. If you want to contribute actual bug fixes or proposed enhancements, use the **[Pull requests](../../pulls)** tab. +--- + --- @@ -23,6 +24,16 @@ Sectigo Certificate Manager +## Keyfactor AnyCA Gateway Framework Supported +The Keyfactor gateway framework implements common logic shared across various gateway implementations and handles communication with Keyfactor Command. The gateway framework hosts gateway implementations or plugins that understand how to communicate with specific CAs. This allows you to integrate your third-party CAs with Keyfactor Command such that they behave in a manner similar to the CAs natively supported by Keyfactor Command. + + + + +This gateway extension was compiled against version of the AnyCA Gateway DCOM Framework. You will need at least this version of the framework Installed. If you have a later AnyGateway Framework Installed you will probably need to add binding redirects in the CAProxyServer.exe.config file to make things work properly. + + +[Keyfactor CAGateway Install Guide](https://software.keyfactor.com/Guides/AnyGateway_Generic/Content/AnyGateway/Introduction.htm) @@ -52,7 +63,7 @@ Additional information about [Sectigo Certificate Manager](https://sectigo.com/k * Code Signing ## Compatibility -This AnyGateway is designed to be used with version 21.3.2 of the Keyfactor AnyGateway Framework +This AnyGateway is designed to be used with version 21.3.2 or newer of the Keyfactor AnyGateway DCOM Framework ## Prerequisites @@ -60,6 +71,10 @@ This AnyGateway is designed to be used with version 21.3.2 of the Keyfactor AnyG In order to enroll for certificates the Keyfactor Command server must trust the trust chain. Once you create your Root and/or Subordinate CA, make sure to import the certificate chain into the Command Server certificate store +### Sectigo Settings +For each Organization/Department you plan on using through the gateway, in your Sectigo portal, go to that Organization, select Certificate Settings -> SSL Certificates, and check the "Enable Web/REST API" checkbox. +In addition, for the admin account you plan to use, make sure it has the API admin type selected in the portal. + ### Migration In the event that a system is being upgraded from the Legacy Sectigo CA Gateway (19.4 or older), a migration from the legacy database format to the AnyGateway format will be required. diff --git a/integration-manifest.json b/integration-manifest.json index 601955f..83af624 100644 --- a/integration-manifest.json +++ b/integration-manifest.json @@ -3,6 +3,9 @@ "integration_type": "ca-gateway", "name": "Sectigo Certificate Manager", "status": "production", + "update_catalog": true, "link_github": true, + "release_dir": "src/SectigoCAProxy/bin/Release", + "support_level": "kf-supported", "description": "Sectigo Certificate Manager is a private certificate authority designed for enterprise use. The Sectigo Gateway enables the following certificate authority management functions via Keyfactor Command: Enrollment of new certificates, Revocation of existing certificates, and Synchronization of previously issued certificates." } diff --git a/readme_source.md b/readme_source.md index 92dc82b..462d352 100644 --- a/readme_source.md +++ b/readme_source.md @@ -21,7 +21,7 @@ Additional information about [Sectigo Certificate Manager](https://sectigo.com/k * Code Signing ## Compatibility -This AnyGateway is designed to be used with version 21.3.2 of the Keyfactor AnyGateway Framework +This AnyGateway is designed to be used with version 21.3.2 or newer of the Keyfactor AnyGateway DCOM Framework ## Prerequisites @@ -29,6 +29,10 @@ This AnyGateway is designed to be used with version 21.3.2 of the Keyfactor AnyG In order to enroll for certificates the Keyfactor Command server must trust the trust chain. Once you create your Root and/or Subordinate CA, make sure to import the certificate chain into the Command Server certificate store +### Sectigo Settings +For each Organization/Department you plan on using through the gateway, in your Sectigo portal, go to that Organization, select Certificate Settings -> SSL Certificates, and check the "Enable Web/REST API" checkbox. +In addition, for the admin account you plan to use, make sure it has the API admin type selected in the portal. + ### Migration In the event that a system is being upgraded from the Legacy Sectigo CA Gateway (19.4 or older), a migration from the legacy database format to the AnyGateway format will be required. diff --git a/src/SectigoCAProxy/Client/SectigoApiClient.cs b/src/SectigoCAProxy/Client/SectigoApiClient.cs index 420633f..907f09e 100644 --- a/src/SectigoCAProxy/Client/SectigoApiClient.cs +++ b/src/SectigoCAProxy/Client/SectigoApiClient.cs @@ -32,7 +32,7 @@ public SectigoApiClient(HttpClient httpClient) public async Task GetCertificate(int sslId) { - var response = await RestClient.GetAsync($"/api/ssl/v1/{sslId}"); + var response = await RestClient.GetAsync($"api/ssl/v1/{sslId}"); return await ProcessResponse(response); } @@ -197,7 +197,7 @@ public async Task ListPersons(int orgId) public async Task ListCustomFields() { - var response = await RestClient.GetAsync("/api/ssl/v1/customFields"); + var response = await RestClient.GetAsync("api/ssl/v1/customFields"); return new ListCustomFieldsResponse { CustomFields = await ProcessResponse>(response) }; } @@ -209,7 +209,7 @@ public async Task ListSslProfiles(int? orgId = null) urlSuffix = $"?organizationId={orgId}"; } - var response = await RestClient.GetAsync($"/api/ssl/v1/types{urlSuffix}"); + var response = await RestClient.GetAsync($"api/ssl/v1/types{urlSuffix}"); return new ListSslProfilesResponse { SslProfiles = await ProcessResponse>(response) }; } @@ -223,7 +223,7 @@ public async Task Enroll(EnrollRequest request) { try { - var response = await RestClient.PostAsJsonAsync("/api/ssl/v1/enroll", request); + var response = await RestClient.PostAsJsonAsync("api/ssl/v1/enroll", request); var enrollResponse = await ProcessResponse(response); return enrollResponse.sslId; @@ -246,7 +246,7 @@ public async Task Renew(int sslId) { try { - var response = await RestClient.PostAsJsonAsync($"/api/ssl/v1/renewById/{sslId}", ""); + var response = await RestClient.PostAsJsonAsync($"api/ssl/v1/renewById/{sslId}", ""); var renewResponse = await ProcessResponse(response); return renewResponse.sslId; @@ -267,7 +267,7 @@ public async Task Renew(int sslId) public async Task PickupCertificate(int sslId, string subject) { - var response = await RestClient.GetAsync($"/api/ssl/v1/collect/{sslId}/x509CO"); + var response = await RestClient.GetAsync($"api/ssl/v1/collect/{sslId}/x509CO"); if (response.IsSuccessStatusCode && response.Content.Headers.ContentLength > 0) { @@ -283,7 +283,7 @@ public async Task PickupCertificate(int sslId, string subject) public async Task Reissue(ReissueRequest request, int sslId) { - var response = await RestClient.PostAsJsonAsync($"/api/ssl/v1/replace/{sslId}", request); + var response = await RestClient.PostAsJsonAsync($"api/ssl/v1/replace/{sslId}", request); response.EnsureSuccessStatusCode(); } diff --git a/src/SectigoCAProxy/SectigoCAProxy.cs b/src/SectigoCAProxy/SectigoCAProxy.cs index b9fe4c3..7948149 100644 --- a/src/SectigoCAProxy/SectigoCAProxy.cs +++ b/src/SectigoCAProxy/SectigoCAProxy.cs @@ -246,8 +246,11 @@ public override EnrollmentResult Enroll(ICertificateDataReader certificateDataRe { Logger.Debug("Parse Subject for Common Name, Organization, and Org Unit"); - string commonName = ParseSubject(subject, "CN="); - Logger.Trace($"Common Name: {commonName}"); + string commonName = ParseSubject(subject, "CN=", false); + if (!string.IsNullOrEmpty(commonName)) + { + Logger.Trace($"Common Name: {commonName}"); + } string orgStr = null; if (productInfo.ProductParameters.ContainsKey("Organization")) @@ -390,7 +393,7 @@ public override EnrollmentResult Enroll(ICertificateDataReader certificateDataRe comments = $"CERTIFICATE_REQUESTOR: {productInfo.ProductParameters["Keyfactor-Requester"]}"//this is how the current gateway passes this data }; - Logger.Debug($"Submit {enrollmentType} request for {subject}"); + Logger.Debug($"Submit {enrollmentType} request"); sslId = Task.Run(async () => await Client.Enroll(request)).Result; newCert = Task.Run(async () => await Client.GetCertificate(sslId)).Result; Logger.Debug($"Enrolled for Certificate {newCert.CommonName} (ID: {newCert.Id}) | Status: {newCert.status}. Attempt to Pickup Certificate."); @@ -635,16 +638,21 @@ private static string ParseSanList(Dictionary san, bool multiD if (!multiDomain) { - if (allSans.Contains(commonName) && allSans.Count() > 1) + if (!string.IsNullOrEmpty(commonName) && allSans.Contains(commonName) && allSans.Count() > 1) { List sans = allSans.ToList(); sans.Remove(commonName); sanList = string.Join(",", sans.ToArray()); } + else + { + List sans = allSans.ToList(); + sanList = string.Join(",", sans.ToArray()); + } } else { - if (allSans.Contains(commonName)) + if (!string.IsNullOrEmpty(commonName) && allSans.Contains(commonName)) { List sans = allSans.ToList(); sans.Remove(commonName); @@ -792,9 +800,15 @@ private static SectigoApiClient InitializeRestClient(Dictionary webRequestHandler.ClientCertificates.Add(authCert); } + string apiEndpoint = localConfig.ApiEndpoint; + if (!apiEndpoint.EndsWith("/")) + { + apiEndpoint += "/"; + } + HttpClient restClient = new HttpClient(webRequestHandler) { - BaseAddress = new Uri(localConfig.ApiEndpoint) + BaseAddress = new Uri(apiEndpoint) }; restClient.DefaultRequestHeaders.Add(Constants.CUSTOMER_URI_KEY, localConfig.CustomerUri);