diff --git a/.github/workflows/keyfactor-bootstrap-workflow-v3.yml b/.github/workflows/keyfactor-bootstrap-workflow-v3.yml
index 042ba5a..0f3d3ae 100644
--- a/.github/workflows/keyfactor-bootstrap-workflow-v3.yml
+++ b/.github/workflows/keyfactor-bootstrap-workflow-v3.yml
@@ -11,10 +11,17 @@ on:
jobs:
call-starter-workflow:
- uses: keyfactor/actions/.github/workflows/starter.yml@v3.1.2
+ 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 }}
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/CHANGELOG.md b/CHANGELOG.md
index 5c6124b..367a89c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+v1.2.0
+- Added support for CSC TrustedSecure EV, Multiple Names; CSC TrustedSecure OV Wildcard, Multiple Names; and CSC TrustedSecure DV Wildcard, Multiple Names certificate products
+
v.1.1.1
- Added Incremental Sync that goes back X Number of days
- Fixed issue with parsing certain certificates that were in zip format
diff --git a/README.md b/README.md
index c68aac4..3ffe2b0 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@
Support
-
+
ยท
Requirements
@@ -33,7 +33,6 @@
-
This integration allows for the Synchronization, Enrollment, and Revocation of certificates from the CSCGlobal. This is the AnyGateway REST version.
## Compatibility
@@ -41,7 +40,7 @@ This integration allows for the Synchronization, Enrollment, and Revocation of c
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 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.
+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.
> 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.
@@ -82,235 +81,321 @@ 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
- * **TemplateSync** - Enable template sync.
- * **SyncFilterDays** - Number of days from today to filter certificates by expiration date during incremental sync.
+ * **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
+
+**CSC TrustedSecure EV, Multiple Names - Details Tab**
+
+CONFIG ELEMENT | DESCRIPTION
+----------------------------|------------------
+Template Short Name | CSC TrustedSecure EV, Multiple Names
+Template Display Name | CSC TrustedSecure EV, Multiple Names
+Friendly Name | CSC TrustedSecure EV, Multiple Names
+Keys Size | 2048
+Enforce RFC 2818 Compliance | True
+CSR Enrollment | True
+Pfx Enrollment | True
+
+
+**CSC TrustedSecure EV, Multiple Names - 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
+Addtl Sans Comma Separated DCV Emails | String | N/A
+
+**CSC TrustedSecure OV Wildcard, Multiple Names - Details Tab**
+
+CONFIG ELEMENT | DESCRIPTION
+----------------------------|------------------
+Template Short Name | CSC TrustedSecure OV Wildcard, Multiple Names
+Template Display Name | CSC TrustedSecure OV Wildcard, Multiple Names
+Friendly Name | CSC TrustedSecure OV Wildcard, Multiple Names
+Keys Size | 2048
+Enforce RFC 2818 Compliance | True
+CSR Enrollment | True
+Pfx Enrollment | True
+
+
+**CSC TrustedSecure OV Wildcard, Multiple Names - 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 DV Wildcard, Multiple Names - Details Tab**
+
+CONFIG ELEMENT | DESCRIPTION
+----------------------------|------------------
+Template Short Name | CSC TrustedSecure DV Wildcard, Multiple Names
+Template Display Name | CSC TrustedSecure DV Wildcard, Multiple Names
+Friendly Name | CSC TrustedSecure DV Wildcard, Multiple Names
+Keys Size | 2048
+Enforce RFC 2818 Compliance | True
+CSR Enrollment | True
+Pfx Enrollment | True
+
+
+**CSC TrustedSecure DV Wildcard, Multiple Names - 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
-
-
+ * **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
## License
@@ -318,4 +403,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).
\ No newline at end of file
+See all [Keyfactor Any CA Gateways (REST)](https://github.com/orgs/Keyfactor/repositories?q=anycagateway).
diff --git a/cscglobal-caplugin/CSCGlobalCAPlugin.cs b/cscglobal-caplugin/CSCGlobalCAPlugin.cs
index e1af2f0..f5ab830 100644
--- a/cscglobal-caplugin/CSCGlobalCAPlugin.cs
+++ b/cscglobal-caplugin/CSCGlobalCAPlugin.cs
@@ -43,10 +43,15 @@ public CSCGlobalCAPlugin()
public void Initialize(IAnyCAPluginConfigProvider configProvider, ICertificateDataReader certificateDataReader)
{
Logger.MethodEntry(LogLevel.Debug);
- _certificateDataReader = certificateDataReader;
+ if (configProvider == null) throw new ArgumentNullException(nameof(configProvider));
+ _certificateDataReader = certificateDataReader ?? throw new ArgumentNullException(nameof(certificateDataReader));
CscGlobalClient = new CscGlobalClient(configProvider);
- var templateSync = configProvider.CAConnectionData["TemplateSync"].ToString();
- if (templateSync.ToUpper() == "ON") EnableTemplateSync = true;
+
+ if (configProvider.CAConnectionData.TryGetValue("TemplateSync", out var templateSyncValue) &&
+ templateSyncValue != null &&
+ string.Equals(templateSyncValue.ToString(), "ON", StringComparison.OrdinalIgnoreCase))
+ EnableTemplateSync = true;
+ Logger.LogInformation($"Template sync is {(EnableTemplateSync ? "enabled" : "disabled")}");
if (configProvider.CAConnectionData.ContainsKey(Constants.SyncFilterDays))
{
@@ -56,7 +61,13 @@ public void Initialize(IAnyCAPluginConfigProvider configProvider, ICertificateDa
SyncFilterDays = syncFilterDays;
Logger.LogDebug($"SyncFilterDays configured to {SyncFilterDays} days");
}
+ else
+ {
+ Logger.LogWarning($"Could not parse {Constants.SyncFilterDays} value '{syncFilterDaysStr}' as an integer; using default");
+ }
}
+
+ Logger.LogInformation("CSCGlobalCAPlugin initialized successfully");
Logger.MethodExit(LogLevel.Debug);
}
@@ -66,7 +77,10 @@ public async Task GetSingleRecord(string caRequestID)
try
{
Logger.MethodEntry(LogLevel.Debug);
- var keyfactorCaId = caRequestID?.Substring(0, 36); //todo fix to use pipe delimiter
+ if (string.IsNullOrEmpty(caRequestID) || caRequestID.Length < 36)
+ throw new ArgumentException($"CA request ID '{caRequestID}' is missing or too short to contain a valid UUID", nameof(caRequestID));
+
+ 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))
@@ -96,6 +110,7 @@ public async Task GetSingleRecord(string caRequestID)
}
catch (Exception e)
{
+ Logger.LogError(e, "Error occurred getting single cert for CA request ID {CaRequestID}: {Message}", caRequestID, e.Message);
throw new Exception($"Error Occurred getting single cert {e.Message}");
}
}
@@ -106,27 +121,39 @@ public async Task Synchronize(BlockingCollection blockin
{
Logger.LogTrace($"Full Sync? {fullSync.ToString()}");
Logger.MethodEntry();
+ using var flow = new FlowLogger(Logger, "Synchronize");
try
{
if (fullSync)
{
- Logger.LogDebug("Performing full sync - no date filter applied");
- await SyncCertificates(blockingBuffer, cancelToken, null);
+ Logger.LogInformation("Performing full sync - no date filter applied");
+ flow.Step("DetermineSyncMode", "Full sync - no date filter applied");
+ await SyncCertificates(blockingBuffer, cancelToken, null, flow);
}
else
{
var filterDays = SyncFilterDays > 0 ? SyncFilterDays : 5;
var filterDate = DateTime.Today.Subtract(TimeSpan.FromDays(filterDays));
var dateFilter = filterDate.ToString("yyyy/MM/dd");
- Logger.LogDebug($"Performing incremental sync with expiration date filter: {dateFilter}");
- await SyncCertificates(blockingBuffer, cancelToken, dateFilter);
+ Logger.LogInformation($"Performing incremental sync with expiration date filter: {dateFilter}");
+ flow.Step("DetermineSyncMode", $"Incremental sync with expiration date filter: {dateFilter}");
+ await SyncCertificates(blockingBuffer, cancelToken, dateFilter, flow);
}
blockingBuffer.CompleteAdding();
+ Logger.LogInformation("Csc Global Synchronize Task completed successfully");
+ }
+ catch (OperationCanceledException)
+ {
+ Logger.LogWarning("Csc Global Synchronize Task was cancelled");
+ flow.Fail("Synchronize", "Task was cancelled");
+ blockingBuffer.CompleteAdding();
+ throw;
}
catch (Exception e)
{
- Logger.LogError($"Csc Global Synchronize Task failed! {LogHandler.FlattenException(e)}");
+ Logger.LogError(e, $"Csc Global Synchronize Task failed! {LogHandler.FlattenException(e)}");
+ flow.Fail("Synchronize", e.Message);
Logger.MethodExit();
blockingBuffer.CompleteAdding();
throw;
@@ -136,10 +163,22 @@ public async Task Synchronize(BlockingCollection blockin
}
private async Task SyncCertificates(BlockingCollection blockingBuffer,
- CancellationToken cancelToken, string? dateFilter)
+ CancellationToken cancelToken, string? dateFilter, FlowLogger flow)
{
- var certs = await CscGlobalClient.SubmitCertificateListRequestAsync(dateFilter);
+ var certs = await flow.StepAsync("SubmitCertificateListRequest",
+ () => CscGlobalClient.SubmitCertificateListRequestAsync(dateFilter));
+ Logger.LogInformation($"Retrieved {certs?.Results?.Count ?? 0} certificate(s) from CSC Global for sync");
+
+ if (certs?.Results == null)
+ {
+ Logger.LogWarning("Certificate list request returned no results collection; nothing to sync");
+ flow.Step("QueueCertificates", "No results collection returned; nothing to sync");
+ return;
+ }
+
+ var queuedCount = 0;
+ var skippedCount = 0;
foreach (var currentResponseItem in certs.Results)
{
cancelToken.ThrowIfCancellationRequested();
@@ -164,6 +203,7 @@ private async Task SyncCertificates(BlockingCollection b
var certData = fileContent.Replace("\r\n", string.Empty);
var certString = GetEndEntityCertificate(certData);
if (certString.Length > 0)
+ {
blockingBuffer.Add(new AnyCAPluginCertificate
{
CARequestID = $"{currentResponseItem?.Uuid}",
@@ -171,36 +211,73 @@ private async Task SyncCertificates(BlockingCollection b
Status = certStatus,
ProductID = productId
}, cancelToken);
+ queuedCount++;
+ }
+ else
+ {
+ Logger.LogWarning($"Could not extract end-entity certificate for {currentResponseItem?.Uuid}; skipping sync of this record");
+ skippedCount++;
+ }
+ }
+ else
+ {
+ Logger.LogWarning($"No certificate content returned by CSC Global for {currentResponseItem?.Uuid}; skipping sync of this record");
+ skippedCount++;
}
}
+ else
+ {
+ Logger.LogTrace($"Skipping Certificate ID {currentResponseItem?.Uuid} - status {currentResponseItem?.Status} is not eligible for sync");
+ skippedCount++;
+ }
}
+
+ flow.Step("QueueCertificates", $"Queued {queuedCount}, skipped {skippedCount}");
+ Logger.LogInformation($"Sync queued {queuedCount} certificate(s), skipped {skippedCount}");
}
//done
public async Task Revoke(string caRequestID, string hexSerialNumber, uint revocationReason)
{
+ Logger.MethodEntry(LogLevel.Debug);
+ using var flow = new FlowLogger(Logger, "Revoke");
try
{
- 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.LogInformation($"Starting Revoke for CA request ID {caRequestID}, reason {revocationReason}");
+ if (string.IsNullOrEmpty(caRequestID) || caRequestID.Length < 36)
+ throw new ArgumentException($"CA request ID '{caRequestID}' is missing or too short to contain a valid UUID", nameof(caRequestID));
+
+ var uuid = caRequestID.Substring(0, 36); //todo fix to use pipe delimiter
+
+ var revokeResponse = await flow.StepAsync("SubmitRevokeCertificate",
+ () => CscGlobalClient.SubmitRevokeCertificateAsync(uuid));
Logger.LogTrace($"Revoke Response JSON: {JsonConvert.SerializeObject(revokeResponse)}");
- Logger.MethodExit(LogLevel.Debug);
var revokeResult = _requestManager.GetRevokeResult(revokeResponse);
if (revokeResult == (int)EndEntityStatus.FAILED)
+ {
if (!string.IsNullOrEmpty(revokeResponse?.RegistrationError?.Description))
+ {
+ flow.Fail("SubmitRevokeCertificate", revokeResponse?.RegistrationError?.Description ?? "Unknown error");
throw new HttpRequestException(
$"Revoke Failed with message {revokeResponse?.RegistrationError?.Description}");
+ }
+ Logger.LogWarning($"Revoke returned a failed status for CA request ID {caRequestID} with no error description");
+ }
+ else
+ {
+ Logger.LogInformation($"Revoke succeeded for CA request ID {caRequestID}");
+ }
+
+ Logger.MethodExit(LogLevel.Debug);
return revokeResult;
}
catch (Exception e)
{
+ Logger.LogError(e, $"Revoke Failed for CA request ID {caRequestID} with message {e?.Message}");
throw new Exception($"Revoke Failed with message {e?.Message}");
}
}
@@ -209,115 +286,194 @@ await CscGlobalClient.SubmitRevokeCertificateAsync(caRequestID.Substring(0, 36))
public async Task Enroll(string csr, string subject, Dictionary san,
EnrollmentProductInfo productInfo, RequestFormat requestFormat, EnrollmentType enrollmentType)
{
+ if (productInfo == null) throw new ArgumentNullException(nameof(productInfo));
+
Logger.MethodEntry(LogLevel.Debug);
+ Logger.LogInformation($"Starting Enroll for product {productInfo.ProductID}, enrollment type {enrollmentType}");
+ using var flow = new FlowLogger(Logger, "Enroll");
- RegistrationRequest enrollmentRequest;
- var priorSn = "";
- ReissueRequest reissueRequest;
- RenewalRequest renewRequest;
- if (productInfo.ProductParameters.ContainsKey("priorcertsn"))
+ try
{
- priorSn = productInfo.ProductParameters["PriorCertSN"];
- Logger.LogDebug($"Prior cert sn: {priorSn}");
- }
+ RegistrationRequest enrollmentRequest;
+ var priorSn = "";
+ ReissueRequest reissueRequest;
+ RenewalRequest renewRequest;
+ var productParameters = productInfo.ProductParameters ?? new Dictionary();
+ if (productParameters.ContainsKey("priorcertsn"))
+ {
+ productParameters.TryGetValue("PriorCertSN", out priorSn);
+ priorSn ??= "";
+ Logger.LogDebug($"Prior cert sn: {priorSn}");
+ }
- string uUId;
- var customFields = await CscGlobalClient.SubmitGetCustomFields();
+ string uUId;
+ var customFields = await flow.StepAsync("SubmitGetCustomFields", () => CscGlobalClient.SubmitGetCustomFields());
- switch (enrollmentType)
- {
- 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"))
- {
- 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
- {
- return new EnrollmentResult
+ switch (enrollmentType)
+ {
+ case EnrollmentType.New:
+ flow.Branch("New Enrollment");
+ //If they renewed an expired cert it gets here and this will not be supported
+ IRegistrationResponse enrollmentResponse;
+ if (!productParameters.ContainsKey("PriorCertSN"))
{
- Status = 30, //failure
- StatusMessage = "You cannot renew an expired cert please perform an new enrollment."
- };
- }
+ enrollmentRequest = _requestManager.GetRegistrationRequest(productInfo, csr, san, customFields);
+ Logger.LogTrace($"Enrollment Request JSON: {JsonConvert.SerializeObject(enrollmentRequest)}");
+ enrollmentResponse = await flow.StepAsync("SubmitRegistration",
+ () => CscGlobalClient.SubmitRegistrationAsync(enrollmentRequest));
+ Logger.LogTrace($"Enrollment Response JSON: {JsonConvert.SerializeObject(enrollmentResponse)}");
+ }
+ else
+ {
+ Logger.LogWarning("Cannot renew an expired cert via new enrollment; a new enrollment must be performed instead");
+ flow.Fail("New Enrollment", "Attempted to renew an expired cert via new enrollment");
+ flow.EndBranch();
+ return new EnrollmentResult
+ {
+ Status = 30, //failure
+ StatusMessage = "You cannot renew an expired cert please perform an new enrollment."
+ };
+ }
- 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;
- }
+ flow.EndBranch();
+ var newResult = _requestManager.GetEnrollmentResult(enrollmentResponse);
+ LogEnrollmentOutcome(newResult, "New Enrollment");
+ Logger.MethodExit(LogLevel.Debug);
+ return newResult;
+ case EnrollmentType.RenewOrReissue:
+ flow.Branch("Renew Or Reissue");
+ if (string.IsNullOrEmpty(priorSn))
+ {
+ Logger.LogWarning($"Renew/Reissue requested for product {productInfo.ProductID} but no prior certificate serial number was supplied");
+ flow.Fail("Renew Or Reissue", "Missing prior certificate serial number");
+ flow.EndBranch();
+ return new EnrollmentResult
+ {
+ Status = 30, //failure
+ StatusMessage = "Cannot renew or reissue: no prior certificate serial number was supplied."
+ };
+ }
- if (expirationDate < DateTime.Now) renewal = true;
- if (renewal)
- {
+ //Logic to determine renew vs reissue
+ var renewal = false;
+ var order_id = await _certificateDataReader.GetRequestIDBySerialNumber(priorSn);
+ if (string.IsNullOrEmpty(order_id))
+ {
+ Logger.LogWarning($"Could not find a Keyfactor request ID for prior certificate serial number {priorSn}");
+ flow.Fail("Renew Or Reissue", $"No request ID found for prior certificate serial number {priorSn}");
+ flow.EndBranch();
+ return new EnrollmentResult
+ {
+ Status = 30, //failure
+ StatusMessage = $"Cannot renew or reissue: no prior request found for serial number {priorSn}."
+ };
+ }
+
+ var expirationDate = _certificateDataReader.GetExpirationDateByRequestId(order_id);
+ if (expirationDate == null)
+ {
+ var localcert = await GetSingleRecord(order_id);
+ expirationDate = localcert?.RevocationDate;
+ }
+
+ if (expirationDate < DateTime.Now) renewal = true;
+ if (renewal)
+ {
+ flow.Step("DetermineRenewOrReissue", "Renewal - cert is expired");
+ //One click won't work for this implementation b/c we are missing enrollment params
+ if (productParameters.ContainsKey("Applicant Last Name"))
+ {
+ //priorCert = _certificateDataReader.get(
+ //DataConversion.HexToBytes(productInfo.ProductParameters["PriorCertSN"]));
+ //uUId = priorCert.CARequestID.Substring(0, 36); //uUId is a GUID
+ uUId = await _certificateDataReader.GetRequestIDBySerialNumber(
+ productParameters.GetValueOrDefault("PriorCertSN", ""));
+ Logger.LogTrace($"Renew uUId: {uUId}");
+ renewRequest = _requestManager.GetRenewalRequest(productInfo, uUId, csr, san, customFields);
+ Logger.LogTrace($"Renewal Request JSON: {JsonConvert.SerializeObject(renewRequest)}");
+ var renewResponse = await flow.StepAsync("SubmitRenewal",
+ () => CscGlobalClient.SubmitRenewalAsync(renewRequest));
+ Logger.LogTrace($"Renewal Response JSON: {JsonConvert.SerializeObject(renewResponse)}");
+ flow.EndBranch();
+ var renewResult = _requestManager.GetRenewResponse(renewResponse);
+ LogEnrollmentOutcome(renewResult, "Renewal");
+ Logger.MethodExit(LogLevel.Debug);
+ return renewResult;
+ }
+
+ Logger.LogWarning($"One click renew is not available for product {productInfo.ProductID}; missing required enrollment parameters");
+ flow.Fail("Renewal", "One click renew is not available; missing Applicant Last Name");
+ flow.EndBranch();
+ return new EnrollmentResult
+ {
+ Status = 30, //failure
+ StatusMessage =
+ "One click Renew Is Not Available for this Certificate Type. Use the configure button instead."
+ };
+ }
+
+ flow.Step("DetermineRenewOrReissue", "Reissue - cert is still valid");
//One click won't work for this implementation b/c we are missing enrollment params
- if (productInfo.ProductParameters.ContainsKey("Applicant Last Name"))
+ if (productParameters.ContainsKey("Applicant Last Name"))
{
- //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)}");
+ var requestid = await _certificateDataReader.GetRequestIDBySerialNumber(
+ productParameters.GetValueOrDefault("PriorCertSN", ""));
+ if (string.IsNullOrEmpty(requestid) || requestid.Length < 36)
+ {
+ Logger.LogWarning($"Could not find a valid Keyfactor request ID for prior certificate serial number for product {productInfo.ProductID}");
+ flow.Fail("Reissue", "No valid request ID found for prior certificate serial number");
+ flow.EndBranch();
+ return new EnrollmentResult
+ {
+ Status = 30, //failure
+ StatusMessage = "Cannot reissue: no prior request found for the supplied certificate serial number."
+ };
+ }
+
+ 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 = await flow.StepAsync("SubmitReissue",
+ () => CscGlobalClient.SubmitReissueAsync(reissueRequest));
+ Logger.LogTrace($"Reissue Response JSON: {JsonConvert.SerializeObject(reissueResponse)}");
+ flow.EndBranch();
+ var reissueResult = _requestManager.GetReIssueResult(reissueResponse);
+ LogEnrollmentOutcome(reissueResult, "Reissue");
Logger.MethodExit(LogLevel.Debug);
- return _requestManager.GetRenewResponse(renewResponse);
+ return reissueResult;
}
+ Logger.LogWarning($"One click reissue is not available for product {productInfo.ProductID}; missing required enrollment parameters");
+ flow.Fail("Reissue", "One click reissue is not available; missing Applicant Last Name");
+ flow.EndBranch();
return new EnrollmentResult
{
Status = 30, //failure
StatusMessage =
"One click Renew Is Not Available for this Certificate Type. Use the configure button instead."
};
- }
-
- 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.LogWarning($"Unhandled enrollment type {enrollmentType} for product {productInfo.ProductID}");
+ Logger.MethodExit(LogLevel.Debug);
+ return null;
}
+ catch (Exception e)
+ {
+ Logger.LogError(e, $"Enroll failed for product {productInfo.ProductID}: {e.Message}");
+ flow.Fail("Enroll", e.Message);
+ throw;
+ }
+ }
- Logger.MethodExit(LogLevel.Debug);
- return null;
+ private void LogEnrollmentOutcome(EnrollmentResult result, string operationName)
+ {
+ if (result == null) return;
+ if (result.Status == (int)EndEntityStatus.FAILED)
+ Logger.LogError($"{operationName} failed: {result.StatusMessage}");
+ else
+ Logger.LogInformation($"{operationName} succeeded: {result.StatusMessage}");
}
//done
@@ -330,7 +486,7 @@ public async Task Ping()
}
catch (Exception e)
{
- Logger.LogError($"There was an error contacting CSCGlobal: {e.Message}.");
+ Logger.LogError(e, $"There was an error contacting CSCGlobal: {e.Message}.");
throw new Exception($"Error attempting to ping CSCGlobal: {e.Message}.", e);
}
@@ -340,19 +496,27 @@ public async Task Ping()
//do
public async Task ValidateCAConnectionInfo(Dictionary connectionInfo)
{
+ Logger.MethodEntry(LogLevel.Debug);
+ Logger.LogDebug($"Validating CA connection info with {connectionInfo?.Count ?? 0} entries");
+ Logger.MethodExit(LogLevel.Debug);
}
//do
public async Task ValidateProductInfo(EnrollmentProductInfo productInfo,
Dictionary connectionInfo)
{
+ Logger.MethodEntry(LogLevel.Debug);
var certType = ProductIDs.productIds.Find(x =>
x.Equals(productInfo.ProductID, StringComparison.InvariantCultureIgnoreCase));
- if (certType == null) throw new ArgumentException($"Cannot find {productInfo.ProductID}", "ProductId");
+ if (certType == null)
+ {
+ Logger.LogError($"Cannot find product ID {productInfo.ProductID} in the list of supported CSC Global products");
+ throw new ArgumentException($"Cannot find {productInfo.ProductID}", "ProductId");
+ }
Logger.LogInformation($"Validated {certType} ({certType})configured for AnyGateway");
-
+ Logger.MethodExit(LogLevel.Debug);
}
//done
diff --git a/cscglobal-caplugin/Client/CscGlobalClient.cs b/cscglobal-caplugin/Client/CscGlobalClient.cs
index 0a5c7c5..ca692e2 100644
--- a/cscglobal-caplugin/Client/CscGlobalClient.cs
+++ b/cscglobal-caplugin/Client/CscGlobalClient.cs
@@ -23,13 +23,25 @@ public sealed class CscGlobalClient : ICscGlobalClient
public CscGlobalClient(IAnyCAPluginConfigProvider config)
{
- Logger = LogHandler.GetClassLogger();
+ Logger = LogHandler.GetClassLogger();
+ if (config == null) throw new ArgumentNullException(nameof(config));
+ if (config.CAConnectionData == null)
+ {
+ Logger.LogError("CA connection data is null; client will not be able to call the CSC Global API");
+ return;
+ }
+
if (config.CAConnectionData.ContainsKey(Constants.CscGlobalApiKey))
{
BaseUrl = new Uri(config.CAConnectionData[Constants.CscGlobalUrl].ToString());
ApiKey = config.CAConnectionData[Constants.CscGlobalApiKey].ToString();
Authorization = config.CAConnectionData[Constants.BearerToken].ToString();
RestClient = ConfigureRestClient();
+ Logger.LogDebug($"CscGlobalClient configured for base URL {BaseUrl}");
+ }
+ else
+ {
+ Logger.LogError($"CA connection data is missing required key '{Constants.CscGlobalApiKey}'; client will not be able to call the CSC Global API");
}
}
@@ -41,6 +53,7 @@ public CscGlobalClient(IAnyCAPluginConfigProvider config)
public async Task SubmitRegistrationAsync(
RegistrationRequest registerRequest)
{
+ Logger.MethodEntry(LogLevel.Debug);
using (var resp = await RestClient.PostAsync("/dbs/api/v2/tls/registration", new StringContent(
JsonConvert.SerializeObject(registerRequest), Encoding.ASCII, "application/json")))
{
@@ -48,18 +61,30 @@ public async Task SubmitRegistrationAsync(
var settings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
if (resp.StatusCode == HttpStatusCode.BadRequest) //Csc Sends Errors back in 400 Json Response
{
+ var rawErrorResponse = await resp.Content.ReadAsStringAsync();
var errorResponse =
- JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync(),
- settings);
+ JsonConvert.DeserializeObject(rawErrorResponse, settings);
+ Logger.LogWarning($"Registration request rejected by CSC Global: {errorResponse?.Description ?? rawErrorResponse}");
var response = new RegistrationResponse();
response.RegistrationError = errorResponse;
response.Result = null;
return response;
}
+ if (!resp.IsSuccessStatusCode)
+ {
+ var errorBody = await resp.Content.ReadAsStringAsync();
+ Logger.LogError($"Registration request failed with status code {resp.StatusCode} | Message: {errorBody}");
+ throw new HttpRequestException($"Registration request failed with status code {resp.StatusCode}: {errorBody}");
+ }
+
var registrationResponse =
JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync(),
settings);
+ if (registrationResponse == null)
+ throw new InvalidOperationException("Registration request succeeded but the response body could not be parsed");
+
+ Logger.MethodExit(LogLevel.Debug);
return registrationResponse;
}
}
@@ -81,17 +106,28 @@ public async Task SubmitRenewalAsync(
var errorResponse =
JsonConvert.DeserializeObject(rawErrorResponse,
settings);
+ Logger.LogWarning($"Renewal request rejected by CSC Global: {errorResponse?.Description ?? rawErrorResponse}");
var response = new RenewalResponse();
response.RegistrationError = errorResponse;
response.Result = null;
return response;
}
+ if (!resp.IsSuccessStatusCode)
+ {
+ var errorBody = await resp.Content.ReadAsStringAsync();
+ Logger.LogError($"Renewal request failed with status code {resp.StatusCode} | Message: {errorBody}");
+ throw new HttpRequestException($"Renewal request failed with status code {resp.StatusCode}: {errorBody}");
+ }
+
var rawRenewResponse = await resp.Content.ReadAsStringAsync();
Logger.LogTrace("Logging Success Response Raw");
Logger.LogTrace(rawRenewResponse);
var renewalResponse =
JsonConvert.DeserializeObject(rawRenewResponse);
+ if (renewalResponse == null)
+ throw new InvalidOperationException("Renewal request succeeded but the response body could not be parsed");
+
return renewalResponse;
}
}
@@ -107,61 +143,110 @@ public async Task SubmitReissueAsync(
var settings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
if (resp.StatusCode == HttpStatusCode.BadRequest) //Csc Sends Errors back in 400 Json Response
{
+ var rawErrorResponse = await resp.Content.ReadAsStringAsync();
var errorResponse =
- JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync(),
- settings);
+ JsonConvert.DeserializeObject(rawErrorResponse, settings);
+ Logger.LogWarning($"Reissue request rejected by CSC Global: {errorResponse?.Description ?? rawErrorResponse}");
var response = new ReissueResponse();
response.RegistrationError = errorResponse;
response.Result = null;
return response;
}
+ if (!resp.IsSuccessStatusCode)
+ {
+ var errorBody = await resp.Content.ReadAsStringAsync();
+ Logger.LogError($"Reissue request failed with status code {resp.StatusCode} | Message: {errorBody}");
+ throw new HttpRequestException($"Reissue request failed with status code {resp.StatusCode}: {errorBody}");
+ }
+
var reissueResponse =
JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync());
+ if (reissueResponse == null)
+ throw new InvalidOperationException("Reissue request succeeded but the response body could not be parsed");
+
return reissueResponse;
}
}
public async Task SubmitGetCertificateAsync(string certificateId)
{
+ Logger.MethodEntry(LogLevel.Debug);
+ Logger.LogTrace($"Getting certificate with ID {certificateId}");
using (var resp = await RestClient.GetAsync($"/dbs/api/v2/tls/certificate/{certificateId}"))
{
+ if (!resp.IsSuccessStatusCode)
+ {
+ var errorBody = await resp.Content.ReadAsStringAsync();
+ Logger.LogError($"Failed to get certificate {certificateId}. Status code {resp.StatusCode} | Message: {errorBody}");
+ }
+
resp.EnsureSuccessStatusCode();
var getCertificateResponse =
JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync());
+ if (getCertificateResponse == null)
+ throw new InvalidOperationException($"Get certificate request for {certificateId} succeeded but the response body could not be parsed");
+
+ Logger.MethodExit(LogLevel.Debug);
return getCertificateResponse;
}
}
public async Task> SubmitGetCustomFields()
{
+ Logger.MethodEntry(LogLevel.Debug);
using (var resp = await RestClient.GetAsync("/dbs/api/v2/admin/customfields"))
{
+ if (!resp.IsSuccessStatusCode)
+ {
+ var errorBody = await resp.Content.ReadAsStringAsync();
+ Logger.LogError($"Failed to get custom fields. Status code {resp.StatusCode} | Message: {errorBody}");
+ }
+
resp.EnsureSuccessStatusCode();
var getCustomFieldsResponse =
JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync());
- return getCustomFieldsResponse.CustomFields;
+ if (getCustomFieldsResponse == null)
+ throw new InvalidOperationException("Get custom fields request succeeded but the response body could not be parsed");
+
+ Logger.LogTrace($"Retrieved {getCustomFieldsResponse.CustomFields?.Count ?? 0} custom field(s)");
+ Logger.MethodExit(LogLevel.Debug);
+ return getCustomFieldsResponse.CustomFields ?? new List();
}
}
public async Task SubmitRevokeCertificateAsync(string uuId)
{
+ Logger.MethodEntry(LogLevel.Debug);
+ Logger.LogTrace($"Revoking certificate with UUID {uuId}");
using (var resp = await RestClient.PutAsync($"/dbs/api/v2/tls/revoke/{uuId}", new StringContent("")))
{
var settings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
if (resp.StatusCode == HttpStatusCode.BadRequest) //Csc Sends Errors back in 400 Json Response
{
+ var rawErrorResponse = await resp.Content.ReadAsStringAsync();
var errorResponse =
- JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync(),
- settings);
+ JsonConvert.DeserializeObject(rawErrorResponse, settings);
+ Logger.LogWarning($"Revoke request rejected by CSC Global for UUID {uuId}: {errorResponse?.Description ?? rawErrorResponse}");
var response = new RevokeResponse();
response.RegistrationError = errorResponse;
response.RevokeSuccess = null;
return response;
}
+ if (!resp.IsSuccessStatusCode)
+ {
+ var errorBody = await resp.Content.ReadAsStringAsync();
+ Logger.LogError($"Revoke request for UUID {uuId} failed with status code {resp.StatusCode} | Message: {errorBody}");
+ throw new HttpRequestException($"Revoke request failed with status code {resp.StatusCode}: {errorBody}");
+ }
+
var getRevokeResponse =
JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync());
+ if (getRevokeResponse == null)
+ throw new InvalidOperationException("Revoke request succeeded but the response body could not be parsed");
+
+ Logger.MethodExit(LogLevel.Debug);
return getRevokeResponse;
}
}
@@ -179,13 +264,18 @@ public async Task SubmitCertificateListRequestAsync(str
if (!resp.IsSuccessStatusCode)
{
- var responseMessage = resp.Content.ReadAsStringAsync().Result;
- Logger.LogError(
- $"Failed Request to Keyfactor. Retrying request. Status Code {resp.StatusCode} | Message: {responseMessage}");
+ var responseMessage = await resp.Content.ReadAsStringAsync();
+ Logger.LogError($"Certificate list request failed. Status Code {resp.StatusCode} | Message: {responseMessage}");
+ throw new HttpRequestException($"Certificate list request failed with status code {resp.StatusCode}: {responseMessage}");
}
var certificateListResponse =
JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync());
+ if (certificateListResponse == null)
+ throw new InvalidOperationException("Certificate list request succeeded but the response body could not be parsed");
+
+ Logger.LogInformation($"Certificate list request returned {certificateListResponse.Results?.Count ?? 0} result(s)");
+ Logger.MethodExit(LogLevel.Debug);
return certificateListResponse;
}
diff --git a/cscglobal-caplugin/Constants.cs b/cscglobal-caplugin/Constants.cs
index 4d6b4da..fd79d7a 100644
--- a/cscglobal-caplugin/Constants.cs
+++ b/cscglobal-caplugin/Constants.cs
@@ -27,7 +27,10 @@ public class ProductIDs
"CSC TrustedSecure Premium Wildcard Certificate",
"CSC TrustedSecure Domain Validated SSL",
"CSC TrustedSecure Domain Validated Wildcard SSL",
- "CSC TrustedSecure Domain Validated UC Certificate"
+ "CSC TrustedSecure Domain Validated UC Certificate",
+ "CSC TrustedSecure EV, Multiple Names",
+ "CSC TrustedSecure OV Wildcard, Multiple Names",
+ "CSC TrustedSecure DV Wildcard, Multiple Names"
};
}
diff --git a/cscglobal-caplugin/FlowLogger.cs b/cscglobal-caplugin/FlowLogger.cs
new file mode 100644
index 0000000..711cac9
--- /dev/null
+++ b/cscglobal-caplugin/FlowLogger.cs
@@ -0,0 +1,267 @@
+// 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;
+
+///
+/// Step-oriented trace logger for CA plugin pipelines. Wraps a sequence of named steps with
+/// timings, branches, and outcomes into a single appended summary block that is attached
+/// to on both success and failure, giving
+/// operators a single scannable breadcrumb trail per plugin operation (Enroll, Revoke, Sync).
+///
+public class FlowLogger : IDisposable
+{
+ private readonly ILogger _logger;
+ private readonly string _flowName;
+ private readonly Stopwatch _overallStopwatch;
+ private readonly List _steps = new();
+ private readonly Stack _branchStack = new();
+
+ public FlowLogger(ILogger logger, string flowName)
+ {
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ _flowName = flowName ?? throw new ArgumentNullException(nameof(flowName));
+ _overallStopwatch = Stopwatch.StartNew();
+ _logger.LogTrace("[FLOW:{FlowName}] === BEGIN ===", _flowName);
+ }
+
+ public void Step(string name, string detail = null)
+ {
+ var step = new FlowStep { Name = name, Detail = detail, Status = StepStatus.Success };
+ _steps.Add(step);
+ var prefix = GetPrefix();
+ if (detail != null)
+ _logger.LogTrace("[FLOW:{FlowName}] {Prefix}[OK] {StepName} - {Detail}", _flowName, prefix, name, detail);
+ else
+ _logger.LogTrace("[FLOW:{FlowName}] {Prefix}[OK] {StepName}", _flowName, prefix, name);
+ }
+
+ public void Step(string name, Action action, string detail = null)
+ {
+ var sw = Stopwatch.StartNew();
+ var step = new FlowStep { Name = name, Detail = detail };
+ try
+ {
+ action();
+ sw.Stop();
+ step.Status = StepStatus.Success;
+ step.ElapsedMs = sw.ElapsedMilliseconds;
+ _steps.Add(step);
+ var prefix = GetPrefix();
+ _logger.LogTrace("[FLOW:{FlowName}] {Prefix}[OK] {StepName} ({Elapsed}ms){DetailSuffix}",
+ _flowName, prefix, name, sw.ElapsedMilliseconds, FormatDetail(detail));
+ }
+ catch (Exception ex)
+ {
+ sw.Stop();
+ step.Status = StepStatus.Failed;
+ step.ElapsedMs = sw.ElapsedMilliseconds;
+ step.ErrorMessage = ex.Message;
+ _steps.Add(step);
+ var prefix = GetPrefix();
+ _logger.LogTrace("[FLOW:{FlowName}] {Prefix}[FAIL] {StepName} ({Elapsed}ms) - {Error}",
+ _flowName, prefix, name, sw.ElapsedMilliseconds, ex.Message);
+ throw;
+ }
+ }
+
+ public async Task StepAsync(string name, Func action, string detail = null)
+ {
+ var sw = Stopwatch.StartNew();
+ var step = new FlowStep { Name = name, Detail = detail };
+ try
+ {
+ await action();
+ sw.Stop();
+ step.Status = StepStatus.Success;
+ step.ElapsedMs = sw.ElapsedMilliseconds;
+ _steps.Add(step);
+ var prefix = GetPrefix();
+ _logger.LogTrace("[FLOW:{FlowName}] {Prefix}[OK] {StepName} ({Elapsed}ms){DetailSuffix}",
+ _flowName, prefix, name, sw.ElapsedMilliseconds, FormatDetail(detail));
+ }
+ catch (Exception ex)
+ {
+ sw.Stop();
+ step.Status = StepStatus.Failed;
+ step.ElapsedMs = sw.ElapsedMilliseconds;
+ step.ErrorMessage = ex.Message;
+ _steps.Add(step);
+ var prefix = GetPrefix();
+ _logger.LogTrace("[FLOW:{FlowName}] {Prefix}[FAIL] {StepName} ({Elapsed}ms) - {Error}",
+ _flowName, prefix, name, sw.ElapsedMilliseconds, ex.Message);
+ throw;
+ }
+ }
+
+ public async Task StepAsync(string name, Func> action, string detail = null)
+ {
+ var sw = Stopwatch.StartNew();
+ var step = new FlowStep { Name = name, Detail = detail };
+ try
+ {
+ var result = await action();
+ sw.Stop();
+ step.Status = StepStatus.Success;
+ step.ElapsedMs = sw.ElapsedMilliseconds;
+ _steps.Add(step);
+ var prefix = GetPrefix();
+ _logger.LogTrace("[FLOW:{FlowName}] {Prefix}[OK] {StepName} ({Elapsed}ms){DetailSuffix}",
+ _flowName, prefix, name, sw.ElapsedMilliseconds, FormatDetail(detail));
+ return result;
+ }
+ catch (Exception ex)
+ {
+ sw.Stop();
+ step.Status = StepStatus.Failed;
+ step.ElapsedMs = sw.ElapsedMilliseconds;
+ step.ErrorMessage = ex.Message;
+ _steps.Add(step);
+ var prefix = GetPrefix();
+ _logger.LogTrace("[FLOW:{FlowName}] {Prefix}[FAIL] {StepName} ({Elapsed}ms) - {Error}",
+ _flowName, prefix, name, sw.ElapsedMilliseconds, ex.Message);
+ throw;
+ }
+ }
+
+ public T Step(string name, Func action, string detail = null)
+ {
+ var sw = Stopwatch.StartNew();
+ var step = new FlowStep { Name = name, Detail = detail };
+ try
+ {
+ var result = action();
+ sw.Stop();
+ step.Status = StepStatus.Success;
+ step.ElapsedMs = sw.ElapsedMilliseconds;
+ _steps.Add(step);
+ var prefix = GetPrefix();
+ _logger.LogTrace("[FLOW:{FlowName}] {Prefix}[OK] {StepName} ({Elapsed}ms){DetailSuffix}",
+ _flowName, prefix, name, sw.ElapsedMilliseconds, FormatDetail(detail));
+ return result;
+ }
+ catch (Exception ex)
+ {
+ sw.Stop();
+ step.Status = StepStatus.Failed;
+ step.ElapsedMs = sw.ElapsedMilliseconds;
+ step.ErrorMessage = ex.Message;
+ _steps.Add(step);
+ var prefix = GetPrefix();
+ _logger.LogTrace("[FLOW:{FlowName}] {Prefix}[FAIL] {StepName} ({Elapsed}ms) - {Error}",
+ _flowName, prefix, name, sw.ElapsedMilliseconds, ex.Message);
+ throw;
+ }
+ }
+
+ public void Fail(string name, string reason)
+ {
+ var step = new FlowStep { Name = name, Status = StepStatus.Failed, ErrorMessage = reason };
+ _steps.Add(step);
+ var prefix = GetPrefix();
+ _logger.LogTrace("[FLOW:{FlowName}] {Prefix}[FAIL] {StepName} - {Reason}", _flowName, prefix, name, reason);
+ }
+
+ public void Skip(string name, string reason)
+ {
+ var step = new FlowStep { Name = name, Status = StepStatus.Skipped, Detail = reason };
+ _steps.Add(step);
+ var prefix = GetPrefix();
+ _logger.LogTrace("[FLOW:{FlowName}] {Prefix}[SKIP] {StepName} - {Reason}", _flowName, prefix, name, reason);
+ }
+
+ public void Branch(string name)
+ {
+ _branchStack.Push(name);
+ var prefix = GetPrefix();
+ _logger.LogTrace("[FLOW:{FlowName}] {Prefix}>> {BranchName}", _flowName, prefix, name);
+ }
+
+ public void EndBranch()
+ {
+ if (_branchStack.Count > 0)
+ {
+ var name = _branchStack.Pop();
+ var prefix = GetPrefix();
+ _logger.LogTrace("[FLOW:{FlowName}] {Prefix}<< {BranchName}", _flowName, prefix, name);
+ }
+ }
+
+ public bool HasFailures => _steps.Any(s => s.Status == StepStatus.Failed);
+
+ public string GetSummary()
+ {
+ var hasFailures = HasFailures;
+ var overallStatus = hasFailures ? "FAILED" : "OK";
+ var total = _steps.Count;
+ var succeeded = _steps.Count(s => s.Status == StepStatus.Success);
+ var failed = _steps.Count(s => s.Status == StepStatus.Failed);
+ var skipped = _steps.Count(s => s.Status == StepStatus.Skipped);
+ var elapsed = _overallStopwatch.ElapsedMilliseconds;
+
+ var sb = new StringBuilder();
+ sb.AppendLine($"Flow: {_flowName} [{overallStatus}] Total: {elapsed}ms");
+ sb.AppendLine($"Steps: {total} total, {succeeded} ok, {failed} failed, {skipped} skipped");
+ sb.AppendLine("----------------------------------------");
+ foreach (var step in _steps)
+ {
+ var icon = step.Status == StepStatus.Success ? "[OK] "
+ : step.Status == StepStatus.Failed ? "[FAIL]"
+ : step.Status == StepStatus.Skipped ? "[SKIP]"
+ : "[...]";
+ var time = step.ElapsedMs.HasValue ? $" ({step.ElapsedMs}ms)" : "";
+ var detail = !string.IsNullOrEmpty(step.ErrorMessage)
+ ? $" - {step.ErrorMessage}"
+ : !string.IsNullOrEmpty(step.Detail)
+ ? $" - {step.Detail}"
+ : "";
+ sb.AppendLine($" {icon} {step.Name}{time}{detail}");
+ }
+ sb.Append("----------------------------------------");
+
+ return sb.ToString();
+ }
+
+ public void Dispose()
+ {
+ _overallStopwatch.Stop();
+ var summary = GetSummary();
+ _logger.LogTrace("[FLOW:{FlowName}] === END ===\n{Summary}", _flowName, summary);
+ }
+
+ private string GetPrefix()
+ {
+ if (_branchStack.Count == 0) return "";
+ return new string(' ', _branchStack.Count * 2) + "| ";
+ }
+
+ private static string FormatDetail(string detail)
+ {
+ return string.IsNullOrEmpty(detail) ? "" : $" - {detail}";
+ }
+
+ private enum StepStatus
+ {
+ Success,
+ Failed,
+ Skipped,
+ InProgress
+ }
+
+ private class FlowStep
+ {
+ public string Name { get; set; }
+ public string Detail { get; set; }
+ public StepStatus Status { get; set; } = StepStatus.InProgress;
+ public long? ElapsedMs { get; set; }
+ public string ErrorMessage { get; set; }
+ }
+}
diff --git a/cscglobal-caplugin/RequestManager.cs b/cscglobal-caplugin/RequestManager.cs
index 776902c..b580e71 100644
--- a/cscglobal-caplugin/RequestManager.cs
+++ b/cscglobal-caplugin/RequestManager.cs
@@ -10,18 +10,36 @@
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 static string GetOptionalParam(EnrollmentProductInfo productInfo, string key)
+ {
+ return productInfo.ProductParameters != null &&
+ productInfo.ProductParameters.TryGetValue(key, out var value)
+ ? value
+ : string.Empty;
+ }
+
private List GetCustomFields(EnrollmentProductInfo productInfo, List customFields)
{
var customFieldList = new List();
+ if (customFields == null)
+ {
+ Logger.LogTrace("No custom field definitions supplied; skipping custom field mapping");
+ return customFieldList;
+ }
+
foreach (var field in customFields)
if (productInfo.ProductParameters.ContainsKey(field.Label))
{
@@ -34,23 +52,49 @@ private List GetCustomFields(EnrollmentProductInfo productInfo, Lis
}
else if (field.Mandatory)
{
+ Logger.LogError($"Custom field {field.Label} is marked as mandatory, but was not supplied in the request.");
throw new Exception(
$"Custom field {field.Label} is marked as mandatory, but was not supplied in the request.");
}
+ Logger.LogTrace($"Mapped {customFieldList.Count} custom field(s) for request");
return customFieldList;
}
public EnrollmentResult GetRenewResponse(RenewalResponse renewResponse)
{
+ if (renewResponse == null)
+ {
+ Logger.LogError("Renewal failed: CSC Global returned no response");
+ return new EnrollmentResult
+ {
+ Status = (int)EndEntityStatus.FAILED, //failure
+ StatusMessage = "CSC Global returned no response for the renewal request"
+ };
+ }
+
if (renewResponse.RegistrationError != null)
+ {
+ Logger.LogError($"Renewal failed: {renewResponse.RegistrationError.Description}");
return new EnrollmentResult
{
Status = (int)EndEntityStatus.FAILED, //failure
- CARequestID = renewResponse?.Result?.Status?.Uuid,
+ CARequestID = renewResponse.Result?.Status?.Uuid,
StatusMessage = renewResponse.RegistrationError.Description
};
+ }
+ if (renewResponse.Result == null)
+ {
+ Logger.LogError("Renewal failed: CSC Global reported success but returned no result");
+ return new EnrollmentResult
+ {
+ Status = (int)EndEntityStatus.FAILED, //failure
+ StatusMessage = "CSC Global reported success but returned no result"
+ };
+ }
+
+ Logger.LogInformation($"Renewal successfully completed for {renewResponse.Result.CommonName}");
return new EnrollmentResult
{
Status = (int)EndEntityStatus.GENERATED, //success
@@ -64,12 +108,35 @@ public EnrollmentResult
GetEnrollmentResult(
IRegistrationResponse registrationResponse)
{
+ if (registrationResponse == null)
+ {
+ Logger.LogError("Enrollment failed: CSC Global returned no response");
+ return new EnrollmentResult
+ {
+ Status = (int)EndEntityStatus.FAILED, //failure
+ StatusMessage = "CSC Global returned no response for the registration request"
+ };
+ }
+
if (registrationResponse.RegistrationError != null)
+ {
+ Logger.LogError($"Enrollment failed: {registrationResponse.RegistrationError.Description}");
return new EnrollmentResult
{
Status = (int)EndEntityStatus.FAILED, //failure
StatusMessage = registrationResponse.RegistrationError.Description
};
+ }
+
+ if (registrationResponse.Result == null)
+ {
+ Logger.LogError("Enrollment failed: CSC Global reported success but returned no result");
+ return new EnrollmentResult
+ {
+ Status = (int)EndEntityStatus.FAILED, //failure
+ StatusMessage = "CSC Global reported success but returned no result"
+ };
+ }
var cnames = new Dictionary();
if (registrationResponse.Result.DcvDetails != null && registrationResponse.Result.DcvDetails.Count > 0)
@@ -86,10 +153,11 @@ public EnrollmentResult
}
}
+ Logger.LogInformation($"Order successfully created with order number {registrationResponse.Result.CommonName}");
return new EnrollmentResult
{
Status = (int)EndEntityStatus.EXTERNALVALIDATION, //success
- CARequestID = registrationResponse.Result.Status.Uuid,
+ CARequestID = registrationResponse.Result.Status?.Uuid,
StatusMessage =
$"Order Successfully Created With Order Number {registrationResponse.Result.CommonName}",
EnrollmentContext = cnames.Count > 0 ? cnames : null
@@ -98,25 +166,58 @@ public EnrollmentResult
public int GetRevokeResult(IRevokeResponse revokeResponse)
{
+ if (revokeResponse == null)
+ {
+ Logger.LogError("Revoke failed: CSC Global returned no response");
+ return (int)EndEntityStatus.FAILED;
+ }
+
if (revokeResponse.RegistrationError != null)
+ {
+ Logger.LogError($"Revoke failed: {revokeResponse.RegistrationError.Description}");
return (int)EndEntityStatus.FAILED;
+ }
return (int)EndEntityStatus.REVOKED;
}
public EnrollmentResult GetReIssueResult(IReissueResponse reissueResponse)
{
+ if (reissueResponse == null)
+ {
+ Logger.LogError("Reissue failed: CSC Global returned no response");
+ return new EnrollmentResult
+ {
+ Status = (int)EndEntityStatus.FAILED, //failure
+ StatusMessage = "CSC Global returned no response for the reissue request"
+ };
+ }
+
if (reissueResponse.RegistrationError != null)
+ {
+ Logger.LogError($"Reissue failed: {reissueResponse.RegistrationError.Description}");
return new EnrollmentResult
{
Status = (int)EndEntityStatus.FAILED, //failure
StatusMessage = reissueResponse.RegistrationError.Description
};
+ }
+ if (reissueResponse.Result == null)
+ {
+ Logger.LogError("Reissue failed: CSC Global reported success but returned no result");
+ return new EnrollmentResult
+ {
+ Status = (int)EndEntityStatus.FAILED, //failure
+ StatusMessage = "CSC Global reported success but returned no result"
+ };
+ }
+
+ Logger.LogInformation($"Reissue successfully completed for {reissueResponse.Result.CommonName}");
return new EnrollmentResult
{
Status = (int)EndEntityStatus.GENERATED, //success
- CARequestID = reissueResponse.Result.Status.Uuid,
+ CARequestID = reissueResponse.Result.Status?.Uuid,
StatusMessage = $"Reissue Successfully Completed For {reissueResponse.Result.CommonName}"
};
}
@@ -124,10 +225,29 @@ public EnrollmentResult GetReIssueResult(IReissueResponse reissueResponse)
public DomainControlValidation GetDomainControlValidation(string methodType, string[] emailAddress,
string domainName)
{
- foreach (var address in emailAddress)
+ if (string.IsNullOrWhiteSpace(domainName))
{
- var email = new MailAddress(address);
- if (domainName.Contains(email.Host.Split('.')[0]))
+ Logger.LogWarning("GetDomainControlValidation called with an empty domain name");
+ return null;
+ }
+
+ foreach (var address in emailAddress ?? Array.Empty())
+ {
+ if (string.IsNullOrWhiteSpace(address)) continue;
+
+ MailAddress email;
+ try
+ {
+ email = new MailAddress(address.Trim());
+ }
+ catch (FormatException fex)
+ {
+ Logger.LogWarning(fex, $"Skipping malformed DCV email address '{address}'");
+ continue;
+ }
+
+ var hostLabels = email.Host.Split('.');
+ if (hostLabels.Length > 0 && domainName.Contains(hostLabels[0]))
return new DomainControlValidation
{
MethodType = methodType,
@@ -135,6 +255,7 @@ public DomainControlValidation GetDomainControlValidation(string methodType, str
};
}
+ Logger.LogWarning($"No matching DCV email address found for domain {domainName}");
return null;
}
@@ -150,6 +271,7 @@ public DomainControlValidation GetDomainControlValidation(string methodType, str
public RegistrationRequest GetRegistrationRequest(EnrollmentProductInfo productInfo, string csr,
Dictionary sans, List customFields)
{
+ Logger.LogTrace($"Building registration request for product {productInfo.ProductID}");
//var cert = "-----BEGIN CERTIFICATE REQUEST-----\r\n";
var cert = Pemify(csr);
//cert = cert + "\r\n-----END CERTIFICATE REQUEST-----";
@@ -157,8 +279,8 @@ public RegistrationRequest GetRegistrationRequest(EnrollmentProductInfo productI
var bytes = Encoding.UTF8.GetBytes(cert);
var encodedString = Convert.ToBase64String(bytes);
- var commonNameValidationEmail = productInfo.ProductParameters["CN DCV Email"];
- var methodType = productInfo.ProductParameters["Domain Control Validation Method"];
+ var commonNameValidationEmail = GetOptionalParam(productInfo, "CN DCV Email");
+ var methodType = GetOptionalParam(productInfo, "Domain Control Validation Method");
var certificateType = GetCertificateType(productInfo.ProductID);
return new RegistrationRequest
@@ -166,19 +288,19 @@ public RegistrationRequest GetRegistrationRequest(EnrollmentProductInfo productI
Csr = encodedString,
ServerSoftware = "-1", //Just default to other, user does not need to fill this in
CertificateType = certificateType,
- 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"],
+ Term = GetOptionalParam(productInfo, "Term"),
+ ApplicantFirstName = GetOptionalParam(productInfo, "Applicant First Name"),
+ ApplicantLastName = GetOptionalParam(productInfo, "Applicant Last Name"),
+ ApplicantEmailAddress = GetOptionalParam(productInfo, "Applicant Email Address"),
+ ApplicantPhoneNumber = GetOptionalParam(productInfo, "Applicant Phone"),
DomainControlValidation = GetDomainControlValidation(methodType, commonNameValidationEmail),
Notifications = GetNotifications(productInfo),
- OrganizationContact = productInfo.ProductParameters["Organization Contact"],
- BusinessUnit = productInfo.ProductParameters["Business Unit"],
+ OrganizationContact = GetOptionalParam(productInfo, "Organization Contact"),
+ BusinessUnit = GetOptionalParam(productInfo, "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
+ SubjectAlternativeNames = MultiNameCertificateTypes.Contains(certificateType) ? GetSubjectAlternativeNames(productInfo, sans) : null,
+ EvCertificateDetails = EvCertificateTypes.Contains(certificateType) ? GetEvCertificateDetails(productInfo) : null
};
}
@@ -206,32 +328,45 @@ private string GetCertificateType(string productId)
return "5";
case "CSC TrustedSecure Domain Validated UC Certificate":
return "6";
+ case "CSC TrustedSecure EV, Multiple Names":
+ return "7";
+ case "CSC TrustedSecure OV Wildcard, Multiple Names":
+ return "8";
+ case "CSC TrustedSecure DV Wildcard, Multiple Names":
+ return "9";
}
+ Logger.LogWarning($"Unrecognized product ID '{productId}'; defaulting certificate type to -1");
return "-1";
}
+ private static readonly HashSet MultiNameCertificateTypes = new() { "2", "7", "8", "9" };
+ private static readonly HashSet EvCertificateTypes = new() { "3", "7" };
+
public Notifications GetNotifications(EnrollmentProductInfo productInfo)
{
+ var notificationEmails = GetOptionalParam(productInfo, "Notification Email(s) Comma Separated");
return new Notifications
{
Enabled = true,
- AdditionalNotificationEmails = productInfo.ProductParameters["Notification Email(s) Comma Separated"]
- .Split(',').ToList()
+ AdditionalNotificationEmails = string.IsNullOrWhiteSpace(notificationEmails)
+ ? new List()
+ : notificationEmails.Split(',').ToList()
};
}
public RenewalRequest GetRenewalRequest(EnrollmentProductInfo productInfo, string uUId, string csr,
Dictionary sans, List customFields)
{
+ Logger.LogTrace($"Building renewal request for product {productInfo.ProductID}, UUID {uUId}");
//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["CN DCV Email"];
- var methodType = productInfo.ProductParameters["Domain Control Validation Method"];
+ var commonNameValidationEmail = GetOptionalParam(productInfo, "CN DCV Email");
+ var methodType = GetOptionalParam(productInfo, "Domain Control Validation Method");
var certificateType = GetCertificateType(productInfo.ProductID);
return new RenewalRequest
@@ -240,19 +375,19 @@ public RenewalRequest GetRenewalRequest(EnrollmentProductInfo productInfo, strin
Csr = encodedString,
ServerSoftware = "-1",
CertificateType = certificateType,
- 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"],
+ Term = GetOptionalParam(productInfo, "Term"),
+ ApplicantFirstName = GetOptionalParam(productInfo, "Applicant First Name"),
+ ApplicantLastName = GetOptionalParam(productInfo, "Applicant Last Name"),
+ ApplicantEmailAddress = GetOptionalParam(productInfo, "Applicant Email Address"),
+ ApplicantPhoneNumber = GetOptionalParam(productInfo, "Applicant Phone"),
DomainControlValidation = GetDomainControlValidation(methodType, commonNameValidationEmail),
Notifications = GetNotifications(productInfo),
- OrganizationContact = productInfo.ProductParameters["Organization Contact"],
- BusinessUnit = productInfo.ProductParameters["Business Unit"],
+ OrganizationContact = GetOptionalParam(productInfo, "Organization Contact"),
+ BusinessUnit = GetOptionalParam(productInfo, "Business Unit"),
ShowPrice = true,
- SubjectAlternativeNames = certificateType == "2" ? GetSubjectAlternativeNames(productInfo, sans) : null,
+ SubjectAlternativeNames = MultiNameCertificateTypes.Contains(certificateType) ? GetSubjectAlternativeNames(productInfo, sans) : null,
CustomFields = GetCustomFields(productInfo, customFields),
- EvCertificateDetails = certificateType == "3" ? GetEvCertificateDetails(productInfo) : null
+ EvCertificateDetails = EvCertificateTypes.Contains(certificateType) ? GetEvCertificateDetails(productInfo) : null
};
}
@@ -260,16 +395,23 @@ private List GetSubjectAlternativeNames(EnrollmentProduc
Dictionary sans)
{
var subjectNameList = new List();
- var methodType = productInfo.ProductParameters["Domain Control Validation Method"];
+ var methodType = GetOptionalParam(productInfo, "Domain Control Validation Method");
- foreach (var v in sans["dnsname"])
+ string[] dnsNames = null;
+ sans?.TryGetValue("dnsname", out dnsNames);
+ foreach (var v in dnsNames ?? Array.Empty())
{
var domainName = v;
var san = new SubjectAlternativeName();
san.DomainName = domainName;
- var emailAddresses = productInfo.ProductParameters["Addtl Sans Comma Separated DVC Emails"].Split(',');
if (methodType.ToUpper() == "EMAIL")
+ {
+ productInfo.ProductParameters.TryGetValue("Addtl Sans Comma Separated DVC Emails", out var addtlSansEmails);
+ var emailAddresses = string.IsNullOrWhiteSpace(addtlSansEmails)
+ ? Array.Empty()
+ : addtlSansEmails.Split(',');
san.DomainControlValidation = GetDomainControlValidation(methodType, emailAddresses, domainName);
+ }
else //it is a CNAME validation so no email is needed
san.DomainControlValidation = GetDomainControlValidation(methodType, "");
@@ -282,14 +424,15 @@ private List GetSubjectAlternativeNames(EnrollmentProduc
public ReissueRequest GetReissueRequest(EnrollmentProductInfo productInfo, string uUId, string csr,
Dictionary sans, List customFields)
{
+ Logger.LogTrace($"Building reissue request for product {productInfo.ProductID}, UUID {uUId}");
//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["CN DCV Email"];
- var methodType = productInfo.ProductParameters["Domain Control Validation Method"];
+ var commonNameValidationEmail = GetOptionalParam(productInfo, "CN DCV Email");
+ var methodType = GetOptionalParam(productInfo, "Domain Control Validation Method");
var certificateType = GetCertificateType(productInfo.ProductID);
return new ReissueRequest
@@ -297,27 +440,27 @@ public ReissueRequest GetReissueRequest(EnrollmentProductInfo productInfo, strin
Uuid = uUId,
Csr = encodedString,
ServerSoftware = "-1",
- 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"],
+ CertificateType = certificateType,
+ Term = GetOptionalParam(productInfo, "Term"),
+ ApplicantFirstName = GetOptionalParam(productInfo, "Applicant First Name"),
+ ApplicantLastName = GetOptionalParam(productInfo, "Applicant Last Name"),
+ ApplicantEmailAddress = GetOptionalParam(productInfo, "Applicant Email Address"),
+ ApplicantPhoneNumber = GetOptionalParam(productInfo, "Applicant Phone"),
DomainControlValidation = GetDomainControlValidation(methodType, commonNameValidationEmail),
Notifications = GetNotifications(productInfo),
- OrganizationContact = productInfo.ProductParameters["Organization Contact"],
- BusinessUnit = productInfo.ProductParameters["Business Unit"],
+ OrganizationContact = GetOptionalParam(productInfo, "Organization Contact"),
+ BusinessUnit = GetOptionalParam(productInfo, "Business Unit"),
ShowPrice = true,
- SubjectAlternativeNames = certificateType == "2" ? GetSubjectAlternativeNames(productInfo, sans) : null,
+ SubjectAlternativeNames = MultiNameCertificateTypes.Contains(certificateType) ? GetSubjectAlternativeNames(productInfo, sans) : null,
CustomFields = GetCustomFields(productInfo, customFields),
- EvCertificateDetails = certificateType == "3" ? GetEvCertificateDetails(productInfo) : null
+ EvCertificateDetails = EvCertificateTypes.Contains(certificateType) ? GetEvCertificateDetails(productInfo) : null
};
}
private EvCertificateDetails GetEvCertificateDetails(EnrollmentProductInfo productInfo)
{
var evDetails = new EvCertificateDetails();
- evDetails.Country = productInfo.ProductParameters["Organization Country"];
+ evDetails.Country = GetOptionalParam(productInfo, "Organization Country");
return evDetails;
}
@@ -340,6 +483,7 @@ public int MapReturnStatus(string cscGlobalStatus)
returnStatus = (int)EndEntityStatus.REVOKED;
break;
default:
+ Logger.LogWarning($"Unrecognized CSC Global status '{cscGlobalStatus}'; mapping to FAILED");
returnStatus = (int)EndEntityStatus.FAILED;
break;
}
diff --git a/docsource/configuration.md b/docsource/configuration.md
index d8c196e..63cc066 100644
--- a/docsource/configuration.md
+++ b/docsource/configuration.md
@@ -216,3 +216,91 @@ 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 EV, Multiple Names - Details Tab**
+
+CONFIG ELEMENT | DESCRIPTION
+----------------------------|------------------
+Template Short Name | CSC TrustedSecure EV, Multiple Names
+Template Display Name | CSC TrustedSecure EV, Multiple Names
+Friendly Name | CSC TrustedSecure EV, Multiple Names
+Keys Size | 2048
+Enforce RFC 2818 Compliance | True
+CSR Enrollment | True
+Pfx Enrollment | True
+
+
+**CSC TrustedSecure EV, Multiple Names - 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
+Addtl Sans Comma Separated DCV Emails | String | N/A
+
+**CSC TrustedSecure OV Wildcard, Multiple Names - Details Tab**
+
+CONFIG ELEMENT | DESCRIPTION
+----------------------------|------------------
+Template Short Name | CSC TrustedSecure OV Wildcard, Multiple Names
+Template Display Name | CSC TrustedSecure OV Wildcard, Multiple Names
+Friendly Name | CSC TrustedSecure OV Wildcard, Multiple Names
+Keys Size | 2048
+Enforce RFC 2818 Compliance | True
+CSR Enrollment | True
+Pfx Enrollment | True
+
+
+**CSC TrustedSecure OV Wildcard, Multiple Names - 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 DV Wildcard, Multiple Names - Details Tab**
+
+CONFIG ELEMENT | DESCRIPTION
+----------------------------|------------------
+Template Short Name | CSC TrustedSecure DV Wildcard, Multiple Names
+Template Display Name | CSC TrustedSecure DV Wildcard, Multiple Names
+Friendly Name | CSC TrustedSecure DV Wildcard, Multiple Names
+Keys Size | 2048
+Enforce RFC 2818 Compliance | True
+CSR Enrollment | True
+Pfx Enrollment | True
+
+
+**CSC TrustedSecure DV Wildcard, Multiple Names - 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
+
diff --git a/integration-manifest.json b/integration-manifest.json
index 2b4b8c4..756b19a 100644
--- a/integration-manifest.json
+++ b/integration-manifest.json
@@ -95,7 +95,10 @@
"CSC TrustedSecure Premium Wildcard Certificate",
"CSC TrustedSecure Domain Validated SSL",
"CSC TrustedSecure Domain Validated Wildcard SSL",
- "CSC TrustedSecure Domain Validated UC Certificate"
+ "CSC TrustedSecure Domain Validated UC Certificate",
+ "CSC TrustedSecure EV, Multiple Names",
+ "CSC TrustedSecure OV Wildcard, Multiple Names",
+ "CSC TrustedSecure DV Wildcard, Multiple Names"
]
}
}