diff --git a/.github/workflows/keyfactor-starter-workflow.yml b/.github/workflows/keyfactor-starter-workflow.yml new file mode 100644 index 0000000..7b909a3 --- /dev/null +++ b/.github/workflows/keyfactor-starter-workflow.yml @@ -0,0 +1,19 @@ +name: Keyfactor Bootstrap Workflow + +on: + workflow_dispatch: + pull_request: + types: [opened, closed, synchronize, edited, reopened] + push: + create: + branches: + - 'release-*.*' + +jobs: + call-starter-workflow: + uses: keyfactor/actions/.github/workflows/starter.yml@v4 + secrets: + token: ${{ secrets.V2BUILDTOKEN}} + gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }} + gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }} + scan_token: ${{ secrets.SAST_TOKEN }} diff --git a/.gitignore b/.gitignore index ce89292..8791d97 100644 --- a/.gitignore +++ b/.gitignore @@ -416,3 +416,5 @@ FodyWeavers.xsd *.msix *.msm *.msp +.claude/settings.local.json +.claude/settings.json diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1470e43 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ +# v2.0.0 +* Converted from AnyCA Gateway (DB) to AnyCA Gateway REST plugin architecture +* Migrated from CAProxy.AnyGateway (BaseCAConnector) to IAnyCAPlugin interface +* Fully async operations throughout (no more Task.Run().Result blocking) +* Self-describing plugin configuration with annotations (no external template JSON files) +* Built-in product registry with 80+ certificate products +* Smart renewal vs. reissue logic with configurable renewal window +* Uses CustomOrderId for stable order tracking +* End-entity certificate extraction using X509Utilities.ExtractEndEntityCertificateContents +* GetSingleRecord now downloads and returns the actual certificate +* Connection validation with required field checks +* Enable/disable toggle for CA connector lifecycle management +* Removed Keyfactor API client dependency (no more direct template updates) + +# v1.1.1 +* SSL Store Api Changed Encoding Rules, needed to fix integration to match + +# v1.1.0 +* Added new AutoWWW field for single domain SSL Store products + +# v1.0.4 +* Original Release Version diff --git a/README.md b/README.md new file mode 100644 index 0000000..e4e1873 --- /dev/null +++ b/README.md @@ -0,0 +1,471 @@ +

+ SSL Store AnyCA Gateway REST Plugin +

+ +

+ +Integration Status: production +Release +Issues +GitHub Downloads (all assets, all releases) +

+ +

+ + + Support + + · + + Requirements + + · + + Installation + + · + + License + + · + + Related Integrations + +

