diff --git a/.claude/settings.json b/.claude/settings.json
deleted file mode 100644
index c64f0fd..0000000
--- a/.claude/settings.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "permissions": {
- "allow": [
- "Bash(git fetch:*)",
- "Bash(git checkout:*)"
- ]
- }
-}
diff --git a/.github/workflows/keyfactor-bootstrap-workflow-v3.yml b/.github/workflows/keyfactor-bootstrap-workflow-v3.yml
index 0f3d3ae..042ba5a 100644
--- a/.github/workflows/keyfactor-bootstrap-workflow-v3.yml
+++ b/.github/workflows/keyfactor-bootstrap-workflow-v3.yml
@@ -11,17 +11,10 @@ on:
jobs:
call-starter-workflow:
- uses: keyfactor/actions/.github/workflows/starter.yml@v5
- with:
- command_token_url: ${{ vars.COMMAND_TOKEN_URL }}
- command_hostname: ${{ vars.COMMAND_HOSTNAME }}
- command_base_api_path: ${{ vars.COMMAND_API_PATH }}
+ uses: keyfactor/actions/.github/workflows/starter.yml@v3.1.2
secrets:
token: ${{ secrets.V2BUILDTOKEN}}
+ APPROVE_README_PUSH: ${{ secrets.APPROVE_README_PUSH}}
gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }}
gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }}
scan_token: ${{ secrets.SAST_TOKEN }}
- entra_username: ${{ secrets.DOCTOOL_ENTRA_USERNAME }}
- entra_password: ${{ secrets.DOCTOOL_ENTRA_PASSWD }}
- command_client_id: ${{ secrets.COMMAND_CLIENT_ID }}
- command_client_secret: ${{ secrets.COMMAND_CLIENT_SECRET }}
diff --git a/README.md b/README.md
index 5effcf2..c68aac4 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
-
+
@@ -14,7 +14,7 @@
Support
-
+
·
Requirements
@@ -33,14 +33,15 @@
+
This integration allows for the Synchronization, Enrollment, and Revocation of certificates from the CSCGlobal. This is the AnyGateway REST version.
## Compatibility
-The CSCGlobal CAPlugin AnyCA Gateway REST plugin is compatible with the Keyfactor AnyCA Gateway REST 26.2.0 and later.
+The CSCGlobal CAPlugin AnyCA Gateway REST plugin is compatible with the Keyfactor AnyCA Gateway REST 24.2.0 and later.
## Support
-The CSCGlobal CAPlugin AnyCA Gateway REST plugin 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.
+The CSCGlobal CAPlugin AnyCA Gateway REST plugin is supported by Keyfactor for Keyfactor customers. If you have a support issue, please open a support ticket with your Keyfactor representative. 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.
@@ -54,15 +55,16 @@ This integration is tested and confirmed as working for Anygateway REST 24.2 and
2. On the server hosting the AnyCA Gateway REST, download and unzip the latest [CSCGlobal CAPlugin AnyCA Gateway REST plugin](https://github.com/Keyfactor/cscglobal-caplugin/releases/latest) from GitHub.
-3. Copy the unzipped directory (usually called `net10.0`) to the Extensions directory:
+3. Copy the unzipped directory (usually called `net6.0` or `net8.0`) to the Extensions directory:
```shell
Depending on your AnyCA Gateway REST version, copy the unzipped directory to one of the following locations:
- Program Files\Keyfactor\AnyCA Gateway\AnyGatewayREST\net10.0\Extensions
+ Program Files\Keyfactor\AnyCA Gateway\AnyGatewayREST\net6.0\Extensions
+ Program Files\Keyfactor\AnyCA Gateway\AnyGatewayREST\net8.0\Extensions
```
- > The directory containing the CSCGlobal CAPlugin AnyCA Gateway REST plugin DLLs (`net10.0`) can be named anything, as long as it is unique within the `Extensions` directory.
+ > The directory containing the CSCGlobal CAPlugin AnyCA Gateway REST plugin DLLs (`net6.0` or `net8.0`) can be named anything, as long as it is unique within the `Extensions` directory.
4. Restart the AnyCA Gateway REST service.
@@ -80,343 +82,235 @@ This integration is tested and confirmed as working for Anygateway REST 24.2 and
Populate using the configuration fields collected in the [requirements](#requirements) section.
- * **CscGlobalUrl** - CSCGlobal API URL
- * **ApiKey** - CSCGlobal API Key
- * **BearerToken** - CSCGlobal Bearer Token
- * **DefaultPageSize** - Default page size for use with the API. Default is 100
- * **SyncFilterDays** - Number of days from today to filter certificates by expiration date during incremental sync.
- * **RenewalWindowDays** - Number of days before the annual order expiry within which a RenewOrReissue triggers a paid Renewal rather than a free Reissue. Default is 30.
- * **DcvPollTimeoutSeconds** - Max seconds to synchronously poll CSC for issuance after submitting an order (and publishing CNAME DCV). 0 disables polling (enrollment returns pending immediately; cert arrives on next sync). When >0, fast-validating orders can return the cert directly. Keep small to avoid long-blocking enrollment requests.
+ * **CscGlobalUrl** - CSCGlobal API URL
+ * **ApiKey** - CSCGlobal API Key
+ * **BearerToken** - CSCGlobal Bearer Token
+ * **DefaultPageSize** - Default page size for use with the API. Default is 100
+ * **TemplateSync** - Enable template sync.
+ * **SyncFilterDays** - Number of days from today to filter certificates by expiration date during incremental sync.
2. PLEASE NOTE, AT THIS TIME THE RAPID_SSL TEMPLATE IS NOT SUPPORTED BY THE CSC API AND WILL NOT WORK WITH THIS INTEGRATION
-The following certificate templates are supported. Please set up the key sizes accordingly in the Certificate Profile menu of Anygateway REST, then enter the remaining details
-and the Enrollment Fields for each Template accordingly using the Certificate Templates section in Command. If you would like to set up default values for enrollment parameters, you can do so the in the Certificate Template Menu of Anygateway REST.
-If a field value is specified as both an Enrollment Field in Command and in the Certificate Template Menu in the REST Gateway, the value in the Enrollment Field will take precedence.
-
-CONFIG ELEMENT | DESCRIPTION
-----------------------------|------------------
-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 Premium Certificate - 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
-
-**CSC TrustedSecure EV Certificate - Details Tab**
-
-CONFIG ELEMENT | DESCRIPTION
-----------------------------|------------------
-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 Certificate - 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
-Organization Country | String | N/A
-
-**CSC TrustedSecure UC Certificate - Details Tab**
-
-CONFIG ELEMENT | DESCRIPTION
-----------------------------|------------------
-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 UC Certificate - 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 Premium Wildcard Certificate - Details Tab**
-
-CONFIG ELEMENT | DESCRIPTION
-----------------------------|------------------
-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 Premium Wildcard Certificate - 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
-
-**CSC TrustedSecure Domain Validated SSL - Details Tab**
-
-CONFIG ELEMENT | DESCRIPTION
-----------------------------|------------------
-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 Domain Validated SSL - 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
-
-**CSC TrustedSecure Domain Validated Wildcard SSL - Details Tab**
-
-CONFIG ELEMENT | DESCRIPTION
-----------------------------|------------------
-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 Domain Validated Wildcard SSL - 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
-
-**CSC TrustedSecure Domain Validated UC Certificate - Details Tab**
-
-CONFIG ELEMENT | DESCRIPTION
-----------------------------|------------------
-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 Domain Validated UC Certificate - 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
+ The following certificate templates are supported. Please set up the key sizes accordingly in the Certificate Profile menu of Anygateway REST, then enter the remaining details
+ and the Enrollment Fields for each Template accordingly using the Certificate Templates section in Command. If you would like to set up default values for enrollment parameters, you can do so the in the Certificate Template Menu of Anygateway REST.
+ If a field value is specified as both an Enrollment Field in Command and in the Certificate Template Menu in the REST Gateway, the value in the Enrollment Field will take precedence.
+
+ CONFIG ELEMENT | DESCRIPTION
+ ----------------------------|------------------
+ 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 Premium Certificate - 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
+
+ **CSC TrustedSecure EV Certificate - Details Tab**
+
+ CONFIG ELEMENT | DESCRIPTION
+ ----------------------------|------------------
+ 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 Certificate - 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
+ Organization Country | String | N/A
+
+ **CSC TrustedSecure UC Certificate - Details Tab**
+
+ CONFIG ELEMENT | DESCRIPTION
+ ----------------------------|------------------
+ 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 UC Certificate - 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 Premium Wildcard Certificate - Details Tab**
+
+ CONFIG ELEMENT | DESCRIPTION
+ ----------------------------|------------------
+ 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 Premium Wildcard Certificate - 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
+
+ **CSC TrustedSecure Domain Validated SSL - Details Tab**
+
+ CONFIG ELEMENT | DESCRIPTION
+ ----------------------------|------------------
+ 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 Domain Validated SSL - 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
+
+ **CSC TrustedSecure Domain Validated Wildcard SSL - Details Tab**
+
+ CONFIG ELEMENT | DESCRIPTION
+ ----------------------------|------------------
+ 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 Domain Validated Wildcard SSL - 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
+
+ **CSC TrustedSecure Domain Validated UC Certificate - Details Tab**
+
+ CONFIG ELEMENT | DESCRIPTION
+ ----------------------------|------------------
+ 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 Domain Validated UC Certificate - 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
3. Follow the [official Keyfactor documentation](https://software.keyfactor.com/Guides/AnyCAGatewayREST/Content/AnyCAGatewayREST/AddCA-Keyfactor.htm) to add each defined Certificate Authority to Keyfactor Command and import the newly defined Certificate Templates.
4. In Keyfactor Command (v12.3+), for each imported Certificate Template, follow the [official documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/ReferenceGuide/Configuring%20Template%20Options.htm) to define enrollment fields for each of the following parameters:
- * **Term** - OPTIONAL: Certificate term (e.g. 12 or 24 months)
- * **Applicant First Name** - OPTIONAL: Applicant First Name
- * **Applicant Last Name** - OPTIONAL: Applicant Last Name
- * **Applicant Email Address** - OPTIONAL: Applicant Email Address
- * **Applicant Phone** - OPTIONAL: Applicant Phone (+nn.nnnnnnnn)
- * **Domain Control Validation Method** - OPTIONAL: Domain Control Validation Method (e.g. EMAIL)
- * **Organization Contact** - OPTIONAL: Organization Contact (selected from CSC configuration)
- * **Business Unit** - OPTIONAL: Business Unit (selected from CSC configuration)
- * **Notification Email(s) Comma Separated** - OPTIONAL: Notification Email(s), comma separated
- * **CN DCV Email** - OPTIONAL: CN DCV Email (e.g. admin@yourdomain.com)
- * **Organization Country** - OPTIONAL: Organization Country
- * **Addtl Sans Comma Separated DCV Emails** - OPTIONAL: Additional SANs DCV Emails, comma separated
-
-## CA Connection Configuration
-
-When defining the Certificate Authority in the AnyCA Gateway REST portal, configure the following fields on the **CA Connection** tab:
-
-CONFIG ELEMENT | DESCRIPTION | DEFAULT
----------------|-------------|--------
-Enabled | Flag to Enable or Disable gateway functionality. Set to `false` to allow creating the CA record before configuration information is available; the plugin then short-circuits Ping, Sync, Enroll, and Revoke with a warning until it is re-enabled. | `true`
-CscGlobalUrl | The base URL for the CSCGlobal API (e.g. `https://apis.cscglobal.com`) | (required)
-ApiKey | Your CSCGlobal API key | (required)
-BearerToken | Your CSCGlobal Bearer token for authentication | (required)
-DefaultPageSize | Page size for API list requests | 100
-SyncFilterDays | Number of days from today used to filter certificates by expiration date during **incremental** sync. Only certificates expiring within this window are returned. Does not apply to full sync. | 5
-RenewalWindowDays | Number of days before the annual order expiry date within which a **RenewOrReissue** request triggers a paid **Renewal** rather than a free **Reissue**. See [Renewal vs. Reissue Logic](#renewal-vs-reissue-logic) below. | 30
-DcvPollTimeoutSeconds | Max seconds to synchronously poll CSC for certificate issuance after submitting an order. `0` disables polling (enrollment returns pending immediately; cert arrives on the next sync). When `>0`, fast-validating orders can return the issued cert directly in the enrollment response. See [Synchronous Issuance Polling](#synchronous-issuance-polling) below. | 0
-
-> **Note:** DNS auto-publishing for CNAME DCV is handled by the AnyCA Gateway REST framework's Domain Validation system (gateway 3.3+). It's configured in the gateway UI under **Domain Validation Configurations**, not on the CA Connection tab. See [DNS Auto-Publishing (CNAME DCV)](#dns-auto-publishing-cname-dcv).
-
-## Renewal vs. Reissue Logic
-
-CSC Global subscriptions are annual orders. When Keyfactor Command sends a **RenewOrReissue** request, the plugin must decide whether to submit a **Renewal** (a new paid order) or a **Reissue** (a free re-key under the existing active order).
-
-The decision is based on the **RenewalWindowDays** setting and works as follows:
-
-1. The plugin fetches the original certificate from CSC and reads its `orderDate`.
-2. It computes the **order expiry** as `orderDate + 1 year`.
-3. It calculates **days remaining** until the order expires.
-4. If `days remaining <= RenewalWindowDays`, the request is treated as a **Renewal** (new paid order).
-5. If `days remaining > RenewalWindowDays`, the request is treated as a **Reissue** (free under the active order).
-
-**Example with default RenewalWindowDays = 30:**
-
-```
-Order Date: 2025-04-08
-Order Expiry: 2026-04-08
-Today: 2026-03-15
-Days Left: 24
-
-24 <= 30 --> RENEWAL (new paid order)
-```
-
-```
-Order Date: 2025-04-08
-Order Expiry: 2026-04-08
-Today: 2025-09-01
-Days Left: 219
-
-219 > 30 --> REISSUE (free under active order)
-```
-
-**Fallback behavior:** If the plugin cannot retrieve the `orderDate` from CSC (e.g., API error or missing field), it falls back to checking the certificate's expiration date. If the certificate is already expired, it treats the request as a Renewal.
-
-**Note:** Both Renewal and Reissue submissions are asynchronous at CSC. The plugin returns a "pending" status and the issued certificate will appear in Keyfactor after the next sync cycle.
-
-## DNS Auto-Publishing (CNAME DCV)
-
-CSC supports two Domain Control Validation (DCV) methods: **EMAIL** and **CNAME**. With CNAME validation, CSC returns a CNAME record (name → target) that must exist in DNS before they will validate the order.
-
-By default this plugin returns the CNAME details to Keyfactor Command for **manual publishing**. To fully automate enrollment, the plugin uses the **AnyCA Gateway REST framework's built-in DNS provider system** (available in framework 3.3 and later). The framework discovers DNS provider plugins deployed alongside the CA plugin and routes each CNAME to whichever provider claims the matching DNS zone.
-
-### Requirements
-
-* AnyCA Gateway REST framework **3.3 or later** (the `IDomainValidatorFactory` interface ships in `Keyfactor.AnyGateway.IAnyCAPlugin` 3.3+).
-* At least one DNS provider DLL (e.g. GoDaddy, Cloudflare, Route 53, Azure) deployed in the gateway `Extensions` folder.
-* A Domain Validation Configuration registered in the gateway UI that maps your domain(s) to the deployed provider (for example, `*.example.com` → GoDaddy).
-
-### How It Works
-
-1. CSC returns the CNAME `name → target` details in the enrollment response.
-2. For each CNAME entry, the plugin calls `IDomainValidatorFactory.ResolveDomainValidator(recordName, "cname")`.
-3. The framework returns the `IDomainValidator` whose Domain Validation Configuration matches the record's zone (or `null` if no match).
-4. The plugin calls `validator.StageValidation(recordName, cnameTarget, ct)` to publish the record.
-5. CSC asynchronously validates the CNAME; the issued certificate appears on the next sync.
-
-### Behavior
-
-* **Resolution is per record, not per CA.** One CA can drive multiple DNS providers (GoDaddy for some domains, Route 53 for others) with no per-CA configuration.
-* **Only invoked for CNAME DCV.** Templates configured with EMAIL validation are unaffected — no DNS publishing occurs.
-* **Best-effort.** If no provider claims the zone, the publish call fails, or the factory wasn't injected (gateway pre-3.3), the enrollment still succeeds and the CNAME details remain in the Keyfactor request so a human can publish manually as a fallback.
-* **Trace-logged.** Every resolution (matched/unresolved) and publish attempt (success/failure) is logged at Info/Trace level.
-* **Validation type string.** The plugin passes `"cname"` to `ResolveDomainValidator`. CSC's DCV requires a **CNAME** record, which is different from ACME's `"dns-01"` challenge (a TXT record). A single DNS provider DLL can ship multiple validator classes — one advertising `"dns-01"` (publishes TXT, for ACME) and one advertising `"cname"` (publishes CNAME, for CSC). You must deploy and configure a validator that advertises `"cname"` or no provider will match.
-* **Trailing dots normalized.** CSC returns FQDN-canonical names with a trailing dot (e.g. `_token.example.com.`). The plugin strips the trailing dot before resolution and publishing, because Domain Validation Configurations and DNS provider APIs expect names without it.
-
-### Configuration in the Gateway UI
-
-In the AnyCA Gateway REST portal, under **Domain Validation Configurations**:
-
-1. **Add** a new configuration.
-2. Pick a **Domain Validator Type** that publishes **CNAME** records and advertises validation type `cname`. For GoDaddy this is `GoDaddyCnameDomainValidator` (the `GoDaddyDomainValidator` variant publishes TXT for ACME and will **not** work for CSC).
-3. Add one or more **domain patterns** (e.g. `*.example.com`).
-4. Fill out the provider-specific **Configuration Settings** (API keys, base URL, etc.).
-5. Save.
-
-Once configured, any CSC enrollment for a domain matching one of those patterns will have its CNAME auto-published.
-
-> **Common pitfall:** If you configure the TXT/`dns-01` validator (e.g. `GoDaddyDomainValidator`) for a CSC domain, the record will publish as a **TXT** and CSC's CNAME validation will never succeed. Make sure you select the **CNAME** validator variant.
-
-## Synchronous Issuance Polling
-
-CSC validates domain control asynchronously — after an order is submitted (and the CNAME DCV record published), CSC/Sectigo polls public DNS on its own schedule and issues the certificate once validation passes. By default this plugin returns a **pending** (`EXTERNALVALIDATION`) result immediately and the issued certificate is picked up on the next gateway **sync** cycle.
-
-For environments where DNS is published automatically (see [DNS Auto-Publishing](#dns-auto-publishing-cname-dcv)) and validation tends to complete quickly, you can have the plugin **poll CSC synchronously** at the end of enrollment and return the issued certificate directly — avoiding the wait for the next sync.
+ * **Term** - OPTIONAL: Certificate term (e.g. 12 or 24 months)
+ * **Applicant First Name** - OPTIONAL: Applicant First Name
+ * **Applicant Last Name** - OPTIONAL: Applicant Last Name
+ * **Applicant Email Address** - OPTIONAL: Applicant Email Address
+ * **Applicant Phone** - OPTIONAL: Applicant Phone (+nn.nnnnnnnn)
+ * **Domain Control Validation Method** - OPTIONAL: Domain Control Validation Method (e.g. EMAIL)
+ * **Organization Contact** - OPTIONAL: Organization Contact (selected from CSC configuration)
+ * **Business Unit** - OPTIONAL: Business Unit (selected from CSC configuration)
+ * **Notification Email(s) Comma Separated** - OPTIONAL: Notification Email(s), comma separated
+ * **CN DCV Email** - OPTIONAL: CN DCV Email (e.g. admin@yourdomain.com)
+ * **Organization Country** - OPTIONAL: Organization Country
+ * **Addtl Sans Comma Separated DCV Emails** - OPTIONAL: Additional SANs DCV Emails, comma separated
-* Set **`DcvPollTimeoutSeconds`** to the maximum number of seconds to poll (e.g. `60`). `0` (default) disables polling entirely.
-* The plugin polls CSC every 10 seconds until the order is issued or the timeout is reached.
-* If the certificate issues within the window, the enrollment returns it immediately with a success status.
-* If the window expires, the plugin falls back to the **pending** result and the certificate arrives on the next sync — exactly as it would with polling disabled.
-**Tradeoff:** Polling blocks the enrollment request for up to `DcvPollTimeoutSeconds`. CSC validation frequently takes minutes to hours, so most orders will still fall through to pending — keep the timeout small (30–90s) to catch only the fast cases without hanging callers. This applies to New enrollments, Renewals, and Reissues.
## License
@@ -424,4 +318,4 @@ Apache License 2.0, see [LICENSE](LICENSE).
## Related Integrations
-See all [Keyfactor Any CA Gateways (REST)](https://github.com/orgs/Keyfactor/repositories?q=anycagateway).
+See all [Keyfactor Any CA Gateways (REST)](https://github.com/orgs/Keyfactor/repositories?q=anycagateway).
\ No newline at end of file
diff --git a/cscglobal-caplugin/CSCGlobalCAPlugin.cs b/cscglobal-caplugin/CSCGlobalCAPlugin.cs
index c22aa32..e1af2f0 100644
--- a/cscglobal-caplugin/CSCGlobalCAPlugin.cs
+++ b/cscglobal-caplugin/CSCGlobalCAPlugin.cs
@@ -23,300 +23,80 @@ namespace Keyfactor.Extensions.CAPlugin.CSCGlobal;
public class CSCGlobalCAPlugin : IAnyCAPlugin
{
- ///
- /// Validation type string passed to .
- /// CSC's Domain Control Validation publishes a CNAME record, so we resolve a DNS provider
- /// that advertises the "cname" validation type (e.g. GoDaddy's GoDaddyCnameDomainValidator).
- /// This is distinct from ACME's "dns-01" challenge, which publishes TXT records — a single
- /// DNS provider DLL can ship separate validator classes for each type.
- ///
- private const string DNS_VALIDATION_TYPE = "cname";
-
- /// Delay between CSC status polls while waiting for DCV to complete.
- private static readonly TimeSpan DcvPollInterval = TimeSpan.FromSeconds(10);
-
private readonly RequestManager _requestManager;
private readonly ILogger Logger;
- private readonly IDomainValidatorFactory? _validatorFactory;
private ICertificateDataReader _certificateDataReader;
- ///
- /// Parameterless constructor retained for compatibility with older gateway hosts that don't
- /// perform DI. When constructed this way the plugin runs without DNS auto-publishing.
- ///
public CSCGlobalCAPlugin()
{
Logger = LogHandler.GetClassLogger();
_requestManager = new RequestManager();
- _validatorFactory = null;
- }
-
- ///
- /// DI constructor used by AnyCA Gateway 3.3+ which injects the framework's domain validator
- /// factory. When non-null, CNAME DCV records returned by CSC are auto-published via the
- /// framework's registered DNS providers (resolved per-domain).
- ///
- public CSCGlobalCAPlugin(IDomainValidatorFactory validatorFactory)
- {
- Logger = LogHandler.GetClassLogger();
- _requestManager = new RequestManager();
- _validatorFactory = validatorFactory;
}
private ICscGlobalClient CscGlobalClient { get; set; }
- ///
- /// Whether the CA is enabled. When false, the plugin returns early from Ping,
- /// ValidateCAConnectionInfo, ValidateProductInfo, Synchronize, Enroll, and Revoke without
- /// calling CSC. Primarily used to allow creation of the CA record prior to configuration
- /// information being available (standard field across Keyfactor CA plugins). Defaults to true
- /// so existing deployments that don't set this key continue to function.
- ///
- public bool Enabled { get; set; } = true;
+ public bool EnableTemplateSync { get; set; }
public int SyncFilterDays { get; set; }
- public int RenewalWindowDays { get; set; }
-
- ///
- /// Maximum seconds to synchronously poll CSC for certificate issuance after submitting an
- /// order (and publishing CNAME DCV). 0 disables polling — the enrollment returns "pending"
- /// immediately and the cert is picked up on the next sync. When > 0, fast-validating
- /// orders can return the issued cert directly in the enrollment response.
- ///
- public int DcvPollTimeoutSeconds { get; set; }
-
//done
public void Initialize(IAnyCAPluginConfigProvider configProvider, ICertificateDataReader certificateDataReader)
{
- using var flow = new FlowLogger(Logger, "Initialize");
Logger.MethodEntry(LogLevel.Debug);
- Logger.LogTrace("Initialize called. configProvider is {Null}, certificateDataReader is {Null2}",
- configProvider == null ? "NULL" : "present",
- certificateDataReader == null ? "NULL" : "present");
-
- flow.Step("ValidateInputs", () =>
- {
- if (configProvider == null)
- throw new ArgumentNullException(nameof(configProvider), "configProvider cannot be null in Initialize");
- if (certificateDataReader == null)
- throw new ArgumentNullException(nameof(certificateDataReader), "certificateDataReader cannot be null in Initialize");
- });
-
_certificateDataReader = certificateDataReader;
+ CscGlobalClient = new CscGlobalClient(configProvider);
+ var templateSync = configProvider.CAConnectionData["TemplateSync"].ToString();
+ if (templateSync.ToUpper() == "ON") EnableTemplateSync = true;
- flow.Step("ValidateConnectionData", () =>
+ if (configProvider.CAConnectionData.ContainsKey(Constants.SyncFilterDays))
{
- if (configProvider.CAConnectionData == null)
+ var syncFilterDaysStr = configProvider.CAConnectionData[Constants.SyncFilterDays]?.ToString();
+ if (int.TryParse(syncFilterDaysStr, out var syncFilterDays))
{
- Logger.LogError("CAConnectionData is null. Cannot read configuration.");
- throw new InvalidOperationException("CAConnectionData is null on configProvider.");
+ SyncFilterDays = syncFilterDays;
+ Logger.LogDebug($"SyncFilterDays configured to {SyncFilterDays} days");
}
- Logger.LogTrace("CAConnectionData keys: {Keys}", string.Join(", ", configProvider.CAConnectionData.Keys));
- });
-
- flow.Step("ReadEnabled", () =>
- {
- Enabled = true; // default
- if (configProvider.CAConnectionData.TryGetValue(Constants.Enabled, out var enabledObj))
- {
- Logger.LogTrace("Enabled raw value: '{Value}'", enabledObj?.ToString() ?? "(null)");
- if (bool.TryParse(enabledObj?.ToString(), out var parsed))
- Enabled = parsed;
- else
- Logger.LogWarning("Enabled value '{Value}' could not be parsed as bool, defaulting to true.", enabledObj);
- }
- else
- {
- Logger.LogTrace("Enabled key not found in CAConnectionData, defaulting to true.");
- }
- Logger.LogInformation("CA is {State}.", Enabled ? "Enabled" : "Disabled");
- }, $"Enabled={Enabled}");
-
- // Construct the CSC client only when enabled. When disabled we allow Initialize to complete
- // without valid API credentials — this is the whole point of the Enabled toggle (so ops can
- // create the CA record before credentials are available).
- if (Enabled)
- {
- flow.Step("CreateCscGlobalClient", () =>
- {
- Logger.LogTrace("Creating CscGlobalClient from configProvider...");
- CscGlobalClient = new CscGlobalClient(configProvider);
- Logger.LogTrace("CscGlobalClient created successfully.");
- });
}
- else
- {
- flow.Skip("CreateCscGlobalClient", "CA is Disabled");
- }
-
- flow.Step("ReadSyncFilterDays", () =>
- {
- if (configProvider.CAConnectionData.ContainsKey(Constants.SyncFilterDays))
- {
- var syncFilterDaysStr = configProvider.CAConnectionData[Constants.SyncFilterDays]?.ToString();
- Logger.LogTrace("SyncFilterDays raw value: '{Value}'", syncFilterDaysStr ?? "(null)");
- if (int.TryParse(syncFilterDaysStr, out var syncFilterDays))
- {
- SyncFilterDays = syncFilterDays;
- Logger.LogDebug("SyncFilterDays configured to {Days} days", SyncFilterDays);
- }
- else
- {
- Logger.LogWarning("SyncFilterDays value '{Value}' could not be parsed as int, using default 0.", syncFilterDaysStr);
- }
- }
- else
- {
- Logger.LogTrace("SyncFilterDays key not found in CAConnectionData, using default 0.");
- }
- });
-
- flow.Step("ReadRenewalWindowDays", () =>
- {
- RenewalWindowDays = 30; // default
- if (configProvider.CAConnectionData.TryGetValue(Constants.RenewalWindowDays, out var renewalWindowObj))
- {
- Logger.LogTrace("RenewalWindowDays raw value: '{Value}'", renewalWindowObj?.ToString() ?? "(null)");
- if (int.TryParse(renewalWindowObj?.ToString(), out var renewalWindowDays) && renewalWindowDays > 0)
- RenewalWindowDays = renewalWindowDays;
- else
- Logger.LogWarning("RenewalWindowDays value '{Value}' could not be parsed or was <= 0, using default 30.", renewalWindowObj);
- }
- else
- {
- Logger.LogTrace("RenewalWindowDays key not found in CAConnectionData, using default 30.");
- }
- Logger.LogDebug("RenewalWindowDays configured to {Days} days", RenewalWindowDays);
- }, $"RenewalWindowDays={RenewalWindowDays}");
-
- flow.Step("ReadDcvPollTimeoutSeconds", () =>
- {
- DcvPollTimeoutSeconds = 0; // default: disabled
- if (configProvider.CAConnectionData.TryGetValue(Constants.DcvPollTimeoutSeconds, out var pollObj))
- {
- Logger.LogTrace("DcvPollTimeoutSeconds raw value: '{Value}'", pollObj?.ToString() ?? "(null)");
- if (int.TryParse(pollObj?.ToString(), out var pollSeconds) && pollSeconds >= 0)
- DcvPollTimeoutSeconds = pollSeconds;
- else
- Logger.LogWarning("DcvPollTimeoutSeconds value '{Value}' could not be parsed or was < 0, using default 0 (disabled).", pollObj);
- }
- else
- {
- Logger.LogTrace("DcvPollTimeoutSeconds key not found in CAConnectionData, using default 0 (disabled).");
- }
- Logger.LogDebug("DcvPollTimeoutSeconds configured to {Seconds}s ({State})",
- DcvPollTimeoutSeconds, DcvPollTimeoutSeconds > 0 ? "enabled" : "disabled");
- });
-
- flow.Step("CheckDnsValidatorFactory", () =>
- {
- if (_validatorFactory == null)
- Logger.LogInformation(
- "No IDomainValidatorFactory was injected by the gateway host. CNAME DCV records will require manual publishing.");
- else
- Logger.LogInformation(
- "IDomainValidatorFactory available from gateway host. CNAME DCV records will be auto-published per-domain via the framework's registered DNS providers (validation type '{Type}').",
- DNS_VALIDATION_TYPE);
- });
-
Logger.MethodExit(LogLevel.Debug);
}
//done
public async Task GetSingleRecord(string caRequestID)
{
- using var flow = new FlowLogger(Logger, $"GetSingleRecord({caRequestID ?? "null"})");
- Logger.MethodEntry(LogLevel.Debug);
- Logger.LogTrace("GetSingleRecord called with caRequestID='{CaRequestId}'", caRequestID ?? "(null)");
-
- flow.Step("ValidateInput", () =>
- {
- if (string.IsNullOrEmpty(caRequestID))
- throw new ArgumentNullException(nameof(caRequestID), "caRequestID cannot be null or empty.");
- if (caRequestID.Length < 36)
- throw new ArgumentException($"caRequestID '{caRequestID}' is too short to extract a UUID (need at least 36 chars).", nameof(caRequestID));
- });
-
try
{
- var keyfactorCaId = caRequestID.Substring(0, 36);
- flow.Step("ExtractUUID", $"keyfactorCaId={keyfactorCaId}");
+ Logger.MethodEntry(LogLevel.Debug);
+ var keyfactorCaId = caRequestID?.Substring(0, 36); //todo fix to use pipe delimiter
+ Logger.LogTrace($"Keyfactor Ca Id: {keyfactorCaId}");
+ var certificateResponse =
+ Task.Run(async () => await CscGlobalClient.SubmitGetCertificateAsync(keyfactorCaId))
+ .Result;
- CertificateResponse certificateResponse = null;
- await flow.StepAsync("FetchCertFromCSC", async () =>
- {
- certificateResponse = await CscGlobalClient.SubmitGetCertificateAsync(keyfactorCaId);
- });
-
- if (certificateResponse == null)
- {
- flow.Fail("ParseResponse", "API returned null");
- Logger.LogWarning("GetSingleRecord: SubmitGetCertificateAsync returned null for keyfactorCaId='{KeyfactorCaId}'", keyfactorCaId);
- return new AnyCAPluginCertificate
- {
- CARequestID = keyfactorCaId,
- Certificate = string.Empty,
- Status = _requestManager.MapReturnStatus(null)
- };
- }
+ Logger.LogTrace($"Single Cert JSON: {JsonConvert.SerializeObject(certificateResponse)}");
- flow.Step("ParseResponse", $"Status={certificateResponse.Status ?? "(null)"}");
- Logger.LogTrace("Single Cert JSON: {Json}", JsonConvert.SerializeObject(certificateResponse));
+ var fileContent =
+ Encoding.ASCII.GetString(
+ Convert.FromBase64String(certificateResponse?.Certificate ?? string.Empty));
- var rawCert = certificateResponse.Certificate ?? string.Empty;
- string fileContent = string.Empty;
- flow.Step("DecodeBase64", () =>
- {
- try
- {
- fileContent = Encoding.ASCII.GetString(Convert.FromBase64String(rawCert));
- }
- catch (FormatException fex)
- {
- Logger.LogError(fex, "GetSingleRecord: Failed to decode Base64 certificate content for keyfactorCaId='{KeyfactorCaId}'", keyfactorCaId);
- fileContent = string.Empty;
- }
- }, $"length={rawCert.Length}");
-
- var certData = fileContent.Replace("\r\n", string.Empty);
+ Logger.LogTrace($"File Content {fileContent}");
+ var certData = fileContent?.Replace("\r\n", string.Empty);
var certString = string.Empty;
if (!string.IsNullOrEmpty(certData))
- {
- flow.Step("ExtractLeafCert", () =>
- {
- certString = GetEndEntityCertificate(certData);
- }, $"inputLength={certData.Length}");
- }
- else
- {
- flow.Skip("ExtractLeafCert", "certData empty after cleanup");
- }
-
- var mappedStatus = _requestManager.MapReturnStatus(certificateResponse.Status);
- flow.Step("MapStatus", $"{certificateResponse.Status ?? "(null)"} -> {mappedStatus}");
+ certString = GetEndEntityCertificate(certData);
+ Logger.LogTrace($"Cert String Content {certString}");
Logger.MethodExit(LogLevel.Debug);
return new AnyCAPluginCertificate
{
CARequestID = keyfactorCaId,
- Certificate = certString ?? string.Empty,
- Status = mappedStatus
+ Certificate = certString,
+ Status = _requestManager.MapReturnStatus(certificateResponse?.Status)
};
}
- catch (AggregateException ae)
- {
- var inner = ae.Flatten().InnerException;
- flow.Fail("UNHANDLED", inner?.Message ?? ae.Message);
- Logger.LogError(inner, "GetSingleRecord: AggregateException for caRequestID='{CaRequestId}': {Message}", caRequestID, inner?.Message ?? ae.Message);
- throw new Exception($"Error Occurred getting single cert for '{caRequestID}': {inner?.Message ?? ae.Message}", inner ?? ae);
- }
catch (Exception e)
{
- flow.Fail("UNHANDLED", e.Message);
- Logger.LogError(e, "GetSingleRecord: Exception for caRequestID='{CaRequestId}': {Message}", caRequestID, e.Message);
- throw new Exception($"Error Occurred getting single cert for '{caRequestID}': {e.Message}", e);
+ throw new Exception($"Error Occurred getting single cert {e.Message}");
}
}
@@ -324,64 +104,31 @@ await flow.StepAsync("FetchCertFromCSC", async () =>
public async Task Synchronize(BlockingCollection blockingBuffer, DateTime? lastSync,
bool fullSync, CancellationToken cancelToken)
{
- var syncType = fullSync ? "Full" : "Incremental";
- using var flow = new FlowLogger(Logger, $"Synchronize-{syncType}");
+ Logger.LogTrace($"Full Sync? {fullSync.ToString()}");
Logger.MethodEntry();
- Logger.LogTrace("Synchronize called. fullSync={FullSync}, lastSync={LastSync}, blockingBuffer is {Null}",
- fullSync, lastSync?.ToString("o") ?? "(null)",
- blockingBuffer == null ? "NULL" : "present");
-
- if (blockingBuffer == null)
- throw new ArgumentNullException(nameof(blockingBuffer), "blockingBuffer cannot be null in Synchronize");
-
- if (!Enabled)
- {
- Logger.LogWarning("The CA is currently in the Disabled state. It must be Enabled to perform operations. Skipping Synchronize.");
- blockingBuffer.CompleteAdding();
- Logger.MethodExit(LogLevel.Debug);
- return;
- }
-
try
{
if (fullSync)
{
- flow.Step("DetermineFilter", "Full sync - no date filter");
- await flow.StepAsync("FetchAndProcessCerts", async () =>
- {
- await SyncCertificates(blockingBuffer, cancelToken, null);
- });
+ Logger.LogDebug("Performing full sync - no date filter applied");
+ await SyncCertificates(blockingBuffer, cancelToken, null);
}
else
{
var filterDays = SyncFilterDays > 0 ? SyncFilterDays : 5;
var filterDate = DateTime.Today.Subtract(TimeSpan.FromDays(filterDays));
var dateFilter = filterDate.ToString("yyyy/MM/dd");
- flow.Step("DetermineFilter", $"Incremental, filterDays={filterDays}, cutoff={dateFilter}");
- await flow.StepAsync("FetchAndProcessCerts", async () =>
- {
- await SyncCertificates(blockingBuffer, cancelToken, dateFilter);
- });
+ Logger.LogDebug($"Performing incremental sync with expiration date filter: {dateFilter}");
+ await SyncCertificates(blockingBuffer, cancelToken, dateFilter);
}
- flow.Step("CompleteAdding");
blockingBuffer.CompleteAdding();
}
- catch (OperationCanceledException)
- {
- flow.Fail("Cancelled", "operation was cancelled");
- Logger.LogWarning("Synchronize: operation was cancelled.");
- if (!blockingBuffer.IsAddingCompleted)
- blockingBuffer.CompleteAdding();
- throw;
- }
catch (Exception e)
{
- flow.Fail("SyncError", e.Message);
- Logger.LogError(e, "Csc Global Synchronize Task failed! {FlatException}", LogHandler.FlattenException(e));
- if (!blockingBuffer.IsAddingCompleted)
- blockingBuffer.CompleteAdding();
+ Logger.LogError($"Csc Global Synchronize Task failed! {LogHandler.FlattenException(e)}");
Logger.MethodExit();
+ blockingBuffer.CompleteAdding();
throw;
}
@@ -391,188 +138,70 @@ await flow.StepAsync("FetchAndProcessCerts", async () =>
private async Task SyncCertificates(BlockingCollection blockingBuffer,
CancellationToken cancelToken, string? dateFilter)
{
- Logger.LogTrace("SyncCertificates: calling SubmitCertificateListRequestAsync with dateFilter='{DateFilter}'", dateFilter ?? "(null)");
var certs = await CscGlobalClient.SubmitCertificateListRequestAsync(dateFilter);
- if (certs == null)
- {
- Logger.LogWarning("SyncCertificates: SubmitCertificateListRequestAsync returned null.");
- return;
- }
-
- if (certs.Results == null)
- {
- Logger.LogWarning("SyncCertificates: certificate list response Results collection is null.");
- return;
- }
-
- Logger.LogTrace("SyncCertificates: received {Count} certificate results.", certs.Results.Count);
- var processedCount = 0;
- var skippedCount = 0;
-
foreach (var currentResponseItem in certs.Results)
{
cancelToken.ThrowIfCancellationRequested();
+ Logger.LogTrace($"Took Certificate ID {currentResponseItem?.Uuid} from Queue");
+ var certStatus = _requestManager.MapReturnStatus(currentResponseItem?.Status);
- if (currentResponseItem == null)
- {
- Logger.LogTrace("SyncCertificates: skipping null result item.");
- skippedCount++;
- continue;
- }
-
- Logger.LogTrace("SyncCertificates: processing certificate UUID={Uuid}, Status='{Status}', CertificateType='{CertType}'",
- currentResponseItem.Uuid ?? "(null)",
- currentResponseItem.Status ?? "(null)",
- currentResponseItem.CertificateType ?? "(null)");
-
- var certStatus = _requestManager.MapReturnStatus(currentResponseItem.Status);
- Logger.LogTrace("SyncCertificates: mapped status for UUID={Uuid}: {MappedStatus}", currentResponseItem.Uuid ?? "(null)", certStatus);
-
+ //Keyfactor sync only seems to work when there is a valid cert and I can only get Active valid certs from Csc Global
if (certStatus == Convert.ToInt32(EndEntityStatus.GENERATED) ||
certStatus == Convert.ToInt32(EndEntityStatus.REVOKED))
{
- var productId = _requestManager.MapCertificateTypeToProductId(currentResponseItem.CertificateType);
+ //One click renewal/reissue won't work for this implementation so there is an option to disable it by not syncing back template
+ var productId = "CscGlobal";
+ if (EnableTemplateSync) productId = currentResponseItem?.CertificateType;
- Logger.LogTrace("SyncCertificates: UUID={Uuid} qualifies for sync. CertificateType='{CertType}' -> ProductId='{ProductId}'",
- currentResponseItem.Uuid, currentResponseItem.CertificateType ?? "(null)", productId);
-
- string fileContent;
- try
- {
- fileContent = PreparePemTextFromApi(currentResponseItem.Certificate ?? string.Empty);
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "SyncCertificates: PreparePemTextFromApi failed for UUID={Uuid}", currentResponseItem.Uuid);
- skippedCount++;
- continue;
- }
+ var fileContent =
+ PreparePemTextFromApi(
+ currentResponseItem?.Certificate ?? string.Empty);
if (fileContent.Length > 0)
{
- Logger.LogTrace("SyncCertificates: fileContent length={Length} for UUID={Uuid}", fileContent.Length, currentResponseItem.Uuid);
+ Logger.LogTrace($"File Content {fileContent}");
var certData = fileContent.Replace("\r\n", string.Empty);
- string certString;
- try
- {
- certString = GetEndEntityCertificate(certData);
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "SyncCertificates: GetEndEntityCertificate failed for UUID={Uuid}", currentResponseItem.Uuid);
- skippedCount++;
- continue;
- }
-
- if (!string.IsNullOrEmpty(certString))
- {
+ var certString = GetEndEntityCertificate(certData);
+ if (certString.Length > 0)
blockingBuffer.Add(new AnyCAPluginCertificate
{
- CARequestID = $"{currentResponseItem.Uuid}",
+ CARequestID = $"{currentResponseItem?.Uuid}",
Certificate = certString,
Status = certStatus,
ProductID = productId
}, cancelToken);
- processedCount++;
- Logger.LogTrace("SyncCertificates: added UUID={Uuid} to buffer.", currentResponseItem.Uuid);
- }
- else
- {
- Logger.LogTrace("SyncCertificates: certString 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++;
- }
}
-
- Logger.LogDebug("SyncCertificates: completed. Processed={Processed}, Skipped={Skipped}, Total={Total}",
- processedCount, skippedCount, certs.Results.Count);
}
//done
public async Task Revoke(string caRequestID, string hexSerialNumber, uint revocationReason)
{
- using var flow = new FlowLogger(Logger, $"Revoke({caRequestID ?? "null"})");
- Logger.MethodEntry(LogLevel.Debug);
- Logger.LogTrace("Revoke called with caRequestID='{CaRequestId}', hexSerialNumber='{SerialNumber}', revocationReason={Reason}",
- caRequestID ?? "(null)", hexSerialNumber ?? "(null)", revocationReason);
-
- if (!Enabled)
- {
- Logger.LogWarning("The CA is currently in the Disabled state. It must be Enabled to perform operations. Rejecting Revoke.");
- throw new InvalidOperationException("The CSC Global CA is in the Disabled state. Enable it to perform revocations.");
- }
-
- flow.Step("ValidateInput", () =>
- {
- if (string.IsNullOrEmpty(caRequestID))
- throw new ArgumentNullException(nameof(caRequestID), "caRequestID cannot be null or empty for Revoke.");
- if (caRequestID.Length < 36)
- throw new ArgumentException($"caRequestID '{caRequestID}' is too short to extract a UUID.", nameof(caRequestID));
- });
-
try
{
- var uuid = caRequestID.Substring(0, 36);
- flow.Step("ExtractUUID", $"uuid={uuid}");
-
- RevokeResponse revokeResponse = null;
- await flow.StepAsync("SubmitRevokeToCSC", async () =>
- {
- revokeResponse = await CscGlobalClient.SubmitRevokeCertificateAsync(uuid);
- });
-
- if (revokeResponse == null)
- {
- flow.Fail("ParseResponse", "API returned null");
- throw new InvalidOperationException($"Revoke received null response for UUID '{uuid}'.");
- }
+ Logger.LogTrace("Staring Revoke Method");
+ var revokeResponse =
+ Task.Run(async () =>
+ await CscGlobalClient.SubmitRevokeCertificateAsync(caRequestID.Substring(0, 36))).Result
+ ; //todo fix to use pipe delimiter
- Logger.LogTrace("Revoke Response JSON: {Json}", JsonConvert.SerializeObject(revokeResponse));
+ Logger.LogTrace($"Revoke Response JSON: {JsonConvert.SerializeObject(revokeResponse)}");
+ Logger.MethodExit(LogLevel.Debug);
var revokeResult = _requestManager.GetRevokeResult(revokeResponse);
- flow.Step("MapResult", $"result={revokeResult}");
if (revokeResult == (int)EndEntityStatus.FAILED)
- {
- var errorDesc = revokeResponse.RegistrationError?.Description;
- flow.Fail("RevokeResult", errorDesc ?? "(no description)");
- Logger.LogError("Revoke: failed for UUID='{Uuid}'. Error description: '{ErrorDesc}'",
- uuid, errorDesc ?? "(no description)");
- if (!string.IsNullOrEmpty(errorDesc))
- throw new HttpRequestException($"Revoke Failed with message {errorDesc}");
- }
+ if (!string.IsNullOrEmpty(revokeResponse?.RegistrationError?.Description))
+ throw new HttpRequestException(
+ $"Revoke Failed with message {revokeResponse?.RegistrationError?.Description}");
- Logger.MethodExit(LogLevel.Debug);
return revokeResult;
}
- catch (AggregateException ae)
- {
- var inner = ae.Flatten().InnerException;
- flow.Fail("UNHANDLED", inner?.Message ?? ae.Message);
- Logger.LogError(inner, "Revoke: AggregateException for caRequestID='{CaRequestId}': {Message}", caRequestID, inner?.Message ?? ae.Message);
- throw new Exception($"Revoke Failed for '{caRequestID}' with message {inner?.Message ?? ae.Message}", inner ?? ae);
- }
- catch (HttpRequestException)
- {
- throw; // already logged in flow above
- }
catch (Exception e)
{
- flow.Fail("UNHANDLED", e.Message);
- Logger.LogError(e, "Revoke: Exception for caRequestID='{CaRequestId}': {Message}", caRequestID, e.Message);
- throw new Exception($"Revoke Failed for '{caRequestID}' with message {e.Message}", e);
+ throw new Exception($"Revoke Failed with message {e?.Message}");
}
}
@@ -580,431 +209,128 @@ await flow.StepAsync("SubmitRevokeToCSC", async () =>
public async Task Enroll(string csr, string subject, Dictionary san,
EnrollmentProductInfo productInfo, RequestFormat requestFormat, EnrollmentType enrollmentType)
{
- using var flow = new FlowLogger(Logger, $"Enroll-{enrollmentType}");
Logger.MethodEntry(LogLevel.Debug);
- Logger.LogTrace("Enroll called. enrollmentType={EnrollmentType}, subject='{Subject}', productId='{ProductId}', requestFormat={RequestFormat}",
- enrollmentType, subject ?? "(null)",
- productInfo?.ProductID ?? "(null)", requestFormat);
- Logger.LogTrace("Enroll: csr is {CsrStatus}, san has {SanCount} entries, productInfo is {PiStatus}",
- string.IsNullOrEmpty(csr) ? "empty/null" : $"present ({csr.Length} chars)",
- san?.Count ?? 0,
- productInfo == null ? "NULL" : "present");
-
- if (!Enabled)
- {
- flow.Fail("Disabled", "CA is Disabled");
- Logger.LogWarning("The CA is currently in the Disabled state. It must be Enabled to perform operations. Rejecting Enroll.");
- return new EnrollmentResult
- {
- Status = (int)EndEntityStatus.FAILED,
- StatusMessage = "The CSC Global CA is in the Disabled state. Enable it to perform enrollments."
- };
- }
-
- flow.Step("ValidateInputs", () =>
- {
- if (productInfo == null)
- throw new ArgumentNullException(nameof(productInfo), "productInfo cannot be null for Enroll.");
- if (productInfo.ProductParameters == null)
- throw new ArgumentNullException(nameof(productInfo), "productInfo.ProductParameters cannot be null for Enroll.");
- if (string.IsNullOrEmpty(csr))
- throw new ArgumentNullException(nameof(csr), "CSR cannot be null or empty for Enroll.");
- });
-
- Logger.LogTrace("Enroll: ProductParameters keys: [{Keys}]",
- string.Join(", ", productInfo.ProductParameters.Keys));
RegistrationRequest enrollmentRequest;
var priorSn = "";
ReissueRequest reissueRequest;
RenewalRequest renewRequest;
+ if (productInfo.ProductParameters.ContainsKey("priorcertsn"))
+ {
+ priorSn = productInfo.ProductParameters["PriorCertSN"];
+ Logger.LogDebug($"Prior cert sn: {priorSn}");
+ }
+
+ string uUId;
+ var customFields = await CscGlobalClient.SubmitGetCustomFields();
- flow.Step("CheckPriorCertSN", () =>
+ switch (enrollmentType)
{
- if (productInfo.ProductParameters.ContainsKey("priorcertsn"))
- {
- if (productInfo.ProductParameters.ContainsKey("PriorCertSN"))
+ case EnrollmentType.New:
+ Logger.LogTrace("Entering New Enrollment");
+ //If they renewed an expired cert it gets here and this will not be supported
+ IRegistrationResponse enrollmentResponse;
+ if (!productInfo.ProductParameters.ContainsKey("PriorCertSN"))
{
- priorSn = productInfo.ProductParameters["PriorCertSN"];
- Logger.LogDebug("Enroll: Prior cert SN: '{PriorSn}'", priorSn ?? "(null)");
+ enrollmentRequest = _requestManager.GetRegistrationRequest(productInfo, csr, san, customFields);
+ Logger.LogTrace($"Enrollment Request JSON: {JsonConvert.SerializeObject(enrollmentRequest)}");
+ enrollmentResponse =
+ Task.Run(async () => await CscGlobalClient.SubmitRegistrationAsync(enrollmentRequest))
+ .Result;
+ Logger.LogTrace($"Enrollment Response JSON: {JsonConvert.SerializeObject(enrollmentResponse)}");
}
else
{
- Logger.LogWarning("Enroll: 'priorcertsn' key exists but 'PriorCertSN' (case-sensitive) not found.");
- }
- }
- }, string.IsNullOrEmpty(priorSn) ? "none" : $"SN={priorSn}");
-
- string uUId;
- List customFields = null;
- await flow.StepAsync("FetchCustomFields", async () =>
- {
- customFields = await CscGlobalClient.SubmitGetCustomFields();
- }, $"count={customFields?.Count ?? 0}");
-
- if (customFields == null)
- {
- Logger.LogWarning("Enroll: SubmitGetCustomFields returned null, using empty list.");
- customFields = new List();
- }
-
- try
- {
- switch (enrollmentType)
- {
- case EnrollmentType.New:
- flow.Step("SelectPath", "New Enrollment");
- IRegistrationResponse enrollmentResponse;
- if (!productInfo.ProductParameters.ContainsKey("PriorCertSN"))
- {
- enrollmentRequest = null;
- flow.Step("BuildRegistrationRequest", () =>
- {
- enrollmentRequest = _requestManager.GetRegistrationRequest(productInfo, csr, san, customFields);
- });
- Logger.LogTrace("Enrollment Request JSON: {Json}", JsonConvert.SerializeObject(enrollmentRequest));
-
- RegistrationResponse regResponse = null;
- await flow.StepAsync("SubmitRegistrationToCSC", async () =>
- {
- regResponse = await CscGlobalClient.SubmitRegistrationAsync(enrollmentRequest);
- });
- enrollmentResponse = regResponse;
-
- if (enrollmentResponse == null)
- {
- flow.Fail("ParseResponse", "API returned null");
- return new EnrollmentResult
- {
- Status = 30,
- StatusMessage = "Enrollment failed: CSC API returned a null response."
- };
- }
- flow.Step("ParseResponse", $"error={enrollmentResponse.RegistrationError != null}");
- Logger.LogTrace("Enrollment Response JSON: {Json}", JsonConvert.SerializeObject(enrollmentResponse));
- }
- else
- {
- flow.Fail("RejectExpiredRenew", "PriorCertSN present on New enrollment");
- return new EnrollmentResult
- {
- 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("PublishCnameDcv", async () =>
- {
- await TryPublishCnameDcvAsync(productInfo, enrollResult);
- });
-
- EnrollmentResult? newPolled = null;
- await flow.StepAsync("PollForIssuance", async () =>
- {
- newPolled = await TryPollForIssuedCertAsync(enrollResult?.CARequestID);
- });
- if (newPolled != null)
- {
- flow.Step("PollResult", "issued during poll window");
- Logger.MethodExit(LogLevel.Debug);
- return newPolled;
- }
-
- Logger.MethodExit(LogLevel.Debug);
- return enrollResult;
-
- case EnrollmentType.RenewOrReissue:
- flow.Step("SelectPath", "RenewOrReissue");
-
- if (string.IsNullOrEmpty(priorSn))
- {
- flow.Fail("ValidatePriorSN", "PriorCertSN is empty");
- return new EnrollmentResult
- {
- Status = 30,
- StatusMessage = "RenewOrReissue failed: PriorCertSN is required but was not provided."
- };
- }
-
- string order_id = null;
- await flow.StepAsync("LookupOrderId", async () =>
- {
- order_id = await _certificateDataReader.GetRequestIDBySerialNumber(priorSn);
- }, $"orderId={order_id ?? "(null)"}");
-
- if (string.IsNullOrEmpty(order_id))
- {
- flow.Fail("ValidateOrderId", $"no order found for SN={priorSn}");
- return new EnrollmentResult
- {
- Status = 30,
- StatusMessage = $"RenewOrReissue failed: could not find order ID for serial number '{priorSn}'."
- };
- }
-
- if (order_id.Length < 36)
- {
- flow.Fail("ValidateOrderId", $"order_id too short ({order_id.Length} chars)");
- return new EnrollmentResult
- {
- Status = 30,
- StatusMessage = $"RenewOrReissue failed: order ID '{order_id}' is too short to extract a UUID."
- };
- }
- flow.Step("ValidateOrderId", $"orderId={order_id}");
-
- // Determine renew vs reissue based on order expiry window.
- var renewal = false;
- try
- {
- CertificateResponse liveCert = null;
- await flow.StepAsync("FetchLiveCertForDecision", async () =>
- {
- liveCert = await CscGlobalClient.SubmitGetCertificateAsync(order_id[..36]);
- });
-
- if (liveCert != null && DateTime.TryParse(liveCert.OrderDate, out var orderDate))
- {
- var orderExpiry = orderDate.AddYears(1);
- var daysUntilOrderExpiry = (orderExpiry - DateTime.UtcNow).TotalDays;
- renewal = daysUntilOrderExpiry <= RenewalWindowDays;
- flow.Step("ComputeRenewalDecision",
- $"orderDate={liveCert.OrderDate}, expiry={orderExpiry:dd-MMM-yyyy}, daysLeft={(int)daysUntilOrderExpiry}, window={RenewalWindowDays}, isRenewal={renewal}");
- }
- else
- {
- flow.Skip("ComputeRenewalDecision", "orderDate unavailable, falling back to cert expiry");
- var expirationDate = _certificateDataReader.GetExpirationDateByRequestId(order_id)
- ?? (await GetSingleRecord(order_id)).RevocationDate;
- renewal = expirationDate < DateTime.Now;
- flow.Step("FallbackExpiryCheck", $"expirationDate={expirationDate?.ToString("o") ?? "(null)"}, isRenewal={renewal}");
- }
- }
- catch (Exception ex)
+ return new EnrollmentResult
{
- flow.Fail("FetchLiveCertForDecision", $"falling back: {ex.Message}");
- Logger.LogWarning(ex, "RenewOrReissue: failed to fetch live cert, falling back to cert expiry.");
- try
- {
- var expirationDate = _certificateDataReader.GetExpirationDateByRequestId(order_id)
- ?? (await GetSingleRecord(order_id)).RevocationDate;
- renewal = expirationDate < DateTime.Now;
- flow.Step("FallbackExpiryCheck", $"isRenewal={renewal}");
- }
- catch (Exception fallbackEx)
- {
- flow.Fail("FallbackExpiryCheck", fallbackEx.Message);
- return new EnrollmentResult
- {
- Status = 30,
- StatusMessage = $"RenewOrReissue failed: unable to determine renewal status for order '{order_id}'. {fallbackEx.Message}"
- };
- }
- }
-
- flow.Step("RenewalDecision", renewal ? "RENEWAL (paid order)" : "REISSUE (free under active order)");
+ Status = 30, //failure
+ StatusMessage = "You cannot renew an expired cert please perform an new enrollment."
+ };
+ }
- if (renewal)
- {
- if (productInfo.ProductParameters.ContainsKey("Applicant Last Name"))
- {
- uUId = null;
- await flow.StepAsync("LookupRenewalUUID", async () =>
- {
- uUId = await _certificateDataReader.GetRequestIDBySerialNumber(
- productInfo.ProductParameters["PriorCertSN"]);
- });
-
- if (string.IsNullOrEmpty(uUId))
- {
- flow.Fail("ValidateRenewalUUID", "could not resolve PriorCertSN");
- return new EnrollmentResult
- {
- Status = 30,
- StatusMessage = "Renewal failed: could not resolve prior certificate serial number to a request ID."
- };
- }
- flow.Step("ValidateRenewalUUID", $"uuid={uUId}");
-
- RenewalRequest builtRenewRequest = null;
- flow.Step("BuildRenewalRequest", () =>
- {
- builtRenewRequest = _requestManager.GetRenewalRequest(productInfo, uUId, csr, san, customFields);
- });
- renewRequest = builtRenewRequest;
- Logger.LogTrace("Renewal Request JSON: {Json}", JsonConvert.SerializeObject(renewRequest));
-
- RenewalResponse renewResponse = null;
- await flow.StepAsync("SubmitRenewalToCSC", async () =>
- {
- renewResponse = await CscGlobalClient.SubmitRenewalAsync(renewRequest);
- });
-
- if (renewResponse == null)
- {
- flow.Fail("ParseRenewalResponse", "API returned null");
- return new EnrollmentResult
- {
- Status = 30,
- StatusMessage = "Renewal failed: CSC API returned a null response."
- };
- }
-
- Logger.LogTrace("Renewal Response JSON: {Json}", JsonConvert.SerializeObject(renewResponse));
- var renewResult = _requestManager.GetRenewResponse(renewResponse);
- flow.Step("MapRenewalResult", $"Status={renewResult?.Status}, Message={renewResult?.StatusMessage ?? "(null)"}");
-
- EnrollmentResult? renewPolled = null;
- await flow.StepAsync("PollForIssuance", async () =>
- {
- renewPolled = await TryPollForIssuedCertAsync(renewResult?.CARequestID);
- });
- Logger.MethodExit(LogLevel.Debug);
- return renewPolled ?? renewResult;
- }
-
- flow.Fail("MissingEnrollmentParams", "Applicant Last Name not present — one-click renew unavailable");
- return new EnrollmentResult
- {
- Status = 30,
- StatusMessage =
- "One click Renew Is Not Available for this Certificate Type. Use the configure button instead."
- };
- }
+ Logger.MethodExit(LogLevel.Debug);
+ return _requestManager.GetEnrollmentResult(enrollmentResponse);
+ case EnrollmentType.RenewOrReissue:
+ Logger.LogTrace("Entering Renew Enrollment");
+ //Logic to determine renew vs reissue
+ var renewal = false;
+ var order_id = await _certificateDataReader.GetRequestIDBySerialNumber(priorSn);
+ var expirationDate = _certificateDataReader.GetExpirationDateByRequestId(order_id);
+ if (expirationDate == null)
+ {
+ var localcert = await GetSingleRecord(order_id);
+ expirationDate = localcert.RevocationDate;
+ }
- // Reissue path
+ if (expirationDate < DateTime.Now) renewal = true;
+ if (renewal)
+ {
+ //One click won't work for this implementation b/c we are missing enrollment params
if (productInfo.ProductParameters.ContainsKey("Applicant Last Name"))
{
- string requestid = null;
- await flow.StepAsync("LookupReissueRequestId", async () =>
- {
- requestid = await _certificateDataReader.GetRequestIDBySerialNumber(
- productInfo.ProductParameters["PriorCertSN"]);
- });
-
- if (string.IsNullOrEmpty(requestid))
- {
- flow.Fail("ValidateReissueRequestId", "could not resolve PriorCertSN");
- return new EnrollmentResult
- {
- Status = 30,
- StatusMessage = "Reissue failed: could not resolve prior certificate serial number to a request ID."
- };
- }
-
- if (requestid.Length < 36)
- {
- flow.Fail("ValidateReissueRequestId", $"requestid too short ({requestid.Length} chars)");
- return new EnrollmentResult
- {
- Status = 30,
- StatusMessage = $"Reissue failed: request ID '{requestid}' is too short to extract a UUID."
- };
- }
-
- uUId = requestid.Substring(0, 36);
- flow.Step("ExtractReissueUUID", $"uuid={uUId}");
-
- ReissueRequest builtReissueRequest = null;
- flow.Step("BuildReissueRequest", () =>
- {
- builtReissueRequest = _requestManager.GetReissueRequest(productInfo, uUId, csr, san, customFields);
- });
- reissueRequest = builtReissueRequest;
- Logger.LogTrace("Reissue JSON: {Json}", JsonConvert.SerializeObject(reissueRequest));
-
- ReissueResponse reissueResponse = null;
- await flow.StepAsync("SubmitReissueToCSC", async () =>
- {
- reissueResponse = await CscGlobalClient.SubmitReissueAsync(reissueRequest);
- });
-
- if (reissueResponse == null)
- {
- flow.Fail("ParseReissueResponse", "API returned null");
- return new EnrollmentResult
- {
- Status = 30,
- StatusMessage = "Reissue failed: CSC API returned a null response."
- };
- }
-
- Logger.LogTrace("Reissue Response JSON: {Json}", JsonConvert.SerializeObject(reissueResponse));
- var reissueResult = _requestManager.GetReIssueResult(reissueResponse);
- flow.Step("MapReissueResult", $"Status={reissueResult?.Status}, Message={reissueResult?.StatusMessage ?? "(null)"}");
-
- EnrollmentResult? reissuePolled = null;
- await flow.StepAsync("PollForIssuance", async () =>
- {
- reissuePolled = await TryPollForIssuedCertAsync(reissueResult?.CARequestID);
- });
+ //priorCert = _certificateDataReader.get(
+ //DataConversion.HexToBytes(productInfo.ProductParameters["PriorCertSN"]));
+ //uUId = priorCert.CARequestID.Substring(0, 36); //uUId is a GUID
+ uUId = await _certificateDataReader.GetRequestIDBySerialNumber(
+ productInfo.ProductParameters["PriorCertSN"]);
+ Logger.LogTrace($"Renew uUId: {uUId}");
+ renewRequest = _requestManager.GetRenewalRequest(productInfo, uUId, csr, san, customFields);
+ Logger.LogTrace($"Renewal Request JSON: {JsonConvert.SerializeObject(renewRequest)}");
+ var renewResponse = Task.Run(async () => await CscGlobalClient.SubmitRenewalAsync(renewRequest))
+ .Result;
+ Logger.LogTrace($"Renewal Response JSON: {JsonConvert.SerializeObject(renewResponse)}");
Logger.MethodExit(LogLevel.Debug);
- return reissuePolled ?? reissueResult;
+ return _requestManager.GetRenewResponse(renewResponse);
}
- flow.Fail("MissingEnrollmentParams", "Applicant Last Name not present — one-click reissue unavailable");
return new EnrollmentResult
{
- Status = 30,
+ Status = 30, //failure
StatusMessage =
"One click Renew Is Not Available for this Certificate Type. Use the configure button instead."
};
+ }
- default:
- flow.Fail("UnhandledType", $"enrollmentType={enrollmentType}");
- return new EnrollmentResult
- {
- Status = 30,
- StatusMessage = $"Enroll failed: unhandled enrollment type '{enrollmentType}'."
- };
- }
- }
- catch (AggregateException ae)
- {
- var inner = ae.Flatten().InnerException;
- flow.Fail("UNHANDLED", inner?.Message ?? ae.Message);
- Logger.LogError(inner, "Enroll: AggregateException during {EnrollmentType}: {Message}", enrollmentType, inner?.Message ?? ae.Message);
- return new EnrollmentResult
- {
- Status = 30,
- StatusMessage = $"Enrollment failed with error: {inner?.Message ?? ae.Message}"
- };
- }
- catch (Exception ex)
- {
- flow.Fail("UNHANDLED", ex.Message);
- Logger.LogError(ex, "Enroll: unhandled exception during {EnrollmentType}: {Message}", enrollmentType, ex.Message);
- return new EnrollmentResult
- {
- Status = 30,
- StatusMessage = $"Enrollment failed with error: {ex.Message}"
- };
+ Logger.LogTrace("Entering Reissue Enrollment");
+ //One click won't work for this implementation b/c we are missing enrollment params
+ if (productInfo.ProductParameters.ContainsKey("Applicant Last Name"))
+ {
+ var requestid = await _certificateDataReader.GetRequestIDBySerialNumber(
+ productInfo.ProductParameters["PriorCertSN"]);
+ uUId = requestid.Substring(0, 36); //uUId is a GUID
+ Logger.LogTrace($"Reissue uUId: {uUId}");
+ reissueRequest = _requestManager.GetReissueRequest(productInfo, uUId, csr, san, customFields);
+ Logger.LogTrace($"Reissue JSON: {JsonConvert.SerializeObject(reissueRequest)}");
+ var reissueResponse = Task.Run(async () => await CscGlobalClient.SubmitReissueAsync(reissueRequest))
+ .Result;
+ Logger.LogTrace($"Reissue Response JSON: {JsonConvert.SerializeObject(reissueResponse)}");
+ Logger.MethodExit(LogLevel.Debug);
+ return _requestManager.GetReIssueResult(reissueResponse);
+ }
+
+ return new EnrollmentResult
+ {
+ Status = 30, //failure
+ StatusMessage =
+ "One click Renew Is Not Available for this Certificate Type. Use the configure button instead."
+ };
}
+
+ Logger.MethodExit(LogLevel.Debug);
+ return null;
}
//done
public async Task Ping()
{
Logger.MethodEntry();
- Logger.LogTrace("Ping: Enabled={Enabled}, CscGlobalClient is {Null}", Enabled, CscGlobalClient == null ? "NULL" : "present");
-
- if (!Enabled)
- {
- Logger.LogWarning("The CA is currently in the Disabled state. It must be Enabled to perform operations. Skipping Ping.");
- Logger.MethodExit();
- return;
- }
-
try
{
Logger.LogInformation("Ping request received");
}
catch (Exception e)
{
- Logger.LogError(e, "There was an error contacting CSCGlobal: {Message}", e.Message);
+ Logger.LogError($"There was an error contacting CSCGlobal: {e.Message}.");
throw new Exception($"Error attempting to ping CSCGlobal: {e.Message}.", e);
}
@@ -1014,83 +340,19 @@ public async Task Ping()
//do
public async Task ValidateCAConnectionInfo(Dictionary connectionInfo)
{
- Logger.MethodEntry(LogLevel.Debug);
- Logger.LogTrace("ValidateCAConnectionInfo called. connectionInfo is {Null}, keys=[{Keys}]",
- connectionInfo == null ? "NULL" : "present",
- connectionInfo != null ? string.Join(", ", connectionInfo.Keys) : "");
-
- if (connectionInfo == null)
- {
- Logger.LogError("ValidateCAConnectionInfo: connectionInfo is null.");
- throw new ArgumentNullException(nameof(connectionInfo), "connectionInfo cannot be null.");
- }
-
- // Honor the Enabled flag from the incoming connectionInfo (which may differ from Initialize's
- // snapshot when the operator is currently editing the CA). If disabled, skip validation so
- // the CA can be saved without valid credentials.
- var incomingEnabled = true;
- if (connectionInfo.TryGetValue(Constants.Enabled, out var enabledObj) &&
- bool.TryParse(enabledObj?.ToString(), out var parsed))
- incomingEnabled = parsed;
-
- if (!incomingEnabled)
- {
- Logger.LogWarning("The CA is currently in the Disabled state. It must be Enabled to perform operations. Skipping ValidateCAConnectionInfo.");
- Logger.MethodExit(LogLevel.Debug);
- return;
- }
-
- Logger.MethodExit(LogLevel.Debug);
}
//do
public async Task ValidateProductInfo(EnrollmentProductInfo productInfo,
Dictionary connectionInfo)
{
- Logger.MethodEntry(LogLevel.Debug);
- Logger.LogTrace("ValidateProductInfo called. productInfo is {Null}, productId='{ProductId}'",
- productInfo == null ? "NULL" : "present",
- productInfo?.ProductID ?? "(null)");
-
- if (productInfo == null)
- {
- Logger.LogError("ValidateProductInfo: productInfo is null.");
- throw new ArgumentNullException(nameof(productInfo), "productInfo cannot be null.");
- }
-
- // Honor the Enabled flag from the incoming connectionInfo. If the CA is disabled, skip
- // validation so a template can be saved on a disabled CA (pre-configuration workflow).
- var incomingEnabled = true;
- if (connectionInfo != null &&
- connectionInfo.TryGetValue(Constants.Enabled, out var enabledObj) &&
- bool.TryParse(enabledObj?.ToString(), out var parsed))
- incomingEnabled = parsed;
-
- if (!incomingEnabled)
- {
- Logger.LogWarning("The CA is currently in the Disabled state. It must be Enabled to perform operations. Skipping ValidateProductInfo.");
- Logger.MethodExit(LogLevel.Debug);
- return;
- }
-
- if (string.IsNullOrEmpty(productInfo.ProductID))
- {
- Logger.LogError("ValidateProductInfo: productInfo.ProductID is null or empty.");
- throw new ArgumentException("ProductID cannot be null or empty.", nameof(productInfo));
- }
-
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");
- }
+ if (certType == null) throw new ArgumentException($"Cannot find {productInfo.ProductID}", "ProductId");
+
+ Logger.LogInformation($"Validated {certType} ({certType})configured for AnyGateway");
- Logger.LogInformation("Validated {CertType} configured for AnyGateway", certType);
- Logger.MethodExit(LogLevel.Debug);
}
//done
@@ -1098,13 +360,6 @@ public Dictionary GetCAConnectorAnnotations()
{
return new Dictionary
{
- [Constants.Enabled] = new()
- {
- Comments = "Flag to Enable or Disable gateway functionality. Disabling is primarily used to allow creation of the CA prior to configuration information being available.",
- Hidden = false,
- DefaultValue = true,
- Type = "Boolean"
- },
[Constants.CscGlobalUrl] = new()
{
Comments = "CSCGlobal API URL",
@@ -1133,25 +388,18 @@ public Dictionary GetCAConnectorAnnotations()
DefaultValue = "100",
Type = "String"
},
- [Constants.SyncFilterDays] = new()
+ [Constants.TemplateSync] = new()
{
- Comments = "Number of days from today to filter certificates by expiration date during incremental sync.",
+ Comments = "Enable template sync.",
Hidden = false,
- DefaultValue = "5",
- Type = "Number"
+ DefaultValue = "false",
+ Type = "Bool"
},
- [Constants.RenewalWindowDays] = new()
- {
- Comments = "Number of days before the annual order expiry within which a RenewOrReissue triggers a paid Renewal rather than a free Reissue. Default is 30.",
- Hidden = false,
- DefaultValue = "30",
- Type = "Number"
- },
- [Constants.DcvPollTimeoutSeconds] = new()
+ [Constants.SyncFilterDays] = new()
{
- Comments = "Max seconds to synchronously poll CSC for issuance after submitting an order (and publishing CNAME DCV). 0 disables polling (enrollment returns pending immediately; cert arrives on next sync). When >0, fast-validating orders can return the cert directly. Keep small to avoid long-blocking enrollment requests.",
+ Comments = "Number of days from today to filter certificates by expiration date during incremental sync.",
Hidden = false,
- DefaultValue = "0",
+ DefaultValue = "5",
Type = "Number"
}
};
@@ -1269,206 +517,6 @@ public List GetProductIds()
#region PRIVATE
- ///
- /// Strip a single trailing dot from a DNS name. CSC returns FQDN-canonical names with
- /// a trailing dot but the framework's Domain Validation Configurations are stored without
- /// one, so the strings have to be normalized before lookup or the equality check fails.
- ///
- private static string StripTrailingDot(string? s)
- {
- if (string.IsNullOrEmpty(s)) return s ?? string.Empty;
- return s.EndsWith('.') ? s[..^1] : s;
- }
-
- ///
- /// Synchronously poll CSC for issuance of the order identified by ,
- /// up to . Returns a GENERATED
- /// carrying the issued leaf certificate if CSC issues within the window, or null if the
- /// window expires (in which case the caller falls back to its pending/EXTERNALVALIDATION result).
- /// No-op (returns null) when polling is disabled or the uuid is missing.
- ///
- private async Task TryPollForIssuedCertAsync(string? uuid)
- {
- if (DcvPollTimeoutSeconds <= 0)
- {
- Logger.LogTrace("TryPollForIssuedCertAsync: polling disabled (DcvPollTimeoutSeconds=0), skipping.");
- return null;
- }
-
- if (string.IsNullOrEmpty(uuid))
- {
- Logger.LogWarning("TryPollForIssuedCertAsync: no UUID/CARequestID to poll, skipping.");
- return null;
- }
-
- var deadline = DateTime.UtcNow.AddSeconds(DcvPollTimeoutSeconds);
- Logger.LogInformation("TryPollForIssuedCertAsync: polling CSC for issuance of '{Uuid}' for up to {Seconds}s (interval {Interval}s).",
- uuid, DcvPollTimeoutSeconds, (int)DcvPollInterval.TotalSeconds);
-
- var attempt = 0;
- while (DateTime.UtcNow < deadline)
- {
- attempt++;
- AnyCAPluginCertificate record;
- try
- {
- record = await GetSingleRecord(uuid);
- }
- catch (Exception ex)
- {
- Logger.LogWarning(ex, "TryPollForIssuedCertAsync: poll attempt {Attempt} for '{Uuid}' threw, will retry. {Error}",
- attempt, uuid, ex.Message);
- record = null;
- }
-
- if (record != null)
- {
- Logger.LogTrace("TryPollForIssuedCertAsync: attempt {Attempt} for '{Uuid}' — status={Status}, cert={CertState}.",
- attempt, uuid, record.Status, string.IsNullOrEmpty(record.Certificate) ? "empty" : "present");
-
- if (record.Status == (int)EndEntityStatus.GENERATED && !string.IsNullOrEmpty(record.Certificate))
- {
- Logger.LogInformation("TryPollForIssuedCertAsync: '{Uuid}' issued after {Attempt} poll(s); returning cert directly.", uuid, attempt);
- return new EnrollmentResult
- {
- Status = (int)EndEntityStatus.GENERATED,
- CARequestID = uuid,
- Certificate = record.Certificate,
- StatusMessage = $"Certificate issued and retrieved for order {uuid}."
- };
- }
- }
-
- // Don't sleep past the deadline.
- if (DateTime.UtcNow.Add(DcvPollInterval) >= deadline)
- break;
-
- await Task.Delay(DcvPollInterval);
- }
-
- Logger.LogInformation("TryPollForIssuedCertAsync: '{Uuid}' not issued within {Seconds}s after {Attempts} attempt(s); falling back to pending.",
- uuid, DcvPollTimeoutSeconds, attempt);
- return null;
- }
-
- ///
- /// Publishes CNAME DCV records via the gateway framework's .
- /// Per-record resolution: each record is routed to whichever DNS provider plugin the framework
- /// 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.
- ///
- private async Task TryPublishCnameDcvAsync(EnrollmentProductInfo productInfo, EnrollmentResult? enrollResult)
- {
- if (_validatorFactory == null)
- {
- Logger.LogTrace("TryPublishCnameDcvAsync: no IDomainValidatorFactory was injected, skipping auto-publish.");
- return;
- }
-
- if (enrollResult?.EnrollmentContext == null || enrollResult.EnrollmentContext.Count == 0)
- {
- Logger.LogTrace("TryPublishCnameDcvAsync: no CNAME entries in EnrollmentContext, skipping.");
- return;
- }
-
- var dcvMethod = productInfo?.ProductParameters != null
- && productInfo.ProductParameters.TryGetValue(EnrollmentConfigConstants.DomainControlValidationMethod, out var m)
- ? m
- : null;
-
- if (string.IsNullOrEmpty(dcvMethod) ||
- !string.Equals(dcvMethod, "CNAME", StringComparison.OrdinalIgnoreCase))
- {
- Logger.LogTrace("TryPublishCnameDcvAsync: DCV method '{Method}' is not CNAME, skipping auto-publish.", dcvMethod ?? "(null)");
- return;
- }
-
- Logger.LogInformation(
- "TryPublishCnameDcvAsync: attempting to publish {Count} CNAME record(s) via framework DNS providers (validation type '{Type}').",
- enrollResult.EnrollmentContext.Count, DNS_VALIDATION_TYPE);
-
- var successCount = 0;
- var failCount = 0;
- var unresolvedCount = 0;
-
- foreach (var entry in enrollResult.EnrollmentContext)
- {
- var rawRecordName = entry.Key;
- var rawCnameTarget = entry.Value;
-
- // CSC may also surface DCV email entries in this dictionary (key == value). Skip those.
- if (string.Equals(rawRecordName, rawCnameTarget, StringComparison.OrdinalIgnoreCase))
- {
- Logger.LogTrace("TryPublishCnameDcvAsync: skipping entry '{Key}' (looks like an email DCV passthrough, not a CNAME).", rawRecordName);
- continue;
- }
-
- // CSC returns FQDN-canonical names with trailing dots (e.g. "foo.example.com.").
- // The framework's Domain Validation Configuration stores domain patterns without
- // the trailing dot, so strip it before resolution and publishing or no provider
- // will match (the framework will look up "*.example.com." which won't equal "*.example.com").
- var recordName = StripTrailingDot(rawRecordName);
- var cnameTarget = StripTrailingDot(rawCnameTarget);
-
- if (recordName != rawRecordName)
- Logger.LogTrace("TryPublishCnameDcvAsync: normalized record name '{Raw}' -> '{Normalized}'.", rawRecordName, recordName);
-
- IDomainValidator? validator;
- try
- {
- validator = _validatorFactory.ResolveDomainValidator(recordName, DNS_VALIDATION_TYPE);
- }
- catch (Exception ex)
- {
- unresolvedCount++;
- Logger.LogWarning(ex, "ResolveDomainValidator threw for '{Record}' (type '{Type}'): {Error}",
- recordName, DNS_VALIDATION_TYPE, ex.Message);
- continue;
- }
-
- if (validator == null)
- {
- unresolvedCount++;
- Logger.LogWarning(
- "No DNS provider matched domain '{Record}' for validation type '{Type}'. Manual publish required for this record.",
- recordName, DNS_VALIDATION_TYPE);
- continue;
- }
-
- try
- {
- Logger.LogTrace("StageValidation: '{Name}' -> '{Target}' via validator type '{ValType}'.",
- recordName, cnameTarget, validator.GetValidationType());
- var result = await validator.StageValidation(recordName, cnameTarget, CancellationToken.None);
-
- if (result?.Success == true)
- {
- successCount++;
- Logger.LogInformation("Published CNAME '{Name}' -> '{Target}' (status='{Status}').",
- recordName, cnameTarget, result.Status ?? "(none)");
- }
- else
- {
- failCount++;
- Logger.LogWarning(
- "StageValidation reported failure for CNAME '{Name}'. Status='{Status}', Error='{Error}'. Manual publish may be required.",
- recordName, result?.Status ?? "(none)", result?.ErrorMessage ?? "(none)");
- }
- }
- catch (Exception ex)
- {
- failCount++;
- Logger.LogError(ex, "StageValidation threw publishing CNAME '{Name}'. Manual publish may be required. {Error}",
- recordName, ex.Message);
- }
- }
-
- Logger.LogInformation(
- "TryPublishCnameDcvAsync: complete. Published={Published}, Failed={Failed}, Unresolved={Unresolved}",
- successCount, failCount, unresolvedCount);
- }
-
//Trying to fix leaf extraction
private static readonly Regex PemBlock = new(
"-----BEGIN CERTIFICATE-----\\s*(?[A-Za-z0-9+/=\\r\\n]+?)\\s*-----END CERTIFICATE-----",
diff --git a/cscglobal-caplugin/CSCGlobalCAPlugin.csproj b/cscglobal-caplugin/CSCGlobalCAPlugin.csproj
index e5f5ff7..5118677 100644
--- a/cscglobal-caplugin/CSCGlobalCAPlugin.csproj
+++ b/cscglobal-caplugin/CSCGlobalCAPlugin.csproj
@@ -3,7 +3,7 @@
true
- net10.0
+ net6.0;net8.0
Keyfactor.Extensions.CAPlugin.CSCGlobal
true
enable
@@ -16,16 +16,24 @@
-
-
+
+
+
+
+
+
+
+
+
+
Always
-
+
\ No newline at end of file
diff --git a/cscglobal-caplugin/Client/CscGlobalClient.cs b/cscglobal-caplugin/Client/CscGlobalClient.cs
index 032f535..0a5c7c5 100644
--- a/cscglobal-caplugin/Client/CscGlobalClient.cs
+++ b/cscglobal-caplugin/Client/CscGlobalClient.cs
@@ -23,58 +23,13 @@ public sealed class CscGlobalClient : ICscGlobalClient
public CscGlobalClient(IAnyCAPluginConfigProvider config)
{
- Logger = LogHandler.GetClassLogger();
-
- if (config == null)
- throw new ArgumentNullException(nameof(config), "config cannot be null in CscGlobalClient constructor.");
-
- if (config.CAConnectionData == null)
- throw new InvalidOperationException("CAConnectionData is null on config provider.");
-
- Logger.LogTrace("CscGlobalClient: CAConnectionData keys=[{Keys}]", string.Join(", ", config.CAConnectionData.Keys));
-
+ Logger = LogHandler.GetClassLogger();
if (config.CAConnectionData.ContainsKey(Constants.CscGlobalApiKey))
{
- var rawUrl = config.CAConnectionData.ContainsKey(Constants.CscGlobalUrl)
- ? config.CAConnectionData[Constants.CscGlobalUrl]?.ToString()
- : null;
- if (string.IsNullOrEmpty(rawUrl))
- {
- Logger.LogError("CscGlobalClient: CscGlobalUrl is missing or empty in CAConnectionData.");
- throw new InvalidOperationException("CscGlobalUrl is required but was not configured.");
- }
-
- Logger.LogTrace("CscGlobalClient: BaseUrl='{BaseUrl}'", rawUrl);
- BaseUrl = new Uri(rawUrl);
-
- ApiKey = config.CAConnectionData[Constants.CscGlobalApiKey]?.ToString();
- if (string.IsNullOrEmpty(ApiKey))
- {
- Logger.LogError("CscGlobalClient: ApiKey is empty or null.");
- throw new InvalidOperationException("ApiKey is required but was not configured.");
- }
- Logger.LogTrace("CscGlobalClient: ApiKey is present (length={Length}).", ApiKey.Length);
-
- if (!config.CAConnectionData.ContainsKey(Constants.BearerToken))
- {
- Logger.LogError("CscGlobalClient: BearerToken key not found in CAConnectionData.");
- throw new InvalidOperationException("BearerToken is required but was not configured.");
- }
- Authorization = config.CAConnectionData[Constants.BearerToken]?.ToString();
- if (string.IsNullOrEmpty(Authorization))
- {
- Logger.LogError("CscGlobalClient: BearerToken is empty or null.");
- throw new InvalidOperationException("BearerToken is required but was empty.");
- }
- Logger.LogTrace("CscGlobalClient: BearerToken is present (length={Length}).", Authorization.Length);
-
+ BaseUrl = new Uri(config.CAConnectionData[Constants.CscGlobalUrl].ToString());
+ ApiKey = config.CAConnectionData[Constants.CscGlobalApiKey].ToString();
+ Authorization = config.CAConnectionData[Constants.BearerToken].ToString();
RestClient = ConfigureRestClient();
- Logger.LogTrace("CscGlobalClient: RestClient configured successfully.");
- }
- else
- {
- Logger.LogError("CscGlobalClient: ApiKey key '{Key}' not found in CAConnectionData. Client will not be functional.", Constants.CscGlobalApiKey);
- throw new InvalidOperationException($"Required key '{Constants.CscGlobalApiKey}' not found in CAConnectionData.");
}
}
@@ -86,42 +41,25 @@ public CscGlobalClient(IAnyCAPluginConfigProvider config)
public async Task SubmitRegistrationAsync(
RegistrationRequest registerRequest)
{
- Logger.LogTrace("SubmitRegistrationAsync: sending registration request...");
- if (registerRequest == null)
- throw new ArgumentNullException(nameof(registerRequest));
-
- var requestJson = JsonConvert.SerializeObject(registerRequest);
- Logger.LogTrace("SubmitRegistrationAsync: request JSON: {Json}", requestJson);
-
using (var resp = await RestClient.PostAsync("/dbs/api/v2/tls/registration", new StringContent(
- requestJson, Encoding.ASCII, "application/json")))
+ JsonConvert.SerializeObject(registerRequest), Encoding.ASCII, "application/json")))
{
- var rawBody = await resp.Content.ReadAsStringAsync();
- Logger.LogTrace("SubmitRegistrationAsync: HTTP {StatusCode}, body length={Length}", (int)resp.StatusCode, rawBody?.Length ?? 0);
- Logger.LogTrace("SubmitRegistrationAsync: response body: {Body}", rawBody ?? "(null)");
-
+ Logger.LogTrace(JsonConvert.SerializeObject(registerRequest));
var settings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
- if (resp.StatusCode == HttpStatusCode.BadRequest)
+ if (resp.StatusCode == HttpStatusCode.BadRequest) //Csc Sends Errors back in 400 Json Response
{
- Logger.LogWarning("SubmitRegistrationAsync: received 400 BadRequest.");
- var errorResponse = JsonConvert.DeserializeObject(rawBody ?? "{}", settings);
- Logger.LogTrace("SubmitRegistrationAsync: error description='{Desc}'", errorResponse?.Description ?? "(null)");
+ var errorResponse =
+ JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync(),
+ settings);
var response = new RegistrationResponse();
response.RegistrationError = errorResponse;
response.Result = null;
return response;
}
- if (!resp.IsSuccessStatusCode)
- {
- Logger.LogError("SubmitRegistrationAsync: unexpected HTTP {StatusCode}: {Body}", (int)resp.StatusCode, rawBody);
- throw new HttpRequestException($"SubmitRegistrationAsync failed with HTTP {(int)resp.StatusCode}: {rawBody}");
- }
-
- var registrationResponse = JsonConvert.DeserializeObject(rawBody ?? "{}", settings);
- Logger.LogTrace("SubmitRegistrationAsync: deserialized response. Result is {Null}, RegistrationError is {Null2}",
- registrationResponse?.Result == null ? "null" : "present",
- registrationResponse?.RegistrationError == null ? "null" : "present");
+ var registrationResponse =
+ JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync(),
+ settings);
return registrationResponse;
}
}
@@ -129,42 +67,31 @@ public async Task SubmitRegistrationAsync(
public async Task SubmitRenewalAsync(
RenewalRequest renewalRequest)
{
- Logger.LogTrace("SubmitRenewalAsync: sending renewal request...");
- if (renewalRequest == null)
- throw new ArgumentNullException(nameof(renewalRequest));
-
- var requestJson = JsonConvert.SerializeObject(renewalRequest);
- Logger.LogTrace("SubmitRenewalAsync: request JSON: {Json}", requestJson);
-
using (var resp = await RestClient.PostAsync("/dbs/api/v2/tls/renewal", new StringContent(
- requestJson, Encoding.ASCII, "application/json")))
+ JsonConvert.SerializeObject(renewalRequest), Encoding.ASCII, "application/json")))
{
- var rawBody = await resp.Content.ReadAsStringAsync();
- Logger.LogTrace("SubmitRenewalAsync: HTTP {StatusCode}, body length={Length}", (int)resp.StatusCode, rawBody?.Length ?? 0);
- Logger.LogTrace("SubmitRenewalAsync: response body: {Body}", rawBody ?? "(null)");
+ Logger.LogTrace(JsonConvert.SerializeObject(renewalRequest));
var settings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
- if (resp.StatusCode == HttpStatusCode.BadRequest)
- {
- Logger.LogWarning("SubmitRenewalAsync: received 400 BadRequest.");
- var errorResponse = JsonConvert.DeserializeObject(rawBody ?? "{}", settings);
- Logger.LogTrace("SubmitRenewalAsync: error description='{Desc}'", errorResponse?.Description ?? "(null)");
+ if (resp.StatusCode == HttpStatusCode.BadRequest) //Csc Sends Errors back in 400 Json Response
+ {
+ var rawErrorResponse = await resp.Content.ReadAsStringAsync();
+ Logger.LogTrace("Logging Error Response Raw");
+ Logger.LogTrace(rawErrorResponse);
+ var errorResponse =
+ JsonConvert.DeserializeObject(rawErrorResponse,
+ settings);
var response = new RenewalResponse();
response.RegistrationError = errorResponse;
response.Result = null;
return response;
}
- if (!resp.IsSuccessStatusCode)
- {
- Logger.LogError("SubmitRenewalAsync: unexpected HTTP {StatusCode}: {Body}", (int)resp.StatusCode, rawBody);
- throw new HttpRequestException($"SubmitRenewalAsync failed with HTTP {(int)resp.StatusCode}: {rawBody}");
- }
-
- var renewalResponse = JsonConvert.DeserializeObject(rawBody ?? "{}");
- Logger.LogTrace("SubmitRenewalAsync: deserialized response. Result is {Null}, RegistrationError is {Null2}",
- renewalResponse?.Result == null ? "null" : "present",
- renewalResponse?.RegistrationError == null ? "null" : "present");
+ var rawRenewResponse = await resp.Content.ReadAsStringAsync();
+ Logger.LogTrace("Logging Success Response Raw");
+ Logger.LogTrace(rawRenewResponse);
+ var renewalResponse =
+ JsonConvert.DeserializeObject(rawRenewResponse);
return renewalResponse;
}
}
@@ -172,145 +99,69 @@ public async Task SubmitRenewalAsync(
public async Task SubmitReissueAsync(
ReissueRequest reissueRequest)
{
- Logger.LogTrace("SubmitReissueAsync: sending reissue request...");
- if (reissueRequest == null)
- throw new ArgumentNullException(nameof(reissueRequest));
-
- var requestJson = JsonConvert.SerializeObject(reissueRequest);
- Logger.LogTrace("SubmitReissueAsync: request JSON: {Json}", requestJson);
-
using (var resp = await RestClient.PostAsync("/dbs/api/v2/tls/reissue", new StringContent(
- requestJson, Encoding.ASCII, "application/json")))
+ JsonConvert.SerializeObject(reissueRequest), Encoding.ASCII, "application/json")))
{
- var rawBody = await resp.Content.ReadAsStringAsync();
- Logger.LogTrace("SubmitReissueAsync: HTTP {StatusCode}, body length={Length}", (int)resp.StatusCode, rawBody?.Length ?? 0);
- Logger.LogTrace("SubmitReissueAsync: response body: {Body}", rawBody ?? "(null)");
+ Logger.LogTrace(JsonConvert.SerializeObject(reissueRequest));
var settings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
- if (resp.StatusCode == HttpStatusCode.BadRequest)
+ if (resp.StatusCode == HttpStatusCode.BadRequest) //Csc Sends Errors back in 400 Json Response
{
- Logger.LogWarning("SubmitReissueAsync: received 400 BadRequest.");
- var errorResponse = JsonConvert.DeserializeObject(rawBody ?? "{}", settings);
- Logger.LogTrace("SubmitReissueAsync: error description='{Desc}'", errorResponse?.Description ?? "(null)");
+ var errorResponse =
+ JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync(),
+ settings);
var response = new ReissueResponse();
response.RegistrationError = errorResponse;
response.Result = null;
return response;
}
- if (!resp.IsSuccessStatusCode)
- {
- Logger.LogError("SubmitReissueAsync: unexpected HTTP {StatusCode}: {Body}", (int)resp.StatusCode, rawBody);
- throw new HttpRequestException($"SubmitReissueAsync failed with HTTP {(int)resp.StatusCode}: {rawBody}");
- }
-
- var reissueResponse = JsonConvert.DeserializeObject(rawBody ?? "{}");
- Logger.LogTrace("SubmitReissueAsync: deserialized response. Result is {Null}, RegistrationError is {Null2}",
- reissueResponse?.Result == null ? "null" : "present",
- reissueResponse?.RegistrationError == null ? "null" : "present");
+ var reissueResponse =
+ JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync());
return reissueResponse;
}
}
public async Task SubmitGetCertificateAsync(string certificateId)
{
- Logger.LogTrace("SubmitGetCertificateAsync: fetching certificate for id='{CertificateId}'", certificateId ?? "(null)");
-
- if (string.IsNullOrEmpty(certificateId))
- throw new ArgumentNullException(nameof(certificateId), "certificateId cannot be null or empty.");
-
using (var resp = await RestClient.GetAsync($"/dbs/api/v2/tls/certificate/{certificateId}"))
{
- var rawBody = await resp.Content.ReadAsStringAsync();
- Logger.LogTrace("SubmitGetCertificateAsync: HTTP {StatusCode}, body length={Length}", (int)resp.StatusCode, rawBody?.Length ?? 0);
-
- if (!resp.IsSuccessStatusCode)
- {
- Logger.LogError("SubmitGetCertificateAsync: HTTP {StatusCode} for certificateId='{CertificateId}': {Body}",
- (int)resp.StatusCode, certificateId, rawBody);
- resp.EnsureSuccessStatusCode(); // will throw
- }
-
- Logger.LogTrace("SubmitGetCertificateAsync: response body: {Body}", rawBody ?? "(null)");
- var getCertificateResponse = JsonConvert.DeserializeObject(rawBody ?? "{}");
- Logger.LogTrace("SubmitGetCertificateAsync: deserialized. Status='{Status}', OrderDate='{OrderDate}', Certificate is {Null}",
- getCertificateResponse?.Status ?? "(null)",
- getCertificateResponse?.OrderDate ?? "(null)",
- string.IsNullOrEmpty(getCertificateResponse?.Certificate) ? "empty/null" : "present");
+ resp.EnsureSuccessStatusCode();
+ var getCertificateResponse =
+ JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync());
return getCertificateResponse;
}
}
public async Task> SubmitGetCustomFields()
{
- Logger.LogTrace("SubmitGetCustomFields: fetching custom fields...");
-
using (var resp = await RestClient.GetAsync("/dbs/api/v2/admin/customfields"))
{
- var rawBody = await resp.Content.ReadAsStringAsync();
- Logger.LogTrace("SubmitGetCustomFields: HTTP {StatusCode}, body length={Length}", (int)resp.StatusCode, rawBody?.Length ?? 0);
-
- if (!resp.IsSuccessStatusCode)
- {
- Logger.LogError("SubmitGetCustomFields: HTTP {StatusCode}: {Body}", (int)resp.StatusCode, rawBody);
- resp.EnsureSuccessStatusCode(); // will throw
- }
-
- Logger.LogTrace("SubmitGetCustomFields: response body: {Body}", rawBody ?? "(null)");
- var getCustomFieldsResponse = JsonConvert.DeserializeObject(rawBody ?? "{}");
-
- if (getCustomFieldsResponse == null)
- {
- Logger.LogWarning("SubmitGetCustomFields: deserialized response is null, returning empty list.");
- return new List();
- }
-
- if (getCustomFieldsResponse.CustomFields == null)
- {
- Logger.LogWarning("SubmitGetCustomFields: CustomFields property is null, returning empty list.");
- return new List();
- }
-
- Logger.LogTrace("SubmitGetCustomFields: received {Count} custom fields.", getCustomFieldsResponse.CustomFields.Count);
+ resp.EnsureSuccessStatusCode();
+ var getCustomFieldsResponse =
+ JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync());
return getCustomFieldsResponse.CustomFields;
}
}
public async Task SubmitRevokeCertificateAsync(string uuId)
{
- Logger.LogTrace("SubmitRevokeCertificateAsync: revoking certificate UUID='{Uuid}'", uuId ?? "(null)");
-
- if (string.IsNullOrEmpty(uuId))
- throw new ArgumentNullException(nameof(uuId), "uuId cannot be null or empty.");
-
using (var resp = await RestClient.PutAsync($"/dbs/api/v2/tls/revoke/{uuId}", new StringContent("")))
{
- var rawBody = await resp.Content.ReadAsStringAsync();
- Logger.LogTrace("SubmitRevokeCertificateAsync: HTTP {StatusCode}, body length={Length}", (int)resp.StatusCode, rawBody?.Length ?? 0);
- Logger.LogTrace("SubmitRevokeCertificateAsync: response body: {Body}", rawBody ?? "(null)");
-
var settings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
- if (resp.StatusCode == HttpStatusCode.BadRequest)
+ if (resp.StatusCode == HttpStatusCode.BadRequest) //Csc Sends Errors back in 400 Json Response
{
- Logger.LogWarning("SubmitRevokeCertificateAsync: received 400 BadRequest for UUID='{Uuid}'.", uuId);
- var errorResponse = JsonConvert.DeserializeObject(rawBody ?? "{}", settings);
- Logger.LogTrace("SubmitRevokeCertificateAsync: error description='{Desc}'", errorResponse?.Description ?? "(null)");
+ var errorResponse =
+ JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync(),
+ settings);
var response = new RevokeResponse();
response.RegistrationError = errorResponse;
response.RevokeSuccess = null;
return response;
}
- if (!resp.IsSuccessStatusCode)
- {
- Logger.LogError("SubmitRevokeCertificateAsync: unexpected HTTP {StatusCode} for UUID='{Uuid}': {Body}", (int)resp.StatusCode, uuId, rawBody);
- throw new HttpRequestException($"SubmitRevokeCertificateAsync failed with HTTP {(int)resp.StatusCode}: {rawBody}");
- }
-
- var getRevokeResponse = JsonConvert.DeserializeObject(rawBody ?? "{}");
- Logger.LogTrace("SubmitRevokeCertificateAsync: deserialized. RevokeSuccess is {Null}, RegistrationError is {Null2}",
- getRevokeResponse?.RevokeSuccess == null ? "null" : "present",
- getRevokeResponse?.RegistrationError == null ? "null" : "present");
+ var getRevokeResponse =
+ JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync());
return getRevokeResponse;
}
}
@@ -318,37 +169,23 @@ public async Task SubmitRevokeCertificateAsync(string uuId)
public async Task SubmitCertificateListRequestAsync(string? dateFilter = null)
{
Logger.MethodEntry(LogLevel.Debug);
- Logger.LogTrace("SubmitCertificateListRequestAsync: dateFilter='{DateFilter}'", dateFilter ?? "(null)");
-
var filterQuery = "filter=status=in=(ACTIVE,REVOKED)";
if (!string.IsNullOrEmpty(dateFilter))
{
filterQuery += $";effectiveDate=ge={dateFilter}";
}
- Logger.LogTrace("SubmitCertificateListRequestAsync: filter query: {FilterQuery}", filterQuery);
-
+ Logger.LogTrace($"Certificate list filter query: {filterQuery}");
var resp = RestClient.GetAsync($"/dbs/api/v2/tls/certificate?{filterQuery}").Result;
- var rawBody = await resp.Content.ReadAsStringAsync();
- Logger.LogTrace("SubmitCertificateListRequestAsync: HTTP {StatusCode}, body length={Length}", (int)resp.StatusCode, rawBody?.Length ?? 0);
if (!resp.IsSuccessStatusCode)
{
+ var responseMessage = resp.Content.ReadAsStringAsync().Result;
Logger.LogError(
- "SubmitCertificateListRequestAsync: failed request. StatusCode={StatusCode}, Body={Body}",
- (int)resp.StatusCode, rawBody);
- }
-
- var certificateListResponse = JsonConvert.DeserializeObject(rawBody ?? "{}");
-
- if (certificateListResponse == null)
- {
- Logger.LogWarning("SubmitCertificateListRequestAsync: deserialized response is null.");
- return new CertificateListResponse();
+ $"Failed Request to Keyfactor. Retrying request. Status Code {resp.StatusCode} | Message: {responseMessage}");
}
- Logger.LogTrace("SubmitCertificateListRequestAsync: Results count={Count}",
- certificateListResponse.Results?.Count ?? 0);
- Logger.MethodExit(LogLevel.Debug);
+ var certificateListResponse =
+ JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync());
return certificateListResponse;
}
diff --git a/cscglobal-caplugin/Constants.cs b/cscglobal-caplugin/Constants.cs
index dc10866..4d6b4da 100644
--- a/cscglobal-caplugin/Constants.cs
+++ b/cscglobal-caplugin/Constants.cs
@@ -9,14 +9,12 @@ namespace Keyfactor.Extensions.CAPlugin.CSCGlobal;
public class Constants
{
- public static string Enabled = "Enabled";
public static string CscGlobalUrl = "CscGlobalUrl";
public static string CscGlobalApiKey = "ApiKey";
public static string BearerToken = "BearerToken";
public static string DefaultPageSize = "DefaultPageSize";
+ public static string TemplateSync = "TemplateSync";
public static string SyncFilterDays = "SyncFilterDays";
- public static string RenewalWindowDays = "RenewalWindowDays";
- public static string DcvPollTimeoutSeconds = "DcvPollTimeoutSeconds";
}
public class ProductIDs
@@ -28,8 +26,8 @@ public class ProductIDs
"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 Domain Validated Wildcard SSL",
+ "CSC TrustedSecure Domain Validated UC Certificate"
};
}
diff --git a/cscglobal-caplugin/FlowLogger.cs b/cscglobal-caplugin/FlowLogger.cs
deleted file mode 100644
index 5696fcd..0000000
--- a/cscglobal-caplugin/FlowLogger.cs
+++ /dev/null
@@ -1,241 +0,0 @@
-// Copyright 2021 Keyfactor
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
-// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
-// and limitations under the License.
-
-using System.Diagnostics;
-using System.Text;
-using Microsoft.Extensions.Logging;
-
-namespace Keyfactor.Extensions.CAPlugin.CSCGlobal;
-
-public enum FlowStepStatus
-{
- Success,
- Failed,
- Skipped,
- InProgress
-}
-
-public class FlowStep
-{
- public string Name { get; set; }
- public FlowStepStatus Status { get; set; }
- public string Detail { get; set; }
- public long ElapsedMs { get; set; }
- public List Children { get; } = new();
-}
-
-///
-/// Tracks high-level operation flow and renders a visual step diagram to Trace logs.
-/// Usage:
-/// using var flow = new FlowLogger(logger, "Enroll-New");
-/// flow.Step("ParseCSR");
-/// flow.Step("ValidateCSR", () => { ... });
-/// flow.Fail("CreateOrder", "API returned 400");
-/// // flow renders automatically on Dispose
-///
-public sealed class FlowLogger : IDisposable
-{
- private readonly ILogger _logger;
- private readonly string _flowName;
- private readonly Stopwatch _totalTimer;
- private readonly List _steps = new();
- private FlowStep _currentParent;
- private bool _disposed;
-
- public FlowLogger(ILogger logger, string flowName)
- {
- _logger = logger;
- _flowName = flowName;
- _totalTimer = Stopwatch.StartNew();
- _logger.LogTrace("===== FLOW START: {FlowName} =====", _flowName);
- }
-
- /// Record a completed step.
- public FlowLogger Step(string name, string detail = null)
- {
- var step = new FlowStep { Name = name, Status = FlowStepStatus.Success, Detail = detail };
- AddStep(step);
- _logger.LogTrace(" [{FlowName}] {StepName} ... OK{Detail}",
- _flowName, name, detail != null ? $" ({detail})" : "");
- return this;
- }
-
- /// Record a step that executes an action and times it.
- public FlowLogger Step(string name, Action action, string detail = null)
- {
- var sw = Stopwatch.StartNew();
- var step = new FlowStep { Name = name, Detail = detail };
- try
- {
- _logger.LogTrace(" [{FlowName}] {StepName} ...", _flowName, name);
- action();
- sw.Stop();
- step.Status = FlowStepStatus.Success;
- step.ElapsedMs = sw.ElapsedMilliseconds;
- 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 an async step that executes and times it.
- public async Task StepAsync(string name, Func action, string detail = null)
- {
- var sw = Stopwatch.StartNew();
- var step = new FlowStep { Name = name, Detail = detail };
- try
- {
- _logger.LogTrace(" [{FlowName}] {StepName} ...", _flowName, name);
- await action();
- sw.Stop();
- step.Status = FlowStepStatus.Success;
- step.ElapsedMs = sw.ElapsedMilliseconds;
- 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)
- {
- var step = new FlowStep { Name = name, Status = FlowStepStatus.Failed, Detail = reason };
- AddStep(step);
- _logger.LogTrace(" [{FlowName}] {StepName} ... FAILED{Reason}",
- _flowName, name, reason != null ? $": {reason}" : "");
- return this;
- }
-
- /// Record a skipped step.
- public FlowLogger Skip(string name, string reason = null)
- {
- var step = new FlowStep { Name = name, Status = FlowStepStatus.Skipped, Detail = reason };
- AddStep(step);
- _logger.LogTrace(" [{FlowName}] {StepName} ... SKIPPED{Reason}",
- _flowName, name, reason != null ? $": {reason}" : "");
- return this;
- }
-
- /// Start a branch (group of child steps).
- public FlowLogger Branch(string name)
- {
- var step = new FlowStep { Name = name, Status = FlowStepStatus.InProgress };
- AddStep(step);
- _currentParent = step;
- _logger.LogTrace(" [{FlowName}] >> Branch: {BranchName}", _flowName, name);
- return this;
- }
-
- /// End the current branch.
- public FlowLogger EndBranch()
- {
- _currentParent = null;
- return this;
- }
-
- private void AddStep(FlowStep step)
- {
- if (_currentParent != null)
- _currentParent.Children.Add(step);
- else
- _steps.Add(step);
- }
-
- /// Render the visual flow diagram to Trace log.
- private string RenderFlow()
- {
- var sb = new StringBuilder();
- sb.AppendLine();
- sb.AppendLine($" ===== FLOW: {_flowName} ({_totalTimer.ElapsedMilliseconds}ms total) =====");
- sb.AppendLine();
-
- for (var i = 0; i < _steps.Count; i++)
- {
- var step = _steps[i];
- var icon = GetStatusIcon(step.Status);
- var elapsed = step.ElapsedMs > 0 ? $" ({step.ElapsedMs}ms)" : "";
- var detail = !string.IsNullOrEmpty(step.Detail) ? $" [{step.Detail}]" : "";
-
- sb.AppendLine($" {icon} {step.Name}{elapsed}{detail}");
-
- // Render children (branch)
- if (step.Children.Count > 0)
- {
- for (var j = 0; j < step.Children.Count; j++)
- {
- var child = step.Children[j];
- var childIcon = GetStatusIcon(child.Status);
- var childElapsed = child.ElapsedMs > 0 ? $" ({child.ElapsedMs}ms)" : "";
- var childDetail = !string.IsNullOrEmpty(child.Detail) ? $" [{child.Detail}]" : "";
- var connector = j < step.Children.Count - 1 ? "| " : " ";
- sb.AppendLine($" |");
- sb.AppendLine($" +-- {childIcon} {child.Name}{childElapsed}{childDetail}");
- }
- }
-
- // Connector between top-level steps
- if (i < _steps.Count - 1)
- {
- sb.AppendLine(" |");
- sb.AppendLine(" v");
- }
- }
-
- sb.AppendLine();
-
- // Final status line
- var finalStatus = _steps.Count > 0 && _steps.Last().Status == FlowStepStatus.Failed
- ? "FAILED" : _steps.Any(s => s.Status == FlowStepStatus.Failed) ? "PARTIAL FAILURE" : "SUCCESS";
- sb.AppendLine($" ===== FLOW RESULT: {finalStatus} =====");
-
- return sb.ToString();
- }
-
- private static string GetStatusIcon(FlowStepStatus status)
- {
- return status switch
- {
- FlowStepStatus.Success => "[OK]",
- FlowStepStatus.Failed => "[FAIL]",
- FlowStepStatus.Skipped => "[SKIP]",
- FlowStepStatus.InProgress => "[...]",
- _ => "[?]"
- };
- }
-
- public void Dispose()
- {
- if (_disposed) return;
- _disposed = true;
- _totalTimer.Stop();
- _logger.LogTrace(RenderFlow());
- }
-}
diff --git a/cscglobal-caplugin/RequestManager.cs b/cscglobal-caplugin/RequestManager.cs
index faf02ac..776902c 100644
--- a/cscglobal-caplugin/RequestManager.cs
+++ b/cscglobal-caplugin/RequestManager.cs
@@ -10,48 +10,19 @@
using Keyfactor.AnyGateway.Extensions;
using Keyfactor.Extensions.CAPlugin.CSCGlobal.Client.Models;
using Keyfactor.Extensions.CAPlugin.CSCGlobal.Interfaces;
-using Keyfactor.Logging;
using Keyfactor.PKI.Enums.EJBCA;
-using Microsoft.Extensions.Logging;
namespace Keyfactor.Extensions.CAPlugin.CSCGlobal;
public class RequestManager
{
- private readonly ILogger Logger = LogHandler.GetClassLogger();
public static Func Pemify = ss =>
ss.Length <= 64 ? ss : ss.Substring(0, 64) + "\n" + Pemify(ss.Substring(64));
private List GetCustomFields(EnrollmentProductInfo productInfo, List customFields)
{
- Logger.LogTrace("GetCustomFields: productInfo is {Null}, customFields count={Count}",
- productInfo == null ? "NULL" : "present",
- customFields?.Count ?? 0);
-
var customFieldList = new List();
- if (customFields == null || productInfo?.ProductParameters == null)
- {
- Logger.LogTrace("GetCustomFields: returning empty list (null customFields or ProductParameters).");
- return customFieldList;
- }
-
foreach (var field in customFields)
- {
- if (field == null)
- {
- Logger.LogTrace("GetCustomFields: skipping null field entry.");
- continue;
- }
-
- Logger.LogTrace("GetCustomFields: checking field Label='{Label}', Mandatory={Mandatory}",
- field.Label ?? "(null)", field.Mandatory);
-
- if (string.IsNullOrEmpty(field.Label))
- {
- Logger.LogTrace("GetCustomFields: skipping field with null/empty label.");
- continue;
- }
-
if (productInfo.ProductParameters.ContainsKey(field.Label))
{
var newField = new CustomField
@@ -59,60 +30,32 @@ private List GetCustomFields(EnrollmentProductInfo productInfo, Lis
Name = field.Label,
Value = productInfo.ProductParameters[field.Label]
};
- Logger.LogTrace("GetCustomFields: matched field '{Label}' = '{Value}'", field.Label, newField.Value ?? "(null)");
customFieldList.Add(newField);
}
else if (field.Mandatory)
{
- Logger.LogError("GetCustomFields: mandatory field '{Label}' was not supplied. Available keys: [{Keys}]",
- field.Label, string.Join(", ", productInfo.ProductParameters.Keys));
throw new Exception(
$"Custom field {field.Label} is marked as mandatory, but was not supplied in the request.");
}
- else
- {
- Logger.LogTrace("GetCustomFields: optional field '{Label}' not found in ProductParameters, skipping.", field.Label);
- }
- }
- Logger.LogTrace("GetCustomFields: returning {Count} custom fields.", customFieldList.Count);
return customFieldList;
}
public EnrollmentResult GetRenewResponse(RenewalResponse renewResponse)
{
- Logger.LogTrace("GetRenewResponse: renewResponse is {Null}", renewResponse == null ? "NULL" : "present");
-
- if (renewResponse == null)
- {
- Logger.LogError("GetRenewResponse: renewResponse is null.");
- return new EnrollmentResult
- {
- Status = (int)EndEntityStatus.FAILED,
- StatusMessage = "Renewal failed: received null response from CSC."
- };
- }
-
if (renewResponse.RegistrationError != null)
- {
- Logger.LogWarning("GetRenewResponse: RegistrationError present. Description='{Desc}'",
- renewResponse.RegistrationError.Description ?? "(null)");
return new EnrollmentResult
{
- Status = (int)EndEntityStatus.FAILED,
- CARequestID = renewResponse.Result?.Status?.Uuid,
- StatusMessage = renewResponse.RegistrationError.Description ?? "Renewal failed with unknown error."
+ Status = (int)EndEntityStatus.FAILED, //failure
+ CARequestID = renewResponse?.Result?.Status?.Uuid,
+ StatusMessage = renewResponse.RegistrationError.Description
};
- }
- var commonName = renewResponse.Result?.CommonName ?? "(unknown)";
- var uuid = renewResponse.Result?.Status?.Uuid;
- Logger.LogTrace("GetRenewResponse: renewal succeeded for CommonName='{CommonName}', UUID='{Uuid}'", commonName, uuid ?? "(null)");
return new EnrollmentResult
{
- Status = (int)EndEntityStatus.EXTERNALVALIDATION,
- CARequestID = uuid,
- StatusMessage = $"Renewal Successfully Submitted For {commonName}. Certificate will be available after next sync."
+ Status = (int)EndEntityStatus.GENERATED, //success
+
+ StatusMessage = $"Renewal Successfully Completed For {renewResponse.Result.CommonName}"
};
}
@@ -121,210 +64,77 @@ public EnrollmentResult
GetEnrollmentResult(
IRegistrationResponse registrationResponse)
{
- Logger.LogTrace("GetEnrollmentResult: registrationResponse is {Null}", registrationResponse == null ? "NULL" : "present");
-
- if (registrationResponse == null)
- {
- Logger.LogError("GetEnrollmentResult: registrationResponse is null.");
- return new EnrollmentResult
- {
- Status = (int)EndEntityStatus.FAILED,
- StatusMessage = "Enrollment failed: received null response from CSC."
- };
- }
-
if (registrationResponse.RegistrationError != null)
- {
- Logger.LogWarning("GetEnrollmentResult: RegistrationError present. Description='{Desc}'",
- registrationResponse.RegistrationError.Description ?? "(null)");
- return new EnrollmentResult
- {
- Status = (int)EndEntityStatus.FAILED,
- StatusMessage = registrationResponse.RegistrationError.Description ?? "Enrollment failed with unknown error."
- };
- }
-
- if (registrationResponse.Result == null)
- {
- Logger.LogError("GetEnrollmentResult: Result is null but no RegistrationError present.");
return new EnrollmentResult
{
- Status = (int)EndEntityStatus.FAILED,
- StatusMessage = "Enrollment failed: response Result is null."
+ Status = (int)EndEntityStatus.FAILED, //failure
+ StatusMessage = registrationResponse.RegistrationError.Description
};
- }
var cnames = new Dictionary();
if (registrationResponse.Result.DcvDetails != null && registrationResponse.Result.DcvDetails.Count > 0)
- {
- Logger.LogTrace("GetEnrollmentResult: processing {Count} DcvDetails.", registrationResponse.Result.DcvDetails.Count);
foreach (var dcv in registrationResponse.Result.DcvDetails)
{
- if (dcv == null)
- {
- Logger.LogTrace("GetEnrollmentResult: skipping null DcvDetail.");
- continue;
- }
-
if (dcv.CName != null && !string.IsNullOrEmpty(dcv.CName.Name) && !string.IsNullOrEmpty(dcv.CName.Value))
{
- if (!cnames.ContainsKey(dcv.CName.Name))
- {
- Logger.LogTrace("GetEnrollmentResult: adding CName '{Name}'='{Value}'", dcv.CName.Name, dcv.CName.Value);
- cnames.Add(dcv.CName.Name, dcv.CName.Value);
- }
- else
- {
- Logger.LogTrace("GetEnrollmentResult: duplicate CName key '{Name}', skipping.", dcv.CName.Name);
- }
+ cnames.Add(dcv.CName.Name, dcv.CName.Value);
}
if (!string.IsNullOrEmpty(dcv.Email) && !cnames.ContainsKey(dcv.Email))
{
- if (!cnames.ContainsKey(dcv.Email))
- {
- Logger.LogTrace("GetEnrollmentResult: adding DCV email '{Email}'", dcv.Email);
- cnames.Add(dcv.Email, dcv.Email);
- }
- else
- {
- Logger.LogTrace("GetEnrollmentResult: duplicate email key '{Email}', skipping.", dcv.Email);
- }
+ cnames.Add(dcv.Email, dcv.Email);
}
}
- }
- else
- {
- Logger.LogTrace("GetEnrollmentResult: no DcvDetails to process.");
- }
-
- var uuid = registrationResponse.Result.Status?.Uuid;
- var commonName = registrationResponse.Result.CommonName ?? "(unknown)";
- Logger.LogTrace("GetEnrollmentResult: success. UUID='{Uuid}', CommonName='{CommonName}', cnames count={Count}",
- uuid ?? "(null)", commonName, cnames.Count);
-
+
return new EnrollmentResult
{
- Status = (int)EndEntityStatus.EXTERNALVALIDATION,
- CARequestID = uuid,
+ Status = (int)EndEntityStatus.EXTERNALVALIDATION, //success
+ CARequestID = registrationResponse.Result.Status.Uuid,
StatusMessage =
- $"Order Successfully Created With Order Number {commonName}",
+ $"Order Successfully Created With Order Number {registrationResponse.Result.CommonName}",
EnrollmentContext = cnames.Count > 0 ? cnames : null
};
}
public int GetRevokeResult(IRevokeResponse revokeResponse)
{
- Logger.LogTrace("GetRevokeResult: revokeResponse is {Null}", revokeResponse == null ? "NULL" : "present");
-
- if (revokeResponse == null)
- {
- Logger.LogError("GetRevokeResult: revokeResponse is null, returning FAILED.");
- return (int)EndEntityStatus.FAILED;
- }
-
if (revokeResponse.RegistrationError != null)
- {
- Logger.LogWarning("GetRevokeResult: RegistrationError present. Description='{Desc}'",
- revokeResponse.RegistrationError.Description ?? "(null)");
return (int)EndEntityStatus.FAILED;
- }
- Logger.LogTrace("GetRevokeResult: returning REVOKED.");
return (int)EndEntityStatus.REVOKED;
}
public EnrollmentResult GetReIssueResult(IReissueResponse reissueResponse)
{
- Logger.LogTrace("GetReIssueResult: reissueResponse is {Null}", reissueResponse == null ? "NULL" : "present");
-
- if (reissueResponse == null)
- {
- Logger.LogError("GetReIssueResult: reissueResponse is null.");
- return new EnrollmentResult
- {
- Status = (int)EndEntityStatus.FAILED,
- StatusMessage = "Reissue failed: received null response from CSC."
- };
- }
-
if (reissueResponse.RegistrationError != null)
- {
- Logger.LogWarning("GetReIssueResult: RegistrationError present. Description='{Desc}'",
- reissueResponse.RegistrationError.Description ?? "(null)");
- return new EnrollmentResult
- {
- Status = (int)EndEntityStatus.FAILED,
- StatusMessage = reissueResponse.RegistrationError.Description ?? "Reissue failed with unknown error."
- };
- }
-
- if (reissueResponse.Result == null)
- {
- Logger.LogError("GetReIssueResult: Result is null but no RegistrationError present.");
return new EnrollmentResult
{
- Status = (int)EndEntityStatus.FAILED,
- StatusMessage = "Reissue failed: response Result is null."
+ Status = (int)EndEntityStatus.FAILED, //failure
+ StatusMessage = reissueResponse.RegistrationError.Description
};
- }
-
- var uuid = reissueResponse.Result.Status?.Uuid;
- var commonName = reissueResponse.Result.CommonName ?? "(unknown)";
- Logger.LogTrace("GetReIssueResult: success. UUID='{Uuid}', CommonName='{CommonName}'", uuid ?? "(null)", commonName);
return new EnrollmentResult
{
- Status = (int)EndEntityStatus.EXTERNALVALIDATION,
- CARequestID = uuid,
- StatusMessage = $"Reissue Successfully Submitted For {commonName}. Certificate will be available after next sync."
+ Status = (int)EndEntityStatus.GENERATED, //success
+ CARequestID = reissueResponse.Result.Status.Uuid,
+ StatusMessage = $"Reissue Successfully Completed For {reissueResponse.Result.CommonName}"
};
}
public DomainControlValidation GetDomainControlValidation(string methodType, string[] emailAddress,
string domainName)
{
- Logger.LogTrace("GetDomainControlValidation(array): methodType='{MethodType}', domainName='{DomainName}', emailAddress count={Count}",
- methodType ?? "(null)", domainName ?? "(null)", emailAddress?.Length ?? 0);
-
- if (emailAddress == null || emailAddress.Length == 0)
- {
- Logger.LogTrace("GetDomainControlValidation(array): no email addresses provided, returning null.");
- return null;
- }
-
foreach (var address in emailAddress)
{
- if (string.IsNullOrEmpty(address))
- {
- Logger.LogTrace("GetDomainControlValidation(array): skipping null/empty email address.");
- continue;
- }
-
- try
- {
- var email = new MailAddress(address);
- var hostPart = email.Host?.Split('.')[0] ?? "";
- Logger.LogTrace("GetDomainControlValidation(array): checking email='{Email}', hostPart='{HostPart}' against domain='{Domain}'",
- address, hostPart, domainName);
-
- if (!string.IsNullOrEmpty(domainName) && domainName.Contains(hostPart))
+ var email = new MailAddress(address);
+ if (domainName.Contains(email.Host.Split('.')[0]))
+ return new DomainControlValidation
{
- Logger.LogTrace("GetDomainControlValidation(array): matched! Returning email='{Email}'", email.ToString());
- return new DomainControlValidation
- {
- MethodType = methodType,
- EmailAddress = email.ToString()
- };
- }
- }
- catch (FormatException fex)
- {
- Logger.LogWarning("GetDomainControlValidation(array): invalid email address '{Address}': {Message}", address, fex.Message);
- }
+ MethodType = methodType,
+ EmailAddress = email.ToString()
+ };
}
- Logger.LogTrace("GetDomainControlValidation(array): no matching email found, returning null.");
return null;
}
@@ -340,190 +150,105 @@ public DomainControlValidation GetDomainControlValidation(string methodType, str
public RegistrationRequest GetRegistrationRequest(EnrollmentProductInfo productInfo, string csr,
Dictionary sans, List customFields)
{
- Logger.LogTrace("GetRegistrationRequest: building registration request. ProductID='{ProductId}'", productInfo?.ProductID ?? "(null)");
+ //var cert = "-----BEGIN CERTIFICATE REQUEST-----\r\n";
+ var cert = Pemify(csr);
+ //cert = cert + "\r\n-----END CERTIFICATE REQUEST-----";
- if (productInfo?.ProductParameters == null)
- throw new ArgumentNullException(nameof(productInfo), "productInfo or ProductParameters cannot be null.");
- if (string.IsNullOrEmpty(csr))
- throw new ArgumentNullException(nameof(csr), "CSR cannot be null or empty.");
- var cert = Pemify(csr);
var bytes = Encoding.UTF8.GetBytes(cert);
var encodedString = Convert.ToBase64String(bytes);
- Logger.LogTrace("GetRegistrationRequest: CSR encoded, length={Length}", encodedString.Length);
-
- var commonNameValidationEmail = productInfo.ProductParameters.ContainsKey("CN DCV Email")
- ? productInfo.ProductParameters["CN DCV Email"] : null;
- var methodType = productInfo.ProductParameters.ContainsKey("Domain Control Validation Method")
- ? productInfo.ProductParameters["Domain Control Validation Method"] : null;
+ var commonNameValidationEmail = productInfo.ProductParameters["CN DCV Email"];
+ var methodType = productInfo.ProductParameters["Domain Control Validation Method"];
var certificateType = GetCertificateType(productInfo.ProductID);
- Logger.LogTrace("GetRegistrationRequest: cnDcvEmail='{Email}', methodType='{Method}', certType='{CertType}'",
- commonNameValidationEmail ?? "(null)", methodType ?? "(null)", certificateType);
-
return new RegistrationRequest
{
Csr = encodedString,
- ServerSoftware = "-1",
+ ServerSoftware = "-1", //Just default to other, user does not need to fill this in
CertificateType = certificateType,
- Term = productInfo.ProductParameters.ContainsKey("Term") ? productInfo.ProductParameters["Term"] : null,
- ApplicantFirstName = productInfo.ProductParameters.ContainsKey("Applicant First Name") ? productInfo.ProductParameters["Applicant First Name"] : null,
- ApplicantLastName = productInfo.ProductParameters.ContainsKey("Applicant Last Name") ? productInfo.ProductParameters["Applicant Last Name"] : null,
- ApplicantEmailAddress = productInfo.ProductParameters.ContainsKey("Applicant Email Address") ? productInfo.ProductParameters["Applicant Email Address"] : null,
- ApplicantPhoneNumber = productInfo.ProductParameters.ContainsKey("Applicant Phone") ? productInfo.ProductParameters["Applicant Phone"] : null,
+ Term = productInfo.ProductParameters["Term"],
+ ApplicantFirstName = productInfo.ProductParameters["Applicant First Name"],
+ ApplicantLastName = productInfo.ProductParameters["Applicant Last Name"],
+ ApplicantEmailAddress = productInfo.ProductParameters["Applicant Email Address"],
+ ApplicantPhoneNumber = productInfo.ProductParameters["Applicant Phone"],
DomainControlValidation = GetDomainControlValidation(methodType, commonNameValidationEmail),
Notifications = GetNotifications(productInfo),
- OrganizationContact = productInfo.ProductParameters.ContainsKey("Organization Contact") ? productInfo.ProductParameters["Organization Contact"] : null,
- BusinessUnit = productInfo.ProductParameters.ContainsKey("Business Unit") ? productInfo.ProductParameters["Business Unit"] : null,
- ShowPrice = true,
+ OrganizationContact = productInfo.ProductParameters["Organization Contact"],
+ BusinessUnit = productInfo.ProductParameters["Business Unit"],
+ ShowPrice = true, //User should not have to fill this out
CustomFields = GetCustomFields(productInfo, customFields),
SubjectAlternativeNames = certificateType == "2" ? GetSubjectAlternativeNames(productInfo, sans) : null,
EvCertificateDetails = certificateType == "3" ? GetEvCertificateDetails(productInfo) : null
};
}
- // Maps Keyfactor product ID -> CSC API certificate type code (used for enrollment requests)
- private static readonly Dictionary ProductIdToCodeMap = new(StringComparer.OrdinalIgnoreCase)
- {
- ["CSC TrustedSecure Premium Certificate"] = "0",
- ["CSC TrustedSecure Premium Wildcard Certificate"] = "1",
- ["CSC TrustedSecure UC Certificate"] = "2",
- ["CSC TrustedSecure EV Certificate"] = "3",
- ["CSC TrustedSecure Domain Validated SSL"] = "4",
- ["CSC Trusted Secure Domain Validated SSL"] = "4",
- ["CSC Trusted Secure Domain Validated Wildcard SSL"] = "5",
- ["CSC Trusted Secure Domain Validated UC Certificate"] = "6",
- };
-
- // 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)
{
- Logger.LogTrace("GetCertificateType: productId='{ProductId}'", productId ?? "(null)");
- if (!string.IsNullOrEmpty(productId) && ProductIdToCodeMap.TryGetValue(productId, out var code))
+ switch (productId)
{
- Logger.LogTrace("GetCertificateType: mapped '{ProductId}' -> '{Code}'", productId, code);
- return code;
+ case "CSC TrustedSecure Premium Certificate":
+ return "0";
+ case "CSC TrustedSecure EV Certificate":
+ return "3";
+ case "CSC TrustedSecure UC Certificate":
+ return "2";
+ case "CSC TrustedSecure Premium Wildcard Certificate":
+ return "1";
+ case "CSC Trusted Secure Domain Validated SSL":
+ return "4";
+ case "CSC Trusted Secure Domain Validated Wildcard SSL":
+ return "5";
+ case "CSC Trusted Secure Domain Validated UC Certificate":
+ return "6";
+ case "CSC TrustedSecure Domain Validated SSL":
+ return "4";
+ case "CSC TrustedSecure Domain Validated Wildcard SSL":
+ return "5";
+ case "CSC TrustedSecure Domain Validated UC Certificate":
+ return "6";
}
- Logger.LogWarning("GetCertificateType: no mapping found for '{ProductId}', returning -1.", productId);
- return "-1";
- }
- ///
- /// Maps a CSC API certificateType value back to a Keyfactor product ID.
- /// Handles numeric codes, descriptive strings, and passthrough of already-correct values.
- ///
- 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";
+ return "-1";
}
public Notifications GetNotifications(EnrollmentProductInfo productInfo)
{
- Logger.LogTrace("GetNotifications: building notifications.");
- var emailsRaw = productInfo?.ProductParameters != null
- && productInfo.ProductParameters.ContainsKey("Notification Email(s) Comma Separated")
- ? productInfo.ProductParameters["Notification Email(s) Comma Separated"]
- : null;
-
- Logger.LogTrace("GetNotifications: raw notification emails='{Emails}'", emailsRaw ?? "(null)");
-
- var emailList = !string.IsNullOrEmpty(emailsRaw)
- ? emailsRaw.Split(',').Where(e => !string.IsNullOrWhiteSpace(e)).ToList()
- : new List();
-
- Logger.LogTrace("GetNotifications: parsed {Count} notification emails.", emailList.Count);
-
return new Notifications
{
Enabled = true,
- AdditionalNotificationEmails = emailList
+ AdditionalNotificationEmails = productInfo.ProductParameters["Notification Email(s) Comma Separated"]
+ .Split(',').ToList()
};
}
public RenewalRequest GetRenewalRequest(EnrollmentProductInfo productInfo, string uUId, string csr,
Dictionary sans, List customFields)
{
- Logger.LogTrace("GetRenewalRequest: building renewal request. UUID='{Uuid}', ProductID='{ProductId}'",
- uUId ?? "(null)", productInfo?.ProductID ?? "(null)");
-
- if (productInfo?.ProductParameters == null)
- throw new ArgumentNullException(nameof(productInfo), "productInfo or ProductParameters cannot be null.");
- if (string.IsNullOrEmpty(csr))
- throw new ArgumentNullException(nameof(csr), "CSR cannot be null or empty.");
- if (string.IsNullOrEmpty(uUId))
- throw new ArgumentNullException(nameof(uUId), "uUId cannot be null or empty.");
-
+ //var cert = "-----BEGIN CERTIFICATE REQUEST-----\r\n";
var cert = Pemify(csr);
+ //cert = cert + "\r\n-----END CERTIFICATE REQUEST-----";
+
var bytes = Encoding.UTF8.GetBytes(cert);
var encodedString = Convert.ToBase64String(bytes);
-
- var commonNameValidationEmail = productInfo.ProductParameters.ContainsKey("CN DCV Email")
- ? productInfo.ProductParameters["CN DCV Email"] : null;
- var methodType = productInfo.ProductParameters.ContainsKey("Domain Control Validation Method")
- ? productInfo.ProductParameters["Domain Control Validation Method"] : null;
+ var commonNameValidationEmail = productInfo.ProductParameters["CN DCV Email"];
+ var methodType = productInfo.ProductParameters["Domain Control Validation Method"];
var certificateType = GetCertificateType(productInfo.ProductID);
- Logger.LogTrace("GetRenewalRequest: cnDcvEmail='{Email}', methodType='{Method}', certType='{CertType}'",
- commonNameValidationEmail ?? "(null)", methodType ?? "(null)", certificateType);
-
return new RenewalRequest
{
Uuid = uUId,
Csr = encodedString,
ServerSoftware = "-1",
CertificateType = certificateType,
- Term = productInfo.ProductParameters.ContainsKey("Term") ? productInfo.ProductParameters["Term"] : null,
- ApplicantFirstName = productInfo.ProductParameters.ContainsKey("Applicant First Name") ? productInfo.ProductParameters["Applicant First Name"] : null,
- ApplicantLastName = productInfo.ProductParameters.ContainsKey("Applicant Last Name") ? productInfo.ProductParameters["Applicant Last Name"] : null,
- ApplicantEmailAddress = productInfo.ProductParameters.ContainsKey("Applicant Email Address") ? productInfo.ProductParameters["Applicant Email Address"] : null,
- ApplicantPhoneNumber = productInfo.ProductParameters.ContainsKey("Applicant Phone") ? productInfo.ProductParameters["Applicant Phone"] : null,
+ Term = productInfo.ProductParameters["Term"],
+ ApplicantFirstName = productInfo.ProductParameters["Applicant First Name"],
+ ApplicantLastName = productInfo.ProductParameters["Applicant Last Name"],
+ ApplicantEmailAddress = productInfo.ProductParameters["Applicant Email Address"],
+ ApplicantPhoneNumber = productInfo.ProductParameters["Applicant Phone"],
DomainControlValidation = GetDomainControlValidation(methodType, commonNameValidationEmail),
Notifications = GetNotifications(productInfo),
- OrganizationContact = productInfo.ProductParameters.ContainsKey("Organization Contact") ? productInfo.ProductParameters["Organization Contact"] : null,
- BusinessUnit = productInfo.ProductParameters.ContainsKey("Business Unit") ? productInfo.ProductParameters["Business Unit"] : null,
+ OrganizationContact = productInfo.ProductParameters["Organization Contact"],
+ BusinessUnit = productInfo.ProductParameters["Business Unit"],
ShowPrice = true,
SubjectAlternativeNames = certificateType == "2" ? GetSubjectAlternativeNames(productInfo, sans) : null,
CustomFields = GetCustomFields(productInfo, customFields),
@@ -534,107 +259,54 @@ public RenewalRequest GetRenewalRequest(EnrollmentProductInfo productInfo, strin
private List GetSubjectAlternativeNames(EnrollmentProductInfo productInfo,
Dictionary sans)
{
- Logger.LogTrace("GetSubjectAlternativeNames: building SANs.");
var subjectNameList = new List();
+ var methodType = productInfo.ProductParameters["Domain Control Validation Method"];
- if (sans == null || !sans.ContainsKey("dnsname"))
+ foreach (var v in sans["dnsname"])
{
- Logger.LogTrace("GetSubjectAlternativeNames: no 'dnsname' key in SANs dictionary, returning empty list.");
- return subjectNameList;
- }
-
- var dnsNames = sans["dnsname"];
- if (dnsNames == null || dnsNames.Length == 0)
- {
- Logger.LogTrace("GetSubjectAlternativeNames: 'dnsname' array is null or empty, returning empty list.");
- return subjectNameList;
- }
-
- var methodType = productInfo?.ProductParameters != null
- && productInfo.ProductParameters.ContainsKey("Domain Control Validation Method")
- ? productInfo.ProductParameters["Domain Control Validation Method"]
- : null;
-
- Logger.LogTrace("GetSubjectAlternativeNames: processing {Count} DNS names, methodType='{MethodType}'",
- dnsNames.Length, methodType ?? "(null)");
-
- foreach (var v in dnsNames)
- {
- if (string.IsNullOrEmpty(v))
- {
- Logger.LogTrace("GetSubjectAlternativeNames: skipping null/empty DNS name.");
- continue;
- }
-
var domainName = v;
var san = new SubjectAlternativeName();
san.DomainName = domainName;
- Logger.LogTrace("GetSubjectAlternativeNames: processing domain='{Domain}'", domainName);
-
- 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"]
- : null;
- var emailAddresses = !string.IsNullOrEmpty(emailsRaw) ? emailsRaw.Split(',') : Array.Empty();
- Logger.LogTrace("GetSubjectAlternativeNames: EMAIL validation, {Count} email addresses for domain='{Domain}'",
- emailAddresses.Length, domainName);
+ var emailAddresses = productInfo.ProductParameters["Addtl Sans Comma Separated DVC Emails"].Split(',');
+ if (methodType.ToUpper() == "EMAIL")
san.DomainControlValidation = GetDomainControlValidation(methodType, emailAddresses, domainName);
- }
- else
- {
- Logger.LogTrace("GetSubjectAlternativeNames: CNAME/other validation for domain='{Domain}'", domainName);
+ else //it is a CNAME validation so no email is needed
san.DomainControlValidation = GetDomainControlValidation(methodType, "");
- }
subjectNameList.Add(san);
}
- Logger.LogTrace("GetSubjectAlternativeNames: returning {Count} SANs.", subjectNameList.Count);
return subjectNameList;
}
public ReissueRequest GetReissueRequest(EnrollmentProductInfo productInfo, string uUId, string csr,
Dictionary sans, List customFields)
{
- Logger.LogTrace("GetReissueRequest: building reissue request. UUID='{Uuid}', ProductID='{ProductId}'",
- uUId ?? "(null)", productInfo?.ProductID ?? "(null)");
-
- if (productInfo?.ProductParameters == null)
- throw new ArgumentNullException(nameof(productInfo), "productInfo or ProductParameters cannot be null.");
- if (string.IsNullOrEmpty(csr))
- throw new ArgumentNullException(nameof(csr), "CSR cannot be null or empty.");
- if (string.IsNullOrEmpty(uUId))
- throw new ArgumentNullException(nameof(uUId), "uUId cannot be null or empty.");
-
+ //var cert = "-----BEGIN CERTIFICATE REQUEST-----\r\n";
var cert = Pemify(csr);
+ //cert = cert + "\r\n-----END CERTIFICATE REQUEST-----";
+
var bytes = Encoding.UTF8.GetBytes(cert);
var encodedString = Convert.ToBase64String(bytes);
-
- var commonNameValidationEmail = productInfo.ProductParameters.ContainsKey("CN DCV Email")
- ? productInfo.ProductParameters["CN DCV Email"] : null;
- var methodType = productInfo.ProductParameters.ContainsKey("Domain Control Validation Method")
- ? productInfo.ProductParameters["Domain Control Validation Method"] : null;
+ var commonNameValidationEmail = productInfo.ProductParameters["CN DCV Email"];
+ var methodType = productInfo.ProductParameters["Domain Control Validation Method"];
var certificateType = GetCertificateType(productInfo.ProductID);
- Logger.LogTrace("GetReissueRequest: cnDcvEmail='{Email}', methodType='{Method}', certType='{CertType}'",
- commonNameValidationEmail ?? "(null)", methodType ?? "(null)", certificateType);
-
return new ReissueRequest
{
Uuid = uUId,
Csr = encodedString,
ServerSoftware = "-1",
- CertificateType = certificateType,
- Term = productInfo.ProductParameters.ContainsKey("Term") ? productInfo.ProductParameters["Term"] : null,
- ApplicantFirstName = productInfo.ProductParameters.ContainsKey("Applicant First Name") ? productInfo.ProductParameters["Applicant First Name"] : null,
- ApplicantLastName = productInfo.ProductParameters.ContainsKey("Applicant Last Name") ? productInfo.ProductParameters["Applicant Last Name"] : null,
- ApplicantEmailAddress = productInfo.ProductParameters.ContainsKey("Applicant Email Address") ? productInfo.ProductParameters["Applicant Email Address"] : null,
- ApplicantPhoneNumber = productInfo.ProductParameters.ContainsKey("Applicant Phone") ? productInfo.ProductParameters["Applicant Phone"] : null,
+ CertificateType = GetCertificateType(productInfo.ProductID),
+ Term = productInfo.ProductParameters["Term"],
+ ApplicantFirstName = productInfo.ProductParameters["Applicant First Name"],
+ ApplicantLastName = productInfo.ProductParameters["Applicant Last Name"],
+ ApplicantEmailAddress = productInfo.ProductParameters["Applicant Email Address"],
+ ApplicantPhoneNumber = productInfo.ProductParameters["Applicant Phone"],
DomainControlValidation = GetDomainControlValidation(methodType, commonNameValidationEmail),
Notifications = GetNotifications(productInfo),
- OrganizationContact = productInfo.ProductParameters.ContainsKey("Organization Contact") ? productInfo.ProductParameters["Organization Contact"] : null,
- BusinessUnit = productInfo.ProductParameters.ContainsKey("Business Unit") ? productInfo.ProductParameters["Business Unit"] : null,
+ OrganizationContact = productInfo.ProductParameters["Organization Contact"],
+ BusinessUnit = productInfo.ProductParameters["Business Unit"],
ShowPrice = true,
SubjectAlternativeNames = certificateType == "2" ? GetSubjectAlternativeNames(productInfo, sans) : null,
CustomFields = GetCustomFields(productInfo, customFields),
@@ -644,28 +316,15 @@ public ReissueRequest GetReissueRequest(EnrollmentProductInfo productInfo, strin
private EvCertificateDetails GetEvCertificateDetails(EnrollmentProductInfo productInfo)
{
- Logger.LogTrace("GetEvCertificateDetails: building EV details.");
- var country = productInfo?.ProductParameters != null
- && productInfo.ProductParameters.ContainsKey("Organization Country")
- ? productInfo.ProductParameters["Organization Country"]
- : null;
- Logger.LogTrace("GetEvCertificateDetails: country='{Country}'", country ?? "(null)");
var evDetails = new EvCertificateDetails();
- evDetails.Country = country;
+ evDetails.Country = productInfo.ProductParameters["Organization Country"];
return evDetails;
}
public int MapReturnStatus(string cscGlobalStatus)
{
- Logger.LogTrace("MapReturnStatus: input status='{Status}'", cscGlobalStatus ?? "(null)");
-
- if (string.IsNullOrEmpty(cscGlobalStatus))
- {
- Logger.LogWarning("MapReturnStatus: status is null or empty, returning FAILED.");
- return (int)EndEntityStatus.FAILED;
- }
+ var returnStatus = 0;
- int returnStatus;
switch (cscGlobalStatus)
{
case "ACTIVE":
@@ -681,12 +340,10 @@ public int MapReturnStatus(string cscGlobalStatus)
returnStatus = (int)EndEntityStatus.REVOKED;
break;
default:
- Logger.LogWarning("MapReturnStatus: unrecognized status '{Status}', returning FAILED.", cscGlobalStatus);
returnStatus = (int)EndEntityStatus.FAILED;
break;
}
- Logger.LogTrace("MapReturnStatus: mapped '{Status}' to {Result}", cscGlobalStatus, returnStatus);
return returnStatus;
}
}
\ No newline at end of file
diff --git a/docsource/configuration.md b/docsource/configuration.md
index 5dee9d3..d8c196e 100644
--- a/docsource/configuration.md
+++ b/docsource/configuration.md
@@ -10,115 +10,6 @@ This integration is tested and confirmed as working for Anygateway REST 24.2 and
The Root certificates for installation on the Anygateway server machine should be obtained from CSC.
-## CA Connection Configuration
-
-When defining the Certificate Authority in the AnyCA Gateway REST portal, configure the following fields on the **CA Connection** tab:
-
-CONFIG ELEMENT | DESCRIPTION | DEFAULT
----------------|-------------|--------
-Enabled | Flag to Enable or Disable gateway functionality. Set to `false` to allow creating the CA record before configuration information is available; the plugin then short-circuits Ping, Sync, Enroll, and Revoke with a warning until it is re-enabled. | `true`
-CscGlobalUrl | The base URL for the CSCGlobal API (e.g. `https://apis.cscglobal.com`) | (required)
-ApiKey | Your CSCGlobal API key | (required)
-BearerToken | Your CSCGlobal Bearer token for authentication | (required)
-DefaultPageSize | Page size for API list requests | 100
-SyncFilterDays | Number of days from today used to filter certificates by expiration date during **incremental** sync. Only certificates expiring within this window are returned. Does not apply to full sync. | 5
-RenewalWindowDays | Number of days before the annual order expiry date within which a **RenewOrReissue** request triggers a paid **Renewal** rather than a free **Reissue**. See [Renewal vs. Reissue Logic](#renewal-vs-reissue-logic) below. | 30
-DcvPollTimeoutSeconds | Max seconds to synchronously poll CSC for certificate issuance after submitting an order. `0` disables polling (enrollment returns pending immediately; cert arrives on the next sync). When `>0`, fast-validating orders can return the issued cert directly in the enrollment response. See [Synchronous Issuance Polling](#synchronous-issuance-polling) below. | 0
-
-> **Note:** DNS auto-publishing for CNAME DCV is handled by the AnyCA Gateway REST framework's Domain Validation system (gateway 3.3+). It's configured in the gateway UI under **Domain Validation Configurations**, not on the CA Connection tab. See [DNS Auto-Publishing (CNAME DCV)](#dns-auto-publishing-cname-dcv).
-
-## Renewal vs. Reissue Logic
-
-CSC Global subscriptions are annual orders. When Keyfactor Command sends a **RenewOrReissue** request, the plugin must decide whether to submit a **Renewal** (a new paid order) or a **Reissue** (a free re-key under the existing active order).
-
-The decision is based on the **RenewalWindowDays** setting and works as follows:
-
-1. The plugin fetches the original certificate from CSC and reads its `orderDate`.
-2. It computes the **order expiry** as `orderDate + 1 year`.
-3. It calculates **days remaining** until the order expires.
-4. If `days remaining <= RenewalWindowDays`, the request is treated as a **Renewal** (new paid order).
-5. If `days remaining > RenewalWindowDays`, the request is treated as a **Reissue** (free under the active order).
-
-**Example with default RenewalWindowDays = 30:**
-
-```
-Order Date: 2025-04-08
-Order Expiry: 2026-04-08
-Today: 2026-03-15
-Days Left: 24
-
-24 <= 30 --> RENEWAL (new paid order)
-```
-
-```
-Order Date: 2025-04-08
-Order Expiry: 2026-04-08
-Today: 2025-09-01
-Days Left: 219
-
-219 > 30 --> REISSUE (free under active order)
-```
-
-**Fallback behavior:** If the plugin cannot retrieve the `orderDate` from CSC (e.g., API error or missing field), it falls back to checking the certificate's expiration date. If the certificate is already expired, it treats the request as a Renewal.
-
-**Note:** Both Renewal and Reissue submissions are asynchronous at CSC. The plugin returns a "pending" status and the issued certificate will appear in Keyfactor after the next sync cycle.
-
-## DNS Auto-Publishing (CNAME DCV)
-
-CSC supports two Domain Control Validation (DCV) methods: **EMAIL** and **CNAME**. With CNAME validation, CSC returns a CNAME record (name → target) that must exist in DNS before they will validate the order.
-
-By default this plugin returns the CNAME details to Keyfactor Command for **manual publishing**. To fully automate enrollment, the plugin uses the **AnyCA Gateway REST framework's built-in DNS provider system** (available in framework 3.3 and later). The framework discovers DNS provider plugins deployed alongside the CA plugin and routes each CNAME to whichever provider claims the matching DNS zone.
-
-### Requirements
-
-* AnyCA Gateway REST framework **3.3 or later** (the `IDomainValidatorFactory` interface ships in `Keyfactor.AnyGateway.IAnyCAPlugin` 3.3+).
-* At least one DNS provider DLL (e.g. GoDaddy, Cloudflare, Route 53, Azure) deployed in the gateway `Extensions` folder.
-* A Domain Validation Configuration registered in the gateway UI that maps your domain(s) to the deployed provider (for example, `*.example.com` → GoDaddy).
-
-### How It Works
-
-1. CSC returns the CNAME `name → target` details in the enrollment response.
-2. For each CNAME entry, the plugin calls `IDomainValidatorFactory.ResolveDomainValidator(recordName, "cname")`.
-3. The framework returns the `IDomainValidator` whose Domain Validation Configuration matches the record's zone (or `null` if no match).
-4. The plugin calls `validator.StageValidation(recordName, cnameTarget, ct)` to publish the record.
-5. CSC asynchronously validates the CNAME; the issued certificate appears on the next sync.
-
-### Behavior
-
-* **Resolution is per record, not per CA.** One CA can drive multiple DNS providers (GoDaddy for some domains, Route 53 for others) with no per-CA configuration.
-* **Only invoked for CNAME DCV.** Templates configured with EMAIL validation are unaffected — no DNS publishing occurs.
-* **Best-effort.** If no provider claims the zone, the publish call fails, or the factory wasn't injected (gateway pre-3.3), the enrollment still succeeds and the CNAME details remain in the Keyfactor request so a human can publish manually as a fallback.
-* **Trace-logged.** Every resolution (matched/unresolved) and publish attempt (success/failure) is logged at Info/Trace level.
-* **Validation type string.** The plugin passes `"cname"` to `ResolveDomainValidator`. CSC's DCV requires a **CNAME** record, which is different from ACME's `"dns-01"` challenge (a TXT record). A single DNS provider DLL can ship multiple validator classes — one advertising `"dns-01"` (publishes TXT, for ACME) and one advertising `"cname"` (publishes CNAME, for CSC). You must deploy and configure a validator that advertises `"cname"` or no provider will match.
-* **Trailing dots normalized.** CSC returns FQDN-canonical names with a trailing dot (e.g. `_token.example.com.`). The plugin strips the trailing dot before resolution and publishing, because Domain Validation Configurations and DNS provider APIs expect names without it.
-
-### Configuration in the Gateway UI
-
-In the AnyCA Gateway REST portal, under **Domain Validation Configurations**:
-
-1. **Add** a new configuration.
-2. Pick a **Domain Validator Type** that publishes **CNAME** records and advertises validation type `cname`. For GoDaddy this is `GoDaddyCnameDomainValidator` (the `GoDaddyDomainValidator` variant publishes TXT for ACME and will **not** work for CSC).
-3. Add one or more **domain patterns** (e.g. `*.example.com`).
-4. Fill out the provider-specific **Configuration Settings** (API keys, base URL, etc.).
-5. Save.
-
-Once configured, any CSC enrollment for a domain matching one of those patterns will have its CNAME auto-published.
-
-> **Common pitfall:** If you configure the TXT/`dns-01` validator (e.g. `GoDaddyDomainValidator`) for a CSC domain, the record will publish as a **TXT** and CSC's CNAME validation will never succeed. Make sure you select the **CNAME** validator variant.
-
-## Synchronous Issuance Polling
-
-CSC validates domain control asynchronously — after an order is submitted (and the CNAME DCV record published), CSC/Sectigo polls public DNS on its own schedule and issues the certificate once validation passes. By default this plugin returns a **pending** (`EXTERNALVALIDATION`) result immediately and the issued certificate is picked up on the next gateway **sync** cycle.
-
-For environments where DNS is published automatically (see [DNS Auto-Publishing](#dns-auto-publishing-cname-dcv)) and validation tends to complete quickly, you can have the plugin **poll CSC synchronously** at the end of enrollment and return the issued certificate directly — avoiding the wait for the next sync.
-
-* Set **`DcvPollTimeoutSeconds`** to the maximum number of seconds to poll (e.g. `60`). `0` (default) disables polling entirely.
-* The plugin polls CSC every 10 seconds until the order is issued or the timeout is reached.
-* If the certificate issues within the window, the enrollment returns it immediately with a success status.
-* If the window expires, the plugin falls back to the **pending** result and the certificate arrives on the next sync — exactly as it would with polling disabled.
-
-**Tradeoff:** Polling blocks the enrollment request for up to `DcvPollTimeoutSeconds`. CSC validation frequently takes minutes to hours, so most orders will still fall through to pending — keep the timeout small (30–90s) to catch only the fast cases without hanging callers. This applies to New enrollments, Renewals, and Reissues.
-
## Certificate Template Creation Step
PLEASE NOTE, AT THIS TIME THE RAPID_SSL TEMPLATE IS NOT SUPPORTED BY THE CSC API AND WILL NOT WORK WITH THIS INTEGRATION
diff --git a/integration-manifest.json b/integration-manifest.json
index e6c5243..2b4b8c4 100644
--- a/integration-manifest.json
+++ b/integration-manifest.json
@@ -2,12 +2,12 @@
"$schema": "https://keyfactor.github.io/integration-manifest-schema.json",
"integration_type": "anyca-plugin",
"name": "CSCGlobal CAPlugin REST Gateway Plugin",
+ "status": "pilot",
"support_level": "kf-supported",
- "status": "production",
"link_github": true,
"update_catalog": true,
"description": "CSCGlobal CAPlugin for the AnyCA REST Gateway framework",
- "gateway_framework": "26.2.0",
+ "gateway_framework": "24.2.0",
"release_project": "cscglobal-caplugin/CSCGlobalCAPlugin.csproj",
"release_dir": "cscglobal-caplugin/bin/Release",
"about": {
@@ -30,16 +30,12 @@
"description": "Default page size for use with the API. Default is 100"
},
{
- "name": "SyncFilterDays",
- "description": "Number of days from today to filter certificates by expiration date during incremental sync."
- },
- {
- "name": "RenewalWindowDays",
- "description": "Number of days before the annual order expiry within which a RenewOrReissue triggers a paid Renewal rather than a free Reissue. Default is 30."
+ "name": "TemplateSync",
+ "description": "Enable template sync."
},
{
- "name": "DcvPollTimeoutSeconds",
- "description": "Max seconds to synchronously poll CSC for issuance after submitting an order (and publishing CNAME DCV). 0 disables polling (enrollment returns pending immediately; cert arrives on next sync). When >0, fast-validating orders can return the cert directly. Keep small to avoid long-blocking enrollment requests."
+ "name": "SyncFilterDays",
+ "description": "Number of days from today to filter certificates by expiration date during incremental sync."
}
],
"enrollment_config": [
@@ -98,9 +94,9 @@
"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 Domain Validated Wildcard SSL",
+ "CSC TrustedSecure Domain Validated UC Certificate"
]
}
}
-}
+}
\ No newline at end of file