+ + +The SSL Store AnyCA Gateway REST plugin extends the capabilities of the SSL Store Certificate Authority Service to Keyfactor Command via the Keyfactor AnyCA Gateway. SSL Store is a certificate reseller providing access to 80+ certificate products from vendors including DigiCert, Sectigo, RapidSSL, GeoTrust, and Comodo through a single REST API. The plugin represents a fully featured AnyCA Plugin with the following capabilities: + +* **CA Sync**: + * Download all certificates issued through SSL Store + * Full synchronization of all orders with paginated retrieval + * Automatic extraction of end-entity certificates from certificate chains + * Resilient retry logic (up to 5 retries) for large certificate inventories +* **Certificate Enrollment**: + * Support for new certificate enrollment with CSR + * Intelligent renewal vs. reissue logic based on configurable renewal window + * Support for DV, OV, and EV certificate products + * Multi-domain (MDC/SAN) and wildcard certificate support + * Automatic domain validation with approver email verification + * 80+ pre-configured certificate products across DigiCert and Sectigo families +* **Certificate Revocation**: + * Request revocation of previously issued certificates via SSL Store refund request API + +## Compatibility + +The SSL Store AnyCA Gateway REST plugin is compatible with the Keyfactor AnyCA Gateway REST 25.5 and later. + +## Support +The SSL Store 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. + +## Requirements + +### SSL Store System Prerequisites + +Before configuring the AnyCA Gateway plugin, ensure the following prerequisites are met: + +1. **SSL Store Account**: + - Active SSL Store partner account with API access enabled + - Access to the SSL Store web-based API (WBAPI) + - SSL Store account configured and operational + +2. **API Credentials**: + - SSL Store Partner Code + - SSL Store Authentication Token + - These credentials must have permissions for: + - Certificate enrollment (new order submission) + - Certificate download + - Certificate revocation (refund request) + - Order query and status retrieval + - Email approver list retrieval + +3. **Network Connectivity**: + - Gateway server must have HTTPS access to the SSL Store API endpoint + - Production endpoint: `https://wbapi.thesslstore.com` + - Sandbox endpoint: `https://sandbox-wbapi.thesslstore.com` + - TLS 1.2 or higher must be supported + +### Obtaining Required Configuration Information + +#### 1. SSL Store Base URL + +The SSL Store Base URL is the root endpoint for the SSL Store REST API. + +**Available environments:** +- Production: `https://wbapi.thesslstore.com` +- Sandbox/Testing: `https://sandbox-wbapi.thesslstore.com` + +**To obtain your Base URL:** +1. Log in to your SSL Store partner portal +2. Determine whether you are using the production or sandbox environment +3. Verify the URL is accessible from the Gateway server + +#### 2. API Authentication Credentials + +The Gateway authenticates to SSL Store using a Partner Code and Authentication Token. + +**Steps to obtain API credentials:** + +1. **Access SSL Store Partner Portal**: + - Log in to your SSL Store partner account + - Navigate to API settings + +2. **Obtain Credentials**: + - **Partner Code**: Your unique partner identifier assigned by SSL Store + - **Authentication Token**: A secret token for API authentication + - Store these credentials securely + +3. **Verify Permissions**: + - Ensure the API credentials have permissions for: + - Order creation (`/rest/order/neworder`) + - Order reissue (`/rest/order/reissue`) + - Order query (`/rest/order/query`) + - Order status (`/rest/order/status`) + - Certificate download (`/rest/order/download`) + - Revocation/refund (`/rest/order/refundrequest`) + - Email approver list (`/rest/order/approverlist`) + +#### 3. Supported Certificate Products + +The plugin supports 80+ certificate products from multiple vendors. Products are organized by validation type and vendor: + +**DigiCert Products:** + +| Product Code | Description | Validation | +|-------------|-------------|------------| +| `digi_securesite_flex` | DigiCert Secure Site | OV | +| `digi_securesite_flex-EO` | DigiCert Secure Site (Enterprise Org) | OV | +| `digi_securesite_ev_flex` | DigiCert Secure Site EV | EV | +| `digi_securesite_ev_flex-EO` | DigiCert Secure Site EV (Enterprise Org) | EV | +| `digi_securesite_pro_flex` | DigiCert Secure Site Pro | OV | +| `digi_securesite_pro_flex-EO` | DigiCert Secure Site Pro (Enterprise Org) | OV | +| `digi_securesite_pro_ev_flex` | DigiCert Secure Site Pro EV | EV | +| `digi_securesite_pro_ev_flex-EO` | DigiCert Secure Site Pro EV (Enterprise Org) | EV | +| `digi_sslwebserver_flex` | DigiCert SSL Web Server | OV | +| `digi_sslwebserver_flex-EO` | DigiCert SSL Web Server (Enterprise Org) | OV | +| `digi_sslwebserver_ev_flex` | DigiCert SSL Web Server EV | EV | +| `digi_sslwebserver_ev_flex-EO` | DigiCert SSL Web Server EV (Enterprise Org) | EV | +| `digi_truebizid_flex` | DigiCert TrueBizID | OV | +| `digi_truebizid_flex-EO` | DigiCert TrueBizID (Enterprise Org) | OV | +| `digi_truebizid_ev_flex` | DigiCert TrueBizID EV | EV | +| `digi_truebizid_ev_flex-EO` | DigiCert TrueBizID EV (Enterprise Org) | EV | +| `digi_ssl_basic` | DigiCert Basic SSL | OV | +| `digi_ssl_basic-EO` | DigiCert Basic SSL (Enterprise Org) | OV | +| `digi_ssl_ev_basic` | DigiCert Basic SSL EV | EV | +| `digi_ssl_ev_basic-EO` | DigiCert Basic SSL EV (Enterprise Org) | EV | +| `digi_rapidssl` | RapidSSL | DV | +| `digi_rapidssl_wc` | RapidSSL Wildcard | DV | +| `digi_ssl_dv_geotrust_flex` | GeoTrust DV SSL | DV | +| `digi_ssl123_flex` | GeoTrust SSL123 | DV | +| `digi_quickssl_md` | DigiCert QuickSSL Multi-Domain | DV | +| `digi_client_premium` | DigiCert Client Premium | Client | +| `digi_csc` | DigiCert Code Signing | Code Signing | +| `digi_csc_ev` | DigiCert EV Code Signing | EV Code Signing | +| `digi_doc_signing_ind_500` | DigiCert Document Signing Individual 500 | Document Signing | +| `digi_doc_signing_ind_2000` | DigiCert Document Signing Individual 2000 | Document Signing | +| `digi_doc_signing_org_2000` | DigiCert Document Signing Organization 2000 | Document Signing | +| `digi_doc_signing_org_5000` | DigiCert Document Signing Organization 5000 | Document Signing | + +**Sectigo/Comodo Products:** + +| Product Code | Description | Validation | +|-------------|-------------|------------| +| `positivessl` | Positive SSL | DV | +| `positivesslwildcard` | Positive SSL Wildcard | DV | +| `positivemdcssl` | Positive SSL Multi-Domain | DV | +| `positivemdcwildcard` | Positive SSL MDC Wildcard | DV | +| `positiveevssl` | Positive EV SSL | EV | +| `positiveevmdc` | Positive EV Multi-Domain | EV | +| `sectigossl` | Sectigo SSL | DV | +| `sectigowildcard` | Sectigo Wildcard | DV | +| `sectigoovssl` | Sectigo OV SSL | OV | +| `sectigoovwildcard` | Sectigo OV Wildcard | OV | +| `sectigoevssl` | Sectigo EV SSL | EV | +| `sectigodvucc` | Sectigo DV UCC | DV | +| `sectigouccwildcard` | Sectigo UCC Wildcard | DV | +| `sectigomdc` | Sectigo Multi-Domain | OV | +| `sectigomdcwildcard` | Sectigo MDC Wildcard | OV | +| `sectigoevmdc` | Sectigo EV Multi-Domain | EV | +| `comodopremiumssl` | Comodo Premium SSL | OV | +| `comodopremiumwildcard` | Comodo Premium Wildcard | OV | +| `comodossl` | Comodo SSL | OV | +| `comodoevssl` | Comodo EV SSL | EV | +| `comodomdc` | Comodo Multi-Domain | OV | +| `comodomdcwildcard` | Comodo MDC Wildcard | OV | +| `comodoevmdc` | Comodo EV Multi-Domain | EV | +| `comodoucc` | Comodo UCC | OV | +| `comodouccwildcard` | Comodo UCC Wildcard | OV | +| `comodowildcard` | Comodo Wildcard | OV | +| `comodocsc` | Comodo Code Signing | Code Signing | +| `comodoevcsc` | Comodo EV Code Signing | EV Code Signing | +| `comododvucc` | Comodo DV UCC | DV | +| `comodopciscan` | Comodo PCI Scan | Scanning | +| `instantssl` | InstantSSL | OV | +| `instantsslpro` | InstantSSL Pro | OV | +| `enterprisepro` | Enterprise Pro SSL | OV | +| `enterpriseprowc` | Enterprise Pro Wildcard | OV | +| `enterpriseproev` | Enterprise Pro EV | EV | +| `enterpriseproevmdc` | Enterprise Pro EV Multi-Domain | EV | +| `enterprisessl` | Enterprise SSL | OV | +| `essentialssl` | Essential SSL | DV | +| `essentialwildcard` | Essential Wildcard | DV | +| `elitessl` | Elite SSL | OV | + +**Note:** Products with the `-EO` suffix are Enterprise Organization variants that use a pre-configured DigiCert organization instead of requiring organization details during enrollment. These products require only a Validity Period and Organization ID. + +#### 4. Certificate Validity Configuration + +Certificate validity is specified in days during enrollment and automatically converted to months for the SSL Store API: + +| Days | Months | +|------|--------| +| 90 | 3 | +| 180 | 6 | +| 365 | 12 | +| 730 | 24 | +| 1095 | 36 | + +#### 5. Renewal vs. Reissue Logic + +The plugin uses a configurable **Renewal Window** (default: 30 days) to determine behavior during certificate renewal: + +- If the existing order is **within** the renewal window (i.e., expiring within N days), the plugin performs a **renewal** (new order linked to the original) +- If the existing order is **outside** the renewal window (still has significant life remaining), the plugin performs a **reissue** on the same order + +## Installation + +1. Install the AnyCA Gateway REST per the [official Keyfactor documentation](https://software.keyfactor.com/Guides/AnyCAGatewayREST/Content/AnyCAGatewayREST/InstallIntroduction.htm). + +2. On the server hosting the AnyCA Gateway REST, download and unzip the latest [SSL Store AnyCA Gateway REST plugin](https://github.com/Keyfactor/sslstore-caplugin/releases/latest) from GitHub. + +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\net6.0\Extensions + Program Files\Keyfactor\AnyCA Gateway\AnyGatewayREST\net8.0\Extensions + ``` + + > The directory containing the SSL Store 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. + +5. Navigate to the AnyCA Gateway REST portal and verify that the Gateway recognizes the SSL Store plugin by hovering over the ⓘ symbol to the right of the Gateway on the top left of the portal. + +## Configuration + +1. Follow the [official AnyCA Gateway REST documentation](https://software.keyfactor.com/Guides/AnyCAGatewayREST/Content/AnyCAGatewayREST/AddCA-Gateway.htm) to define a new Certificate Authority, and use the notes below to configure the **Gateway Registration** and **CA Connection** tabs: + + * **Gateway Registration** + + ### CA Connection Configuration + + When registering the SSL Store CA in the AnyCA Gateway, you'll need to provide the following configuration parameters: + + | Parameter | Description | Required | Default | + |-----------|-------------|----------|---------| + | **SSLStoreURL** | Full URL to the SSL Store API endpoint | Yes | `https://sandbox-wbapi.thesslstore.com` | + | **PartnerCode** | Partner Code obtained from SSL Store | Yes | | + | **AuthToken** | Authentication Token obtained from SSL Store | Yes | | + | **PageSize** | Number of records per page during synchronization | No | `100` | + | **Enabled** | Flag to Enable or Disable the CA connector | No | `true` | + | **RenewalWindow** | Days before order expiry to trigger renewal vs. reissue | No | `30` | + + ### Gateway Registration Notes + + - Each defined Certificate Authority in the AnyCA Gateway REST can support one SSL Store API endpoint + - If you have multiple SSL Store environments (production/sandbox), define separate Certificate Authorities for each + - Each CA configuration will manifest in Command as a separate CA entry + - The plugin uses REST API authentication with Partner Code and Authentication Token + - The plugin automatically handles: + - Product discovery (80+ products) + - Certificate status mapping (Active, Pending, Cancelled) + - End-entity certificate extraction from certificate chains + - Paginated order synchronization with retry logic + + ### Security Considerations + + 1. **Credential Storage**: The AuthToken field is configured as a secret/hidden field and should be stored securely + 2. **Network Security**: Ensure TLS/SSL is properly configured for all API communications + 3. **Least Privilege**: Request API credentials with minimal required permissions + 4. **Audit Logging**: Enable comprehensive logging in both the Gateway and SSL Store for security monitoring + 5. **Credential Rotation**: Regularly rotate API credentials according to your security policy + 6. **Sandbox Testing**: Use the sandbox endpoint (`https://sandbox-wbapi.thesslstore.com`) for initial configuration and testing before switching to production + + ### CA Connection Fields + + Populate using the configuration fields collected in the [requirements](#requirements) section. + + * **SSLStoreURL** - The base URL for the SSL Store API endpoint. Use `https://wbapi.thesslstore.com` for production or `https://sandbox-wbapi.thesslstore.com` for testing. + * **PartnerCode** - The Partner Code obtained from your SSL Store partner account. + * **AuthToken** - The Authentication Token obtained from your SSL Store partner account. + * **PageSize** - Number of records to retrieve per page during certificate synchronization. Default is 100. + * **Enabled** - Flag to enable or disable the CA connector. Set to `true` to enable. + * **RenewalWindow** - Number of days before an order's expiration date to trigger a renewal (new order) instead of a reissue (same order). Default is 30 days. + + * **CA Connection** + + Populate using the configuration fields collected in the [requirements](#requirements) section. + + * **SSLStoreURL** - The Base URL for the SSL Store API endpoint (e.g. https://sandbox-wbapi.thesslstore.com). + * **PartnerCode** - The Partner Code obtained from SSL Store. + * **AuthToken** - The Authentication Token obtained from SSL Store. + * **PageSize** - The number of records to return per page during synchronization. + * **Enabled** - Flag to Enable or Disable the CA connector. + * **RenewalWindow** - Number of days before order expiry to trigger a renewal instead of a reissue. + +2. ### Template (Product) Configuration + + After adding the CA to the Gateway, certificate templates are automatically discovered from the plugin's built-in product registry. Each template may require different enrollment fields depending on the product type and validation level. + + **Enrollment fields vary by product type. The following categories exist:** + + #### DV Products (Minimal Fields) + + Products like `positivessl`, `sectigossl`, `sectigowildcard`: + + | Parameter | Description | Required | + |-----------|-------------|----------| + | **Admin Contact - Email** | Administrative contact email | Yes | + | **Approver Email** | Domain validation approver email | Yes | + | **Validity Period (In Days)** | Certificate validity in days | Yes | + + #### OV Products (Organization Fields) + + Products like `sectigoovssl`, `comodopremiumssl`, `instantssl`: + + | Parameter | Description | Required | + |-----------|-------------|----------| + | **Admin Contact - Email** | Administrative contact email | Yes | + | **Approver Email** | Domain validation approver email | Yes | + | **Validity Period (In Days)** | Certificate validity in days | Yes | + | **Organization Name** | Organization name | Yes | + | **Organization Address** | Organization street address | Yes | + | **Organization State/Province** | Organization state or province | Yes | + | **Organization Postal Code** | Organization postal/zip code | Yes | + | **Organization Country** | Two-letter country code (e.g. US) | Yes | + | **Organization Phone** | Organization phone number | Yes | + + #### DigiCert OV Flex Products + + Products like `digi_securesite_flex`, `digi_sslwebserver_flex`, `digi_truebizid_flex`: + + | Parameter | Description | Required | + |-----------|-------------|----------| + | **Admin Contact - First Name** | Administrative contact first name | Yes | + | **Admin Contact - Last Name** | Administrative contact last name | Yes | + | **Admin Contact - Phone** | Administrative contact phone | Yes | + | **Admin Contact - Email** | Administrative contact email | Yes | + | **Approver Email** | Domain validation approver email | Yes | + | **Validity Period (In Days)** | Certificate validity in days | Yes | + | **Organization Name** | Organization name | Yes | + | **Organization Address** | Organization street address | Yes | + | **Organization City** | Organization city | Yes | + | **Organization State/Province** | Organization state or province | Yes | + | **Organization Postal Code** | Organization postal/zip code | Yes | + | **Organization Country** | Two-letter country code | Yes | + | **Organization Phone** | Organization phone number | Yes | + + #### DigiCert EV Flex Products + + Products like `digi_securesite_ev_flex`, `digi_ssl_ev_basic`, `digi_truebizid_ev_flex`: + + Same as DigiCert OV Flex, plus: + + | Parameter | Description | Required | + |-----------|-------------|----------| + | **Admin Contact - Title** | Administrative contact job title | Yes | + + #### Enterprise Organization (-EO) Products + + Products like `digi_securesite_flex-EO`, `digi_sslwebserver_ev_flex-EO`: + + | Parameter | Description | Required | + |-----------|-------------|----------| + | **Validity Period (In Days)** | Certificate validity in days | Yes | + | **Organization ID** | DigiCert Organization ID | Yes | + + #### EV Products with Jurisdiction + + Products like `enterpriseproev`, `positiveevssl`, `positiveevmdc`: + + Same as OV Products, plus: + + | Parameter | Description | Required | + |-----------|-------------|----------| + | **Organization Jurisdiction Country** | Jurisdiction country code for EV validation | Yes | + + ### Domain Validation - Approver Emails + + The plugin validates approver emails against SSL Store's approved list for each domain before enrollment: + + - **DigiCert products**: Exactly one approver email is required and must be from the approved list + - **Sectigo/Comodo products**: At least one approver email must be from the approved list + - Emails are validated per-domain for multi-domain certificates + + ### Important Notes + + - Product IDs are automatically registered from the plugin's built-in product registry + - The `Validity Period (In Days)` is automatically converted to months for the SSL Store API + - For `-EO` (Enterprise Organization) products, the Organization ID dropdown is populated from your DigiCert account's active organizations + - DNS names (SANs) are extracted from the Keyfactor enrollment request; they do not need to be provided as a separate enrollment field + - The Common Name (CN) is extracted from the CSR subject + +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: + + * **Approver Email** - Comma-separated approver email address(es) for domain validation. + * **Validity Period (In Days)** - Certificate validity period in days (e.g. 90, 365, 730). + * **Admin Contact - First Name** - Administrative contact first name. + * **Admin Contact - Last Name** - Administrative contact last name. + * **Admin Contact - Phone** - Administrative contact phone number. + * **Admin Contact - Email** - Administrative contact email address. + * **Admin Contact - Title** - Administrative contact job title. + * **Admin Contact - Organization Name** - Administrative contact organization name. + * **Admin Contact - Address** - Administrative contact street address. + * **Admin Contact - City** - Administrative contact city. + * **Admin Contact - Region** - Administrative contact state/province/region. + * **Admin Contact - Postal Code** - Administrative contact postal/zip code. + * **Admin Contact - Country** - Administrative contact two-letter country code (e.g. US). + * **Technical Contact - First Name** - Technical contact first name. + * **Technical Contact - Last Name** - Technical contact last name. + * **Technical Contact - Phone** - Technical contact phone number. + * **Technical Contact - Email** - Technical contact email address. + * **Technical Contact - Organization Name** - Technical contact organization name. + * **Technical Contact - Address** - Technical contact street address. + * **Technical Contact - City** - Technical contact city. + * **Technical Contact - Region** - Technical contact state/province/region. + * **Technical Contact - Postal Code** - Technical contact postal/zip code. + * **Technical Contact - Country** - Technical contact two-letter country code (e.g. US). + * **Organization Name** - Organization name for the certificate. + * **Organization Address** - Organization street address. + * **Organization City** - Organization city. + * **Organization Region** - Organization state/province/region. + * **Organization State/Province** - Organization state or province. + * **Organization Postal Code** - Organization postal/zip code. + * **Organization Country** - Organization two-letter country code (e.g. US). + * **Organization Phone** - Organization phone number. + * **Organization Jurisdiction Country** - Jurisdiction country code for EV certificates. + * **Organization ID** - DigiCert organization ID for EO (Enterprise Organization) products. + * **Server Count** - Number of server licenses for the certificate. + * **Web Server Type** - Web server type (e.g. apacheopenssl, iis, tomcat, Other). + * **Signature Hash Algorithm** - Signature hash algorithm (PREFER_SHA2, REQUIRE_SHA2, PREFER_SHA1). + * **File Auth Domain Validation** - Use file-based domain validation (True/False). + * **CName Auth Domain Validation** - Use CNAME-based domain validation (True/False). + * **Is CU Order?** - Is this a CU (Customer) order (True/False). + * **Is Renewal Order?** - Is this a renewal order (True/False). + * **Is Trial Order?** - Is this a trial order (True/False). + + + +## License + +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 diff --git a/SslStoreCaProxy.slnx b/SslStoreCaProxy.slnx new file mode 100644 index 0000000..ba17d13 --- /dev/null +++ b/SslStoreCaProxy.slnx @@ -0,0 +1,3 @@ + + + diff --git a/SslStoreCaProxy/Client/Models/AddSan.cs b/SslStoreCaProxy/Client/Models/AddSan.cs new file mode 100644 index 0000000..733d3ed --- /dev/null +++ b/SslStoreCaProxy/Client/Models/AddSan.cs @@ -0,0 +1,10 @@ +using Keyfactor.AnyGateway.SslStore.Interfaces; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class AddSan : IAddSan + { + public string OldValue { get; set; } + public string NewValue { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Client/Models/AdminContact.cs b/SslStoreCaProxy/Client/Models/AdminContact.cs new file mode 100644 index 0000000..c0d6fa9 --- /dev/null +++ b/SslStoreCaProxy/Client/Models/AdminContact.cs @@ -0,0 +1,23 @@ +using Keyfactor.AnyGateway.SslStore.Interfaces; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class AdminContact : IAdminContact + { + public string FirstName { get; set; } + public string LastName { get; set; } + public string SubjectFirstName { get; set; } + public string SubjectLastName { get; set; } + public string Phone { get; set; } + public string Fax { get; set; } + public string Email { get; set; } + public string Title { get; set; } + public string OrganizationName { get; set; } + public string AddressLine1 { get; set; } + public string AddressLine2 { get; set; } + public string City { get; set; } + public string Region { get; set; } + public string PostalCode { get; set; } + public string Country { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Client/Models/AuthRequest.cs b/SslStoreCaProxy/Client/Models/AuthRequest.cs new file mode 100644 index 0000000..2405499 --- /dev/null +++ b/SslStoreCaProxy/Client/Models/AuthRequest.cs @@ -0,0 +1,22 @@ +using Keyfactor.AnyGateway.SslStore.Interfaces; +using Newtonsoft.Json; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class AuthRequest : IAuthRequest + { + public string PartnerCode { get; set; } + public string AuthToken { get; set; } + public string ReplayToken { get; set; } + public string UserAgent { get; set; } + + [JsonProperty("TokenID")] public string TokenId { get; set; } + + public string TokenCode { get; set; } + + [JsonProperty("IPAddress")] public string IpAddress { get; set; } + + public bool IsUsedForTokenSystem { get; set; } + public string Token { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Client/Models/AuthResponse.cs b/SslStoreCaProxy/Client/Models/AuthResponse.cs new file mode 100644 index 0000000..77a95be --- /dev/null +++ b/SslStoreCaProxy/Client/Models/AuthResponse.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using Keyfactor.AnyGateway.SslStore.Interfaces; +using Newtonsoft.Json; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class AuthResponse : IAuthResponse + { + [JsonProperty("isError")] public bool IsError { get; set; } + + public List Message { get; set; } + public string Timestamp { get; set; } + public string ReplayToken { get; set; } + public string InvokingPartnerCode { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Client/Models/AuthenticationStatus.cs b/SslStoreCaProxy/Client/Models/AuthenticationStatus.cs new file mode 100644 index 0000000..8f4651c --- /dev/null +++ b/SslStoreCaProxy/Client/Models/AuthenticationStatus.cs @@ -0,0 +1,11 @@ +using Keyfactor.AnyGateway.SslStore.Interfaces; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class AuthenticationStatus : IAuthenticationStatus + { + public string AuthenticationStep { get; set; } + public string Status { get; set; } + public string LastUpdated { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Client/Models/Certificate.cs b/SslStoreCaProxy/Client/Models/Certificate.cs new file mode 100644 index 0000000..1a99e81 --- /dev/null +++ b/SslStoreCaProxy/Client/Models/Certificate.cs @@ -0,0 +1,8 @@ +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class Certificate + { + public string FileContent { get; set; } + public string FileName { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Client/Models/DeleteSan.cs b/SslStoreCaProxy/Client/Models/DeleteSan.cs new file mode 100644 index 0000000..4bc6ff4 --- /dev/null +++ b/SslStoreCaProxy/Client/Models/DeleteSan.cs @@ -0,0 +1,8 @@ +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class DeleteSan + { + public string OldValue { get; set; } + public string NewValue { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Client/Models/DomainAuthVettingStatus.cs b/SslStoreCaProxy/Client/Models/DomainAuthVettingStatus.cs new file mode 100644 index 0000000..8bb0335 --- /dev/null +++ b/SslStoreCaProxy/Client/Models/DomainAuthVettingStatus.cs @@ -0,0 +1,27 @@ +using System; +using Keyfactor.AnyGateway.SslStore.Interfaces; +using Newtonsoft.Json; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class DomainAuthVettingStatus : IDomainAuthVettingStatus + { + [JsonProperty("domain")] public string Domain { get; set; } + + [JsonProperty("dcvMethod")] public string DcvMethod { get; set; } + + [JsonProperty("dcvStatus")] public string DcvStatus { get; set; } + + public string FileName { get; set; } + public string FileContents { get; set; } + + [JsonProperty("DNSName")] public string DnsName { get; set; } + + [JsonProperty("DNSEntry")] public string DnsEntry { get; set; } + + public string PollStatus { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public DateTime LastPollDate { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Client/Models/DownloadCertificateRequest.cs b/SslStoreCaProxy/Client/Models/DownloadCertificateRequest.cs new file mode 100644 index 0000000..76b3439 --- /dev/null +++ b/SslStoreCaProxy/Client/Models/DownloadCertificateRequest.cs @@ -0,0 +1,12 @@ +using Keyfactor.AnyGateway.SslStore.Interfaces; +using Newtonsoft.Json; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class DownloadCertificateRequest : IDownloadCertificateRequest + { + public AuthRequest AuthRequest { get; set; } + [JsonProperty("TheSSLStoreOrderID", NullValueHandling = NullValueHandling.Ignore)] public string TheSslStoreOrderId { get; set; } + [JsonProperty("CustomOrderID", NullValueHandling = NullValueHandling.Ignore)] public string CustomOrderId { get; set; } + } +} diff --git a/SslStoreCaProxy/Client/Models/DownloadCertificateResponse.cs b/SslStoreCaProxy/Client/Models/DownloadCertificateResponse.cs new file mode 100644 index 0000000..de4093b --- /dev/null +++ b/SslStoreCaProxy/Client/Models/DownloadCertificateResponse.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using Keyfactor.AnyGateway.SslStore.Interfaces; +using Newtonsoft.Json; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class DownloadCertificateResponse : IDownloadCertificateResponse + { + public AuthResponse AuthResponse { get; set; } + public string CertificateEndDate { get; set; } + + [JsonProperty("CertificateEndDateInUTC")] + public string CertificateEndDateInUtc { get; set; } + + public string CertificateStartDate { get; set; } + + [JsonProperty("CertificateStartDateInUTC")] + public string CertificateStartDateInUtc { get; set; } + + public string CertificateStatus { get; set; } + public List Certificates { get; set; } + [JsonProperty("PartnerOrderID")] public string PartnerOrderId { get; set; } + [JsonProperty("TheSSLStoreOrderID")] public object TheSslStoreOrderId { get; set; } + public string ValidationStatus { get; set; } + [JsonProperty("VendorOrderID")] public object VendorOrderId { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Client/Models/EditSan.cs b/SslStoreCaProxy/Client/Models/EditSan.cs new file mode 100644 index 0000000..184c01c --- /dev/null +++ b/SslStoreCaProxy/Client/Models/EditSan.cs @@ -0,0 +1,8 @@ +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class EditSan + { + public string OldValue { get; set; } + public string NewValue { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Client/Models/EmailApproverRequest.cs b/SslStoreCaProxy/Client/Models/EmailApproverRequest.cs new file mode 100644 index 0000000..98eeeae --- /dev/null +++ b/SslStoreCaProxy/Client/Models/EmailApproverRequest.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Keyfactor.AnyGateway.SslStore.Interfaces; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class EmailApproverRequest : IEmailApproverRequest + { + public AuthRequest AuthRequest { get; set; } + public string ProductCode { get; set; } + public string DomainName { get; set; } + } +} diff --git a/SslStoreCaProxy/Client/Models/EmailApproverResponse.cs b/SslStoreCaProxy/Client/Models/EmailApproverResponse.cs new file mode 100644 index 0000000..4c48104 --- /dev/null +++ b/SslStoreCaProxy/Client/Models/EmailApproverResponse.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Keyfactor.AnyGateway.SslStore.Interfaces; +using Newtonsoft.Json; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class EmailApproverResponse : IEmailApproverResponse + { + public List ApproverEmailList { get; set; } + public AuthResponse AuthResponse { get; set; } + [JsonProperty("baseDomainName", NullValueHandling = NullValueHandling.Ignore)] + public string BaseDomainName { get; set; } + } +} diff --git a/SslStoreCaProxy/Client/Models/EnrollmentField.cs b/SslStoreCaProxy/Client/Models/EnrollmentField.cs new file mode 100644 index 0000000..580b8a8 --- /dev/null +++ b/SslStoreCaProxy/Client/Models/EnrollmentField.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Keyfactor.AnyGateway.SslStore.Interfaces; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class EnrollmentField : IEnrollmentField + { + public int Id { get; set; } + public string Name { get; set; } + public List Options { get; set; } = new List(); + public int DataType { get; set; } + } +} diff --git a/SslStoreCaProxy/Client/Models/NewOrderRequest.cs b/SslStoreCaProxy/Client/Models/NewOrderRequest.cs new file mode 100644 index 0000000..7877e6d --- /dev/null +++ b/SslStoreCaProxy/Client/Models/NewOrderRequest.cs @@ -0,0 +1,96 @@ +using System.Collections.Generic; +using Keyfactor.AnyGateway.SslStore.Interfaces; +using Newtonsoft.Json; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class NewOrderRequest : INewOrderRequest + { + public AuthRequest AuthRequest { get; set; } + + [JsonProperty("CustomOrderID", NullValueHandling = NullValueHandling.Ignore)] + public string CustomOrderId { get; set; } + + public string ProductCode { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string ExtraProductCodes { get; set; } + + public OrganizationInfo OrganizationInfo { get; set; } + + [JsonProperty("TSSOrganizationId", NullValueHandling = NullValueHandling.Ignore)] + public long TssOrganizationId { get; set; } + + public long ValidityPeriod { get; set; } + + public long ServerCount { get; set; } + + [JsonProperty("CSR")] public string Csr { get; set; } + + public string DomainName { get; set; } + + public string WebServerType { get; set; } + + [JsonProperty("DNSNames", NullValueHandling = NullValueHandling.Ignore)] + public List DnsNames { get; set; } + + [JsonProperty("isCUOrder")] public bool IsCuOrder { get; set; } + + [JsonProperty("AutoWWW", NullValueHandling = NullValueHandling.Ignore,DefaultValueHandling =DefaultValueHandling.Ignore)] + public bool? AutoWWW { get; set; } + + [JsonProperty("isRenewalOrder")] public bool IsRenewalOrder { get; set; } + + public string SpecialInstructions { get; set; } + + [JsonProperty("RelatedTheSSLStoreOrderID", NullValueHandling = NullValueHandling.Ignore)] + public string RelatedTheSslStoreOrderId { get; set; } + + [JsonProperty("isTrialOrder")] public bool IsTrialOrder { get; set; } + + public AdminContact AdminContact { get; set; } + + public TechnicalContact TechnicalContact { get; set; } + + public string ApproverEmail { get; set; } + + [JsonProperty("ReserveSANCount", NullValueHandling = NullValueHandling.Ignore)] + public long ReserveSanCount { get; set; } + + public bool AddInstallationSupport { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string EmailLanguageCode { get; set; } + + [JsonProperty("FileAuthDVIndicator")] public bool? FileAuthDvIndicator { get; set; } + + [JsonProperty("CNAMEAuthDVIndicator", NullValueHandling = NullValueHandling.Ignore)] + public bool? CnameAuthDvIndicator { get; set; } + + [JsonProperty("HTTPSFileAuthDVIndicator", NullValueHandling = NullValueHandling.Ignore)] + public bool HttpsFileAuthDvIndicator { get; set; } + + public string SignatureHashAlgorithm { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public bool CertTransparencyIndicator { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public long RenewalDays { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string DateTimeCulture { get; set; } + + [JsonProperty("CSRUniqueValue", NullValueHandling = NullValueHandling.Ignore)] + public string CsrUniqueValue { get; set; } + + [JsonProperty("isPKCS10", NullValueHandling = NullValueHandling.Ignore)] + public bool IsPkcs10 { get; set; } + + [JsonProperty("WildcardReserveSANCount", NullValueHandling = NullValueHandling.Ignore)] + public long WildcardReserveSanCount { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string ProvisioningMethod { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Client/Models/NewOrderResponse.cs b/SslStoreCaProxy/Client/Models/NewOrderResponse.cs new file mode 100644 index 0000000..b664105 --- /dev/null +++ b/SslStoreCaProxy/Client/Models/NewOrderResponse.cs @@ -0,0 +1,75 @@ +using Keyfactor.AnyGateway.SslStore.Interfaces; +using Newtonsoft.Json; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class NewOrderResponse : INewOrderResponse + { + public AdminContact AdminContact { get; set; } + public string ApproverEmail { get; set; } + public string AuthFileContent { get; set; } + public string AuthFileName { get; set; } + public AuthResponse AuthResponse { get; set; } + [JsonProperty("CNAMEAuthName")] public string CnameAuthName { get; set; } + [JsonProperty("CNAMEAuthValue")] public string CnameAuthValue { get; set; } + public string CertificateEndDate { get; set; } + + [JsonProperty("CertificateEndDateInUTC")] + public string CertificateEndDateInUtc { get; set; } + + public string CertificateStartDate { get; set; } + + [JsonProperty("CertificateStartDateInUTC")] + public string CertificateStartDateInUtc { get; set; } + + public string CommonName { get; set; } + public string Country { get; set; } + [JsonProperty("CustomOrderID")] public string CustomOrderId { get; set; } + [JsonProperty("CustomerID")] public int CustomerId { get; set; } + public string CustomerLoginName { get; set; } + public string CustomerPassword { get; set; } + [JsonProperty("DNSNames")] public string DnsNames { get; set; } + [JsonProperty("DUNS")] public string Duns { get; set; } + public string Locality { get; set; } + public string OrderAmount { get; set; } + public string OrderExpiryDate { get; set; } + [JsonProperty("OrderExpiryDateInUTC")] public string OrderExpiryDateInUtc { get; set; } + public OrderStatus OrderStatus { get; set; } + public string Organization { get; set; } + public string OrganizationAddress { get; set; } + public string OrganizationPhone { get; set; } + public string OrganizationPostalcode { get; set; } + public string OrganizationalUnit { get; set; } + [JsonProperty("PartnerOrderID")] public string PartnerOrderId { get; set; } + public string PollDate { get; set; } + [JsonProperty("PollDateInUTC")] public string PollDateInUtc { get; set; } + public string PollStatus { get; set; } + public string ProductCode { get; set; } + public string ProductName { get; set; } + public string PurchaseDate { get; set; } + [JsonProperty("PurchaseDateInUTC")] public string PurchaseDateInUtc { get; set; } + [JsonProperty("RefundRequestID")] public string RefundRequestId { get; set; } + public string ReissueSuccessCode { get; set; } + [JsonProperty("SANCount")] public int SanCount { get; set; } + public string SerialNumber { get; set; } + public int ServerCount { get; set; } + public string SignatureEncryptionAlgorithm { get; set; } + public string SignatureHashAlgorithm { get; set; } + public string SiteSealurl { get; set; } + public string State { get; set; } + public string SubVendorName { get; set; } + [JsonProperty("TSSOrganizationId")] public int TssOrganizationId { get; set; } + public TechnicalContact TechnicalContact { get; set; } + [JsonProperty("TheSSLStoreOrderID")] public string TheSslStoreOrderId { get; set; } + public string TinyOrderLink { get; set; } + public string Token { get; set; } + public string TokenCode { get; set; } + [JsonProperty("TokenID")] public string TokenId { get; set; } + public int Validity { get; set; } + public string VendorName { get; set; } + [JsonProperty("VendorOrderID")] public string VendorOrderId { get; set; } + public string WebServerType { get; set; } + [JsonProperty("isRefundApproved")] public bool IsRefundApproved { get; set; } + [JsonProperty("wildcardSANCount")] public int WildcardSanCount { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Client/Models/OrderNote.cs b/SslStoreCaProxy/Client/Models/OrderNote.cs new file mode 100644 index 0000000..1ae23f6 --- /dev/null +++ b/SslStoreCaProxy/Client/Models/OrderNote.cs @@ -0,0 +1,10 @@ +using Keyfactor.AnyGateway.SslStore.Interfaces; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class OrderNote : IOrderNote + { + public string Comments { get; set; } + public string DateAdded { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Client/Models/OrderStatus.cs b/SslStoreCaProxy/Client/Models/OrderStatus.cs new file mode 100644 index 0000000..068f970 --- /dev/null +++ b/SslStoreCaProxy/Client/Models/OrderStatus.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using Keyfactor.AnyGateway.SslStore.Interfaces; +using Newtonsoft.Json; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class OrderStatus : IOrderStatus + { + [JsonProperty("isTinyOrder")] public bool IsTinyOrder { get; set; } + + [JsonProperty("isTinyOrderClaimed")] public bool IsTinyOrderClaimed { get; set; } + + public string MajorStatus { get; set; } + public string MinorStatus { get; set; } + public List AuthenticationStatuses { get; set; } + public string AuthenticationComments { get; set; } + public List OrderNotes { get; set; } + public List DomainAuthVettingStatus { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Client/Models/OrderStatusRequest.cs b/SslStoreCaProxy/Client/Models/OrderStatusRequest.cs new file mode 100644 index 0000000..f69cc76 --- /dev/null +++ b/SslStoreCaProxy/Client/Models/OrderStatusRequest.cs @@ -0,0 +1,12 @@ +using Keyfactor.AnyGateway.SslStore.Interfaces; +using Newtonsoft.Json; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class OrderStatusRequest : IOrderStatusRequest + { + public AuthRequest AuthRequest { get; set; } + [JsonProperty("TheSSLStoreOrderID", NullValueHandling = NullValueHandling.Ignore)] public string TheSslStoreOrderId { get; set; } + [JsonProperty("CustomOrderID", NullValueHandling = NullValueHandling.Ignore)] public string CustomOrderId { get; set; } + } +} diff --git a/SslStoreCaProxy/Client/Models/OrderStatusResponse.cs b/SslStoreCaProxy/Client/Models/OrderStatusResponse.cs new file mode 100644 index 0000000..7a23f52 --- /dev/null +++ b/SslStoreCaProxy/Client/Models/OrderStatusResponse.cs @@ -0,0 +1,68 @@ +using Keyfactor.AnyGateway.SslStore.Interfaces; +using Newtonsoft.Json; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class OrderStatusResponse : IOrderStatusResponse + { + public AuthResponse AuthResponse { get; set; } + [JsonProperty("PartnerOrderID")] public string PartnerOrderId { get; set; } + [JsonProperty("CustomOrderID")] public string CustomOrderId { get; set; } + [JsonProperty("TheSSLStoreOrderID")] public string TheSslStoreOrderId { get; set; } + [JsonProperty("VendorOrderID")] public string VendorOrderId { get; set; } + [JsonProperty("RefundRequestID")] public string RefundRequestId { get; set; } + [JsonProperty("isRefundApproved")] public bool IsRefundApproved { get; set; } + public string TinyOrderLink { get; set; } + public OrderStatus OrderStatus { get; set; } + public string OrderAmount { get; set; } + public string PurchaseDate { get; set; } + public string CertificateStartDate { get; set; } + public string CertificateEndDate { get; set; } + public string CommonName { get; set; } + [JsonProperty("DNSNames")] public string DnsNames { get; set; } + [JsonProperty("SANCount")] public long SanCount { get; set; } + public long ServerCount { get; set; } + public long Validity { get; set; } + public string Organization { get; set; } + public string OrganizationalUnit { get; set; } + public string State { get; set; } + public string Country { get; set; } + public string Locality { get; set; } + public string OrganizationPhone { get; set; } + public string OrganizationAddress { get; set; } + public string OrganizationPostalcode { get; set; } + [JsonProperty("DUNS")] public string Duns { get; set; } + public string WebServerType { get; set; } + public string ApproverEmail { get; set; } + public string ProductName { get; set; } + public AdminContact AdminContact { get; set; } + public TechnicalContact TechnicalContact { get; set; } + public string ReissueSuccessCode { get; set; } + public string AuthFileName { get; set; } + public string AuthFileContent { get; set; } + public string PollStatus { get; set; } + public string PollDate { get; set; } + public string CustomerLoginName { get; set; } + public string CustomerPassword { get; set; } + [JsonProperty("CustomerID")] public long CustomerId { get; set; } + [JsonProperty("TokenID")] public string TokenId { get; set; } + public string TokenCode { get; set; } + public string SiteSealurl { get; set; } + [JsonProperty("CNAMEAuthName")] public string CnameAuthName { get; set; } + [JsonProperty("CNAMEAuthValue")] public string CnameAuthValue { get; set; } + public string SignatureEncryptionAlgorithm { get; set; } + public string SignatureHashAlgorithm { get; set; } + public string VendorName { get; set; } + public string SubVendorName { get; set; } + public string Token { get; set; } + + [JsonProperty("CertificateStartDateInUTC")] + public string CertificateStartDateInUtc { get; set; } + + [JsonProperty("CertificateEndDateInUTC")] + public string CertificateEndDateInUtc { get; set; } + + [JsonProperty("PurchaseDateInUTC")] public string PurchaseDateInUtc { get; set; } + [JsonProperty("PollDateInUTC")] public string PollDateInUtc { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Client/Models/Organization.cs b/SslStoreCaProxy/Client/Models/Organization.cs new file mode 100644 index 0000000..7b6410d --- /dev/null +++ b/SslStoreCaProxy/Client/Models/Organization.cs @@ -0,0 +1,25 @@ +using Keyfactor.AnyGateway.SslStore.Interfaces; +using Newtonsoft.Json; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class Organization : IOrganization + { + public string Address { get; set; } + public string Address2 { get; set; } + public object ApproversContact { get; set; } + public string AssumedName { get; set; } + public string City { get; set; } + public string Country { get; set; } + public string Name { get; set; } + public OrganizationContact OrganizationContact { get; set; } + [JsonProperty("Organization_Phone", NullValueHandling = NullValueHandling.Ignore)] + public string OrganizationPhone { get; set; } + public string State { get; set; } + public string Zip { get; set; } + public string Status { get; set; } + [JsonProperty("TSSOrganizationId", NullValueHandling = NullValueHandling.Ignore)] + public int TssOrganizationId { get; set; } + public int VendorOrganizationId { get; set; } + } +} diff --git a/SslStoreCaProxy/Client/Models/OrganizationAddress.cs b/SslStoreCaProxy/Client/Models/OrganizationAddress.cs new file mode 100644 index 0000000..d2d6b95 --- /dev/null +++ b/SslStoreCaProxy/Client/Models/OrganizationAddress.cs @@ -0,0 +1,18 @@ +using Keyfactor.AnyGateway.SslStore.Interfaces; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class OrganizationAddress : IOrganizationAddress + { + public string AddressLine1 { get; set; } + public string AddressLine2 { get; set; } + public string AddressLine3 { get; set; } + public string City { get; set; } + public string Region { get; set; } + public string PostalCode { get; set; } + public string Country { get; set; } + public string Phone { get; set; } + public string Fax { get; set; } + public string LocalityName { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Client/Models/OrganizationContact.cs b/SslStoreCaProxy/Client/Models/OrganizationContact.cs new file mode 100644 index 0000000..61e953c --- /dev/null +++ b/SslStoreCaProxy/Client/Models/OrganizationContact.cs @@ -0,0 +1,18 @@ + +using Keyfactor.AnyGateway.SslStore.Interfaces; +using Newtonsoft.Json; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class OrganizationContact : IOrganizationContact + { + public string Email { get; set; } + public string Firstname { get; set; } + public string JobTitle { get; set; } + public string Lastname { get; set; } + public string Phone { get; set; } + [JsonProperty("Phone_Extension", NullValueHandling = NullValueHandling.Ignore)] + public string PhoneExtension { get; set; } + public string Username { get; set; } + } +} diff --git a/SslStoreCaProxy/Client/Models/OrganizationInfo.cs b/SslStoreCaProxy/Client/Models/OrganizationInfo.cs new file mode 100644 index 0000000..ef76148 --- /dev/null +++ b/SslStoreCaProxy/Client/Models/OrganizationInfo.cs @@ -0,0 +1,21 @@ +using Keyfactor.AnyGateway.SslStore.Interfaces; +using Newtonsoft.Json; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class OrganizationInfo : IOrganizationInfo + { + public string OrganizationName { get; set; } + + [JsonProperty("DUNS")] public string Duns { get; set; } + + public string Division { get; set; } + public string IncorporatingAgency { get; set; } + public string RegistrationNumber { get; set; } + public string JurisdictionCity { get; set; } + public string JurisdictionRegion { get; set; } + public string JurisdictionCountry { get; set; } + public string AssumedName { get; set; } + public OrganizationAddress OrganizationAddress { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Client/Models/OrganizationListRequest.cs b/SslStoreCaProxy/Client/Models/OrganizationListRequest.cs new file mode 100644 index 0000000..16dffb3 --- /dev/null +++ b/SslStoreCaProxy/Client/Models/OrganizationListRequest.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Keyfactor.AnyGateway.SslStore.Interfaces; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class OrganizationListRequest : IOrganizationListRequest + { + public string PartnerCode { get; set; } + public string AuthToken { get; set; } + } +} diff --git a/SslStoreCaProxy/Client/Models/OrganizationResponse.cs b/SslStoreCaProxy/Client/Models/OrganizationResponse.cs new file mode 100644 index 0000000..6ef3038 --- /dev/null +++ b/SslStoreCaProxy/Client/Models/OrganizationResponse.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Keyfactor.AnyGateway.SslStore.Interfaces; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class OrganizationResponse : IOrganizationResponse + { + public AuthResponse AuthResponse { get; set; } + public List OrganizationList { get; set; } + } +} diff --git a/SslStoreCaProxy/Client/Models/QueryOrderRequest.cs b/SslStoreCaProxy/Client/Models/QueryOrderRequest.cs new file mode 100644 index 0000000..dd90de4 --- /dev/null +++ b/SslStoreCaProxy/Client/Models/QueryOrderRequest.cs @@ -0,0 +1,41 @@ +using System; +using Keyfactor.AnyGateway.SslStore.Interfaces; +using Newtonsoft.Json; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class QueryOrderRequest : IQueryOrderRequest + { + public AuthRequest AuthRequest { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public DateTime? StartDate { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public DateTime? EndDate { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public DateTime? CertificateExpireToDate { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public DateTime? CertificateExpireFromDate { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string DomainName { get; set; } + + [JsonProperty("SubUserID", NullValueHandling = NullValueHandling.Ignore)] + public string SubUserId { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string ProductCode { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string DateTimeCulture { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public long PageNumber { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public long PageSize { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Client/Models/ReIssueRequest.cs b/SslStoreCaProxy/Client/Models/ReIssueRequest.cs new file mode 100644 index 0000000..d1aaf66 --- /dev/null +++ b/SslStoreCaProxy/Client/Models/ReIssueRequest.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using Keyfactor.AnyGateway.SslStore.Interfaces; +using Newtonsoft.Json; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class ReIssueRequest : IReIssueRequest + { + public AuthRequest AuthRequest { get; set; } + [JsonProperty("TheSSLStoreOrderID")] public string TheSslStoreOrderId { get; set; } + [JsonProperty("CustomOrderID", NullValueHandling = NullValueHandling.Ignore)] public string CustomOrderId { get; set; } + [JsonProperty("CSR")] public string Csr { get; set; } + public string WebServerType { get; set; } + [JsonProperty("DNSNames")] public List DnsNames { get; set; } + [JsonProperty("isRenewalOrder")] public bool IsRenewalOrder { get; set; } + public string SpecialInstructions { get; set; } + [JsonProperty("EditSAN")] public List EditSan { get; set; } + [JsonProperty("DeleteSAN")] public List DeleteSan { get; set; } + [JsonProperty("AddSAN")] public List AddSan { get; set; } + [JsonProperty("isWildCard")] public bool IsWildCard { get; set; } + public string ReissueEmail { get; set; } + public bool PreferEnrollmentLink { get; set; } + public string SignatureHashAlgorithm { get; set; } + [JsonProperty("FileAuthDVIndicator")] public bool FileAuthDvIndicator { get; set; } + + [JsonProperty("HTTPSFileAuthDVIndicator")] + public bool HttpsFileAuthDvIndicator { get; set; } + + [JsonProperty("CNAMEAuthDVIndicator")] public bool CNameAuthDvIndicator { get; set; } + public string ApproverEmails { get; set; } + public string DateTimeCulture { get; set; } + [JsonProperty("CSRUniqueValue")] public string CsrUniqueValue { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Client/Models/RevokeOrderRequest.cs b/SslStoreCaProxy/Client/Models/RevokeOrderRequest.cs new file mode 100644 index 0000000..8faf24b --- /dev/null +++ b/SslStoreCaProxy/Client/Models/RevokeOrderRequest.cs @@ -0,0 +1,13 @@ +using Keyfactor.AnyGateway.SslStore.Interfaces; +using Newtonsoft.Json; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class RevokeOrderRequest : IRevokeOrderRequest + { + public AuthRequest AuthRequest { get; set; } + [JsonProperty("TheSSLStoreOrderID", NullValueHandling = NullValueHandling.Ignore)] public string TheSslStoreOrderId { get; set; } + [JsonProperty("CustomOrderID", NullValueHandling = NullValueHandling.Ignore)] public string CustomOrderId { get; set; } + public string SerialNumber { get; set; } + } +} diff --git a/SslStoreCaProxy/Client/Models/RevokeOrderResponse.cs b/SslStoreCaProxy/Client/Models/RevokeOrderResponse.cs new file mode 100644 index 0000000..95abb0b --- /dev/null +++ b/SslStoreCaProxy/Client/Models/RevokeOrderResponse.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using Keyfactor.AnyGateway.SslStore.Interfaces; +using Newtonsoft.Json; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class RevokeOrderResponse : IRevokeOrderResponse + { + public string InvokingPartnerCode { get; set; } + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public List Message { get; set; } + + public object ReplayToken { get; set; } + public string Timestamp { get; set; } + [JsonProperty("isError")] public bool IsError { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Client/Models/TechnicalContact.cs b/SslStoreCaProxy/Client/Models/TechnicalContact.cs new file mode 100644 index 0000000..53c96f4 --- /dev/null +++ b/SslStoreCaProxy/Client/Models/TechnicalContact.cs @@ -0,0 +1,23 @@ +using Keyfactor.AnyGateway.SslStore.Interfaces; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class TechnicalContact : ITechnicalContact + { + public string FirstName { get; set; } + public string LastName { get; set; } + public string SubjectFirstName { get; set; } + public string SubjectLastName { get; set; } + public string Phone { get; set; } + public string Fax { get; set; } + public string Email { get; set; } + public string Title { get; set; } + public string OrganizationName { get; set; } + public string AddressLine1 { get; set; } + public string AddressLine2 { get; set; } + public string City { get; set; } + public string Region { get; set; } + public string PostalCode { get; set; } + public string Country { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Client/Models/Template.cs b/SslStoreCaProxy/Client/Models/Template.cs new file mode 100644 index 0000000..b89e361 --- /dev/null +++ b/SslStoreCaProxy/Client/Models/Template.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using Keyfactor.AnyGateway.SslStore.Interfaces; +using Newtonsoft.Json; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class Template : ITemplate + { + public int Id { get; set; } + public string CommonName { get; set; } + public string TemplateName { get; set; } + public string Oid { get; set; } + public string KeySize { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string KeyType { get; set; } + public string ForestRoot { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string FriendlyName { get; set; } + public string KeyRetention { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public int? KeyRetentionDays { get; set; } + public bool KeyArchival { get; set; } + public List EnrollmentFields { get; set; } + public int AllowedEnrollmentTypes { get; set; } + public List TemplateRegexes { get; set; } + public bool UseAllowedRequesters { get; set; } + public List AllowedRequesters { get; set; } + public string DisplayName { get; set; } + } +} diff --git a/SslStoreCaProxy/Client/Models/TemplateNewOrderRequest.cs b/SslStoreCaProxy/Client/Models/TemplateNewOrderRequest.cs new file mode 100644 index 0000000..5e03d61 --- /dev/null +++ b/SslStoreCaProxy/Client/Models/TemplateNewOrderRequest.cs @@ -0,0 +1,270 @@ +using System.Collections.Generic; +using Keyfactor.AnyGateway.SslStore.Interfaces; +using Newtonsoft.Json; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class TemplateNewOrderRequest : ITemplateNewOrderRequest + { + public TemplateAuthRequest AuthRequest { get; set; } + public ProductCode ProductCode { get; set; } + public TemplateOrganizationInfo OrganizationInfo { get; set; } + public ValidityPeriod ValidityPeriod { get; set; } + public ServerCount ServerCount { get; set; } + [JsonProperty("CSR")] public Csr Csr { get; set; } + public DomainName DomainName { get; set; } + public WebServerType WebServerType { get; set; } + [JsonProperty("DNSNames")] public DnsNames DnsNames { get; set; } + [JsonProperty("isCUOrder")] public IsCuOrder IsCuOrder { get; set; } + [JsonProperty("AutoWWW", NullValueHandling = NullValueHandling.Ignore)] public AutoWWW AutoWWW { get; set; } + [JsonProperty("isRenewalOrder")] public IsRenewalOrder IsRenewalOrder { get; set; } + [JsonProperty("isTrialOrder")] public IsTrialOrder IsTrialOrder { get; set; } + public TemplateAdminContact AdminContact { get; set; } + public TemplateTechnicalContact TechnicalContact { get; set; } + public ApproverEmail ApproverEmail { get; set; } + [JsonProperty("FileAuthDVIndicator")] public FileAuthDvIndicator FileAuthDvIndicator { get; set; } + [JsonProperty("CNAMEAuthDVIndicator")] public CNameAuthDvIndicator CNameAuthDvIndicator { get; set; } + public SignatureHashAlgorithm SignatureHashAlgorithm { get; set; } + } + + public class FieldData + { + public List RequiredForProducts { get; set; } + public string EnrollmentFieldMapping { get; set; } + public bool Array { get; set; } + } + + public class PartnerCode + { + public FieldData FieldData { get; set; } + } + + public class AuthToken + { + public FieldData FieldData { get; set; } + } + + [JsonObject("AuthRequest")] + public class TemplateAuthRequest + { + public PartnerCode PartnerCode { get; set; } + public AuthToken AuthToken { get; set; } + } + + public class ProductCode + { + public FieldData FieldData { get; set; } + } + + public class OrganizationName + { + public FieldData FieldData { get; set; } + } + + public class RegistrationNumber + { + public FieldData FieldData { get; set; } + } + + public class JurisdictionCountry + { + public FieldData FieldData { get; set; } + } + + public class AddressLine1 + { + public FieldData FieldData { get; set; } + } + + public class Region + { + public FieldData FieldData { get; set; } + } + + public class PostalCode + { + public FieldData FieldData { get; set; } + } + + public class LocalityName + { + public FieldData FieldData { get; set; } + } + + [JsonObject("OrganizationAddress")] + public class TemplateOrganizationAddress + { + public AddressLine1 AddressLine1 { get; set; } + public Region Region { get; set; } + public PostalCode PostalCode { get; set; } + public LocalityName LocalityName { get; set; } + public Country Country { get; set; } + public Phone Phone { get; set; } + + } + + [JsonObject("OrganizationInfo")] + public class TemplateOrganizationInfo + { + public OrganizationName OrganizationName { get; set; } + public RegistrationNumber RegistrationNumber { get; set; } + public JurisdictionCountry JurisdictionCountry { get; set; } + public TemplateOrganizationAddress OrganizationAddress { get; set; } + } + + public class ValidityPeriod + { + public FieldData FieldData { get; set; } + } + + public class ServerCount + { + public FieldData FieldData { get; set; } + } + + [JsonObject("CSR")] + public class Csr + { + public FieldData FieldData { get; set; } + } + + public class DomainName + { + public FieldData FieldData { get; set; } + } + + public class WebServerType + { + public FieldData FieldData { get; set; } + } + + [JsonObject("DNSNames")] + public class DnsNames + { + public FieldData FieldData { get; set; } + } + + [JsonObject("isCUOrder")] + public class IsCuOrder + { + public FieldData FieldData { get; set; } + } + + [JsonObject("AutoWWW",ItemNullValueHandling =NullValueHandling.Ignore)] + public class AutoWWW + { + public FieldData FieldData { get; set; } + } + + [JsonObject("isRenewalOrder")] + public class IsRenewalOrder + { + public FieldData FieldData { get; set; } + } + + [JsonObject("isTrialOrder")] + public class IsTrialOrder + { + public FieldData FieldData { get; set; } + } + + public class FirstName + { + public FieldData FieldData { get; set; } + } + + public class LastName + { + public FieldData FieldData { get; set; } + } + + public class Phone + { + public FieldData FieldData { get; set; } + } + + public class Email + { + public FieldData FieldData { get; set; } + } + + public class Title + { + public FieldData FieldData { get; set; } + } + + public class City + { + public FieldData FieldData { get; set; } + } + + public class Country + { + public FieldData FieldData { get; set; } + } + + [JsonObject("AdminContact")] + public class TemplateAdminContact + { + public FirstName FirstName { get; set; } + public LastName LastName { get; set; } + public Phone Phone { get; set; } + public Email Email { get; set; } + public Title Title { get; set; } + public OrganizationName OrganizationName { get; set; } + public AddressLine1 AddressLine1 { get; set; } + public City City { get; set; } + public Region Region { get; set; } + public PostalCode PostalCode { get; set; } + public Country Country { get; set; } + } + + public class SubjectFirstName + { + public FieldData FieldData { get; set; } + } + + public class SubjectLastName + { + public FieldData FieldData { get; set; } + } + + [JsonObject("TechnicalContact")] + public class TemplateTechnicalContact + { + public FirstName FirstName { get; set; } + public LastName LastName { get; set; } + public SubjectFirstName SubjectFirstName { get; set; } + public SubjectLastName SubjectLastName { get; set; } + public Phone Phone { get; set; } + public Email Email { get; set; } + public Title Title { get; set; } + public AddressLine1 AddressLine1 { get; set; } + public City City { get; set; } + public Region Region { get; set; } + public PostalCode PostalCode { get; set; } + public Country Country { get; set; } + } + + public class ApproverEmail + { + public FieldData FieldData { get; set; } + } + + [JsonObject("FileAuthDVIndicator")] + public class FileAuthDvIndicator + { + public FieldData FieldData { get; set; } + } + + [JsonObject("CNAMEAuthDVIndicator")] + public class CNameAuthDvIndicator + { + public FieldData FieldData { get; set; } + } + + public class SignatureHashAlgorithm + { + public FieldData FieldData { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Client/Models/TemplateRegex.cs b/SslStoreCaProxy/Client/Models/TemplateRegex.cs new file mode 100644 index 0000000..27eaab6 --- /dev/null +++ b/SslStoreCaProxy/Client/Models/TemplateRegex.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Keyfactor.AnyGateway.SslStore.Interfaces; + +namespace Keyfactor.AnyGateway.SslStore.Client.Models +{ + public class TemplateRegex : ITemplateRegex + { + public int TemplateId { get; set; } + public string SubjectPart { get; set; } + public string Regex { get; set; } + public string Error { get; set; } + } +} diff --git a/SslStoreCaProxy/Client/SslStoreClient.cs b/SslStoreCaProxy/Client/SslStoreClient.cs new file mode 100644 index 0000000..3d8b721 --- /dev/null +++ b/SslStoreCaProxy/Client/SslStoreClient.cs @@ -0,0 +1,246 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Keyfactor.AnyGateway.Extensions; +using Keyfactor.AnyGateway.SslStore.Client.Models; +using Keyfactor.AnyGateway.SslStore.Exceptions; +using Keyfactor.AnyGateway.SslStore.Interfaces; +using Keyfactor.Logging; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Keyfactor.AnyGateway.SslStore.Client +{ + public sealed class SslStoreClient : ISslStoreClient + { + private static readonly ILogger _logger = LogHandler.GetClassLogger(); + + // Use an explicit JsonSerializer to ensure [JsonProperty] attributes are respected, + // regardless of the host application's global JsonConvert.DefaultSettings. + private static readonly JsonSerializer _serializer = new JsonSerializer + { + ContractResolver = new DefaultContractResolver(), + NullValueHandling = NullValueHandling.Ignore + }; + + private static string Serialize(object obj) + { + var sb = new StringBuilder(); + using (var sw = new System.IO.StringWriter(sb)) + using (var jw = new JsonTextWriter(sw)) + { + _serializer.Serialize(jw, obj); + } + return sb.ToString(); + } + + public SslStoreClient(IAnyCAPluginConfigProvider config) + { + if (config.CAConnectionData.ContainsKey(Constants.SslStoreUrl)) + { + BaseUrl = new Uri(config.CAConnectionData[Constants.SslStoreUrl].ToString()); + RestClient = ConfigureRestClient(); + } + } + + private Uri BaseUrl { get; } + private HttpClient RestClient { get; } + private int PageSize { get; } = 100; + + public async Task SubmitNewOrderRequestAsync(NewOrderRequest newOrderRequest) + { + using (var resp = await RestClient.PostAsync("/rest/order/neworder", new StringContent( + Serialize(newOrderRequest), Encoding.UTF8, "application/json"))) + { + _logger.LogTrace(Serialize(newOrderRequest)); + resp.EnsureSuccessStatusCode(); + var enrollmentResponse = + JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync()); + return enrollmentResponse; + } + } + + public async Task SubmitEmailApproverRequestAsync(EmailApproverRequest newApproverRequest) + { + using (var resp = await RestClient.PostAsync("/rest/order/approverlist", new StringContent( + Serialize(newApproverRequest), Encoding.UTF8, "application/json"))) + { + _logger.LogTrace(Serialize(newApproverRequest)); + resp.EnsureSuccessStatusCode(); + var enrollmentResponse = + JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync()); + return enrollmentResponse; + } + } + + public async Task SubmitReIssueRequestAsync(ReIssueRequest reIssueOrderRequest) + { + using (var resp = await RestClient.PostAsync("/rest/order/reissue", new StringContent( + Serialize(reIssueOrderRequest), Encoding.UTF8, "application/json"))) + { + var orderStatusResponse = + JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync()); + return orderStatusResponse; + } + } + + public async Task SubmitRenewRequestAsync(NewOrderRequest renewOrderRequest) + { + using (var resp = await RestClient.PostAsync("/rest/order/neworder", new StringContent( + Serialize(renewOrderRequest), Encoding.UTF8, "application/json"))) + { + _logger.LogTrace(Serialize(renewOrderRequest)); + resp.EnsureSuccessStatusCode(); + var enrollmentResponse = + JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync()); + return enrollmentResponse; + } + } + + public async Task SubmitDownloadCertificateAsync( + DownloadCertificateRequest downloadOrderRequest) + { + using (var resp = await RestClient.PostAsync("/rest/order/download", new StringContent( + Serialize(downloadOrderRequest), Encoding.UTF8, "application/json"))) + { + _logger.LogTrace(Serialize(downloadOrderRequest)); + resp.EnsureSuccessStatusCode(); + var downloadOrderResponse = + JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync()); + return downloadOrderResponse; + } + } + + public async Task SubmitQueryOrderRequestAsync(BlockingCollection bc, CancellationToken ct, + RequestManager requestManager) + { + _logger.MethodEntry(); + try + { + var itemsProcessed = 0; + var pageCounter = 0; + var isComplete = false; + var retryCount = 0; + do + { + pageCounter++; + var queryOrderRequest = requestManager.GetQueryOrderRequest(PageSize, pageCounter); + var batchItemsProcessed = 0; + using (var resp = await RestClient.PostAsync("/rest/order/query", new StringContent( + Serialize(queryOrderRequest), Encoding.UTF8, "application/json"))) + { + if (!resp.IsSuccessStatusCode) + { + var responseMessage = resp.Content.ReadAsStringAsync().Result; + _logger.LogError( + $"Failed Request to SslStore. Retrying request. Status Code {resp.StatusCode} | Message: {responseMessage}"); + retryCount++; + if (retryCount > 5) + throw new RetryCountExceededException( + $"5 consecutive failures to {resp.RequestMessage.RequestUri}"); + + continue; + } + + var batchResponse = + JsonConvert.DeserializeObject>( + await resp.Content.ReadAsStringAsync()); + + _logger.LogTrace($"Order List JSON {Serialize(batchResponse)}"); + + var batchCount = batchResponse.Count; + + _logger.LogTrace($"Processing {batchCount} items in batch"); + do + { + var r = batchResponse[batchItemsProcessed]; + if (bc.TryAdd(r, 10, ct)) + { + _logger.LogTrace($"Added Certificate ID {r.TheSslStoreOrderId} to Queue for processing"); + batchItemsProcessed++; + itemsProcessed++; + _logger.LogTrace($"Processed {batchItemsProcessed} of {batchCount}"); + _logger.LogTrace($"Total Items Processed: {itemsProcessed}"); + } + else + { + _logger.LogTrace($"Adding {r} blocked. Retry"); + } + } while (batchItemsProcessed < batchCount); + } + + if (batchItemsProcessed < PageSize) + isComplete = true; + } while (!isComplete); + + bc.CompleteAdding(); + } + catch (OperationCanceledException cancelEx) + { + _logger.LogWarning($"Synchronize method was cancelled. Message: {cancelEx.Message}"); + bc.CompleteAdding(); + _logger.MethodExit(); + throw; + } + catch (RetryCountExceededException retryEx) + { + _logger.LogError($"Retries Failed: {retryEx.Message}"); + _logger.MethodExit(); + } + catch (HttpRequestException ex) + { + _logger.LogError($"HttpRequest Failed: {ex.Message}"); + _logger.MethodExit(); + } + + _logger.MethodExit(); + } + + public async Task SubmitRevokeCertificateAsync(RevokeOrderRequest revokeOrderRequest) + { + using (var resp = await RestClient.PostAsync("/rest/order/refundrequest", new StringContent( + Serialize(revokeOrderRequest), Encoding.UTF8, "application/json"))) + { + var revocationResponse = + JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync()); + return revocationResponse; + } + } + + public async Task SubmitOrderStatusRequestAsync(OrderStatusRequest orderStatusRequest) + { + using (var resp = await RestClient.PostAsync("/rest/order/status", new StringContent( + Serialize(orderStatusRequest), Encoding.UTF8, "application/json"))) + { + var orderStatusResponse = + JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync()); + return orderStatusResponse; + } + } + + public async Task SubmitOrganizationListAsync(OrganizationListRequest organizationListRequest) + { + using (var resp = await RestClient.PostAsync("/rest/digicert/organizationlist", new StringContent( + Serialize(organizationListRequest), Encoding.UTF8, "application/json"))) + { + var organizationListResponse = + JsonConvert.DeserializeObject(await resp.Content.ReadAsStringAsync()); + return organizationListResponse; + } + } + + private HttpClient ConfigureRestClient() + { + var clientHandler = new HttpClientHandler(); + var returnClient = new HttpClient(clientHandler, true) { BaseAddress = BaseUrl }; + returnClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + return returnClient; + } + } +} diff --git a/SslStoreCaProxy/Constants.cs b/SslStoreCaProxy/Constants.cs new file mode 100644 index 0000000..36caa82 --- /dev/null +++ b/SslStoreCaProxy/Constants.cs @@ -0,0 +1,11 @@ +namespace Keyfactor.AnyGateway.SslStore +{ + internal class Constants + { + public static string SslStoreUrl = "SSLStoreURL"; + public static string PartnerCode = "PartnerCode"; + public static string AuthToken = "AuthToken"; + public static string PageSize = "PageSize"; + public static int DefaultPageSize = 100; + } +} diff --git a/SslStoreCaProxy/Exceptions/RetryCountExceededException.cs b/SslStoreCaProxy/Exceptions/RetryCountExceededException.cs new file mode 100644 index 0000000..125c666 --- /dev/null +++ b/SslStoreCaProxy/Exceptions/RetryCountExceededException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Keyfactor.AnyGateway.SslStore.Exceptions +{ + public class RetryCountExceededException : Exception + { + public RetryCountExceededException(string message) : base(message) + { + } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IAddSan.cs b/SslStoreCaProxy/Interfaces/IAddSan.cs new file mode 100644 index 0000000..6bf7277 --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IAddSan.cs @@ -0,0 +1,8 @@ +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IAddSan + { + string OldValue { get; set; } + string NewValue { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IAdminContact.cs b/SslStoreCaProxy/Interfaces/IAdminContact.cs new file mode 100644 index 0000000..deb6573 --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IAdminContact.cs @@ -0,0 +1,21 @@ +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IAdminContact + { + string FirstName { get; set; } + string LastName { get; set; } + string SubjectFirstName { get; set; } + string SubjectLastName { get; set; } + string Phone { get; set; } + string Fax { get; set; } + string Email { get; set; } + string Title { get; set; } + string OrganizationName { get; set; } + string AddressLine1 { get; set; } + string AddressLine2 { get; set; } + string City { get; set; } + string Region { get; set; } + string PostalCode { get; set; } + string Country { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IAuthRequest.cs b/SslStoreCaProxy/Interfaces/IAuthRequest.cs new file mode 100644 index 0000000..17d4526 --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IAuthRequest.cs @@ -0,0 +1,15 @@ +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IAuthRequest + { + string PartnerCode { get; set; } + string AuthToken { get; set; } + string ReplayToken { get; set; } + string UserAgent { get; set; } + string TokenId { get; set; } + string TokenCode { get; set; } + string IpAddress { get; set; } + bool IsUsedForTokenSystem { get; set; } + string Token { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IAuthResponse.cs b/SslStoreCaProxy/Interfaces/IAuthResponse.cs new file mode 100644 index 0000000..161a3ee --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IAuthResponse.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IAuthResponse + { + bool IsError { get; set; } + List Message { get; set; } + string Timestamp { get; set; } + string ReplayToken { get; set; } + string InvokingPartnerCode { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IAuthenticationStatus.cs b/SslStoreCaProxy/Interfaces/IAuthenticationStatus.cs new file mode 100644 index 0000000..d4c66f9 --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IAuthenticationStatus.cs @@ -0,0 +1,9 @@ +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IAuthenticationStatus + { + string AuthenticationStep { get; set; } + string Status { get; set; } + string LastUpdated { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IDomainAuthVettingStatus.cs b/SslStoreCaProxy/Interfaces/IDomainAuthVettingStatus.cs new file mode 100644 index 0000000..a6dcdfc --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IDomainAuthVettingStatus.cs @@ -0,0 +1,17 @@ +using System; + +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IDomainAuthVettingStatus + { + string Domain { get; set; } + string DcvMethod { get; set; } + string DcvStatus { get; set; } + string FileName { get; set; } + string FileContents { get; set; } + string DnsName { get; set; } + string DnsEntry { get; set; } + string PollStatus { get; set; } + DateTime LastPollDate { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IDownloadCertificateRequest.cs b/SslStoreCaProxy/Interfaces/IDownloadCertificateRequest.cs new file mode 100644 index 0000000..af9f698 --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IDownloadCertificateRequest.cs @@ -0,0 +1,10 @@ +using Keyfactor.AnyGateway.SslStore.Client.Models; + +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IDownloadCertificateRequest + { + AuthRequest AuthRequest { get; set; } + string TheSslStoreOrderId { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IDownloadCertificateResponse.cs b/SslStoreCaProxy/Interfaces/IDownloadCertificateResponse.cs new file mode 100644 index 0000000..88e8872 --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IDownloadCertificateResponse.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using Keyfactor.AnyGateway.SslStore.Client.Models; + +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IDownloadCertificateResponse + { + AuthResponse AuthResponse { get; set; } + string CertificateEndDate { get; set; } + string CertificateEndDateInUtc { get; set; } + string CertificateStartDate { get; set; } + string CertificateStartDateInUtc { get; set; } + string CertificateStatus { get; set; } + List Certificates { get; set; } + string PartnerOrderId { get; set; } + object TheSslStoreOrderId { get; set; } + string ValidationStatus { get; set; } + object VendorOrderId { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IEmailApproverRequest.cs b/SslStoreCaProxy/Interfaces/IEmailApproverRequest.cs new file mode 100644 index 0000000..722f83e --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IEmailApproverRequest.cs @@ -0,0 +1,11 @@ +using Keyfactor.AnyGateway.SslStore.Client.Models; + +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IEmailApproverRequest + { + AuthRequest AuthRequest { get; set; } + string ProductCode { get; set; } + string DomainName { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IEmailApproverResponse.cs b/SslStoreCaProxy/Interfaces/IEmailApproverResponse.cs new file mode 100644 index 0000000..a0d353a --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IEmailApproverResponse.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Keyfactor.AnyGateway.SslStore.Client.Models; + +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IEmailApproverResponse + { + List ApproverEmailList { get; set; } + AuthResponse AuthResponse { get; set; } + string BaseDomainName { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IEnrollmentField.cs b/SslStoreCaProxy/Interfaces/IEnrollmentField.cs new file mode 100644 index 0000000..ff377d7 --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IEnrollmentField.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IEnrollmentField + { + int Id { get; set; } + string Name { get; set; } + List Options { get; set; } + int DataType { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/INewOrderRequest.cs b/SslStoreCaProxy/Interfaces/INewOrderRequest.cs new file mode 100644 index 0000000..59e87fb --- /dev/null +++ b/SslStoreCaProxy/Interfaces/INewOrderRequest.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using Keyfactor.AnyGateway.SslStore.Client.Models; + +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface INewOrderRequest + { + AuthRequest AuthRequest { get; set; } + string CustomOrderId { get; set; } + string ProductCode { get; set; } + string ExtraProductCodes { get; set; } + OrganizationInfo OrganizationInfo { get; set; } + long TssOrganizationId { get; set; } + long ValidityPeriod { get; set; } + long ServerCount { get; set; } + string Csr { get; set; } + string DomainName { get; set; } + string WebServerType { get; set; } + List DnsNames { get; set; } + bool IsCuOrder { get; set; } + bool? AutoWWW { get; set; } + bool IsRenewalOrder { get; set; } + string SpecialInstructions { get; set; } + string RelatedTheSslStoreOrderId { get; set; } + bool IsTrialOrder { get; set; } + AdminContact AdminContact { get; set; } + TechnicalContact TechnicalContact { get; set; } + string ApproverEmail { get; set; } + long ReserveSanCount { get; set; } + bool AddInstallationSupport { get; set; } + string EmailLanguageCode { get; set; } + bool? FileAuthDvIndicator { get; set; } + bool? CnameAuthDvIndicator { get; set; } + bool HttpsFileAuthDvIndicator { get; set; } + string SignatureHashAlgorithm { get; set; } + bool CertTransparencyIndicator { get; set; } + long RenewalDays { get; set; } + string DateTimeCulture { get; set; } + string CsrUniqueValue { get; set; } + bool IsPkcs10 { get; set; } + long WildcardReserveSanCount { get; set; } + string ProvisioningMethod { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/INewOrderResponse.cs b/SslStoreCaProxy/Interfaces/INewOrderResponse.cs new file mode 100644 index 0000000..ec1f3aa --- /dev/null +++ b/SslStoreCaProxy/Interfaces/INewOrderResponse.cs @@ -0,0 +1,68 @@ +using Keyfactor.AnyGateway.SslStore.Client.Models; + +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface INewOrderResponse + { + AdminContact AdminContact { get; set; } + string ApproverEmail { get; set; } + string AuthFileContent { get; set; } + string AuthFileName { get; set; } + AuthResponse AuthResponse { get; set; } + string CnameAuthName { get; set; } + string CnameAuthValue { get; set; } + string CertificateEndDate { get; set; } + string CertificateEndDateInUtc { get; set; } + string CertificateStartDate { get; set; } + string CertificateStartDateInUtc { get; set; } + string CommonName { get; set; } + string Country { get; set; } + string CustomOrderId { get; set; } + int CustomerId { get; set; } + string CustomerLoginName { get; set; } + string CustomerPassword { get; set; } + string DnsNames { get; set; } + string Duns { get; set; } + string Locality { get; set; } + string OrderAmount { get; set; } + string OrderExpiryDate { get; set; } + string OrderExpiryDateInUtc { get; set; } + OrderStatus OrderStatus { get; set; } + string Organization { get; set; } + string OrganizationAddress { get; set; } + string OrganizationPhone { get; set; } + string OrganizationPostalcode { get; set; } + string OrganizationalUnit { get; set; } + string PartnerOrderId { get; set; } + string PollDate { get; set; } + string PollDateInUtc { get; set; } + string PollStatus { get; set; } + string ProductCode { get; set; } + string ProductName { get; set; } + string PurchaseDate { get; set; } + string PurchaseDateInUtc { get; set; } + string RefundRequestId { get; set; } + string ReissueSuccessCode { get; set; } + int SanCount { get; set; } + string SerialNumber { get; set; } + int ServerCount { get; set; } + string SignatureEncryptionAlgorithm { get; set; } + string SignatureHashAlgorithm { get; set; } + string SiteSealurl { get; set; } + string State { get; set; } + string SubVendorName { get; set; } + int TssOrganizationId { get; set; } + TechnicalContact TechnicalContact { get; set; } + string TheSslStoreOrderId { get; set; } + string TinyOrderLink { get; set; } + string Token { get; set; } + string TokenCode { get; set; } + string TokenId { get; set; } + int Validity { get; set; } + string VendorName { get; set; } + string VendorOrderId { get; set; } + string WebServerType { get; set; } + bool IsRefundApproved { get; set; } + int WildcardSanCount { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IOrderNote.cs b/SslStoreCaProxy/Interfaces/IOrderNote.cs new file mode 100644 index 0000000..2d7c65b --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IOrderNote.cs @@ -0,0 +1,8 @@ +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IOrderNote + { + string Comments { get; set; } + string DateAdded { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IOrderStatus.cs b/SslStoreCaProxy/Interfaces/IOrderStatus.cs new file mode 100644 index 0000000..e4d55df --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IOrderStatus.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using Keyfactor.AnyGateway.SslStore.Client.Models; + +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IOrderStatus + { + bool IsTinyOrder { get; set; } + bool IsTinyOrderClaimed { get; set; } + string MajorStatus { get; set; } + string MinorStatus { get; set; } + List AuthenticationStatuses { get; set; } + string AuthenticationComments { get; set; } + List OrderNotes { get; set; } + List DomainAuthVettingStatus { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IOrderStatusRequest.cs b/SslStoreCaProxy/Interfaces/IOrderStatusRequest.cs new file mode 100644 index 0000000..fcd8e43 --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IOrderStatusRequest.cs @@ -0,0 +1,10 @@ +using Keyfactor.AnyGateway.SslStore.Client.Models; + +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IOrderStatusRequest + { + AuthRequest AuthRequest { get; set; } + string TheSslStoreOrderId { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IOrderStatusResponse.cs b/SslStoreCaProxy/Interfaces/IOrderStatusResponse.cs new file mode 100644 index 0000000..883455e --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IOrderStatusResponse.cs @@ -0,0 +1,62 @@ +using Keyfactor.AnyGateway.SslStore.Client.Models; + +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IOrderStatusResponse + { + AuthResponse AuthResponse { get; set; } + string PartnerOrderId { get; set; } + string CustomOrderId { get; set; } + string TheSslStoreOrderId { get; set; } + string VendorOrderId { get; set; } + string RefundRequestId { get; set; } + bool IsRefundApproved { get; set; } + string TinyOrderLink { get; set; } + OrderStatus OrderStatus { get; set; } + string OrderAmount { get; set; } + string PurchaseDate { get; set; } + string CertificateStartDate { get; set; } + string CertificateEndDate { get; set; } + string CommonName { get; set; } + string DnsNames { get; set; } + long SanCount { get; set; } + long ServerCount { get; set; } + long Validity { get; set; } + string Organization { get; set; } + string OrganizationalUnit { get; set; } + string State { get; set; } + string Country { get; set; } + string Locality { get; set; } + string OrganizationPhone { get; set; } + string OrganizationAddress { get; set; } + string OrganizationPostalcode { get; set; } + string Duns { get; set; } + string WebServerType { get; set; } + string ApproverEmail { get; set; } + string ProductName { get; set; } + AdminContact AdminContact { get; set; } + TechnicalContact TechnicalContact { get; set; } + string ReissueSuccessCode { get; set; } + string AuthFileName { get; set; } + string AuthFileContent { get; set; } + string PollStatus { get; set; } + string PollDate { get; set; } + string CustomerLoginName { get; set; } + string CustomerPassword { get; set; } + long CustomerId { get; set; } + string TokenId { get; set; } + string TokenCode { get; set; } + string SiteSealurl { get; set; } + string CnameAuthName { get; set; } + string CnameAuthValue { get; set; } + string SignatureEncryptionAlgorithm { get; set; } + string SignatureHashAlgorithm { get; set; } + string VendorName { get; set; } + string SubVendorName { get; set; } + string Token { get; set; } + string CertificateStartDateInUtc { get; set; } + string CertificateEndDateInUtc { get; set; } + string PurchaseDateInUtc { get; set; } + string PollDateInUtc { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IOrganization.cs b/SslStoreCaProxy/Interfaces/IOrganization.cs new file mode 100644 index 0000000..6f369d2 --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IOrganization.cs @@ -0,0 +1,22 @@ +using Keyfactor.AnyGateway.SslStore.Client.Models; + +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IOrganization + { + string Address { get; set; } + string Address2 { get; set; } + object ApproversContact { get; set; } + string AssumedName { get; set; } + string City { get; set; } + string Country { get; set; } + string Name { get; set; } + OrganizationContact OrganizationContact { get; set; } + string OrganizationPhone { get; set; } + string State { get; set; } + string Zip { get; set; } + string Status { get; set; } + int TssOrganizationId { get; set; } + int VendorOrganizationId { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IOrganizationAddress.cs b/SslStoreCaProxy/Interfaces/IOrganizationAddress.cs new file mode 100644 index 0000000..e1831a9 --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IOrganizationAddress.cs @@ -0,0 +1,16 @@ +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IOrganizationAddress + { + string AddressLine1 { get; set; } + string AddressLine2 { get; set; } + string AddressLine3 { get; set; } + string City { get; set; } + string Region { get; set; } + string PostalCode { get; set; } + string Country { get; set; } + string Phone { get; set; } + string Fax { get; set; } + string LocalityName { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IOrganizationContact.cs b/SslStoreCaProxy/Interfaces/IOrganizationContact.cs new file mode 100644 index 0000000..b283d21 --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IOrganizationContact.cs @@ -0,0 +1,13 @@ +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IOrganizationContact + { + string Email { get; set; } + string Firstname { get; set; } + string JobTitle { get; set; } + string Lastname { get; set; } + string Phone { get; set; } + string PhoneExtension { get; set; } + string Username { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IOrganizationInfo.cs b/SslStoreCaProxy/Interfaces/IOrganizationInfo.cs new file mode 100644 index 0000000..1ffe4d3 --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IOrganizationInfo.cs @@ -0,0 +1,18 @@ +using Keyfactor.AnyGateway.SslStore.Client.Models; + +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IOrganizationInfo + { + string OrganizationName { get; set; } + string Duns { get; set; } + string Division { get; set; } + string IncorporatingAgency { get; set; } + string RegistrationNumber { get; set; } + string JurisdictionCity { get; set; } + string JurisdictionRegion { get; set; } + string JurisdictionCountry { get; set; } + string AssumedName { get; set; } + OrganizationAddress OrganizationAddress { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IOrganizationListRequest.cs b/SslStoreCaProxy/Interfaces/IOrganizationListRequest.cs new file mode 100644 index 0000000..1e98104 --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IOrganizationListRequest.cs @@ -0,0 +1,8 @@ +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IOrganizationListRequest + { + string PartnerCode { get; set; } + string AuthToken { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IOrganizationResponse.cs b/SslStoreCaProxy/Interfaces/IOrganizationResponse.cs new file mode 100644 index 0000000..489a96a --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IOrganizationResponse.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Keyfactor.AnyGateway.SslStore.Client.Models; + +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IOrganizationResponse + { + AuthResponse AuthResponse { get; set; } + List OrganizationList { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IQueryOrderRequest.cs b/SslStoreCaProxy/Interfaces/IQueryOrderRequest.cs new file mode 100644 index 0000000..5042278 --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IQueryOrderRequest.cs @@ -0,0 +1,20 @@ +using System; +using Keyfactor.AnyGateway.SslStore.Client.Models; + +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IQueryOrderRequest + { + AuthRequest AuthRequest { get; set; } + DateTime? StartDate { get; set; } + DateTime? EndDate { get; set; } + DateTime? CertificateExpireToDate { get; set; } + DateTime? CertificateExpireFromDate { get; set; } + string DomainName { get; set; } + string SubUserId { get; set; } + string ProductCode { get; set; } + string DateTimeCulture { get; set; } + long PageNumber { get; set; } + long PageSize { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IReIssueRequest.cs b/SslStoreCaProxy/Interfaces/IReIssueRequest.cs new file mode 100644 index 0000000..72fde8b --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IReIssueRequest.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using Keyfactor.AnyGateway.SslStore.Client.Models; + +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IReIssueRequest + { + AuthRequest AuthRequest { get; set; } + string TheSslStoreOrderId { get; set; } + string Csr { get; set; } + string WebServerType { get; set; } + List DnsNames { get; set; } + bool IsRenewalOrder { get; set; } + string SpecialInstructions { get; set; } + List EditSan { get; set; } + List DeleteSan { get; set; } + List AddSan { get; set; } + bool IsWildCard { get; set; } + string ReissueEmail { get; set; } + bool PreferEnrollmentLink { get; set; } + string SignatureHashAlgorithm { get; set; } + bool FileAuthDvIndicator { get; set; } + bool HttpsFileAuthDvIndicator { get; set; } + bool CNameAuthDvIndicator { get; set; } + string ApproverEmails { get; set; } + string DateTimeCulture { get; set; } + string CsrUniqueValue { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IRequestManager.cs b/SslStoreCaProxy/Interfaces/IRequestManager.cs new file mode 100644 index 0000000..a4bb581 --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IRequestManager.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using Keyfactor.AnyGateway.Extensions; +using Keyfactor.AnyGateway.SslStore.Client.Models; + +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IRequestManager + { + NewOrderRequest GetEnrollmentRequest(string csr, string subject, Dictionary san, + EnrollmentProductInfo productInfo, IAnyCAPluginConfigProvider configProvider, bool isRenewalOrder); + + AuthRequest GetAuthRequest(); + ReIssueRequest GetReIssueRequest(INewOrderResponse orderData, string csr, bool isRenewal); + AdminContact GetAdminContact(EnrollmentProductInfo productInfo); + TechnicalContact GetTechnicalContact(EnrollmentProductInfo productInfo); + DownloadCertificateRequest GetCertificateRequest(string theSslStoreOrderId); + RevokeOrderRequest GetRevokeOrderRequest(string theSslStoreOrderId); + int GetClientPageSize(IAnyCAPluginConfigProvider config); + QueryOrderRequest GetQueryOrderRequest(int pageSize, int pageNumber); + OrderStatusRequest GetOrderStatusRequest(string theSslStoreId); + int MapReturnStatus(string sslStoreStatus); + } +} diff --git a/SslStoreCaProxy/Interfaces/IRevokeOrderRequest.cs b/SslStoreCaProxy/Interfaces/IRevokeOrderRequest.cs new file mode 100644 index 0000000..58ed409 --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IRevokeOrderRequest.cs @@ -0,0 +1,11 @@ +using Keyfactor.AnyGateway.SslStore.Client.Models; + +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IRevokeOrderRequest + { + AuthRequest AuthRequest { get; set; } + string TheSslStoreOrderId { get; set; } + string SerialNumber { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/IRevokeOrderResponse.cs b/SslStoreCaProxy/Interfaces/IRevokeOrderResponse.cs new file mode 100644 index 0000000..3a9b422 --- /dev/null +++ b/SslStoreCaProxy/Interfaces/IRevokeOrderResponse.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface IRevokeOrderResponse + { + string InvokingPartnerCode { get; set; } + List Message { get; set; } + object ReplayToken { get; set; } + string Timestamp { get; set; } + bool IsError { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/ISslStoreClient.cs b/SslStoreCaProxy/Interfaces/ISslStoreClient.cs new file mode 100644 index 0000000..fd5cf0a --- /dev/null +++ b/SslStoreCaProxy/Interfaces/ISslStoreClient.cs @@ -0,0 +1,30 @@ +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; +using Keyfactor.AnyGateway.SslStore.Client.Models; + +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface ISslStoreClient + { + Task SubmitNewOrderRequestAsync(NewOrderRequest newOrderRequest); + + Task SubmitReIssueRequestAsync(ReIssueRequest reIssueOrderRequest); + + Task SubmitRenewRequestAsync(NewOrderRequest renewOrderRequest); + + Task SubmitDownloadCertificateAsync( + DownloadCertificateRequest downloadOrderRequest); + + Task SubmitQueryOrderRequestAsync(BlockingCollection bc, CancellationToken ct, + RequestManager requestManager); + + Task SubmitRevokeCertificateAsync(RevokeOrderRequest revokeOrderRequest); + + Task SubmitOrganizationListAsync(OrganizationListRequest organizationListRequest); + + Task SubmitOrderStatusRequestAsync(OrderStatusRequest orderStatusRequest); + + Task SubmitEmailApproverRequestAsync(EmailApproverRequest newApproverRequest); + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/ITechnicalContact.cs b/SslStoreCaProxy/Interfaces/ITechnicalContact.cs new file mode 100644 index 0000000..0c8a4f5 --- /dev/null +++ b/SslStoreCaProxy/Interfaces/ITechnicalContact.cs @@ -0,0 +1,21 @@ +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface ITechnicalContact + { + string FirstName { get; set; } + string LastName { get; set; } + string SubjectFirstName { get; set; } + string SubjectLastName { get; set; } + string Phone { get; set; } + string Fax { get; set; } + string Email { get; set; } + string Title { get; set; } + string OrganizationName { get; set; } + string AddressLine1 { get; set; } + string AddressLine2 { get; set; } + string City { get; set; } + string Region { get; set; } + string PostalCode { get; set; } + string Country { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/ITemplate.cs b/SslStoreCaProxy/Interfaces/ITemplate.cs new file mode 100644 index 0000000..1fbbeb5 --- /dev/null +++ b/SslStoreCaProxy/Interfaces/ITemplate.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Keyfactor.AnyGateway.SslStore.Client.Models; + +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface ITemplate + { + int Id { get; set; } + string CommonName { get; set; } + string TemplateName { get; set; } + string Oid { get; set; } + string KeySize { get; set; } + string KeyType { get; set; } + string ForestRoot { get; set; } + string FriendlyName { get; set; } + string KeyRetention { get; set; } + int? KeyRetentionDays { get; set; } + bool KeyArchival { get; set; } + List EnrollmentFields { get; set; } + int AllowedEnrollmentTypes { get; set; } + List TemplateRegexes { get; set; } + bool UseAllowedRequesters { get; set; } + List AllowedRequesters { get; set; } + string DisplayName { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/ITemplateNewOrderRequest.cs b/SslStoreCaProxy/Interfaces/ITemplateNewOrderRequest.cs new file mode 100644 index 0000000..bc46601 --- /dev/null +++ b/SslStoreCaProxy/Interfaces/ITemplateNewOrderRequest.cs @@ -0,0 +1,27 @@ +using Keyfactor.AnyGateway.SslStore.Client.Models; + +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface ITemplateNewOrderRequest + { + TemplateAuthRequest AuthRequest { get; set; } + ProductCode ProductCode { get; set; } + TemplateOrganizationInfo OrganizationInfo { get; set; } + ValidityPeriod ValidityPeriod { get; set; } + ServerCount ServerCount { get; set; } + Csr Csr { get; set; } + DomainName DomainName { get; set; } + WebServerType WebServerType { get; set; } + DnsNames DnsNames { get; set; } + AutoWWW AutoWWW { get; set; } + IsCuOrder IsCuOrder { get; set; } + IsRenewalOrder IsRenewalOrder { get; set; } + IsTrialOrder IsTrialOrder { get; set; } + TemplateAdminContact AdminContact { get; set; } + TemplateTechnicalContact TechnicalContact { get; set; } + ApproverEmail ApproverEmail { get; set; } + FileAuthDvIndicator FileAuthDvIndicator { get; set; } + CNameAuthDvIndicator CNameAuthDvIndicator { get; set; } + SignatureHashAlgorithm SignatureHashAlgorithm { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/Interfaces/ITemplateRegex.cs b/SslStoreCaProxy/Interfaces/ITemplateRegex.cs new file mode 100644 index 0000000..66f2dbe --- /dev/null +++ b/SslStoreCaProxy/Interfaces/ITemplateRegex.cs @@ -0,0 +1,10 @@ +namespace Keyfactor.AnyGateway.SslStore.Interfaces +{ + public interface ITemplateRegex + { + int TemplateId { get; set; } + string SubjectPart { get; set; } + string Regex { get; set; } + string Error { get; set; } + } +} \ No newline at end of file diff --git a/SslStoreCaProxy/ProductDefinitions.cs b/SslStoreCaProxy/ProductDefinitions.cs new file mode 100644 index 0000000..331fbd3 --- /dev/null +++ b/SslStoreCaProxy/ProductDefinitions.cs @@ -0,0 +1,476 @@ +using System.Collections.Generic; +using System.Linq; +using Keyfactor.AnyGateway.SslStore.Client.Models; + +namespace Keyfactor.AnyGateway.SslStore +{ + /// + /// Static registry of all SSL Store products and their enrollment fields. + /// Template-level properties (Id, OID, KeySize, etc.) are configured in Keyfactor Command. + /// + public static class ProductDefinitions + { + private static int _nextFieldId = 1; + + #region Shared Option Lists + + private static readonly List ValidityStandard = new List { "12", "24", "36", "48", "60" }; + private static readonly List ValidityExtended = new List { "6", "12", "24", "36", "48", "60" }; + private static readonly List ValidityDigicert = new List { "12", "24", "36", "48", "60", "72" }; + + private static readonly List BoolOptions = new List { "False", "True" }; + + private static readonly List CountryCodes = new List + { + "US","AF","AX","AL","DZ","AS","AD","AO","AI","AQ","AG","AR","AM","AW","AU","AT","AZ", + "BS","BH","BD","BB","BY","BE","BZ","BJ","BM","BT","BO","BQ","BA","BW","BV","BR","IO", + "BN","BG","BF","BI","CV","KH","CM","CA","KY","CF","TD","CL","CN","CX","CC","CO","KM", + "CG","CD","CK","CR","CI","HR","CU","CW","CY","CZ","DK","DJ","DM","DO","EC","EG","SV", + "GQ","ER","EE","SZ","ET","FK","FO","FJ","FI","FR","GF","PF","TF","GA","GM","GE","DE", + "GH","GI","GR","GL","GD","GP","GU","GT","GG","GN","GW","GY","HT","HM","VA","HN","HK", + "HU","IS","IN","ID","IR","IQ","IE","IM","IL","IT","JM","JP","JE","JO","KZ","KE","KI", + "KP","KR","KW","KG","LA","LV","LB","LS","LR","LY","LI","LT","LU","MO","MG","MW","MY", + "MV","ML","MT","MH","MQ","MR","MU","YT","MX","FM","MD","MC","MN","ME","MS","MA","MZ", + "MM","NA","NR","NP","NL","NC","NZ","NI","NE","NG","NU","NF","MK","MP","NO","OM","PK", + "PW","PS","PA","PG","PY","PE","PH","PN","PL","PT","PR","QA","RE","RO","RU","RW","BL", + "SH","KN","LC","MF","PM","VC","WS","SM","ST","SA","SN","RS","SC","SL","SG","SX","SK", + "SI","SB","SO","ZA","GS","SS","ES","LK","SD","SR","SJ","SE","CH","SY","TW","TJ","TZ", + "TH","TL","TG","TK","TO","TT","TN","TR","TM","TC","TV","UG","UA","AE","GB","UM","UY", + "UZ","VU","VE","VN","VG","VI","WF","EH","YE","ZM","ZW" + }; + + private static readonly List SignatureHashAlgorithms = new List + { + "PREFER_SHA2", "REQUIRE_SHA2", "PREFER_SHA1", "" + }; + + private static readonly List WebServerTypes = new List + { + "aol","apachessl","apacheraven","apachessleay","iis","iis4","iis5","c2net","Ibmhttp", + "Ibminternet","Iplanet","Dominogo4625","Dominogo4626","Domino","Netscape", + "NetscapeFastTrack","zeusv3","Other","apacheopenssl","apache2","apacheapachessl", + "cobaltseries","covalentserver","cpanel","ensim","hsphere","ipswitch","plesk","tomcat", + "WebLogic","website","webstar","sapwebserver","webten","redhat","reven","r3ssl","quid", + "oracle","javawebserver","cisco3000","citrix" + }; + + #endregion + + #region Enrollment Field Builders + + private static EnrollmentField TextField(string name) + { + return new EnrollmentField { Id = _nextFieldId++, Name = name, Options = new List { "" }, DataType = 1 }; + } + + private static EnrollmentField DropdownField(string name, List options) + { + return new EnrollmentField { Id = _nextFieldId++, Name = name, Options = new List(options), DataType = 2 }; + } + + // Group 1: Legacy Full (36 fields) - comodossl, comodoevssl, etc. + private static List LegacyFullFields(List validity) + { + return new List + { + DropdownField("Is CU Order?", BoolOptions), + DropdownField("Is Renewal Order?", BoolOptions), + DropdownField("Is Trial Order?", BoolOptions), + TextField("Admin Contact - First Name"), + TextField("Admin Contact - Last Name"), + TextField("Admin Contact - Phone"), + TextField("Admin Contact - Email"), + TextField("Admin Contact - Organization Name"), + TextField("Admin Contact - Address"), + TextField("Admin Contact - City"), + TextField("Admin Contact - Region"), + TextField("Admin Contact - Postal Code"), + DropdownField("Admin Contact - Country", CountryCodes), + TextField("Technical Contact - First Name"), + TextField("Technical Contact - Last Name"), + TextField("Technical Contact - Phone"), + TextField("Technical Contact - Email"), + TextField("Technical Contact - Organization Name"), + TextField("Technical Contact - Address"), + TextField("Technical Contact - City"), + TextField("Technical Contact - Region"), + TextField("Technical Contact - Postal Code"), + DropdownField("Technical Contact - Country", CountryCodes), + TextField("Approver Email"), + DropdownField("File Auth Domain Validation", BoolOptions), + DropdownField("CName Auth Domain Validation", BoolOptions), + DropdownField("Signature Hash Algorithm", SignatureHashAlgorithms), + DropdownField("Web Server Type", WebServerTypes), + TextField("Server Count"), + TextField("Validity Period (In Days)"), + TextField("Organization Name"), + TextField("Organization Address"), + TextField("Organization Region"), + TextField("Organization Postal Code"), + TextField("Organization Country"), + TextField("Organization Phone") + }; + } + + // Group 1b: Legacy Full + DNS Names + Jurisdiction (38 fields) - digi_quickssl_md + private static List LegacyFullDnsJurisdictionFields(List validity) + { + var fields = new List(); + fields.AddRange(LegacyFullFields(validity)); + // Insert Jurisdiction Country before Organization Phone (at the end) + fields.Insert(fields.Count - 1, DropdownField("Organization Jurisdiction Country", CountryCodes)); + return fields; + } + + // Group 2: EO Minimal (3 fields) - digi_*-EO products, digi_securesite_pro_flex + private static List EoMinimalFields() + { + return new List + { + + TextField("Validity Period (In Days)"), + DropdownField("Organization ID", new List()) + }; + } + + // Group 3: Sectigo/Comodo OV (9 fields) - instantssl, comodopremiumssl, etc. + private static List SectigoOvFields() + { + return new List + { + TextField("Admin Contact - Email"), + TextField("Approver Email"), + TextField("Validity Period (In Days)"), + TextField("Organization Name"), + TextField("Organization Address"), + TextField("Organization State/Province"), + TextField("Organization Postal Code"), + DropdownField("Organization Country", CountryCodes), + TextField("Organization Phone") + }; + } + + // Group 4: DigiCert OV Flex (14 fields) - digi_securesite_flex, digi_sslwebserver_flex, etc. + private static List DigiCertOvFlexFields() + { + return new List + { + + TextField("Admin Contact - First Name"), + TextField("Admin Contact - Last Name"), + TextField("Admin Contact - Phone"), + TextField("Admin Contact - Email"), + TextField("Approver Email"), + TextField("Validity Period (In Days)"), + TextField("Organization Name"), + TextField("Organization Address"), + TextField("Organization City"), + TextField("Organization State/Province"), + TextField("Organization Postal Code"), + DropdownField("Organization Country", CountryCodes), + TextField("Organization Phone") + }; + } + + // Group 5: DV Minimal (3 fields) - positivessl, sectigossl, etc. + private static List DvMinimalFields() + { + return new List + { + TextField("Admin Contact - Email"), + TextField("Approver Email"), + TextField("Validity Period (In Days)") + }; + } + + // Group 6: DigiCert EV Flex (15 fields) - digi_securesite_ev_flex, digi_ssl_ev_basic, etc. + private static List DigiCertEvFlexFields(string regionFieldName = "Organization State/Province") + { + return new List + { + + TextField("Admin Contact - First Name"), + TextField("Admin Contact - Last Name"), + TextField("Admin Contact - Phone"), + TextField("Admin Contact - Email"), + TextField("Admin Contact - Title"), + TextField("Approver Email"), + TextField("Validity Period (In Days)"), + TextField("Organization Name"), + TextField("Organization Address"), + TextField("Organization City"), + TextField(regionFieldName), + TextField("Organization Postal Code"), + DropdownField("Organization Country", CountryCodes), + TextField("Organization Phone") + }; + } + + // Group 7: DV MDC (4 fields) - positivemdcssl, sectigodvucc, etc. + private static List DvMdcFields() + { + return new List + { + + TextField("Admin Contact - Email"), + TextField("Approver Email"), + TextField("Validity Period (In Days)") + }; + } + + // Group 8: DigiCert DV RapidSSL (3 fields) - digi_rapidssl, digi_rapidssl_wc + private static List DigiCertDvRapidSslFields() + { + return new List + { + TextField("Technical Contact - Email"), + TextField("Approver Email"), + TextField("Validity Period (In Days)") + }; + } + + // Group 9: DigiCert DV GeoTrust/SSL123 (4 fields) - digi_ssl_dv_geotrust_flex, digi_ssl123_flex + private static List DigiCertDvGeoTrustFields() + { + return new List + { + + TextField("Technical Contact - Email"), + TextField("Approver Email"), + TextField("Validity Period (In Days)") + }; + } + + // Group 10: EV with Jurisdiction (10 fields) - enterpriseproev, positiveevssl + private static List EvJurisdictionFields() + { + return new List + { + TextField("Admin Contact - Email"), + TextField("Approver Email"), + TextField("Validity Period (In Days)"), + TextField("Organization Name"), + TextField("Organization Address"), + TextField("Organization State/Province"), + TextField("Organization Postal Code"), + DropdownField("Organization Country", CountryCodes), + DropdownField("Organization Jurisdiction Country", CountryCodes), + TextField("Organization Phone") + }; + } + + // Group 11: EV MDC with Jurisdiction (11 fields) - positiveevmdc, sectigoevmdc + private static List EvMdcJurisdictionFields() + { + return new List + { + + TextField("Admin Contact - Email"), + TextField("Approver Email"), + TextField("Validity Period (In Days)"), + TextField("Organization Name"), + TextField("Organization Address"), + TextField("Organization State/Province"), + TextField("Organization Postal Code"), + DropdownField("Organization Country", CountryCodes), + DropdownField("Organization Jurisdiction Country", CountryCodes), + TextField("Organization Phone") + }; + } + + // Group 11b: EV MDC with Jurisdiction (Country before Jurisdiction) - enterpriseproevmdc + private static List EvMdcJurisdictionAltFields() + { + return new List + { + + TextField("Admin Contact - Email"), + TextField("Approver Email"), + TextField("Validity Period (In Days)"), + TextField("Organization Name"), + TextField("Organization Address"), + TextField("Organization State/Province"), + TextField("Organization Postal Code"), + DropdownField("Organization Jurisdiction Country", CountryCodes), + DropdownField("Organization Country", CountryCodes), + TextField("Organization Phone") + }; + } + + // Group 12: OV MDC (10 fields) - sectigomdc, sectigomdcwildcard + private static List OvMdcFields() + { + return new List + { + + TextField("Admin Contact - Email"), + TextField("Approver Email"), + TextField("Validity Period (In Days)"), + TextField("Organization Name"), + TextField("Organization Address"), + TextField("Organization State/Province"), + TextField("Organization Postal Code"), + DropdownField("Organization Country", CountryCodes), + TextField("Organization Phone") + }; + } + + // Group 15: Enterprise Pro OV (9 fields) - enterprisepro (Admin First Name instead of Email) + private static List EnterpriseProOvFields() + { + return new List + { + TextField("Admin Contact - First Name"), + TextField("Approver Email"), + TextField("Validity Period (In Days)"), + TextField("Organization Name"), + TextField("Organization Address"), + TextField("Organization State/Province"), + TextField("Organization Postal Code"), + DropdownField("Organization Country", CountryCodes), + TextField("Organization Phone") + }; + } + + #endregion + + #region Product Registry + + private static readonly Dictionary> _products = BuildRegistry(); + + private static Dictionary> BuildRegistry() + { + var registry = new Dictionary>(); + + // --- Group 1: Legacy Full (validity: 6,12,24,36,48,60) --- + foreach (var code in new[] + { + "comododvucc", "comodoevcsc", "comodoevmdc", "comodoevssl", "comodomdc", + "comodomdcwildcard", "comodopciscan", "comodossl", "comodoucc", + "comodouccwildcard", "comodowildcard", "digi_client_premium", "digi_csc", + "digi_csc_ev", "digi_doc_signing_ind_2000", "digi_doc_signing_ind_500", + "digi_doc_signing_org_2000", "digi_doc_signing_org_5000", + "elitessl", "enterprisessl", "essentialssl", "essentialwildcard", + "hackerprooftm", "hgpcicontrolscan", "pacbasic", "pacpro", "pacenterprise" + }) + { + registry[code] = LegacyFullFields(ValidityExtended); + } + + // comodocsc: Legacy Full but with standard validity (12,24,36,48,60) + registry["comodocsc"] = LegacyFullFields(ValidityStandard); + + // --- Group 1b: Legacy Full + DNS + Jurisdiction --- + registry["digi_quickssl_md"] = LegacyFullDnsJurisdictionFields(ValidityExtended); + + // --- Group 2: EO Minimal --- + foreach (var code in new[] + { + "digi_securesite_ev_flex-EO", "digi_securesite_flex-EO", + "digi_securesite_pro_ev_flex-EO", "digi_securesite_pro_flex", + "digi_securesite_pro_flex-EO", "digi_ssl_basic-EO", "digi_ssl_ev_basic-EO", + "digi_sslwebserver_ev_flex-EO", "digi_sslwebserver_flex-EO", + "digi_truebizid_ev_flex-EO", "digi_truebizid_flex-EO" + }) + { + registry[code] = EoMinimalFields(); + } + + // --- Group 3: Sectigo/Comodo OV --- + foreach (var code in new[] + { + "comodopremiumssl", "comodopremiumwildcard", "enterpriseprowc", + "instantssl", "instantsslpro", "sectigoovssl", "sectigoovwildcard" + }) + { + registry[code] = SectigoOvFields(); + } + + // --- Group 4: DigiCert OV Flex --- + foreach (var code in new[] + { + "digi_securesite_flex", "digi_ssl_basic", + "digi_sslwebserver_flex", "digi_truebizid_flex" + }) + { + registry[code] = DigiCertOvFlexFields(); + } + + // --- Group 5: DV Minimal --- + foreach (var code in new[] + { + "positivessl", "positivesslwildcard", "sectigoevssl", + "sectigossl", "sectigowildcard" + }) + { + registry[code] = DvMinimalFields(); + } + + // --- Group 6: DigiCert EV Flex (State/Province) --- + foreach (var code in new[] + { + "digi_securesite_ev_flex", "digi_ssl_ev_basic", + "digi_sslwebserver_ev_flex", "digi_truebizid_ev_flex" + }) + { + registry[code] = DigiCertEvFlexFields(); + } + + // Group 6b: DigiCert EV Flex with "Organization Region" instead of "State/Province" + registry["digi_securesite_pro_ev_flex"] = DigiCertEvFlexFields("Organization Region"); + + // --- Group 7: DV MDC --- + foreach (var code in new[] + { + "positivemdcssl", "positivemdcwildcard", "sectigodvucc", "sectigouccwildcard" + }) + { + registry[code] = DvMdcFields(); + } + + // --- Group 8: DigiCert DV RapidSSL --- + registry["digi_rapidssl"] = DigiCertDvRapidSslFields(); + registry["digi_rapidssl_wc"] = DigiCertDvRapidSslFields(); + + // --- Group 9: DigiCert DV GeoTrust/SSL123 --- + registry["digi_ssl_dv_geotrust_flex"] = DigiCertDvGeoTrustFields(); + registry["digi_ssl123_flex"] = DigiCertDvGeoTrustFields(); + + // --- Group 10: EV with Jurisdiction --- + registry["enterpriseproev"] = EvJurisdictionFields(); + registry["positiveevssl"] = EvJurisdictionFields(); + + // --- Group 11: EV MDC with Jurisdiction --- + registry["positiveevmdc"] = EvMdcJurisdictionFields(); + registry["sectigoevmdc"] = EvMdcJurisdictionFields(); + + // --- Group 11b: EV MDC Jurisdiction (alt field order) --- + registry["enterpriseproevmdc"] = EvMdcJurisdictionAltFields(); + + // --- Group 12: OV MDC --- + registry["sectigomdc"] = OvMdcFields(); + registry["sectigomdcwildcard"] = OvMdcFields(); + + // --- Group 15: Enterprise Pro OV --- + registry["enterprisepro"] = EnterpriseProOvFields(); + + return registry; + } + + #endregion + + #region Public API + + public static List GetProductIds() + { + return _products.Keys.ToList(); + } + + public static List GetEnrollmentFields(string productCode) + { + return _products.TryGetValue(productCode, out var fields) ? fields : null; + } + + #endregion + } +} diff --git a/SslStoreCaProxy/RequestManager.cs b/SslStoreCaProxy/RequestManager.cs new file mode 100644 index 0000000..30718f8 --- /dev/null +++ b/SslStoreCaProxy/RequestManager.cs @@ -0,0 +1,392 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Text.RegularExpressions; +using Keyfactor.AnyGateway.Extensions; +using Keyfactor.AnyGateway.SslStore.Client.Models; +using Keyfactor.AnyGateway.SslStore.Interfaces; +using Keyfactor.Logging; +using Keyfactor.PKI.Enums.EJBCA; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; + +namespace Keyfactor.AnyGateway.SslStore +{ + public class RequestManager : IRequestManager + { + private static readonly ILogger _logger = LogHandler.GetClassLogger(); + private readonly SslStoreCaProxy _sslStoreCaProxy; + + public RequestManager(SslStoreCaProxy sslStoreCaProxy) + { + _sslStoreCaProxy = sslStoreCaProxy; + } + + public NewOrderRequest GetEnrollmentRequest(string csr, string subject, Dictionary san, + EnrollmentProductInfo productInfo, IAnyCAPluginConfigProvider configProvider, bool isRenewalOrder) + { + var pemCsr = ConvertCsrToPem(csr); + return BuildNewOrderRequest(productInfo, pemCsr, subject, san, isRenewalOrder); + } + + private string ConvertCsrToPem(string csr) + { + try + { + var csrBytes = Convert.FromBase64String(csr); + var base64 = Convert.ToBase64String(csrBytes); + var sb = new StringBuilder(); + sb.AppendLine("-----BEGIN CERTIFICATE REQUEST-----"); + for (int i = 0; i < base64.Length; i += 64) + { + sb.AppendLine(base64.Substring(i, Math.Min(64, base64.Length - i))); + } + sb.AppendLine("-----END CERTIFICATE REQUEST-----"); + return sb.ToString(); + } + catch + { + return csr; + } + } + + public EmailApproverRequest GetEmailApproverListRequest(string productId, string productName) + { + return new EmailApproverRequest() + { + AuthRequest = GetAuthRequest(), + ProductCode = productId, + DomainName = productName + }; + } + + public OrganizationListRequest GetOrganizationListRequest() + { + return new OrganizationListRequest() + { + PartnerCode = _sslStoreCaProxy.PartnerCode, + AuthToken = _sslStoreCaProxy.AuthenticationToken + }; + } + + public AuthRequest GetAuthRequest() + { + return new AuthRequest + { + PartnerCode = _sslStoreCaProxy.PartnerCode, + AuthToken = _sslStoreCaProxy.AuthenticationToken + }; + } + + public ReIssueRequest GetReIssueRequest(INewOrderResponse orderData, string csr, bool isRenewal) + { + return new ReIssueRequest + { + AuthRequest = GetAuthRequest(), + TheSslStoreOrderId = orderData.TheSslStoreOrderId, + CustomOrderId = Guid.NewGuid().ToString(), + Csr = csr, + IsRenewalOrder = isRenewal, + IsWildCard = orderData.ProductCode.Contains("wc") || orderData.ProductCode.Contains("wildcard"), + ReissueEmail = orderData.AdminContact.Email, + ApproverEmails = orderData.ApproverEmail, + PreferEnrollmentLink = false, + FileAuthDvIndicator = orderData.OrderStatus.DomainAuthVettingStatus == null ? false : orderData.OrderStatus.DomainAuthVettingStatus.Exists(x => x.FileName != null), + CNameAuthDvIndicator = orderData.OrderStatus.DomainAuthVettingStatus == null ? false : orderData.OrderStatus.DomainAuthVettingStatus.Exists(x => x.DnsName != null), + WebServerType = orderData.WebServerType + }; + } + + public AdminContact GetAdminContact(EnrollmentProductInfo productInfo) + { + return new AdminContact + { + FirstName = productInfo.ProductParameters["Admin Contact - First Name"], + LastName = productInfo.ProductParameters["Admin Contact - Last Name"], + Phone = productInfo.ProductParameters["Admin Contact - Phone"], + Email = productInfo.ProductParameters["Admin Contact - Email"], + OrganizationName = productInfo.ProductParameters["Admin Contact - Organization Name"], + AddressLine1 = productInfo.ProductParameters["Admin Contact - Address"], + City = productInfo.ProductParameters["Admin Contact - City"], + Region = productInfo.ProductParameters["Admin Contact - Region"], + PostalCode = productInfo.ProductParameters["Admin Contact - Postal Code"], + Country = productInfo.ProductParameters["Admin Contact - Country"] + }; + } + + public TechnicalContact GetTechnicalContact(EnrollmentProductInfo productInfo) + { + return new TechnicalContact + { + FirstName = productInfo.ProductParameters["Technical Contact - First Name"], + LastName = productInfo.ProductParameters["Technical Contact - Last Name"], + Phone = productInfo.ProductParameters["Technical Contact - Phone"], + Email = productInfo.ProductParameters["Technical Contact - Email"], + OrganizationName = productInfo.ProductParameters["Technical Contact - Organization Name"], + AddressLine1 = productInfo.ProductParameters["Technical Contact - Address"], + City = productInfo.ProductParameters["Technical Contact - City"], + Region = productInfo.ProductParameters["Technical Contact - Region"], + PostalCode = productInfo.ProductParameters["Technical Contact - Postal Code"], + Country = productInfo.ProductParameters["Technical Contact - Country"] + }; + } + + public DownloadCertificateRequest GetCertificateRequest(string customOrderId) + { + return new DownloadCertificateRequest + { + AuthRequest = GetAuthRequest(), + CustomOrderId = customOrderId + }; + } + + public DownloadCertificateRequest GetCertificateRequestBySslStoreId(string theSslStoreOrderId) + { + return new DownloadCertificateRequest + { + AuthRequest = GetAuthRequest(), + TheSslStoreOrderId = theSslStoreOrderId + }; + } + + public RevokeOrderRequest GetRevokeOrderRequest(string customOrderId) + { + return new RevokeOrderRequest + { + AuthRequest = GetAuthRequest(), + CustomOrderId = customOrderId + }; + } + + public RevokeOrderRequest GetRevokeOrderRequestBySslStoreId(string theSslStoreOrderId) + { + return new RevokeOrderRequest + { + AuthRequest = GetAuthRequest(), + TheSslStoreOrderId = theSslStoreOrderId + }; + } + + public int GetClientPageSize(IAnyCAPluginConfigProvider config) + { + if (config.CAConnectionData.ContainsKey(Constants.PageSize)) + return int.Parse(config.CAConnectionData[Constants.PageSize].ToString()); + return Constants.DefaultPageSize; + } + + public QueryOrderRequest GetQueryOrderRequest(int pageSize, int pageNumber) + { + return new QueryOrderRequest + { + AuthRequest = GetAuthRequest(), + PageSize = pageSize, + PageNumber = pageNumber + }; + } + + public OrderStatusRequest GetOrderStatusRequest(string customOrderId) + { + return new OrderStatusRequest + { + AuthRequest = GetAuthRequest(), + CustomOrderId = customOrderId + }; + } + + public OrderStatusRequest GetOrderStatusRequestBySslStoreId(string theSslStoreOrderId) + { + return new OrderStatusRequest + { + AuthRequest = GetAuthRequest(), + TheSslStoreOrderId = theSslStoreOrderId + }; + } + + public int MapReturnStatus(string sslStoreStatus) + { + switch (sslStoreStatus) + { + case "Active": + return (int)EndEntityStatus.GENERATED; + case "Initial": + case "Pending": + return (int)EndEntityStatus.EXTERNALVALIDATION; + case "Cancelled": + return (int)EndEntityStatus.REVOKED; + default: + return (int)EndEntityStatus.NEW; + } + } + + public NewOrderRequest GetRenewalRequest(INewOrderResponse orderData, string csr) + { + return new NewOrderRequest + { + AuthRequest = GetAuthRequest(), + CustomOrderId = Guid.NewGuid().ToString(), + RelatedTheSslStoreOrderId = orderData.TheSslStoreOrderId, + ProductCode = orderData.ProductCode, + AdminContact = GetAdminContact(orderData), + TechnicalContact = GetTechnicalContact(orderData), + ApproverEmail = orderData.ApproverEmail, + SignatureHashAlgorithm = orderData.SignatureHashAlgorithm, + WebServerType = orderData.WebServerType, + ValidityPeriod = orderData.Validity, + ServerCount = orderData.ServerCount, + IsRenewalOrder = true, + FileAuthDvIndicator = orderData.OrderStatus?.DomainAuthVettingStatus?.Exists(x => x.FileName != null), + CnameAuthDvIndicator = orderData.OrderStatus?.DomainAuthVettingStatus?.Exists(x => x.DnsName != null), + Csr = csr + }; + } + + public AdminContact GetAdminContact(INewOrderResponse productInfo) + { + return new AdminContact + { + FirstName = productInfo.AdminContact.FirstName, + LastName = productInfo.AdminContact.LastName, + Phone = productInfo.AdminContact.Phone, + Email = productInfo.AdminContact.Email + }; + } + + public TechnicalContact GetTechnicalContact(INewOrderResponse productInfo) + { + return new TechnicalContact + { + FirstName = productInfo.AdminContact.FirstName, + LastName = productInfo.AdminContact.LastName, + Phone = productInfo.AdminContact.Phone, + Email = productInfo.AdminContact.Email + }; + } + + private NewOrderRequest BuildNewOrderRequest(EnrollmentProductInfo productInfo, + string csr, string subject, Dictionary san, bool isRenewal) + { + var p = productInfo.ProductParameters; + + // Extract domain name from CSR subject CN + var domainName = subject?.Split(',') + .Select(part => part.Trim()) + .Where(part => part.StartsWith("CN=", StringComparison.OrdinalIgnoreCase)) + .Select(part => part.Substring(3)) + .FirstOrDefault() ?? ""; + + // Extract DNS SANs from Keyfactor san parameter + var dnsNames = san != null && san.ContainsKey("dnsname") ? san["dnsname"].ToList() : new List(); + + return new NewOrderRequest + { + AuthRequest = GetAuthRequest(), + ProductCode = productInfo.ProductID.Replace("-EO", ""), + CustomOrderId = Guid.NewGuid().ToString(), + TssOrganizationId = p.ContainsKey("Organization ID") ? long.TryParse(ExtractOrgId(p["Organization ID"]), out var orgId) ? orgId : 0 : 0, + OrganizationInfo = new OrganizationInfo + { + OrganizationName = GetParam(p, "Organization Name"), + JurisdictionCountry = GetParam(p, "Organization Jurisdiction Country"), + OrganizationAddress = new OrganizationAddress + { + AddressLine1 = GetParam(p, "Organization Address"), + Region = GetParam(p, "Organization Region") ?? GetParam(p, "Organization State/Province"), + PostalCode = GetParam(p, "Organization Postal Code"), + Country = GetParam(p, "Organization Country"), + Phone = GetParam(p, "Organization Phone"), + LocalityName = GetParam(p, "Organization City") + } + }, + ValidityPeriod = ConvertDaysToMonths(productInfo), + ServerCount = 1, + Csr = csr, + DomainName = domainName, + WebServerType = GetParam(p, "Web Server Type") ?? "Other", + DnsNames = dnsNames, + IsCuOrder = false, + IsRenewalOrder = isRenewal, + IsTrialOrder = false, + AdminContact = new AdminContact + { + FirstName = GetParam(p, "Admin Contact - First Name"), + LastName = GetParam(p, "Admin Contact - Last Name"), + Phone = GetParam(p, "Admin Contact - Phone"), + Email = GetParam(p, "Admin Contact - Email"), + Title = GetParam(p, "Admin Contact - Title"), + OrganizationName = GetParam(p, "Admin Contact - Organization Name"), + AddressLine1 = GetParam(p, "Admin Contact - Address"), + City = GetParam(p, "Admin Contact - City"), + Region = GetParam(p, "Admin Contact - Region"), + PostalCode = GetParam(p, "Admin Contact - Postal Code"), + Country = GetParam(p, "Admin Contact - Country") + }, + TechnicalContact = new TechnicalContact + { + FirstName = GetParam(p, "Technical Contact - First Name"), + LastName = GetParam(p, "Technical Contact - Last Name"), + Phone = GetParam(p, "Technical Contact - Phone"), + Email = GetParam(p, "Technical Contact - Email"), + Title = GetParam(p, "Technical Contact - Title"), + OrganizationName = GetParam(p, "Technical Contact - Organization Name"), + AddressLine1 = GetParam(p, "Technical Contact - Address"), + City = GetParam(p, "Technical Contact - City"), + Region = GetParam(p, "Technical Contact - Region"), + PostalCode = GetParam(p, "Technical Contact - Postal Code"), + Country = GetParam(p, "Technical Contact - Country") + }, + ApproverEmail = GetParam(p, "Approver Email"), + FileAuthDvIndicator = false, + CnameAuthDvIndicator = false, + SignatureHashAlgorithm = GetParam(p, "Signature Hash Algorithm") ?? "PREFER_SHA2" + }; + } + + private static string GetParam(Dictionary parameters, string key) + { + return parameters.ContainsKey(key) ? parameters[key] : null; + } + + public string GetCertificateContent(List certificates, string commonName) + { + foreach (var c in certificates) + { + var cert = new X509Certificate2(Encoding.UTF8.GetBytes(c.FileContent)); + if (cert.SubjectName.Name != null && cert.SubjectName.Name.Contains(commonName)) return c.FileContent; + } + + return ""; + } + + private long ConvertDaysToMonths(EnrollmentProductInfo productInfo) + { + if (productInfo.ProductParameters.ContainsKey("Validity Period (In Days)") && + long.TryParse(productInfo.ProductParameters["Validity Period (In Days)"], out var days)) + { + // Round to nearest standard month value (30.44 days/month average) + // SSL Store only accepts specific values like 1, 3, 6, 12, 24, 36, etc. + var months = Math.Max(1, (long)Math.Round(days / 30.44)); + _logger.LogTrace($"Validity conversion: {days} days -> {months} months"); + return months; + } + + _logger.LogWarning("Validity Period (In Days) not found or invalid, defaulting to 12 months"); + return 12; + } + + private string ExtractOrgId(string organization) + { + if (organization != null) + { + Regex pattern = new Regex(@"(\([^0-9]*\d+[^0-9]*\))"); + Match match = pattern.Match(organization); + return match.Value.Replace("(", "").Replace(")", ""); + } + else + { + return null; + } + } + } +} diff --git a/SslStoreCaProxy/SslStoreCAPluginConfig.cs b/SslStoreCaProxy/SslStoreCAPluginConfig.cs new file mode 100644 index 0000000..2c375a9 --- /dev/null +++ b/SslStoreCaProxy/SslStoreCAPluginConfig.cs @@ -0,0 +1,373 @@ +using Keyfactor.AnyGateway.Extensions; +using System.Collections.Generic; + +namespace Keyfactor.AnyGateway.SslStore +{ + public class SslStoreCAPluginConfig + { + public const int DefaultPageSize = 100; + + public class ConfigConstants + { + public static string SSLStoreURL = "SSLStoreURL"; + public static string PartnerCode = "PartnerCode"; + public static string AuthToken = "AuthToken"; + public static string PageSize = "PageSize"; + public static string Enabled = "Enabled"; + public static string RenewalWindow = "RenewalWindow"; + } + + public class Config + { + public string SSLStoreURL { get; set; } + public string PartnerCode { get; set; } + public string AuthToken { get; set; } + public int PageSize { get; set; } = DefaultPageSize; + public bool Enabled { get; set; } + public int RenewalWindow { get; set; } = 30; + } + + public static Dictionary GetPluginAnnotations() + { + return new Dictionary() + { + [ConfigConstants.SSLStoreURL] = new PropertyConfigInfo() + { + Comments = "The Base URL for the SSL Store API endpoint (e.g. https://sandbox-wbapi.thesslstore.com).", + Hidden = false, + DefaultValue = "https://sandbox-wbapi.thesslstore.com", + Type = "String" + }, + [ConfigConstants.PartnerCode] = new PropertyConfigInfo() + { + Comments = "The Partner Code obtained from SSL Store.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + [ConfigConstants.AuthToken] = new PropertyConfigInfo() + { + Comments = "The Authentication Token obtained from SSL Store.", + Hidden = true, + DefaultValue = "", + Type = "Secret" + }, + [ConfigConstants.PageSize] = new PropertyConfigInfo() + { + Comments = "The number of records to return per page during synchronization.", + Hidden = false, + DefaultValue = DefaultPageSize, + Type = "Number" + }, + [ConfigConstants.Enabled] = new PropertyConfigInfo() + { + Comments = "Flag to Enable or Disable the CA connector.", + Hidden = false, + DefaultValue = true, + Type = "Bool" + }, + [ConfigConstants.RenewalWindow] = new PropertyConfigInfo() + { + Comments = "Number of days before order expiry to trigger a renewal instead of a reissue.", + Hidden = false, + DefaultValue = 30, + Type = "Number" + } + }; + } + + public static Dictionary GetTemplateParameterAnnotations() + { + return new Dictionary() + { + ["Approver Email"] = new PropertyConfigInfo() + { + Comments = "Comma-separated approver email address(es) for domain validation.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Validity Period (In Days)"] = new PropertyConfigInfo() + { + Comments = "Certificate validity period in days (e.g. 90, 365, 730).", + Hidden = false, + DefaultValue = "365", + Type = "String" + }, + ["Admin Contact - First Name"] = new PropertyConfigInfo() + { + Comments = "Administrative contact first name.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Admin Contact - Last Name"] = new PropertyConfigInfo() + { + Comments = "Administrative contact last name.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Admin Contact - Phone"] = new PropertyConfigInfo() + { + Comments = "Administrative contact phone number.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Admin Contact - Email"] = new PropertyConfigInfo() + { + Comments = "Administrative contact email address.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Admin Contact - Title"] = new PropertyConfigInfo() + { + Comments = "Administrative contact job title.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Admin Contact - Organization Name"] = new PropertyConfigInfo() + { + Comments = "Administrative contact organization name.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Admin Contact - Address"] = new PropertyConfigInfo() + { + Comments = "Administrative contact street address.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Admin Contact - City"] = new PropertyConfigInfo() + { + Comments = "Administrative contact city.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Admin Contact - Region"] = new PropertyConfigInfo() + { + Comments = "Administrative contact state/province/region.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Admin Contact - Postal Code"] = new PropertyConfigInfo() + { + Comments = "Administrative contact postal/zip code.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Admin Contact - Country"] = new PropertyConfigInfo() + { + Comments = "Administrative contact two-letter country code (e.g. US).", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Technical Contact - First Name"] = new PropertyConfigInfo() + { + Comments = "Technical contact first name.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Technical Contact - Last Name"] = new PropertyConfigInfo() + { + Comments = "Technical contact last name.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Technical Contact - Phone"] = new PropertyConfigInfo() + { + Comments = "Technical contact phone number.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Technical Contact - Email"] = new PropertyConfigInfo() + { + Comments = "Technical contact email address.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Technical Contact - Organization Name"] = new PropertyConfigInfo() + { + Comments = "Technical contact organization name.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Technical Contact - Address"] = new PropertyConfigInfo() + { + Comments = "Technical contact street address.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Technical Contact - City"] = new PropertyConfigInfo() + { + Comments = "Technical contact city.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Technical Contact - Region"] = new PropertyConfigInfo() + { + Comments = "Technical contact state/province/region.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Technical Contact - Postal Code"] = new PropertyConfigInfo() + { + Comments = "Technical contact postal/zip code.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Technical Contact - Country"] = new PropertyConfigInfo() + { + Comments = "Technical contact two-letter country code (e.g. US).", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Organization Name"] = new PropertyConfigInfo() + { + Comments = "Organization name for the certificate.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Organization Address"] = new PropertyConfigInfo() + { + Comments = "Organization street address.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Organization City"] = new PropertyConfigInfo() + { + Comments = "Organization city.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Organization Region"] = new PropertyConfigInfo() + { + Comments = "Organization state/province/region.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Organization State/Province"] = new PropertyConfigInfo() + { + Comments = "Organization state or province.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Organization Postal Code"] = new PropertyConfigInfo() + { + Comments = "Organization postal/zip code.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Organization Country"] = new PropertyConfigInfo() + { + Comments = "Organization two-letter country code (e.g. US).", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Organization Phone"] = new PropertyConfigInfo() + { + Comments = "Organization phone number.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Organization Jurisdiction Country"] = new PropertyConfigInfo() + { + Comments = "Jurisdiction country code for EV certificates.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Organization ID"] = new PropertyConfigInfo() + { + Comments = "DigiCert organization ID for EO (Enterprise Organization) products.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + ["Server Count"] = new PropertyConfigInfo() + { + Comments = "Number of server licenses for the certificate.", + Hidden = false, + DefaultValue = "1", + Type = "String" + }, + ["Web Server Type"] = new PropertyConfigInfo() + { + Comments = "Web server type (e.g. apacheopenssl, iis, tomcat, Other).", + Hidden = false, + DefaultValue = "Other", + Type = "String" + }, + ["Signature Hash Algorithm"] = new PropertyConfigInfo() + { + Comments = "Signature hash algorithm (PREFER_SHA2, REQUIRE_SHA2, PREFER_SHA1).", + Hidden = false, + DefaultValue = "PREFER_SHA2", + Type = "String" + }, + ["File Auth Domain Validation"] = new PropertyConfigInfo() + { + Comments = "Use file-based domain validation (True/False).", + Hidden = false, + DefaultValue = "False", + Type = "String" + }, + ["CName Auth Domain Validation"] = new PropertyConfigInfo() + { + Comments = "Use CNAME-based domain validation (True/False).", + Hidden = false, + DefaultValue = "False", + Type = "String" + }, + ["Is CU Order?"] = new PropertyConfigInfo() + { + Comments = "Is this a CU (Customer) order (True/False).", + Hidden = false, + DefaultValue = "False", + Type = "String" + }, + ["Is Renewal Order?"] = new PropertyConfigInfo() + { + Comments = "Is this a renewal order (True/False).", + Hidden = false, + DefaultValue = "False", + Type = "String" + }, + ["Is Trial Order?"] = new PropertyConfigInfo() + { + Comments = "Is this a trial order (True/False).", + Hidden = false, + DefaultValue = "False", + Type = "String" + } + }; + } + } +} diff --git a/SslStoreCaProxy/SslStoreCaProxy.cs b/SslStoreCaProxy/SslStoreCaProxy.cs new file mode 100644 index 0000000..04bd232 --- /dev/null +++ b/SslStoreCaProxy/SslStoreCaProxy.cs @@ -0,0 +1,521 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Keyfactor.AnyGateway.Extensions; +using Keyfactor.AnyGateway.SslStore.Client; +using Keyfactor.AnyGateway.SslStore.Client.Models; +using Keyfactor.AnyGateway.SslStore.Interfaces; +using Keyfactor.Logging; +using Keyfactor.PKI.Enums.EJBCA; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using System.Linq; +using Keyfactor.PKI.X509; + + +namespace Keyfactor.AnyGateway.SslStore +{ + public class SslStoreCaProxy : IAnyCAPlugin + { + private static readonly ILogger _logger = LogHandler.GetClassLogger(); + private RequestManager _requestManager; + private IAnyCAPluginConfigProvider Config { get; set; } + private ICertificateDataReader _certDataReader; + private SslStoreCAPluginConfig.Config _config; + + public string PartnerCode { get; set; } + public string AuthenticationToken { get; set; } + public int PageSize { get; set; } + public int RenewalWindow { get; set; } + + public void Initialize(IAnyCAPluginConfigProvider configProvider, ICertificateDataReader certificateDataReader) + { + _logger.MethodEntry(); + try + { + _certDataReader = certificateDataReader; + Config = configProvider; + var rawData = JsonConvert.SerializeObject(configProvider.CAConnectionData); + _config = JsonConvert.DeserializeObject(rawData); + + PartnerCode = _config.PartnerCode; + AuthenticationToken = _config.AuthToken; + PageSize = _config.PageSize > 0 ? _config.PageSize : SslStoreCAPluginConfig.DefaultPageSize; + RenewalWindow = _config.RenewalWindow > 0 ? _config.RenewalWindow : 30; + + _requestManager = new RequestManager(this); + + _logger.LogTrace($"Initialize - Enabled: {_config.Enabled}"); + } + catch (Exception ex) + { + _logger.LogError($"Failed to initialize SslStore CAPlugin: {ex}"); + throw; + } + } + + public async Task Ping() + { + _logger.MethodEntry(); + if (!_config.Enabled) + { + _logger.LogWarning("The CA is currently in the Disabled state. Skipping connectivity test..."); + _logger.MethodExit(); + return; + } + _logger.LogDebug("Pinging SslStore to validate connection"); + _logger.MethodExit(); + } + + public Task ValidateCAConnectionInfo(Dictionary connectionInfo) + { + _logger.MethodEntry(); + _logger.LogDebug("Validating SslStore CA Connection properties"); + var rawData = JsonConvert.SerializeObject(connectionInfo); + var config = JsonConvert.DeserializeObject(rawData); + + if (!config.Enabled) + { + _logger.LogWarning("The CA is currently in the Disabled state. Skipping config validation..."); + _logger.MethodExit(); + return Task.CompletedTask; + } + + List missingFields = new List(); + if (string.IsNullOrEmpty(config.SSLStoreURL)) missingFields.Add(nameof(config.SSLStoreURL)); + if (string.IsNullOrEmpty(config.PartnerCode)) missingFields.Add(nameof(config.PartnerCode)); + if (string.IsNullOrEmpty(config.AuthToken)) missingFields.Add(nameof(config.AuthToken)); + + if (missingFields.Count > 0) + { + throw new ArgumentException($"The following required fields are missing or empty: {string.Join(", ", missingFields)}"); + } + + _config = config; + _logger.MethodExit(); + return Ping(); + } + + public Task ValidateProductInfo(EnrollmentProductInfo productInfo, Dictionary connectionInfo) + { + _logger.MethodEntry(); + _logger.MethodExit(); + return Task.CompletedTask; + } + + public List GetProductIds() + { + return ProductDefinitions.GetProductIds(); + } + + public Dictionary GetCAConnectorAnnotations() + { + _logger.MethodEntry(); + _logger.MethodExit(); + return SslStoreCAPluginConfig.GetPluginAnnotations(); + } + + public Dictionary GetTemplateParameterAnnotations() + { + _logger.MethodEntry(); + _logger.MethodExit(); + return SslStoreCAPluginConfig.GetTemplateParameterAnnotations(); + } + + public async Task Revoke(string caRequestId, string hexSerialNumber, uint revocationReason) + { + _logger.MethodEntry(); + var sslStoreOrderId = ParseSslStoreOrderId(caRequestId); + RevokeOrderRequest revokeOrderRequest; + if (sslStoreOrderId != null) + { + revokeOrderRequest = _requestManager.GetRevokeOrderRequestBySslStoreId(sslStoreOrderId); + } + else + { + revokeOrderRequest = _requestManager.GetRevokeOrderRequest(caRequestId); + } + _logger.LogTrace($"Revoke Request JSON {JsonConvert.SerializeObject(revokeOrderRequest)}"); + try + { + var client = new SslStoreClient(Config); + var requestResponse = await client.SubmitRevokeCertificateAsync(revokeOrderRequest); + + _logger.LogTrace($"Revoke Response JSON {JsonConvert.SerializeObject(requestResponse)}"); + + if (requestResponse.AuthResponse.IsError) + { + _logger.LogError("Revoke Error Occurred"); + _logger.MethodExit(); + return (int)EndEntityStatus.FAILED; + } + + _logger.MethodExit(); + return (int)EndEntityStatus.REVOKED; + } + catch (Exception e) + { + _logger.LogError($"An Error has occurred during the revoke process {e.Message}"); + return (int)EndEntityStatus.FAILED; + } + } + + public async Task Enroll(string csr, string subject, Dictionary san, + EnrollmentProductInfo productInfo, RequestFormat requestFormat, EnrollmentType enrollmentType) + { + _logger.MethodEntry(); + var client = new SslStoreClient(Config); + + try + { + INewOrderResponse enrollmentResponse = null; + + if (enrollmentType == EnrollmentType.New) + { + _logger.LogTrace("Entering New Enrollment"); + + if (!productInfo.ProductParameters.ContainsKey("PriorCertSN")) + { + // Extract domain name from CSR subject and SANs from the Keyfactor san parameter + var domainName = subject?.Split(',') + .Select(p => p.Trim()) + .Where(p => p.StartsWith("CN=", StringComparison.OrdinalIgnoreCase)) + .Select(p => p.Substring(3)) + .FirstOrDefault() ?? ""; + _logger.LogTrace($"Domain Name from subject: {domainName}"); + + var dnsNames = san != null && san.ContainsKey("dns") ? san["dns"] : Array.Empty(); + _logger.LogTrace($"DNS Names from SAN: {string.Join(",", dnsNames)}"); + + string[] arrayApproverEmails = Array.Empty(); + if (productInfo.ProductParameters.ContainsKey("Approver Email")) + { + _logger.LogTrace($"Approver Email {productInfo.ProductParameters["Approver Email"]}"); + arrayApproverEmails = productInfo.ProductParameters["Approver Email"].Split(new char[] { ',' }); + } + + // Validate approver emails against all domains (CN + SANs) + var allDomains = new List(); + if (!string.IsNullOrEmpty(domainName)) allDomains.Add(domainName); + allDomains.AddRange(dnsNames.Where(d => !string.Equals(d, domainName, StringComparison.OrdinalIgnoreCase))); + + var count = 1; + foreach (var domain in allDomains) + { + var emailApproverRequest = _requestManager.GetEmailApproverListRequest(productInfo.ProductID, domain); + _logger.LogTrace($"Email Approver Request JSON {JsonConvert.SerializeObject(emailApproverRequest)}"); + + var emailApproverResponse = await client.SubmitEmailApproverRequestAsync(emailApproverRequest); + _logger.LogTrace($"Email Approver Response JSON {JsonConvert.SerializeObject(emailApproverResponse)}"); + + var emailValidation = ValidateEmails(emailApproverResponse, arrayApproverEmails, productInfo, count); + _logger.LogTrace($"Email Validation Result {emailValidation}"); + + if (emailValidation.Length > 0) + { + return new EnrollmentResult + { + Status = (int)EndEntityStatus.FAILED, + StatusMessage = emailValidation + }; + } + count++; + } + + var enrollmentRequest = _requestManager.GetEnrollmentRequest(csr, subject, san, productInfo, Config, false); + _logger.LogTrace($"enrollmentRequest JSON {JsonConvert.SerializeObject(enrollmentRequest)}"); + + enrollmentResponse = await client.SubmitNewOrderRequestAsync(enrollmentRequest); + _logger.LogTrace($"enrollmentResponse JSON {JsonConvert.SerializeObject(enrollmentResponse)}"); + } + else + { + return new EnrollmentResult + { + Status = (int)EndEntityStatus.FAILED, + StatusMessage = "You cannot renew an expired cert please perform a new enrollment." + }; + } + } + else if (enrollmentType == EnrollmentType.RenewOrReissue) + { + _logger.LogTrace("Entering Renew/Reissue Logic..."); + + var sn = productInfo.ProductParameters["PriorCertSN"]; + _logger.LogTrace($"Prior Cert Serial Number: {sn}"); + + var caRequestId = await _certDataReader.GetRequestIDBySerialNumber(sn); + _logger.LogTrace($"Prior CA Request ID: {caRequestId}"); + + var priorSslStoreOrderId = ParseSslStoreOrderId(caRequestId); + OrderStatusRequest orderStatusRequest; + if (priorSslStoreOrderId != null) + { + _logger.LogTrace($"Parsed TheSSLStoreOrderID: {priorSslStoreOrderId}"); + orderStatusRequest = _requestManager.GetOrderStatusRequestBySslStoreId(priorSslStoreOrderId); + } + else + { + _logger.LogTrace($"Legacy GUID format, querying by CustomOrderId: {caRequestId}"); + orderStatusRequest = _requestManager.GetOrderStatusRequest(caRequestId); + } + _logger.LogTrace($"orderStatusRequest JSON {JsonConvert.SerializeObject(orderStatusRequest)}"); + + var orderStatusResponse = await client.SubmitOrderStatusRequestAsync(orderStatusRequest); + _logger.LogTrace($"orderStatusResponse JSON {JsonConvert.SerializeObject(orderStatusResponse)}"); + + // Determine renewal vs reissue based on order expiry and RenewalWindow + var shouldRenew = false; + if (DateTime.TryParse(orderStatusResponse.OrderExpiryDateInUtc, out var orderExpiry)) + { + var daysUntilOrderExpiry = (orderExpiry - DateTime.UtcNow).TotalDays; + _logger.LogTrace($"Order expiry: {orderExpiry:u}, days remaining: {daysUntilOrderExpiry:F0}, renewal window: {RenewalWindow} days"); + shouldRenew = daysUntilOrderExpiry <= RenewalWindow; + } + else + { + _logger.LogWarning($"Could not parse OrderExpiryDateInUTC '{orderStatusResponse.OrderExpiryDateInUtc}', defaulting to renewal"); + shouldRenew = true; + } + + if (shouldRenew) + { + _logger.LogTrace("Order is within renewal window, performing renewal (new order)..."); + var renewRequest = _requestManager.GetRenewalRequest(orderStatusResponse, csr); + _logger.LogTrace($"renewRequest JSON {JsonConvert.SerializeObject(renewRequest)}"); + + enrollmentResponse = await client.SubmitRenewRequestAsync(renewRequest); + _logger.LogTrace($"enrollmentResponse JSON {JsonConvert.SerializeObject(enrollmentResponse)}"); + } + else + { + _logger.LogTrace("Order has life remaining, performing reissue (same order)..."); + var reIssueRequest = _requestManager.GetReIssueRequest(orderStatusResponse, csr, false); + _logger.LogTrace($"reIssueRequest JSON {JsonConvert.SerializeObject(reIssueRequest)}"); + + enrollmentResponse = await client.SubmitReIssueRequestAsync(reIssueRequest); + _logger.LogTrace($"reissue enrollmentResponse JSON {JsonConvert.SerializeObject(enrollmentResponse)}"); + } + } + + return GetEnrollmentResult(enrollmentResponse); + } + finally + { + _logger.MethodExit(); + } + } + + /// + /// Builds a composite CARequestID in the format "{TheSSLStoreOrderID}-{PartnerOrderID}". + /// This ensures uniqueness across reissues (same order, different PartnerOrderID). + /// + private static string BuildCompositeRequestId(string theSslStoreOrderId, string partnerOrderId) + { + return $"{theSslStoreOrderId}-{partnerOrderId}"; + } + + /// + /// Parses the TheSSLStoreOrderID from a CARequestID. Supports both composite format + /// ("{TheSSLStoreOrderID}-{PartnerOrderID}") and legacy GUID format (falls back to + /// treating the whole string as a CustomOrderId for backward compatibility). + /// + private static string ParseSslStoreOrderId(string caRequestId) + { + if (string.IsNullOrEmpty(caRequestId)) return caRequestId; + + var dashIndex = caRequestId.IndexOf('-'); + // Composite IDs have a numeric TheSSLStoreOrderID before the first dash. + // Legacy GUIDs have hex chars before the first dash so we check for digits only. + if (dashIndex > 0 && caRequestId.Substring(0, dashIndex).All(char.IsDigit)) + { + return caRequestId.Substring(0, dashIndex); + } + + // Legacy GUID format — return as-is for backward compatibility + return null; + } + + private EnrollmentResult GetEnrollmentResult(INewOrderResponse newOrderResponse) + { + if (newOrderResponse != null && newOrderResponse.AuthResponse.IsError) + { + _logger.MethodExit(); + return new EnrollmentResult + { + Status = (int)EndEntityStatus.FAILED, + StatusMessage = newOrderResponse.AuthResponse.Message[0] + }; + } + + var majorStatus = newOrderResponse?.OrderStatus?.MajorStatus; + var status = _requestManager.MapReturnStatus(majorStatus); + var compositeId = BuildCompositeRequestId(newOrderResponse?.TheSslStoreOrderId, newOrderResponse?.PartnerOrderId); + + _logger.LogTrace($"Order {compositeId} (SSLStoreOrderId: {newOrderResponse?.TheSslStoreOrderId}, PartnerOrderId: {newOrderResponse?.PartnerOrderId}) status: {majorStatus} -> mapped to {status}"); + _logger.MethodExit(); + + return new EnrollmentResult + { + CARequestID = compositeId, + Status = status, + StatusMessage = $"Order Successfully Created With Order Number {compositeId}" + }; + } + + public async Task GetSingleRecord(string caRequestId) + { + _logger.MethodEntry(); + + var client = new SslStoreClient(Config); + var sslStoreOrderId = ParseSslStoreOrderId(caRequestId); + + OrderStatusRequest orderStatusRequest; + if (sslStoreOrderId != null) + { + _logger.LogTrace($"Parsed TheSSLStoreOrderID: {sslStoreOrderId} from CARequestID: {caRequestId}"); + orderStatusRequest = _requestManager.GetOrderStatusRequestBySslStoreId(sslStoreOrderId); + } + else + { + _logger.LogTrace($"Legacy GUID format, querying by CustomOrderId: {caRequestId}"); + orderStatusRequest = _requestManager.GetOrderStatusRequest(caRequestId); + } + + var orderStatusResponse = await client.SubmitOrderStatusRequestAsync(orderStatusRequest); + _logger.LogTrace($"orderStatusResponse JSON {JsonConvert.SerializeObject(orderStatusResponse)}"); + + var certStatus = _requestManager.MapReturnStatus(orderStatusResponse?.OrderStatus.MajorStatus); + var certificate = string.Empty; + + if (certStatus == (int)EndEntityStatus.GENERATED) + { + var downloadCertificateRequest = _requestManager.GetCertificateRequestBySslStoreId(sslStoreOrderId ?? orderStatusResponse.TheSslStoreOrderId); + var certResponse = await client.SubmitDownloadCertificateAsync(downloadCertificateRequest); + if (!certResponse.AuthResponse.IsError) + { + var fullChain = string.Join("\n", certResponse.Certificates.Select(c => c.FileContent)); + var endEntityCert = X509Utilities.ExtractEndEntityCertificateContents(fullChain, null); + certificate = Convert.ToBase64String(endEntityCert.RawData); + } + } + + _logger.MethodExit(); + return new AnyCAPluginCertificate + { + CARequestID = caRequestId, + Certificate = certificate, + Status = certStatus + }; + } + + public async Task Synchronize(BlockingCollection blockingBuffer, + DateTime? lastSync, bool fullSync, CancellationToken cancelToken) + { + _logger.MethodEntry(); + + try + { + var client = new SslStoreClient(Config); + var certs = new BlockingCollection(100); + _ = client.SubmitQueryOrderRequestAsync(certs, cancelToken, _requestManager); + + foreach (var currentResponseItem in certs.GetConsumingEnumerable(cancelToken)) + { + if (cancelToken.IsCancellationRequested) + { + _logger.LogError("Synchronize was canceled."); + break; + } + + try + { + _logger.LogTrace($"Took Certificate ID {currentResponseItem?.TheSslStoreOrderId} (CustomOrderId: {currentResponseItem?.CustomOrderId}) from Queue"); + + // Use TheSslStoreOrderId for sync lookups since that's what the query returns + var orderStatusRequest = _requestManager.GetOrderStatusRequestBySslStoreId(currentResponseItem?.TheSslStoreOrderId); + var orderStatusResponse = await client.SubmitOrderStatusRequestAsync(orderStatusRequest); + + var theSslStoreOrderId = orderStatusResponse.TheSslStoreOrderId; + var partnerOrderId = orderStatusResponse.PartnerOrderId; + if (string.IsNullOrEmpty(theSslStoreOrderId) || string.IsNullOrEmpty(partnerOrderId)) + { + _logger.LogTrace($"Order {currentResponseItem?.TheSslStoreOrderId} missing required IDs, skipping"); + continue; + } + + var compositeId = BuildCompositeRequestId(theSslStoreOrderId, partnerOrderId); + var fileContent = ""; + var certStatus = _requestManager.MapReturnStatus(orderStatusResponse.OrderStatus.MajorStatus); + + if (certStatus == (int)EndEntityStatus.GENERATED) + { + var downloadCertificateRequest = _requestManager.GetCertificateRequestBySslStoreId(theSslStoreOrderId); + var certResponse = await client.SubmitDownloadCertificateAsync(downloadCertificateRequest); + if (!certResponse.AuthResponse.IsError) + { + var fullChain = string.Join("\n", certResponse.Certificates.Select(c => c.FileContent)); + var endEntityCert = X509Utilities.ExtractEndEntityCertificateContents(fullChain, null); + fileContent = Convert.ToBase64String(endEntityCert.RawData); + } + } + + if ((certStatus == (int)EndEntityStatus.GENERATED && fileContent.Length > 0) || + certStatus == (int)EndEntityStatus.REVOKED) + { + blockingBuffer.Add(new AnyCAPluginCertificate + { + CARequestID = compositeId, + Certificate = fileContent, + Status = certStatus, + ProductID = $"{orderStatusResponse.ProductCode}" + }, cancelToken); + } + } + catch (OperationCanceledException) + { + _logger.LogError("Synchronize was canceled."); + break; + } + } + } + catch (AggregateException) + { + _logger.LogError("SslStore Synchronize Task failed!"); + throw; + } + + _logger.MethodExit(); + } + + private string ValidateEmails(EmailApproverResponse validEmails, string[] arrayApproverEmails, EnrollmentProductInfo productInfo, int count) + { + if (arrayApproverEmails.Length > 1 && productInfo.ProductID.Contains("digi")) + { + return "There should only be one approval email for Digicert products."; + } + + if (count == 1 && productInfo.ProductID.Contains("digi") && arrayApproverEmails.Length > 0) + { + if (!validEmails.ApproverEmailList.Contains(arrayApproverEmails[0])) + { + return $"Digicert Approver Email must be one of the following {string.Join(",", validEmails.ApproverEmailList)}"; + } + } + + if (!productInfo.ProductID.Contains("digi")) + { + if (!validEmails.ApproverEmailList.Intersect(arrayApproverEmails).Any()) + { + return $"Sectigo Approver Email must be one of the following {string.Join(",", validEmails.ApproverEmailList)}"; + } + } + + return ""; + } + } +} diff --git a/SslStoreCaProxy/SslStoreCaProxy.csproj b/SslStoreCaProxy/SslStoreCaProxy.csproj new file mode 100644 index 0000000..303fdcd --- /dev/null +++ b/SslStoreCaProxy/SslStoreCaProxy.csproj @@ -0,0 +1,23 @@ + + + net6.0;net8.0;net10.0 + disable + true + false + Keyfactor.AnyGateway.SslStore + SslStoreCaProxy + + + + + + + + + + + + Always + + + diff --git a/SslStoreCaProxy/manifest.json b/SslStoreCaProxy/manifest.json new file mode 100644 index 0000000..047caab --- /dev/null +++ b/SslStoreCaProxy/manifest.json @@ -0,0 +1,10 @@ +{ + "extensions": { + "Keyfactor.AnyGateway.Extensions.IAnyCAPlugin": { + "SslStoreCaPlugin": { + "assemblypath": "SslStoreCaProxy.dll", + "TypeFullName": "Keyfactor.AnyGateway.SslStore.SslStoreCaProxy" + } + } + } +} diff --git a/docsource/configuration.md b/docsource/configuration.md new file mode 100644 index 0000000..d3592a4 --- /dev/null +++ b/docsource/configuration.md @@ -0,0 +1,338 @@ +## Overview + +The SSL Store AnyCA Gateway REST plugin extends the capabilities of the SSL Store Certificate Authority Service to Keyfactor Command via the Keyfactor AnyCA Gateway. SSL Store is a certificate reseller providing access to 80+ certificate products from vendors including DigiCert, Sectigo, RapidSSL, GeoTrust, and Comodo through a single REST API. The plugin represents a fully featured AnyCA Plugin with the following capabilities: + +* **CA Sync**: + * Download all certificates issued through SSL Store + * Full synchronization of all orders with paginated retrieval + * Automatic extraction of end-entity certificates from certificate chains + * Resilient retry logic (up to 5 retries) for large certificate inventories +* **Certificate Enrollment**: + * Support for new certificate enrollment with CSR + * Intelligent renewal vs. reissue logic based on configurable renewal window + * Support for DV, OV, and EV certificate products + * Multi-domain (MDC/SAN) and wildcard certificate support + * Automatic domain validation with approver email verification + * 80+ pre-configured certificate products across DigiCert and Sectigo families +* **Certificate Revocation**: + * Request revocation of previously issued certificates via SSL Store refund request API + +## Requirements + +### SSL Store System Prerequisites + +Before configuring the AnyCA Gateway plugin, ensure the following prerequisites are met: + +1. **SSL Store Account**: + - Active SSL Store partner account with API access enabled + - Access to the SSL Store web-based API (WBAPI) + - SSL Store account configured and operational + +2. **API Credentials**: + - SSL Store Partner Code + - SSL Store Authentication Token + - These credentials must have permissions for: + - Certificate enrollment (new order submission) + - Certificate download + - Certificate revocation (refund request) + - Order query and status retrieval + - Email approver list retrieval + +3. **Network Connectivity**: + - Gateway server must have HTTPS access to the SSL Store API endpoint + - Production endpoint: `https://wbapi.thesslstore.com` + - Sandbox endpoint: `https://sandbox-wbapi.thesslstore.com` + - TLS 1.2 or higher must be supported + +### Obtaining Required Configuration Information + +#### 1. SSL Store Base URL + +The SSL Store Base URL is the root endpoint for the SSL Store REST API. + +**Available environments:** +- Production: `https://wbapi.thesslstore.com` +- Sandbox/Testing: `https://sandbox-wbapi.thesslstore.com` + +**To obtain your Base URL:** +1. Log in to your SSL Store partner portal +2. Determine whether you are using the production or sandbox environment +3. Verify the URL is accessible from the Gateway server + +#### 2. API Authentication Credentials + +The Gateway authenticates to SSL Store using a Partner Code and Authentication Token. + +**Steps to obtain API credentials:** + +1. **Access SSL Store Partner Portal**: + - Log in to your SSL Store partner account + - Navigate to API settings + +2. **Obtain Credentials**: + - **Partner Code**: Your unique partner identifier assigned by SSL Store + - **Authentication Token**: A secret token for API authentication + - Store these credentials securely + +3. **Verify Permissions**: + - Ensure the API credentials have permissions for: + - Order creation (`/rest/order/neworder`) + - Order reissue (`/rest/order/reissue`) + - Order query (`/rest/order/query`) + - Order status (`/rest/order/status`) + - Certificate download (`/rest/order/download`) + - Revocation/refund (`/rest/order/refundrequest`) + - Email approver list (`/rest/order/approverlist`) + +#### 3. Supported Certificate Products + +The plugin supports 80+ certificate products from multiple vendors. Products are organized by validation type and vendor: + +**DigiCert Products:** + +| Product Code | Description | Validation | +|-------------|-------------|------------| +| `digi_securesite_flex` | DigiCert Secure Site | OV | +| `digi_securesite_flex-EO` | DigiCert Secure Site (Enterprise Org) | OV | +| `digi_securesite_ev_flex` | DigiCert Secure Site EV | EV | +| `digi_securesite_ev_flex-EO` | DigiCert Secure Site EV (Enterprise Org) | EV | +| `digi_securesite_pro_flex` | DigiCert Secure Site Pro | OV | +| `digi_securesite_pro_flex-EO` | DigiCert Secure Site Pro (Enterprise Org) | OV | +| `digi_securesite_pro_ev_flex` | DigiCert Secure Site Pro EV | EV | +| `digi_securesite_pro_ev_flex-EO` | DigiCert Secure Site Pro EV (Enterprise Org) | EV | +| `digi_sslwebserver_flex` | DigiCert SSL Web Server | OV | +| `digi_sslwebserver_flex-EO` | DigiCert SSL Web Server (Enterprise Org) | OV | +| `digi_sslwebserver_ev_flex` | DigiCert SSL Web Server EV | EV | +| `digi_sslwebserver_ev_flex-EO` | DigiCert SSL Web Server EV (Enterprise Org) | EV | +| `digi_truebizid_flex` | DigiCert TrueBizID | OV | +| `digi_truebizid_flex-EO` | DigiCert TrueBizID (Enterprise Org) | OV | +| `digi_truebizid_ev_flex` | DigiCert TrueBizID EV | EV | +| `digi_truebizid_ev_flex-EO` | DigiCert TrueBizID EV (Enterprise Org) | EV | +| `digi_ssl_basic` | DigiCert Basic SSL | OV | +| `digi_ssl_basic-EO` | DigiCert Basic SSL (Enterprise Org) | OV | +| `digi_ssl_ev_basic` | DigiCert Basic SSL EV | EV | +| `digi_ssl_ev_basic-EO` | DigiCert Basic SSL EV (Enterprise Org) | EV | +| `digi_rapidssl` | RapidSSL | DV | +| `digi_rapidssl_wc` | RapidSSL Wildcard | DV | +| `digi_ssl_dv_geotrust_flex` | GeoTrust DV SSL | DV | +| `digi_ssl123_flex` | GeoTrust SSL123 | DV | +| `digi_quickssl_md` | DigiCert QuickSSL Multi-Domain | DV | +| `digi_client_premium` | DigiCert Client Premium | Client | +| `digi_csc` | DigiCert Code Signing | Code Signing | +| `digi_csc_ev` | DigiCert EV Code Signing | EV Code Signing | +| `digi_doc_signing_ind_500` | DigiCert Document Signing Individual 500 | Document Signing | +| `digi_doc_signing_ind_2000` | DigiCert Document Signing Individual 2000 | Document Signing | +| `digi_doc_signing_org_2000` | DigiCert Document Signing Organization 2000 | Document Signing | +| `digi_doc_signing_org_5000` | DigiCert Document Signing Organization 5000 | Document Signing | + +**Sectigo/Comodo Products:** + +| Product Code | Description | Validation | +|-------------|-------------|------------| +| `positivessl` | Positive SSL | DV | +| `positivesslwildcard` | Positive SSL Wildcard | DV | +| `positivemdcssl` | Positive SSL Multi-Domain | DV | +| `positivemdcwildcard` | Positive SSL MDC Wildcard | DV | +| `positiveevssl` | Positive EV SSL | EV | +| `positiveevmdc` | Positive EV Multi-Domain | EV | +| `sectigossl` | Sectigo SSL | DV | +| `sectigowildcard` | Sectigo Wildcard | DV | +| `sectigoovssl` | Sectigo OV SSL | OV | +| `sectigoovwildcard` | Sectigo OV Wildcard | OV | +| `sectigoevssl` | Sectigo EV SSL | EV | +| `sectigodvucc` | Sectigo DV UCC | DV | +| `sectigouccwildcard` | Sectigo UCC Wildcard | DV | +| `sectigomdc` | Sectigo Multi-Domain | OV | +| `sectigomdcwildcard` | Sectigo MDC Wildcard | OV | +| `sectigoevmdc` | Sectigo EV Multi-Domain | EV | +| `comodopremiumssl` | Comodo Premium SSL | OV | +| `comodopremiumwildcard` | Comodo Premium Wildcard | OV | +| `comodossl` | Comodo SSL | OV | +| `comodoevssl` | Comodo EV SSL | EV | +| `comodomdc` | Comodo Multi-Domain | OV | +| `comodomdcwildcard` | Comodo MDC Wildcard | OV | +| `comodoevmdc` | Comodo EV Multi-Domain | EV | +| `comodoucc` | Comodo UCC | OV | +| `comodouccwildcard` | Comodo UCC Wildcard | OV | +| `comodowildcard` | Comodo Wildcard | OV | +| `comodocsc` | Comodo Code Signing | Code Signing | +| `comodoevcsc` | Comodo EV Code Signing | EV Code Signing | +| `comododvucc` | Comodo DV UCC | DV | +| `comodopciscan` | Comodo PCI Scan | Scanning | +| `instantssl` | InstantSSL | OV | +| `instantsslpro` | InstantSSL Pro | OV | +| `enterprisepro` | Enterprise Pro SSL | OV | +| `enterpriseprowc` | Enterprise Pro Wildcard | OV | +| `enterpriseproev` | Enterprise Pro EV | EV | +| `enterpriseproevmdc` | Enterprise Pro EV Multi-Domain | EV | +| `enterprisessl` | Enterprise SSL | OV | +| `essentialssl` | Essential SSL | DV | +| `essentialwildcard` | Essential Wildcard | DV | +| `elitessl` | Elite SSL | OV | + +**Note:** Products with the `-EO` suffix are Enterprise Organization variants that use a pre-configured DigiCert organization instead of requiring organization details during enrollment. These products require only a Validity Period and Organization ID. + +#### 4. Certificate Validity Configuration + +Certificate validity is specified in days during enrollment and automatically converted to months for the SSL Store API: + +| Days | Months | +|------|--------| +| 90 | 3 | +| 180 | 6 | +| 365 | 12 | +| 730 | 24 | +| 1095 | 36 | + +#### 5. Renewal vs. Reissue Logic + +The plugin uses a configurable **Renewal Window** (default: 30 days) to determine behavior during certificate renewal: + +- If the existing order is **within** the renewal window (i.e., expiring within N days), the plugin performs a **renewal** (new order linked to the original) +- If the existing order is **outside** the renewal window (still has significant life remaining), the plugin performs a **reissue** on the same order + +## Gateway Registration + +### CA Connection Configuration + +When registering the SSL Store CA in the AnyCA Gateway, you'll need to provide the following configuration parameters: + +| Parameter | Description | Required | Default | +|-----------|-------------|----------|---------| +| **SSLStoreURL** | Full URL to the SSL Store API endpoint | Yes | `https://sandbox-wbapi.thesslstore.com` | +| **PartnerCode** | Partner Code obtained from SSL Store | Yes | | +| **AuthToken** | Authentication Token obtained from SSL Store | Yes | | +| **PageSize** | Number of records per page during synchronization | No | `100` | +| **Enabled** | Flag to Enable or Disable the CA connector | No | `true` | +| **RenewalWindow** | Days before order expiry to trigger renewal vs. reissue | No | `30` | + +### Gateway Registration Notes + +- Each defined Certificate Authority in the AnyCA Gateway REST can support one SSL Store API endpoint +- If you have multiple SSL Store environments (production/sandbox), define separate Certificate Authorities for each +- Each CA configuration will manifest in Command as a separate CA entry +- The plugin uses REST API authentication with Partner Code and Authentication Token +- The plugin automatically handles: + - Product discovery (80+ products) + - Certificate status mapping (Active, Pending, Cancelled) + - End-entity certificate extraction from certificate chains + - Paginated order synchronization with retry logic + +### Security Considerations + +1. **Credential Storage**: The AuthToken field is configured as a secret/hidden field and should be stored securely +2. **Network Security**: Ensure TLS/SSL is properly configured for all API communications +3. **Least Privilege**: Request API credentials with minimal required permissions +4. **Audit Logging**: Enable comprehensive logging in both the Gateway and SSL Store for security monitoring +5. **Credential Rotation**: Regularly rotate API credentials according to your security policy +6. **Sandbox Testing**: Use the sandbox endpoint (`https://sandbox-wbapi.thesslstore.com`) for initial configuration and testing before switching to production + +### CA Connection Fields + +Populate using the configuration fields collected in the [requirements](#requirements) section. + +* **SSLStoreURL** - The base URL for the SSL Store API endpoint. Use `https://wbapi.thesslstore.com` for production or `https://sandbox-wbapi.thesslstore.com` for testing. +* **PartnerCode** - The Partner Code obtained from your SSL Store partner account. +* **AuthToken** - The Authentication Token obtained from your SSL Store partner account. +* **PageSize** - Number of records to retrieve per page during certificate synchronization. Default is 100. +* **Enabled** - Flag to enable or disable the CA connector. Set to `true` to enable. +* **RenewalWindow** - Number of days before an order's expiration date to trigger a renewal (new order) instead of a reissue (same order). Default is 30 days. + +## Certificate Template Creation Step + +### Template (Product) Configuration + +After adding the CA to the Gateway, certificate templates are automatically discovered from the plugin's built-in product registry. Each template may require different enrollment fields depending on the product type and validation level. + +**Enrollment fields vary by product type. The following categories exist:** + +#### DV Products (Minimal Fields) + +Products like `positivessl`, `sectigossl`, `sectigowildcard`: + +| Parameter | Description | Required | +|-----------|-------------|----------| +| **Admin Contact - Email** | Administrative contact email | Yes | +| **Approver Email** | Domain validation approver email | Yes | +| **Validity Period (In Days)** | Certificate validity in days | Yes | + +#### OV Products (Organization Fields) + +Products like `sectigoovssl`, `comodopremiumssl`, `instantssl`: + +| Parameter | Description | Required | +|-----------|-------------|----------| +| **Admin Contact - Email** | Administrative contact email | Yes | +| **Approver Email** | Domain validation approver email | Yes | +| **Validity Period (In Days)** | Certificate validity in days | Yes | +| **Organization Name** | Organization name | Yes | +| **Organization Address** | Organization street address | Yes | +| **Organization State/Province** | Organization state or province | Yes | +| **Organization Postal Code** | Organization postal/zip code | Yes | +| **Organization Country** | Two-letter country code (e.g. US) | Yes | +| **Organization Phone** | Organization phone number | Yes | + +#### DigiCert OV Flex Products + +Products like `digi_securesite_flex`, `digi_sslwebserver_flex`, `digi_truebizid_flex`: + +| Parameter | Description | Required | +|-----------|-------------|----------| +| **Admin Contact - First Name** | Administrative contact first name | Yes | +| **Admin Contact - Last Name** | Administrative contact last name | Yes | +| **Admin Contact - Phone** | Administrative contact phone | Yes | +| **Admin Contact - Email** | Administrative contact email | Yes | +| **Approver Email** | Domain validation approver email | Yes | +| **Validity Period (In Days)** | Certificate validity in days | Yes | +| **Organization Name** | Organization name | Yes | +| **Organization Address** | Organization street address | Yes | +| **Organization City** | Organization city | Yes | +| **Organization State/Province** | Organization state or province | Yes | +| **Organization Postal Code** | Organization postal/zip code | Yes | +| **Organization Country** | Two-letter country code | Yes | +| **Organization Phone** | Organization phone number | Yes | + +#### DigiCert EV Flex Products + +Products like `digi_securesite_ev_flex`, `digi_ssl_ev_basic`, `digi_truebizid_ev_flex`: + +Same as DigiCert OV Flex, plus: + +| Parameter | Description | Required | +|-----------|-------------|----------| +| **Admin Contact - Title** | Administrative contact job title | Yes | + +#### Enterprise Organization (-EO) Products + +Products like `digi_securesite_flex-EO`, `digi_sslwebserver_ev_flex-EO`: + +| Parameter | Description | Required | +|-----------|-------------|----------| +| **Validity Period (In Days)** | Certificate validity in days | Yes | +| **Organization ID** | DigiCert Organization ID | Yes | + +#### EV Products with Jurisdiction + +Products like `enterpriseproev`, `positiveevssl`, `positiveevmdc`: + +Same as OV Products, plus: + +| Parameter | Description | Required | +|-----------|-------------|----------| +| **Organization Jurisdiction Country** | Jurisdiction country code for EV validation | Yes | + +### Domain Validation - Approver Emails + +The plugin validates approver emails against SSL Store's approved list for each domain before enrollment: + +- **DigiCert products**: Exactly one approver email is required and must be from the approved list +- **Sectigo/Comodo products**: At least one approver email must be from the approved list +- Emails are validated per-domain for multi-domain certificates + +### Important Notes + +- Product IDs are automatically registered from the plugin's built-in product registry +- The `Validity Period (In Days)` is automatically converted to months for the SSL Store API +- For `-EO` (Enterprise Organization) products, the Organization ID dropdown is populated from your DigiCert account's active organizations +- DNS names (SANs) are extracted from the Keyfactor enrollment request; they do not need to be provided as a separate enrollment field +- The Common Name (CN) is extracted from the CSR subject diff --git a/integration-manifest.json b/integration-manifest.json new file mode 100644 index 0000000..86be3f5 --- /dev/null +++ b/integration-manifest.json @@ -0,0 +1,288 @@ +{ + "$schema": "https://keyfactor.github.io/v2/integration-manifest-schema.json", + "name": "SSL Store AnyCA REST plugin", + "release_dir": "SslStoreCaProxy/bin/Release", + "release_project": "SslStoreCaProxy/SslStoreCaProxy.csproj", + "description": "AnyCA Gateway REST plugin that extends SSL Store Certificate Authority Service to Keyfactor Command. SSL Store is a certificate reseller providing access to 80+ certificate products from vendors including DigiCert, Sectigo, RapidSSL, GeoTrust, and Comodo through a single API.", + "status": "production", + "integration_type": "anyca-plugin", + "support_level": "kf-supported", + "link_github": false, + "update_catalog": false, + "gateway_framework": "25.5", + "about": { + "carest": { + "ca_plugin_config": [ + { + "name": "SSLStoreURL", + "description": "The Base URL for the SSL Store API endpoint (e.g. https://sandbox-wbapi.thesslstore.com)." + }, + { + "name": "PartnerCode", + "description": "The Partner Code obtained from SSL Store." + }, + { + "name": "AuthToken", + "description": "The Authentication Token obtained from SSL Store." + }, + { + "name": "PageSize", + "description": "The number of records to return per page during synchronization." + }, + { + "name": "Enabled", + "description": "Flag to Enable or Disable the CA connector." + }, + { + "name": "RenewalWindow", + "description": "Number of days before order expiry to trigger a renewal instead of a reissue." + } + ], + "enrollment_config": [ + { + "name": "Approver Email", + "description": "Comma-separated approver email address(es) for domain validation." + }, + { + "name": "Validity Period (In Days)", + "description": "Certificate validity period in days (e.g. 90, 365, 730)." + }, + { + "name": "Admin Contact - First Name", + "description": "Administrative contact first name." + }, + { + "name": "Admin Contact - Last Name", + "description": "Administrative contact last name." + }, + { + "name": "Admin Contact - Phone", + "description": "Administrative contact phone number." + }, + { + "name": "Admin Contact - Email", + "description": "Administrative contact email address." + }, + { + "name": "Admin Contact - Title", + "description": "Administrative contact job title." + }, + { + "name": "Admin Contact - Organization Name", + "description": "Administrative contact organization name." + }, + { + "name": "Admin Contact - Address", + "description": "Administrative contact street address." + }, + { + "name": "Admin Contact - City", + "description": "Administrative contact city." + }, + { + "name": "Admin Contact - Region", + "description": "Administrative contact state/province/region." + }, + { + "name": "Admin Contact - Postal Code", + "description": "Administrative contact postal/zip code." + }, + { + "name": "Admin Contact - Country", + "description": "Administrative contact two-letter country code (e.g. US)." + }, + { + "name": "Technical Contact - First Name", + "description": "Technical contact first name." + }, + { + "name": "Technical Contact - Last Name", + "description": "Technical contact last name." + }, + { + "name": "Technical Contact - Phone", + "description": "Technical contact phone number." + }, + { + "name": "Technical Contact - Email", + "description": "Technical contact email address." + }, + { + "name": "Technical Contact - Organization Name", + "description": "Technical contact organization name." + }, + { + "name": "Technical Contact - Address", + "description": "Technical contact street address." + }, + { + "name": "Technical Contact - City", + "description": "Technical contact city." + }, + { + "name": "Technical Contact - Region", + "description": "Technical contact state/province/region." + }, + { + "name": "Technical Contact - Postal Code", + "description": "Technical contact postal/zip code." + }, + { + "name": "Technical Contact - Country", + "description": "Technical contact two-letter country code (e.g. US)." + }, + { + "name": "Organization Name", + "description": "Organization name for the certificate." + }, + { + "name": "Organization Address", + "description": "Organization street address." + }, + { + "name": "Organization City", + "description": "Organization city." + }, + { + "name": "Organization Region", + "description": "Organization state/province/region." + }, + { + "name": "Organization State/Province", + "description": "Organization state or province." + }, + { + "name": "Organization Postal Code", + "description": "Organization postal/zip code." + }, + { + "name": "Organization Country", + "description": "Organization two-letter country code (e.g. US)." + }, + { + "name": "Organization Phone", + "description": "Organization phone number." + }, + { + "name": "Organization Jurisdiction Country", + "description": "Jurisdiction country code for EV certificates." + }, + { + "name": "Organization ID", + "description": "DigiCert organization ID for EO (Enterprise Organization) products." + }, + { + "name": "Server Count", + "description": "Number of server licenses for the certificate." + }, + { + "name": "Web Server Type", + "description": "Web server type (e.g. apacheopenssl, iis, tomcat, Other)." + }, + { + "name": "Signature Hash Algorithm", + "description": "Signature hash algorithm (PREFER_SHA2, REQUIRE_SHA2, PREFER_SHA1)." + }, + { + "name": "File Auth Domain Validation", + "description": "Use file-based domain validation (True/False)." + }, + { + "name": "CName Auth Domain Validation", + "description": "Use CNAME-based domain validation (True/False)." + }, + { + "name": "Is CU Order?", + "description": "Is this a CU (Customer) order (True/False)." + }, + { + "name": "Is Renewal Order?", + "description": "Is this a renewal order (True/False)." + }, + { + "name": "Is Trial Order?", + "description": "Is this a trial order (True/False)." + } + ], + "product_ids": [ + "comododvucc", + "comodoevcsc", + "comodoevmdc", + "comodoevssl", + "comodomdc", + "comodomdcwildcard", + "comodopciscan", + "comodossl", + "comodoucc", + "comodouccwildcard", + "comodowildcard", + "digi_client_premium", + "digi_csc", + "digi_csc_ev", + "digi_doc_signing_ind_2000", + "digi_doc_signing_ind_500", + "digi_doc_signing_org_2000", + "digi_doc_signing_org_5000", + "elitessl", + "enterprisessl", + "essentialssl", + "essentialwildcard", + "hackerprooftm", + "hgpcicontrolscan", + "pacbasic", + "pacpro", + "pacenterprise", + "comodocsc", + "digi_quickssl_md", + "digi_securesite_ev_flex-EO", + "digi_securesite_flex-EO", + "digi_securesite_pro_ev_flex-EO", + "digi_securesite_pro_flex", + "digi_securesite_pro_flex-EO", + "digi_ssl_basic-EO", + "digi_ssl_ev_basic-EO", + "digi_sslwebserver_ev_flex-EO", + "digi_sslwebserver_flex-EO", + "digi_truebizid_ev_flex-EO", + "digi_truebizid_flex-EO", + "comodopremiumssl", + "comodopremiumwildcard", + "enterpriseprowc", + "instantssl", + "instantsslpro", + "sectigoovssl", + "sectigoovwildcard", + "digi_securesite_flex", + "digi_ssl_basic", + "digi_sslwebserver_flex", + "digi_truebizid_flex", + "positivessl", + "positivesslwildcard", + "sectigoevssl", + "sectigossl", + "sectigowildcard", + "digi_securesite_ev_flex", + "digi_ssl_ev_basic", + "digi_sslwebserver_ev_flex", + "digi_truebizid_ev_flex", + "digi_securesite_pro_ev_flex", + "positivemdcssl", + "positivemdcwildcard", + "sectigodvucc", + "sectigouccwildcard", + "digi_rapidssl", + "digi_rapidssl_wc", + "digi_ssl_dv_geotrust_flex", + "digi_ssl123_flex", + "enterpriseproev", + "positiveevssl", + "positiveevmdc", + "sectigoevmdc", + "enterpriseproevmdc", + "sectigomdc", + "sectigomdcwildcard", + "enterprisepro" + ] + } + } +} \ No newline at end of file diff --git a/readme_source.md b/readme_source.md new file mode 100644 index 0000000..ec93003 --- /dev/null +++ b/readme_source.md @@ -0,0 +1,10 @@ + +## Compatibility + +The SSL Store AnyCA Gateway REST plugin is compatible with the Keyfactor AnyCA Gateway REST 24.2 and later. + +## Support +The SSL Store 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. +