diff --git a/.github/workflows/keyfactor-bootstrap-workflow.yml b/.github/workflows/keyfactor-bootstrap-workflow.yml new file mode 100644 index 0000000..64919a4 --- /dev/null +++ b/.github/workflows/keyfactor-bootstrap-workflow.yml @@ -0,0 +1,20 @@ +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@v3 + secrets: + token: ${{ secrets.V2BUILDTOKEN}} + APPROVE_README_PUSH: ${{ secrets.APPROVE_README_PUSH}} + gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }} + gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }} + scan_token: ${{ secrets.SAST_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29..6163096 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +1.0.0 +Inital Release. Support for Enroll, Sync, and Revocation. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 16f2a3e..aaca6be 100644 --- a/README.md +++ b/README.md @@ -1,93 +1,113 @@ -# cpr-cagateway-template +

+ Entrust ECS Gateway AnyCA Gateway REST Plugin +

-## Template for new CA Gateway integrations +

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

-### Use this repository to create new integrations for new CA Gateway integration types. +

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

-1. [Use this repository](#using-the-repository) -1. [Update the integration-manifest.json](#updating-the-integration-manifest.json) -1. [Add Keyfactor Bootstrap Workflow (keyfactor-bootstrap-workflow.yml)](#add-bootstrap) -1. [Create required branches](#create-required-branches) -1. [Replace template files/folders](#replace-template-files-and-folders) -1. [Create initial prerelease](#create-initial-prerelease) ---- +The Entrust ECS AnyCA Gateway REST plugin extends the capabilities of Entrust Certificate Services to Keyfactor Command via the Keyfactor AnyCA Gateway REST. The plugin represents a fully featured AnyCA REST Plugin with the following capabilies: +* SSL Certificate Synchronization +* SSL Certificate Enrollment +* SSL Certificate Revocation -#### Using the repository -1. Select the ```Use this template``` button at the top of this page -1. Update the repository name following [these guidelines](https://keyfactorinc.sharepoint.com/sites/IntegrationWiki/SitePages/GitHub-Processes.aspx#repository-naming-conventions) - 1. All repositories must be in lower-case - 1. General pattern: company-product-type - 1. e.g. hashicorp-vault-orchestator -1. Click the ```Create repository``` button +## Compatibility ---- +The Entrust ECS Gateway AnyCA Gateway REST plugin is compatible with the Keyfactor AnyCA Gateway REST 24.2.0 and later. -#### Updating the integration-manifest.json +## Support +The Entrust ECS Gateway AnyCA Gateway REST plugin is supported by Keyfactor for Keyfactor customers. If you have a support issue, please open a support ticket with your Keyfactor representative. If you have a support issue, please open a support ticket via the Keyfactor Support Portal at https://support.keyfactor.com. -*The following properties must be updated in the integration-manifest.json* +> 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. -Clone the repository locally, use vsdev.io, or the GitHub online editor to update the file. +## Requirements -* "name": "Friendly name for the integration" - * This will be used in the readme file generation and catalog entries -* "description": "Brief description of the integration." - * This will be used in the readme file generation - * If the repository description is empty this value will be used for the repository description upon creating a release branch -* "release_dir": "PATH\\\TO\\\BINARY\\\RELEASE\\\OUTPUT\\\FOLDER" - * Path separators can be "\\\\" or "/" - * Be sure to specify the release folder name. This can be found by running a Release build and noting the output folder - * Example: "AzureAppGatewayOrchestrator\\bin\\Release" -* "gateway_framework": "" string denoting the required command gateway framework version ---- -#### Add Bootstrap -Add Keyfactor Bootstrap Workflow (keyfactor-bootstrap-workflow.yml). This can be copied directly from the workflow templates or through the Actions tab -* Directly: - 1. Create a file named ```.github\workflows\keyfactor-bootstrap-workflow.yml``` - 1. Copy the contents of [keyfactor/.github/workflow-templates/keyfactor-bootstrap-workflow.yml](https://raw.githubusercontent.com/Keyfactor/.github/main/workflow-templates/keyfactor-bootstrap-workflow.yml) into the file created in the previous step -* Actions tab: - 1. Navigate to the [Actions tab](./actions) in the new repository - 1. Click the ```New workflow``` button - 1. Find the ```Keyfactor Bootstrap Workflow``` and click the ```Configure``` button - 1. Click the ```Commit changes...``` button on this screen and the next to add the bootstrap workflow to the main branch - -A new build will run the tasks of a *Push* trigger on the main branch -*Ensure there are no errors during the workflow run in the Actions tab.* +## Installation ---- +1. Install the AnyCA Gateway REST per the [official Keyfactor documentation](https://software.keyfactor.com/Guides/AnyCAGatewayREST/Content/AnyCAGatewayREST/InstallIntroduction.htm). -#### Create required branches -1. Create a release branch from main: release-1.0 -1. Create a dev branch from the starting with the devops id in the format ab#\, e.g. ab#53535. - 1. For the cleanest pull request merge, create the dev branch from the release branch. - 1. Optionally, add a suffix to the branch name indicating initial release. e.g. ab#53535-initial-release +2. On the server hosting the AnyCA Gateway REST, download and unzip the latest [Entrust ECS Gateway AnyCA Gateway REST plugin](https://github.com/Keyfactor/entrust-ecs-caplugin/releases/latest) from GitHub. ---- +3. Copy the unzipped directory (usually called `net6.0`) to the Extensions directory: + ```shell + Program Files\Keyfactor\AnyCA Gateway\AnyGatewayREST\net6.0\Extensions + ``` -#### Replace template files and folders -1. Replace the contents of readme_source.md -1. Create a CHANGELOG.md file in the root of the repository indicating ```1.0: Initial release``` -1. Replace the SampleOrchestratorExtension.sln solution file and SampleOrchestratorExtension folder with your new orchestrator dotnet solution -1. Push your updates to the dev branch (ab#xxxxx) + > The directory containing the Entrust ECS Gateway AnyCA Gateway REST plugin DLLs (`net6.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 Entrust ECS Gateway plugin by hovering over the ⓘ symbol to the right of the Gateway on the top left of the portal. -#### Create initial prerelease -1. Create a pull request from the dev branch to the release-1.0 branch +## 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** -When the repository is ready for SE Demo, change the following property: -* "status": "pilot" + In order to enroll for certificates the Keyfactor Command server must trust the trust chain. Once you know your Root and/or Subordinate CA in your Entrust account, make sure to download and import the certificate chain into the Command Server certificate store -When the integration has been approved by Support and Delivery teams, change the following property: -* "status": "production" + * **CA Connection** -If the repository is ready to be published in the public catalog, the following properties must be updated: -* "update_catalog": true -* "link_github": true + Populate using the configuration fields collected in the [requirements](#requirements) section. + + * **AuthUsername** - Username for the gateway to authenticate with Entrust + * **AuthPassword** - Password for the account used to authenticate with Entrust + * **ClientCertificate** - The client certificate information used to authenticate with Entrust (if configured to use certificate authentication). This can be either a Windows cert store name and location (e.g. 'My' and 'LocalMachine' for the Local Computer personal cert store) and thumbprint, or a PFX file and password. + * **Name** - The default requester name + * **Email** - The default requester email address + * **PhoneNumber** - The default requester phone number + * **IgnoreExpired** - If set to true, will not sync expired certs from Entrust + * **Enabled** - Flag to Enable or Disable gateway functionality. Disabling is primarily used to allow creation of the CA prior to configuration information being available. + +2. TODO Certificate Template Creation Step is a required section + +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: + + * **LifetimeMonths** - OPTIONAL: The number of months of validity to use when requesting certs. If not provided, default is 12. + * **Organization** - OPTIONAL: For requests that will not have a subject (such as ACME) you can use this field to provide an organization name. Value supplied here will override any CSR values, so do not include this field if you want the organization from the CSR to be used. + * **CertificateUsage** - Required for public SSL certificate types. Represents the key usage for the certificates enrolled against this template. Valid values are 'server', 'client', or 'serverclient'. Do not provide a value for cert types that are not public SSL. + * **RenewalWindowDays** - OPTIONAL: The number of days from certificate expiration that the gateway should do a renewal rather than a reissue. If not provided, default is 90. + + + +## 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/cagateway-template/APIProxy/ProductNameBaseCall.cs b/cagateway-template/APIProxy/ProductNameBaseCall.cs deleted file mode 100644 index 1b92523..0000000 --- a/cagateway-template/APIProxy/ProductNameBaseCall.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Newtonsoft.Json; - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Keyfactor.Extensions.AnyGateway.Company.Product.APIProxy -{ - public abstract class ProductNameBaseRequest - { - [JsonIgnore] - public string Resource { get; internal set; } - - [JsonIgnore] - public string Method { get; internal set; } - - [JsonIgnore] - public string targetURI { get; set; } - - public string BuildParameters() - { - return ""; - } - } -} \ No newline at end of file diff --git a/cagateway-template/Client/ProductNameClient.cs b/cagateway-template/Client/ProductNameClient.cs deleted file mode 100644 index cbd16ab..0000000 --- a/cagateway-template/Client/ProductNameClient.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Keyfactor.Extensions.AnyGateway.Company.Product.Client -{ - public class ProductNameClient - { - } -} \ No newline at end of file diff --git a/cagateway-template/Constants.cs b/cagateway-template/Constants.cs deleted file mode 100644 index af6c50e..0000000 --- a/cagateway-template/Constants.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Keyfactor.Extensions.AnyGateway.Company.Product -{ - public class Constants - { - //Define any constants needed here (mostly field names for config parameters) - } -} \ No newline at end of file diff --git a/cagateway-template/GatewayNameCAConnector.cs b/cagateway-template/GatewayNameCAConnector.cs deleted file mode 100644 index abbe2ab..0000000 --- a/cagateway-template/GatewayNameCAConnector.cs +++ /dev/null @@ -1,193 +0,0 @@ -using CAProxy.AnyGateway; -using CAProxy.AnyGateway.Interfaces; -using CAProxy.AnyGateway.Models; -using CAProxy.AnyGateway.Models.Configuration; -using CAProxy.Common; - -using CSS.PKI; - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -using GatewayNameConstants = Keyfactor.Extensions.AnyGateway.Company.Product.Constants; - -namespace Keyfactor.Extensions.AnyGateway.Company.Product -{ - public class GatewayNameCAConnector : BaseCAConnector, ICAConnectorConfigInfoProvider - { - #region Fields and Constructors - - /// - /// Provides configuration information for the - /// - private ICAConnectorConfigProvider ConfigProvider { get; set; } - - //Define any additional private fields here - - #endregion Fields and Constructors - - #region ICAConnector Methods - - /// - /// Initialize the - /// - /// The config provider contains information required to connect to the CA. - public override void Initialize(ICAConnectorConfigProvider configProvider) - { - ConfigProvider = configProvider; - } - - /// - /// Enrolls for a certificate through the API. - /// - /// Reads certificate data from the database. - /// The certificate request CSR in PEM format. - /// The subject of the certificate request. - /// Any SANs added to the request. - /// Information about the CA product type. - /// The format of the request. - /// The type of the enrollment, i.e. new, renew, or reissue. - /// - public override EnrollmentResult Enroll(ICertificateDataReader certificateDataReader, string csr, string subject, Dictionary san, EnrollmentProductInfo productInfo, PKIConstants.X509.RequestFormat requestFormat, RequestUtilities.EnrollmentType enrollmentType) - { - throw new NotImplementedException(); - } - - /// - /// Returns a single certificate record by its serial number. - /// - /// The CA request ID for the certificate. - /// - public override CAConnectorCertificate GetSingleRecord(string caRequestID) - { - throw new NotImplementedException(); - } - - /// - /// Attempts to reach the CA over the network. - /// - public override void Ping() - { - throw new NotImplementedException(); - } - - /// - /// Revokes a certificate by its serial number. - /// - /// The CA request ID. - /// The hex-encoded serial number. - /// The revocation reason. - /// - public override int Revoke(string caRequestID, string hexSerialNumber, uint revocationReason) - { - throw new NotImplementedException(); - } - - /// - /// Synchronizes the gateway with the external CA - /// - /// Provides information about the gateway's certificate database. - /// Buffer into which certificates are places from the CA. - /// Information about the last CA sync. - /// The cancellation token. - public override void Synchronize(ICertificateDataReader certificateDataReader, BlockingCollection blockingBuffer, CertificateAuthoritySyncInfo certificateAuthoritySyncInfo, CancellationToken cancelToken) - { - throw new NotImplementedException(); - } - - /// - /// Validates that the CA connection info is correct. - /// - /// The information used to connect to the CA. - public override void ValidateCAConnectionInfo(Dictionary connectionInfo) - { - throw new NotImplementedException(); - } - - /// - /// Validates that the product information for the CA is correct - /// - /// The product information. - /// The CA connection information. - public override void ValidateProductInfo(EnrollmentProductInfo productInfo, Dictionary connectionInfo) - { - throw new NotImplementedException(); - } - - [Obsolete] - public override EnrollmentResult Enroll(string csr, string subject, Dictionary san, EnrollmentProductInfo productInfo, PKIConstants.X509.RequestFormat requestFormat, RequestUtilities.EnrollmentType enrollmentType) - { - throw new NotImplementedException(); - } - - [Obsolete] - public override void Synchronize(ICertificateDataReader certificateDataReader, BlockingCollection blockingBuffer, CertificateAuthoritySyncInfo certificateAuthoritySyncInfo, CancellationToken cancelToken, string logicalName) - { - throw new NotImplementedException(); - } - - #endregion ICAConnector Methods - - #region ICAConnectorConfigInfoProvider Methods - - /// - /// Returns the default CA connector section of the config file. - /// - /// - public Dictionary GetDefaultCAConnectorConfig() - { - return new Dictionary() - { - }; - } - - /// - /// Gets the default comment on the default product type. - /// - /// - public string GetProductIDComment() - { - return ""; - } - - /// - /// Gets annotations for the CA connector properties. - /// - /// - public Dictionary GetCAConnectorAnnotations() - { - return new Dictionary(); - } - - /// - /// Gets annotations for the template mapping parameters - /// - /// - public Dictionary GetTemplateParameterAnnotations() - { - throw new NotImplementedException(); - } - - /// - /// Gets default template map parameters for GlobalSign Atlas product types. - /// - /// - public Dictionary GetDefaultTemplateParametersConfig() - { - throw new NotImplementedException(); - } - - #endregion ICAConnectorConfigInfoProvider Methods - - #region Helper Methods - - // All private helper methods go here - - #endregion Helper Methods - } -} \ No newline at end of file diff --git a/cagateway-template/Properties/AssemblyInfo.cs b/cagateway-template/Properties/AssemblyInfo.cs deleted file mode 100644 index 8b68512..0000000 --- a/cagateway-template/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("cagateway-template")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("cagateway-template")] -[assembly: AssemblyCopyright("Copyright © 2022")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("9d2d6ed9-4626-430c-879d-0fe0febed146")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/cagateway-template/app.config b/cagateway-template/app.config deleted file mode 100644 index ad48466..0000000 --- a/cagateway-template/app.config +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/cagateway-template/cagateway-template.csproj b/cagateway-template/cagateway-template.csproj deleted file mode 100644 index a71a7f5..0000000 --- a/cagateway-template/cagateway-template.csproj +++ /dev/null @@ -1,93 +0,0 @@ - - - - - Debug - AnyCPU - {9D2D6ED9-4626-430C-879D-0FE0FEBED146} - Library - Properties - cagateway_template - cagateway-template - v4.7.2 - 512 - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\BouncyCastle.1.8.9\lib\BouncyCastle.Crypto.dll - - - ..\packages\Keyfactor.AnyGateway.SDK.21.3.2\lib\net462\CAProxy.AnyGateway.Core.dll - - - ..\packages\Keyfactor.AnyGateway.SDK.21.3.2\lib\net462\CAProxy.Interfaces.dll - - - ..\packages\Keyfactor.AnyGateway.SDK.21.3.2\lib\net462\CAProxyDAL.dll - - - ..\packages\Common.Logging.3.4.1\lib\net40\Common.Logging.dll - - - ..\packages\Common.Logging.Core.3.4.1\lib\net40\Common.Logging.Core.dll - - - ..\packages\Keyfactor.AnyGateway.SDK.21.3.2\lib\net462\CommonCAProxy.dll - - - ..\packages\CSS.Common.1.7.0\lib\net462\CSS.Common.dll - - - ..\packages\CSS.PKI.2.13.0\lib\net462\CSS.PKI.dll - - - ..\packages\Keyfactor.Logging.1.1.0\lib\netstandard2.0\Keyfactor.Logging.dll - - - ..\packages\Microsoft.Extensions.Logging.Abstractions.5.0.0\lib\net461\Microsoft.Extensions.Logging.Abstractions.dll - - - ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/cagateway-template/packages.config b/cagateway-template/packages.config deleted file mode 100644 index 5fd12f1..0000000 --- a/cagateway-template/packages.config +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docsource/configuration.md b/docsource/configuration.md new file mode 100644 index 0000000..7f99e1c --- /dev/null +++ b/docsource/configuration.md @@ -0,0 +1,17 @@ +## Overview + +The Entrust ECS AnyCA Gateway REST plugin extends the capabilities of Entrust Certificate Services to Keyfactor Command via the Keyfactor AnyCA Gateway REST. The plugin represents a fully featured AnyCA REST Plugin with the following capabilies: +* SSL Certificate Synchronization +* SSL Certificate Enrollment +* SSL Certificate Revocation + +## Requirements + +## Gateway Registration + +In order to enroll for certificates the Keyfactor Command server must trust the trust chain. Once you know your Root and/or Subordinate CA in your Entrust account, make sure to download and import the certificate chain into the Command Server certificate store + +## Certificate Template Creation Step + +TODO Certificate Template Creation Step is a required section + diff --git a/cagateway-template.sln b/entrust-ecs-caplugin.sln similarity index 63% rename from cagateway-template.sln rename to entrust-ecs-caplugin.sln index b953ecb..588be93 100644 --- a/cagateway-template.sln +++ b/entrust-ecs-caplugin.sln @@ -1,27 +1,27 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31729.503 +# Visual Studio Version 17 +VisualStudioVersion = 17.10.35004.147 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "cagateway-template", "cagateway-template\cagateway-template.csproj", "{9D2D6ED9-4626-430C-879D-0FE0FEBED146}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{431498A1-F30A-4307-9FBF-B1D634326444}" ProjectSection(SolutionItems) = preProject CHANGELOG.md = CHANGELOG.md + docsource\configuration.md = docsource\configuration.md integration-manifest.json = integration-manifest.json - readme_source.md = readme_source.md EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "entrust-ecs-caplugin", "entrust-ecs-caplugin\entrust-ecs-caplugin.csproj", "{900B0455-FAC3-4743-B742-23447420677A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {9D2D6ED9-4626-430C-879D-0FE0FEBED146}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9D2D6ED9-4626-430C-879D-0FE0FEBED146}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9D2D6ED9-4626-430C-879D-0FE0FEBED146}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9D2D6ED9-4626-430C-879D-0FE0FEBED146}.Release|Any CPU.Build.0 = Release|Any CPU + {900B0455-FAC3-4743-B742-23447420677A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {900B0455-FAC3-4743-B742-23447420677A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {900B0455-FAC3-4743-B742-23447420677A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {900B0455-FAC3-4743-B742-23447420677A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/entrust-ecs-caplugin/API/Certificate.cs b/entrust-ecs-caplugin/API/Certificate.cs new file mode 100644 index 0000000..cff7b77 --- /dev/null +++ b/entrust-ecs-caplugin/API/Certificate.cs @@ -0,0 +1,299 @@ +// Copyright 2024 Keyfactor +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using Keyfactor.Extensions.CAPlugin.Entrust.Models; + +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Keyfactor.Extensions.CAPlugin.Entrust.API +{ + public class PatchCertificateRequest : ECSBaseRequest + { + public PatchCertificateRequest(int trackingId) + { + this.Resource = $"certificates/{trackingId.ToString()}"; + this.Method = "PATCH"; + } + } + + public class PatchCertificateRequestBody + { + [JsonProperty("operation")] + public string Operation { get; set; } + + [JsonProperty("declineReason")] + public string DeclineReason { get; set; } + } + + public class GetCertificatesRequest : ECSBaseRequest + { + private int limit, offset; + private Dictionary queryParams; + + public GetCertificatesRequest(int limit, int offset) : this(limit, offset, new Dictionary()) + { + } + + public GetCertificatesRequest(int limit, int offset, Dictionary queryParams) + { + this.limit = limit; + this.offset = offset; + this.Resource = "certificates"; + this.Method = "GET"; + this.queryParams = queryParams; + } + + public new string BuildParameters() + { + StringBuilder sbParamters = new StringBuilder(); + sbParamters.Append("limit=").Append(this.limit.ToString()); + sbParamters.Append("&offset=").Append(this.offset.ToString()); + + foreach (KeyValuePair k in queryParams) + { + sbParamters.Append("&" + k.Key + "=").Append(k.Value); + } + return sbParamters.ToString(); + } + } + + public class GetCertificateByThumbprintRequest : ECSBaseRequest + { + public GetCertificateByThumbprintRequest(string thumbprint) + { + this.Resource = "certificates/thumbprints/" + thumbprint; + this.Method = "GET"; + } + } + + public class GetCertificateByTrackingIdRequest : ECSBaseRequest + { + public GetCertificateByTrackingIdRequest(int trackingId) + { + this.Resource = "certificates/" + trackingId; + this.Method = "GET"; + } + } + + public class GetCertificatesResponse + { + [JsonProperty("summary")] + public Summary summary { get; set; } + + [JsonProperty("certificates")] + public List certificates { get; set; } + } + + public class Certificate + { + /// + /// Gets or Sets Status + /// + [JsonProperty("status")] + public string Status { get; set; } + + /// + /// Gets or Sets TrackingId + /// + [JsonProperty("trackingId")] + public int TrackingId { get; set; } + + /// + /// The URI of the certificate. + /// + [JsonProperty("uri")] + public string URI { get; set; } + + /// + /// Distinguished name + /// + /// Distinguished name + [JsonProperty("dn")] + public string Dn { get; set; } + + /// + /// Serial number in hexadecimal format. + /// + /// Serial number in hexadecimal format. + [JsonProperty("serialNumber")] + public string SerialNumber { get; set; } + + /// + /// The date and time, in RFC3339 format, for when the certificate was issued + /// + /// The date and time, in RFC3339 format, for when the certificate was issued + [JsonProperty("issueDateTime")] + public DateTime? IssueDateTime { get; set; } + + /// + /// The date and time, in RFC3339 format, after which the certificate is no longer valid + /// + /// The date and time, in RFC3339 format, after which the certificate is no longer valid + [JsonProperty("expiresAfter")] + public DateTime? ExpiresAfter { get; set; } + + /// + /// Signing algorithm + /// + /// Signing algorithm + [JsonProperty("signingAlg")] + public string SigningAlg { get; set; } + + /// + /// Extended Key Usage - applicable to all public SSL certificate types + /// + /// Extended Key Usage - applicable to all public SSL certificate types + [JsonProperty("eku")] + public string Eku { get; set; } + + /// + /// Key size + /// + /// Key size + [JsonProperty("keySize")] + public int? KeySize { get; set; } + + /// + /// Organization in DN + /// + /// Organization in DN + [JsonProperty("org")] + public string Org { get; set; } + + /// + /// Organizational unit. + /// + /// Organizational unit. + [JsonProperty("ou")] + public List Ou { get; set; } + + /// + /// Certificate type, for example: * STANDARD_SSL * ADVANTAGE_SSL * UC_SSL * EV_SSL * WILDCARD_SSL * PRIVATE_SSL * SMIME_ENT + /// + /// Certificate type, for example: * STANDARD_SSL * ADVANTAGE_SSL * UC_SSL * EV_SSL * WILDCARD_SSL * PRIVATE_SSL * SMIME_ENT + [JsonProperty("certType")] + public string CertType { get; set; } + + /// + /// Domain used + /// + /// Domain used + [JsonProperty("domainUsed")] + public string DomainUsed { get; set; } + + /// + /// Whether this is a third-party certificate + /// + /// Whether this is a third-party certificate + [JsonProperty("isThirdParty")] + public bool? IsThirdParty { get; set; } + + } + + public class Summary + { + /// + /// The date and time instance, in RFC3339 format, for when we received the request. + /// + /// The date and time instance, in RFC3339 format, for when we received the request. + [JsonProperty("timestamp")] + public DateTime? Timestamp { get; set; } + + /// + /// Elapsed time it took to process the request, in milliseconds + /// + /// Elapsed time it took to process the request, in milliseconds + [JsonProperty("elapsed")] + public int? Elapsed { get; set; } + + /// + /// Offset of the result set + /// + /// Offset of the result set + [JsonProperty("offset")] + public int? Offset { get; set; } + + /// + /// Maximum number of items to return + /// + /// Maximum number of items to return + [JsonProperty("limit")] + public int? Limit { get; set; } + + /// + /// Count of total number of items + /// + /// Count of total number of items + [JsonProperty("total")] + public int? Total { get; set; } + + /// + /// Ordering of the results + /// + /// Ordering of the results + [JsonProperty("sort")] + public string Sort { get; set; } + } + + public class SubjectAltName + { + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("value")] + public string Value { get; set; } + } + + + public class CertificateExt : Certificate + { + + public CertificateExt(Certificate c) + { + Status = c.Status; + TrackingId = c.TrackingId; + Dn = c.Dn; + SerialNumber = c.SerialNumber; + IssueDateTime = c.IssueDateTime; + ExpiresAfter = c.ExpiresAfter; + SigningAlg = c.SigningAlg; + Eku = c.Eku; + KeySize = c.KeySize; + Org = c.Org; + Ou = c.Ou; + CertType = c.CertType; + DomainUsed = c.DomainUsed; + IsThirdParty = c.IsThirdParty; + } + + public CertificateExt() { } + + [JsonProperty("subjectAltName")] + public List SubjectAltName { get; set; } + + [JsonProperty("tracking")] + public Tracking Tracking { get; set; } + + [JsonProperty("endEntityCert")] + public string EndEntityCert { get; set; } + + [JsonProperty("csr")] + public string Csr { get; set; } + + [JsonProperty("chainCerts")] + public string[] ChainCerts { get; set; } + + [JsonProperty("creatorName")] + public string CreatorName { get; set; } + + [JsonIgnore] + public string CertificateTemplate { get; set; } + } +} diff --git a/entrust-ecs-caplugin/API/CertificateResponse.cs b/entrust-ecs-caplugin/API/CertificateResponse.cs new file mode 100644 index 0000000..c40db5e --- /dev/null +++ b/entrust-ecs-caplugin/API/CertificateResponse.cs @@ -0,0 +1,65 @@ +// Copyright 2024 Keyfactor +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Keyfactor.Extensions.CAPlugin.Entrust.API +{ + public class CertificateResponse + { + + /// + /// Gets or Sets TrackingId + /// + [JsonProperty("trackingId")] + public int TrackingId { get; set; } + + /// + /// PEM-encoded certificate + /// + /// PEM-encoded certificate + [JsonProperty("endEntityCert")] + public string EndEntityCert { get; set; } + + /// + /// Gets or Sets ChainCerts + /// + [JsonProperty("chainCerts")] + public List ChainCerts { get; set; } + + /// + /// Serial number in hexadecimal format + /// + /// Serial number in hexadecimal format + [JsonProperty("serialNumber")] + public string SerialNumber { get; set; } + + /// + /// The date and time, in RFC3339 format, after which the certificate is no longer valid. + /// + /// The date and time, in RFC3339 format, after which the certificate is no longer valid. + [JsonProperty("expiresAfter")] + public DateTime? ExpiresAfter { get; set; } + + /// + /// Gets or Sets PickupUrl + /// + [JsonProperty("pickupUrl")] + public string PickupUrl { get; set; } + + /// + /// S/MIME certificate and private key in PKCS12 format protected by the provided password. Only returned for SMIME_ENT certtype and only if no CSR is supplied. + /// + /// S/MIME certificate and private key in PKCS12 format protected by the provided password. Only returned for SMIME_ENT certtype and only if no CSR is supplied. + [JsonProperty("pkcs12")] + public string Pkcs12 { get; set; } + + + } +} diff --git a/entrust-ecs-caplugin/API/Client.cs b/entrust-ecs-caplugin/API/Client.cs new file mode 100644 index 0000000..ba53a84 --- /dev/null +++ b/entrust-ecs-caplugin/API/Client.cs @@ -0,0 +1,77 @@ +// Copyright 2024 Keyfactor +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using Newtonsoft.Json; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.CAPlugin.Entrust.API +{ + public class GetClientsRequest : ECSBaseRequest + { + public GetClientsRequest() + { + this.Resource = "clients"; + this.Method = "GET"; + } + } + + public class GetClientsResponse + { + [JsonProperty("clients")] + public List Clients { get; set; } + } + + public class ClientInfo + { + /// + /// Gets or Sets VerificationStatus + /// + [JsonProperty("evVerificationStatus")] + public string EVVerificationStatus { get; set; } + + /// + /// Gets or Sets VerificationStatus + /// + [JsonProperty("verificationStatus")] + public string VerificationStatus { get; set; } + + /// + /// Client ID of client. For the primary client, this is 1. + /// + /// Client ID of client. For the primary client, this is 1. + [JsonProperty("clientId")] + public int ClientId { get; set; } + + /// + /// The company name of the client + /// + /// The company name of the client + [JsonProperty("clientName")] + public string ClientName { get; set; } + + /// + /// Gets or Sets FriendlyClientName + /// + [JsonProperty("friendlyClientName")] + public string FriendlyClientName { get; set; } + + /// + /// OV information expiry date - - only present if client has been APPROVED + /// + /// OV information expiry date - - only present if client has been APPROVED + [JsonProperty("ovExpiryDate")] + public DateTime? OvExpiryDate { get; set; } + + [JsonProperty("evExpiryDate")] + public DateTime? EvExpiryDate { get; set; } + } +} diff --git a/entrust-ecs-caplugin/API/ECSAPIBase.cs b/entrust-ecs-caplugin/API/ECSAPIBase.cs new file mode 100644 index 0000000..ed7de3c --- /dev/null +++ b/entrust-ecs-caplugin/API/ECSAPIBase.cs @@ -0,0 +1,62 @@ +// Copyright 2024 Keyfactor +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Keyfactor.Extensions.CAPlugin.Entrust.API +{ + public abstract class ECSBaseRequest + { + [JsonIgnore] + public string Resource { get; internal set; } + + [JsonIgnore] + public string Method { get; internal set; } + + [JsonIgnore] + public string TargetURI { get; set; } + + public string BuildParameters() + { + return ""; + } + } + + public abstract class ECSBaseResponse + { + public enum StatusType + { + SUCCESS, + ERROR, + WARNING + } + + public enum ContentTypes + { + XML, + JSON, + TEXT + } + + [JsonIgnore] + public ContentTypes ContentType { get; internal set; } + + [JsonIgnore] + public StatusType Status { get; set; } + + [JsonIgnore] + public List Errors { get; set; } + + public ECSBaseResponse() + { + Errors = new List(); + Status = StatusType.SUCCESS; + ContentType = ContentTypes.JSON; + } + } +} diff --git a/entrust-ecs-caplugin/API/Error.cs b/entrust-ecs-caplugin/API/Error.cs new file mode 100644 index 0000000..127f41f --- /dev/null +++ b/entrust-ecs-caplugin/API/Error.cs @@ -0,0 +1,32 @@ +// Copyright 2024 Keyfactor +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using Newtonsoft.Json; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.CAPlugin.Entrust.API +{ + public class Error + { + [JsonProperty("message")] + public string Message { get; set; } + } + + public class ErrorResponse + { + [JsonProperty("errors")] + public List Errors { get; set; } + + [JsonProperty("status")] + public int Status { get; set; } + } +} diff --git a/entrust-ecs-caplugin/API/Inventory.cs b/entrust-ecs-caplugin/API/Inventory.cs new file mode 100644 index 0000000..c81151c --- /dev/null +++ b/entrust-ecs-caplugin/API/Inventory.cs @@ -0,0 +1,63 @@ +// Copyright 2024 Keyfactor +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using Newtonsoft.Json; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.CAPlugin.Entrust.API +{ + public class GetInventoryRequest : ECSBaseRequest + { + public GetInventoryRequest() + { + this.Resource = "inventories"; + this.Method = "GET"; + } + } + + public class InventoryItem + { + + /// + /// Gets or Sets ProductType + /// + [JsonProperty("productType")] + public string ProductType { get; set; } + + /// + /// Total inventory for this product type ever added to the account + /// + /// Total inventory for this product type ever added to the account + [JsonProperty("totalCount")] + public int? TotalCount { get; set; } + + /// + /// Inventory for this product type that has not been used, and has not expired + /// + /// Inventory for this product type that has not been used, and has not expired + [JsonProperty("remainingCount")] + public int? RemainingCount { get; set; } + + /// + /// Count of consumed inventory for this product type. This count does not include expired inventory + /// + /// Count of consumed inventory for this product type. This count does not include expired inventory + [JsonProperty("usedCount")] + public int? UsedCount { get; set; } + } + + public class GetInventoryResponse + { + [JsonProperty("inventories")] + public List Inventories { get; set; } + } +} diff --git a/entrust-ecs-caplugin/API/NewCertificateRequest.cs b/entrust-ecs-caplugin/API/NewCertificateRequest.cs new file mode 100644 index 0000000..72417ca --- /dev/null +++ b/entrust-ecs-caplugin/API/NewCertificateRequest.cs @@ -0,0 +1,43 @@ +// Copyright 2024 Keyfactor +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using Newtonsoft.Json; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.CAPlugin.Entrust.API +{ + public class NewCertificateCall : ECSBaseRequest + { + public NewCertificateCall() + { + Resource = "enterprise/v2/certificates"; + Method = "POST"; + } + } + public partial class NewCertificateRequest : RenewCertificateRequestBody + { + /// + /// Certificate type to request Supported types are: * STANDARD_SSL * ADVANTAGE_SSL * UC_SSL * EV_SSL * QWAC_SSL * QWACPSD2_SSL * WILDCARD_SSL * PRIVATE_SSL * PD_SSL * CODE_SIGNING * EV_CODE_SIGNING * CDS_INDIVIDUAL * CDS_GROUP * CDS_ENT_LITE * CDS_ENT_PRO * SMIME_ENT + /// + /// Certificate type to request Supported types are: * STANDARD_SSL * ADVANTAGE_SSL * UC_SSL * EV_SSL * QWAC_SSL * QWACPSD2_SSL * WILDCARD_SSL * PRIVATE_SSL * PD_SSL * CODE_SIGNING * EV_CODE_SIGNING * CDS_INDIVIDUAL * CDS_GROUP * CDS_ENT_LITE * CDS_ENT_PRO * SMIME_ENT + [JsonProperty("certType")] + public string CertType { get; set; } + + /// + /// For all SSL and Document Signing certificate types&#58; * If set to true, certificate request is queued for approval by an administrator. * If set to false (default), the certificate is generated immediately. + /// + /// For all SSL and Document Signing certificate types&#58; * If set to true, certificate request is queued for approval by an administrator. * If set to false (default), the certificate is generated immediately. + [JsonProperty("queueForApproval")] + public bool? QueueForApproval { get; set; } + + } +} diff --git a/entrust-ecs-caplugin/API/Organization.cs b/entrust-ecs-caplugin/API/Organization.cs new file mode 100644 index 0000000..5ddae79 --- /dev/null +++ b/entrust-ecs-caplugin/API/Organization.cs @@ -0,0 +1,61 @@ +// Copyright 2024 Keyfactor +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using Newtonsoft.Json; + +using Org.BouncyCastle.Asn1.Cmp; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.CAPlugin.Entrust.API +{ + public class GetOrganizationsRequest : ECSBaseRequest + { + /// + /// Default constructor. + /// + public GetOrganizationsRequest() + { + Resource = "organizations"; + Method = "GET"; + } + } + + public class GetOrganizationsResponse : ECSBaseResponse + { + /// + /// The collection of organizations returned by Entrust. + /// + [JsonProperty("organizations")] + public List Organizations { get; set; } + } + + public class Organization + { + /// + /// The organization's name. + /// + [JsonProperty("name")] + public string Name { get; set; } + + /// + /// The status of the organization. + /// + [JsonProperty("verificationStatus")] + public string VerificationStatus { get; set; } + + /// + /// The ID of the client associated with this organization. + /// + [JsonProperty("clientId")] + public int ClientId { get; set; } + } +} diff --git a/entrust-ecs-caplugin/API/ReissueCertificateRequest.cs b/entrust-ecs-caplugin/API/ReissueCertificateRequest.cs new file mode 100644 index 0000000..176a4c9 --- /dev/null +++ b/entrust-ecs-caplugin/API/ReissueCertificateRequest.cs @@ -0,0 +1,135 @@ +// Copyright 2024 Keyfactor +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using Keyfactor.Extensions.CAPlugin.Entrust.Models; + +using Newtonsoft.Json; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.CAPlugin.Entrust.API +{ + public class ReissueCertificateRequest : ECSBaseRequest + { + + public ReissueCertificateRequest(int trackingId) + { + this.Resource = $"certificates/{trackingId.ToString()}/reissues"; + this.Method = "POST"; + + } + } + public class ReissueCertificateRequestBody + { + + /// + /// Signing algorithm of certificate (SHA-1 or SHA-2). The account default is used if not specified. - This parameter is only applicable when the account preference is set to \"Select signing algorithm at certificate generation time.\" - As of January 1, 2016 any certificates except Private SSL (PRIVATE_SSL) certificates being issued, reissued, or renewed will use SHA2, even if the SHA1 algorithm is specified in the request. Private SSL certificates can continue to use SHA-1. + /// + /// Signing algorithm of certificate (SHA-1 or SHA-2). The account default is used if not specified. - This parameter is only applicable when the account preference is set to \"Select signing algorithm at certificate generation time.\" - As of January 1, 2016 any certificates except Private SSL (PRIVATE_SSL) certificates being issued, reissued, or renewed will use SHA2, even if the SHA1 algorithm is specified in the request. Private SSL certificates can continue to use SHA-1. + [JsonProperty("signingAlg")] + public string SigningAlg { get; set; } + + + /// + /// Extended Key Usage - applicable to all public SSL certificate types + /// + /// Extended Key Usage - applicable to all public SSL certificate types (SERVER_AUTH, CLIENT_AUTH, SERVER_AND_CLIENT_AUTH) + [JsonProperty("eku")] + public string Eku { get; set; } + + /// + /// Base-64 encoded Certificate Signing Request (CSR). CSR is accepted with or without PEM formatting around the Base-64 string. + /// + /// Base-64 encoded Certificate Signing Request (CSR). CSR is accepted with or without PEM formatting around the Base-64 string. + [JsonProperty("csr")] + public string Csr { get; set; } + + /// + /// The subjectAltName identifier, as an array of values (applies to STANDARD_SSL, ADVANTAGE_SSL, UC_SSL, EV_SSL, QWAC_SSL, QWACPSD2_SSL, WILDCARD_SSL, PRIVATE_SSL, and PD_SSL certificate types). * If you are requesting a new SSL certificate, and you pass a subjectAltName parameter, any SAN names in the CSR are ignored. If no subjectAltName parameter is passed, the SAN names in the CSR are used. * See the requesttype parameter (further in this table) to understand more about SANs during reissues and renewals. * In the case of Standard certificates, if the CN of the certificate is <domain>.<tld> only the www.<domain>.<tld> value is accepted. If the CN of the certificate is www.<domain>.<tld> only the <domain>.<tld> value is accepted. + /// + /// The subjectAltName identifier, as an array of values (applies to STANDARD_SSL, ADVANTAGE_SSL, UC_SSL, EV_SSL, QWAC_SSL, QWACPSD2_SSL, WILDCARD_SSL, PRIVATE_SSL, and PD_SSL certificate types). * If you are requesting a new SSL certificate, and you pass a subjectAltName parameter, any SAN names in the CSR are ignored. If no subjectAltName parameter is passed, the SAN names in the CSR are used. * See the requesttype parameter (further in this table) to understand more about SANs during reissues and renewals. * In the case of Standard certificates, if the CN of the certificate is <domain>.<tld> only the www.<domain>.<tld> value is accepted. If the CN of the certificate is www.<domain>.<tld> only the <domain>.<tld> value is accepted. + [JsonProperty("subjectAltName")] + public List SubjectAltName { get; set; } + + + + /// + /// In compliance with browser requirements, this certificate may be posted to the Certificate Transparency (CT) logs. This is a best practice technique that helps domain owners monitor certificates issued to their domains. Note that not all certificates are eligible for CT logging. * If ctLog is not specified, the certificate uses the account default. * If ctLog is specified and the account settings allow it, ctLog overrides the account default. * If ctLog is set to *false*, but the account settings is set to \"always log\", the certificate generation will fail. + /// + /// In compliance with browser requirements, this certificate may be posted to the Certificate Transparency (CT) logs. This is a best practice technique that helps domain owners monitor certificates issued to their domains. Note that not all certificates are eligible for CT logging. * If ctLog is not specified, the certificate uses the account default. * If ctLog is specified and the account settings allow it, ctLog overrides the account default. * If ctLog is set to *false*, but the account settings is set to \"always log\", the certificate generation will fail. + [JsonProperty("ctLog")] + public bool? CtLog { get; set; } + + /// + /// Common Name (CN) attribute in the DN. Applicable to S/MIME and Document Signing only. + /// + /// Common Name (CN) attribute in the DN. Applicable to S/MIME and Document Signing only. + [JsonProperty("cn")] + public string Cn { get; set; } + + /// + /// email attribute in the DN. Applicable to S/MIME and Document Signing only. + /// + /// email attribute in the DN. Applicable to S/MIME and Document Signing only. + [JsonProperty("certEmail")] + public string CertEmail { get; set; } + + /// + /// User Principal Name. Applicable to the SMIME_ENT certificate types only. If specified, it must be a valid email address and its domain must be the approved domain for that client. + /// + /// User Principal Name. Applicable to the SMIME_ENT certificate types only. If specified, it must be a valid email address and its domain must be the approved domain for that client. + [JsonProperty("upn")] + public string Upn { get; set; } + + /// + /// The client ID. The ID of the primary client is 1. If the clientId is not specified, 1 is used. + /// + /// The client ID. The ID of the primary client is 1. If the clientId is not specified, 1 is used. + [JsonProperty("clientId")] + public int? ClientId { get; set; } + + /// + /// When there is an org parameter specified in the request, it is used in the certificate created, even if there is already an Org in the CSR (O=). When there is no org parameter specified in the request, the organization from the Client is used when creating all certificate types except for Private Dedicated SSL (PDSSL). In the case of PDSSL certificates only: if there is no org parameter in the request, the organization in the CSR is used, when it is available. If there is no org value in the CSR, then the client organization is used. Note that the org parameter is valid for use only with clientId=1, for all certificate types except for PDSSL. When requesting PDSSL certificates, the org parameter can be used in requests for any clientId. + /// + /// When there is an org parameter specified in the request, it is used in the certificate created, even if there is already an Org in the CSR (O=). When there is no org parameter specified in the request, the organization from the Client is used when creating all certificate types except for Private Dedicated SSL (PDSSL). In the case of PDSSL certificates only: if there is no org parameter in the request, the organization in the CSR is used, when it is available. If there is no org value in the CSR, then the client organization is used. Note that the org parameter is valid for use only with clientId=1, for all certificate types except for PDSSL. When requesting PDSSL certificates, the org parameter can be used in requests for any clientId. + [JsonProperty("org")] + public string Org { get; set; } + + /// + /// The organizational unit. This parameter can be set to the name of the 'ou' or '' (i.e. ignore CSR ou and do not set the OU). See the behavior below. This parameter is valid for SSL and S/MIME certificate types. 'ou' behavior is dependent on whether organizational units are enabled for your account. If ou is disabled for your account: * New certificates- OUs from CSRs or the 'ou' input parameters are ignored. * Reissued certificates- OUs from CSRs, or the 'ou' input parameters are ignored. * Renewed certificates- OUs from CSRs, or the 'ou' input parameters are ignored. If OUs are enabled for your account: * New certificates- Valid OUs from CSRs are used. Invalid OUs from CSRs are ignored. The OU in the CSR is overridden by a valid \"ou\" from the input parameter, however if the OU is invalid, an \"Unapproved OU\" error is generated. * Reissued certificates- If the CSR is not specified when reissuing, then the OU from the CSR of the original certificate is used as the default OU. The OU is ignored if it is invalid. If a new CSR is used when the certificate is reissued, the OU from the CSR is used as the default OU. If a new CSR with no OU is used, the certificate is reissued without an OU. The original OU in the CSRis overridden by a valid 'ou' or '' from the input parameter, however if the OU is invalid, an \"Unapproved OU\" error is generated. * Renewed certificates- If no CSR is specified when the certificate is renewed, the OU of the CSR from the original certificate is used. The OU is ignored if it is invalid. If a new CSR is used and contains a valid OU, the OU from the CSR is used. If the CSR is replaced and contains no OU, the certificate is renewed without an OU. The original OU in the certificate is overridden by a valid 'ou' or '' (i.e. no OU) from the input parameter, or by the OU in a replacement CSR, however if the OU is invalid an \"Unapproved OU\" error is generated. Multiple OUs are reserved for future products. A maximum of one OU may be specified for current products. + /// + /// The organizational unit. This parameter can be set to the name of the 'ou' or '' (i.e. ignore CSR ou and do not set the OU). See the behavior below. This parameter is valid for SSL and S/MIME certificate types. 'ou' behavior is dependent on whether organizational units are enabled for your account. If ou is disabled for your account: * New certificates- OUs from CSRs or the 'ou' input parameters are ignored. * Reissued certificates- OUs from CSRs, or the 'ou' input parameters are ignored. * Renewed certificates- OUs from CSRs, or the 'ou' input parameters are ignored. If OUs are enabled for your account: * New certificates- Valid OUs from CSRs are used. Invalid OUs from CSRs are ignored. The OU in the CSR is overridden by a valid \"ou\" from the input parameter, however if the OU is invalid, an \"Unapproved OU\" error is generated. * Reissued certificates- If the CSR is not specified when reissuing, then the OU from the CSR of the original certificate is used as the default OU. The OU is ignored if it is invalid. If a new CSR is used when the certificate is reissued, the OU from the CSR is used as the default OU. If a new CSR with no OU is used, the certificate is reissued without an OU. The original OU in the CSRis overridden by a valid 'ou' or '' from the input parameter, however if the OU is invalid, an \"Unapproved OU\" error is generated. * Renewed certificates- If no CSR is specified when the certificate is renewed, the OU of the CSR from the original certificate is used. The OU is ignored if it is invalid. If a new CSR is used and contains a valid OU, the OU from the CSR is used. If the CSR is replaced and contains no OU, the certificate is renewed without an OU. The original OU in the certificate is overridden by a valid 'ou' or '' (i.e. no OU) from the input parameter, or by the OU in a replacement CSR, however if the OU is invalid an \"Unapproved OU\" error is generated. Multiple OUs are reserved for future products. A maximum of one OU may be specified for current products. + [JsonProperty("ou")] + public List Ou { get; set; } + + /// + /// The certificate pickup password. * A password must be used if it is set to \"required\" in the account under Options > Certificate Pickup Password. A password is used to protect SMIME_ENT certificates without CSRs. The password protects the returned PKCS12 containing the private key and certificate. * If a password and CSR are provided in an SMIME_ENT certificate request, the CSR will be used, and the password will be ignored. + /// + /// The certificate pickup password. * A password must be used if it is set to \"required\" in the account under Options > Certificate Pickup Password. A password is used to protect SMIME_ENT certificates without CSRs. The password protects the returned PKCS12 containing the private key and certificate. * If a password and CSR are provided in an SMIME_ENT certificate request, the CSR will be used, and the password will be ignored. + [JsonProperty("password")] + public string Password { get; set; } + + /// + /// Gets or Sets Tracking + /// + [JsonProperty("tracking")] + public Tracking Tracking { get; set; } + + /// + /// The end user of the Code Signing certificate must generate and store the private key for this request on cryptographically secure hardware to be compliant with the Entrust CSP and Subscription agreement. You must set the endUserKeyStorageAgreement flag to true, to acknowledge that you will inform the user of this requirement. Applicable to Code Signing certificate types only. + /// + /// The end user of the Code Signing certificate must generate and store the private key for this request on cryptographically secure hardware to be compliant with the Entrust CSP and Subscription agreement. You must set the endUserKeyStorageAgreement flag to true, to acknowledge that you will inform the user of this requirement. Applicable to Code Signing certificate types only. + [JsonProperty("endUserKeyStorageAgreement")] + public bool? EndUserKeyStorageAgreement { get; set; } + + + } +} diff --git a/entrust-ecs-caplugin/API/RenewCertificateRequest.cs b/entrust-ecs-caplugin/API/RenewCertificateRequest.cs new file mode 100644 index 0000000..4e4eb14 --- /dev/null +++ b/entrust-ecs-caplugin/API/RenewCertificateRequest.cs @@ -0,0 +1,49 @@ +// Copyright 2024 Keyfactor +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using Newtonsoft.Json; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.CAPlugin.Entrust.API +{ + public class RenewCertificateRequest : ECSBaseRequest + { + public RenewCertificateRequest(int trackingId) + { + this.Resource = $"certificates/{trackingId}/renewals"; + this.Method = "POST"; + } + } + public class RenewCertificateRequestBody : ReissueCertificateRequestBody + { + /// + /// If the validateOnly flag is set to true, the request contents will be validated for correctness but will not otherwise be processed. No inventory will be consumed and no certificate will be generated. + /// + /// If the validateOnly flag is set to true, the request contents will be validated for correctness but will not otherwise be processed. No inventory will be consumed and no certificate will be generated. + [JsonProperty("validateOnly")] + public bool? ValidateOnly { get; set; } + + /// + /// The date the certificate is set to expire (pooling accounts only). An RFC3339 compliant date, for example&#58; YYYY-MM-DD Note that only the date (day, month, year) is supported for specifying expiry date. If you choose to specify an expiry time with the expiry date, the time will be adjusted to Eastern Standard Time (EST). This could have the unintended effect of moving your expiry date to the previous day. + /// + /// The date the certificate is set to expire (pooling accounts only). An RFC3339 compliant date, for example&#58; YYYY-MM-DD Note that only the date (day, month, year) is supported for specifying expiry date. If you choose to specify an expiry time with the expiry date, the time will be adjusted to Eastern Standard Time (EST). This could have the unintended effect of moving your expiry date to the previous day. + [JsonProperty("certExpiryDate")] + public DateTime? CertExpiryDate { get; set; } + + /// + /// The lifetime of the certificate. Applies to all non-pooling accounts and to CDS_INDIVIDUAL, CDS_GROUP, CDS_ENT_LITE, CDS_ENT_PRO, and SMIME_ENT certificates, regardless of account type. This value is specified as an ISO 8601 duration. Allowed values are: 'P1Y', 'P2Y', and 'P3Y'. + /// + /// The lifetime of the certificate. Applies to all non-pooling accounts and to CDS_INDIVIDUAL, CDS_GROUP, CDS_ENT_LITE, CDS_ENT_PRO, and SMIME_ENT certificates, regardless of account type. This value is specified as an ISO 8601 duration. Allowed values are: 'P1Y', 'P2Y', and 'P3Y'. + [JsonProperty("certLifetime")] + public string CertLifetime { get; set; } + } +} diff --git a/entrust-ecs-caplugin/API/RevokeCertificateRequest.cs b/entrust-ecs-caplugin/API/RevokeCertificateRequest.cs new file mode 100644 index 0000000..f44bd09 --- /dev/null +++ b/entrust-ecs-caplugin/API/RevokeCertificateRequest.cs @@ -0,0 +1,45 @@ +// Copyright 2024 Keyfactor +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using Newtonsoft.Json; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.CAPlugin.Entrust.API +{ + public partial class RevokeCertificateRequest + { + /// + /// Gets or Sets CrlReason + /// + [JsonProperty("crlReason")] + public string CrlReason { get; set; } + + /// + /// Comment field to explain the reason for revocation + /// + /// Comment field to explain the reason for revocation + [JsonProperty("revocationComment")] + public string RevocationComment { get; set; } + + } + + public class RevokeCertificateCall : ECSBaseRequest + { + + + public RevokeCertificateCall(int trackingId) + { + this.Resource = "certificates/" + trackingId.ToString() + "/revocations"; + this.Method = "POST"; + } + } +} diff --git a/entrust-ecs-caplugin/API/Version.cs b/entrust-ecs-caplugin/API/Version.cs new file mode 100644 index 0000000..b98018c --- /dev/null +++ b/entrust-ecs-caplugin/API/Version.cs @@ -0,0 +1,35 @@ +// Copyright 2024 Keyfactor +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using Newtonsoft.Json; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.CAPlugin.Entrust.API +{ + public class VersionRequest : ECSBaseRequest + { + public VersionRequest() + { + this.Resource = "application/version"; + this.Method = "GET"; + } + + public class VersionResponse + { + /// + /// Gets or Sets Status + /// + [JsonProperty("version")] + public string Version { get; set; } + } + } +} diff --git a/entrust-ecs-caplugin/Client/ECSClient.cs b/entrust-ecs-caplugin/Client/ECSClient.cs new file mode 100644 index 0000000..f2ca270 --- /dev/null +++ b/entrust-ecs-caplugin/Client/ECSClient.cs @@ -0,0 +1,535 @@ +// Copyright 2024 Keyfactor +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using Keyfactor.Extensions.CAPlugin.Entrust.API; +using Keyfactor.Extensions.CAPlugin.Entrust.Models; +using Keyfactor.Logging; + +using Microsoft.Extensions.Logging; + +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +using static Keyfactor.Extensions.CAPlugin.Entrust.API.VersionRequest; + +using Certificate = Keyfactor.Extensions.CAPlugin.Entrust.API.Certificate; + +namespace Keyfactor.Extensions.CAPlugin.Entrust.Client +{ + public class ECSClient + { + private static ILogger Logger => LogHandler.GetClassLogger(); + + private string UserName { get; set; } + private string Password { get; set; } + private string BaseUrl { get; set; } + private X509Certificate2 AuthCert { get; set; } + + public ECSClient(string username, string password, X509Certificate2 authCert, string baseUrl) + { + UserName = username; + Password = password; + AuthCert = authCert; + BaseUrl = baseUrl; + } + + public ECSClient(string username, string password, X509Certificate2 authCert) + : this(username, password, authCert, "https://api.entrust.net/enterprise/v2/") + { + } + + private class ECSResponse + { + public ECSResponse() + { + Success = true; + Response = ""; + } + + public bool Success { get; set; } + public string Response { get; set; } + } + + private ECSResponse Request(ECSBaseRequest request) + { + return Request(request, ""); + } + + private ECSResponse Request(ECSBaseRequest request, string parameters) + { + ECSResponse response = new ECSResponse(); + bool rateLimited = true; + int retryAfter = 0; + + while (rateLimited) + { + System.Threading.Thread.Sleep(retryAfter * 1000); + try + { + string targetUri; + if (request.Method == "POST" || request.Method == "PUT" || request.Method == "PATCH") + { + targetUri = BaseUrl + request.Resource; + } + else + { + if (string.IsNullOrEmpty(parameters)) + { + targetUri = BaseUrl + request.Resource; + } + else + { + targetUri = BaseUrl + request.Resource + "?" + parameters; + } + } + Logger.LogTrace($"Entered Entrust request method: {request.Method} - URL: {targetUri}"); + + HttpWebRequest objRequest = (HttpWebRequest)WebRequest.Create(targetUri); + objRequest.Method = request.Method; + objRequest.ContentType = "application/json"; + objRequest.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes(UserName + ":" + Password)); + if (AuthCert != null) + { + objRequest.ClientCertificates.Add(AuthCert); + } + + + if (!String.IsNullOrEmpty(parameters) && (objRequest.Method == "POST" || objRequest.Method == "PUT" || objRequest.Method == "PATCH")) + { + byte[] postBytes = Encoding.UTF8.GetBytes(parameters); + objRequest.ContentLength = postBytes.Length; + Stream requestStream = objRequest.GetRequestStream(); + requestStream.Write(postBytes, 0, postBytes.Length); + requestStream.Close(); + } + + Stopwatch watch = new Stopwatch(); + watch.Start(); + + using (HttpWebResponse objResponse = (HttpWebResponse)objRequest.GetResponse()) + { + response.Response = new StreamReader(objResponse.GetResponseStream()).ReadToEnd(); + + Logger.LogTrace($"Entrust API returned response {objResponse.StatusCode} ({response.Response.Length} characters) in {watch.ElapsedMilliseconds}ms"); + } + + Logger.LogTrace("Full Response Body: " + response.Response); + rateLimited = false; + } + catch (WebException wex) + { + if (wex.Response != null) + { + using (HttpWebResponse errorResponse = (HttpWebResponse)wex.Response) + { + using (StreamReader reader = new StreamReader(errorResponse.GetResponseStream())) + { + response.Response = reader.ReadToEnd(); + string retrySeconds = errorResponse.Headers["Retry-After"]; + + if (!Int32.TryParse(retrySeconds, out retryAfter)) + { + rateLimited = false; + } + else + { + retryAfter += 1; // Add one second to ensure we're not losing a decimal place. + Logger.LogTrace("Rate Limit exceeded. Resubmitting request after {0} seconds.", retryAfter); + } + } + } + } + else + { + Logger.LogError($"Entrust Response Error: {wex.Message}"); + throw new Exception($"Unable to establish connection to Entrust web service: {wex.Message}", wex); + } + } + catch (Exception ex) + { + Logger.LogError($"Entrust Response Error: {ex.Message}"); + throw new Exception($"Unable to establish connection to Entrust web service: {ex.Message}", ex); + } + } + + return response; + } + + private bool IsError(string response) + { + return response.Contains("errors"); + } + + public VersionResponse GetApplicationVersion() + { + VersionRequest oRequest = new VersionRequest(); + ECSResponse oResponse = Request(oRequest, oRequest.BuildParameters()); + VersionResponse response; + + if (IsError(oResponse.Response)) + { + ErrorResponse e = JsonConvert.DeserializeObject(oResponse.Response); + Logger.LogError($"Error occurred requesting application version from the Entrust REST API - {e.Errors.First().Message}"); + throw new Exception(e.Errors.First().Message); + } + else + { + response = JsonConvert.DeserializeObject(oResponse.Response); + } + + return response; + } + + public List GetOrganizations() + { + GetOrganizationsRequest request = new GetOrganizationsRequest(); + ECSResponse apiResponse = Request(request, string.Empty); + + if (IsError(apiResponse.Response)) + { + ErrorResponse e = JsonConvert.DeserializeObject(apiResponse.Response); + + Logger.LogError($"Error occurred requesting organizations from the Entrust REST API: {e.Errors.First().Message}"); + + throw new Exception(e.Errors.First().Message); + } + else + { + GetOrganizationsResponse response = JsonConvert.DeserializeObject(apiResponse.Response); + return response.Organizations; + } + } + + public List GetClients() + { + GetClientsRequest oRequest = new GetClientsRequest(); + ECSResponse oResponse = Request(oRequest, oRequest.BuildParameters()); + GetClientsResponse response; + + if (IsError(oResponse.Response)) + { + ErrorResponse e = JsonConvert.DeserializeObject(oResponse.Response); + + Logger.LogError($"Error occurred requesting client list from the Entrust REST API - {e.Errors.First().Message}"); + + throw new Exception(e.Errors.First().Message); + } + else + { + response = JsonConvert.DeserializeObject(oResponse.Response); + } + + return response.Clients; + } + + public List GetAllCertificates() + { + List result = new List(); + int limit = 1000; + int received = 0; + int? total = 0; + bool requestStarted = false; + + while (!requestStarted || received != total) + { + GetCertificatesRequest oRequest = new GetCertificatesRequest(limit, received); + ECSResponse oResponse = Request(oRequest, oRequest.BuildParameters()); + GetCertificatesResponse response; + + if (IsError(oResponse.Response)) + { + ErrorResponse e = JsonConvert.DeserializeObject(oResponse.Response); + Logger.LogError($"Error occurred requesting certificate list from Entrust REST API - {e.Errors.First().Message}"); + throw new Exception(e.Errors.First().Message); + } + else + { + response = JsonConvert.DeserializeObject(oResponse.Response); + total = response.summary.Total; + received += response.certificates.Count; + result.AddRange(response.certificates); + } + requestStarted = true; + } + + return result; + } + + public CertificateExt GetCertificateByTrackingId(int trackingId) + { + GetCertificateByTrackingIdRequest oRequest = new GetCertificateByTrackingIdRequest(trackingId); + ECSResponse oResponse = Request(oRequest, oRequest.BuildParameters()); + CertificateExt response; + + if (IsError(oResponse.Response)) + { + ErrorResponse e = JsonConvert.DeserializeObject(oResponse.Response); + Logger.LogError($"Error occurred requesting certificate for trackingId {trackingId} from the Entrust REST API - Error status code {e.Status} : {e.Errors.First().Message}"); + throw new Exception(e.Errors.First().Message); + } + else + { + response = JsonConvert.DeserializeObject(oResponse.Response); + } + + return response; + } + + public CertificateExt GetCertificateByThumbprint(string thumbprint) + { + GetCertificateByThumbprintRequest oRequest = new GetCertificateByThumbprintRequest(thumbprint); + ECSResponse oResponse = Request(oRequest, oRequest.BuildParameters()); + CertificateExt response; + + if (IsError(oResponse.Response)) + { + ErrorResponse e = JsonConvert.DeserializeObject(oResponse.Response); + Logger.LogError("Error occurred requesting certificate for thumbprint {0} from the Entrust REST API - Error status code {1} : {2}", thumbprint, e.Status, e.Errors.First().Message); + throw new Exception(e.Errors.First().Message); + } + else + { + response = JsonConvert.DeserializeObject(oResponse.Response); + } + + return response; + } + + public CertificateResponse RequestNewCertificate(NewCertificateRequest request) + { + NewCertificateCall call = new NewCertificateCall(); + ECSResponse oResponse = Request(call, JsonConvert.SerializeObject(request, Newtonsoft.Json.Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); + CertificateResponse response; + + if (IsError(oResponse.Response)) + { + ErrorResponse e = JsonConvert.DeserializeObject(oResponse.Response); + Logger.LogError($"Error occurred requesting new certificate from Entrust REST API - {e.Errors.First().Message}"); + throw new Exception(e.Errors.First().Message); + } + else + { + response = JsonConvert.DeserializeObject(oResponse.Response); + } + + return response; + } + + public CertificateResponse ReissueCertificate(ReissueCertificateRequestBody request, int trackingId) + { + ECSResponse oResponse = Request(new ReissueCertificateRequest(trackingId), JsonConvert.SerializeObject(request, Newtonsoft.Json.Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); + CertificateResponse response; + + if (IsError(oResponse.Response)) + { + ErrorResponse e = JsonConvert.DeserializeObject(oResponse.Response); + Logger.LogError($"Error occurred reissuing certificate with trackingId {trackingId} from Entrust REST API - {e.Errors.First().Message}"); + throw new Exception(e.Errors.First().Message); + } + else + { + response = JsonConvert.DeserializeObject(oResponse.Response); + } + + return response; + } + + public CertificateResponse RenewCertificate(RenewCertificateRequestBody request, int trackingId) + { + ECSResponse oResponse = Request(new RenewCertificateRequest(trackingId), JsonConvert.SerializeObject(request, Newtonsoft.Json.Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); + CertificateResponse response; + + if (IsError(oResponse.Response)) + { + ErrorResponse e = JsonConvert.DeserializeObject(oResponse.Response); + Logger.LogError($"Error occurred renewing certificate with trackingId {trackingId} from Entrust REST API - {e.Errors.First().Message}"); + throw new Exception(e.Errors.First().Message); + } + else + { + response = JsonConvert.DeserializeObject(oResponse.Response); + } + + return response; + } + + public ValueTuple ValidateRequestNewCertificate(NewCertificateRequest request) + { + // We switch the value here so that callers don't have to create a new request. + bool? originalValue = request.ValidateOnly; + request.ValidateOnly = true; + ECSResponse oResponse = Request(new NewCertificateCall(), JsonConvert.SerializeObject(request, Newtonsoft.Json.Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); + request.ValidateOnly = originalValue; + + if (IsError(oResponse.Response)) + { + ErrorResponse response = JsonConvert.DeserializeObject(oResponse.Response); + Error requestError = response.Errors[0]; + return (false, requestError.Message); + } + return (true, oResponse.Response); + } + + public Certificate GetCertificateBySerialNumber(string serialNumber) + { + string trimmedSerialNumber = serialNumber.TrimStart('0'); + List result = new List(); + + Dictionary qParams = new Dictionary(); + qParams.Add("serialNumber", trimmedSerialNumber); + GetCertificatesRequest oRequest = new GetCertificatesRequest(1, 0, qParams); + ECSResponse oResponse = Request(oRequest, oRequest.BuildParameters()); + GetCertificatesResponse response; + + if (IsError(oResponse.Response)) + { + ErrorResponse e = JsonConvert.DeserializeObject(oResponse.Response); + if (e.Status == 404) + { + return null; + } + Logger.LogError($"Error occurred requesting certificate with serial number {trimmedSerialNumber} from Entrust REST API - {e.Errors.First().Message}"); + throw new Exception(e.Errors.First().Message); + } + else + { + response = JsonConvert.DeserializeObject(oResponse.Response); + } + + if (response.certificates.Count > 0) + { + return response.certificates[0]; + } + else + { + return null; + } + } + + public void RevokeCertificate(int trackingId, string reason, string comment) + { + RevokeCertificateCall oRequest = new RevokeCertificateCall(trackingId); + RevokeCertificateRequest request = new RevokeCertificateRequest() + { + CrlReason = reason, + RevocationComment = comment + }; + + ECSResponse oResponse = Request(oRequest, JsonConvert.SerializeObject(request, Newtonsoft.Json.Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); + + if (IsError(oResponse.Response)) + { + ErrorResponse e = JsonConvert.DeserializeObject(oResponse.Response); + Logger.LogError($"Error occurred revoking certificate with trackingId {trackingId} from Entrust REST API - {e.Errors.First().Message}"); + throw new Exception(e.Errors.First().Message); + } + } + + public List GetInventories() + { + GetInventoryRequest oRequest = new GetInventoryRequest(); + ECSResponse oResponse = Request(oRequest, oRequest.BuildParameters()); + GetInventoryResponse response; + + if (IsError(oResponse.Response)) + { + ErrorResponse e = JsonConvert.DeserializeObject(oResponse.Response); + Logger.LogError($"Error occurred requesting inventory from Entrust REST API - {e.Errors.First().Message}"); + throw new Exception(e.Errors.First().Message); + } + else + { + response = JsonConvert.DeserializeObject(oResponse.Response); + } + + return response.Inventories; + } + + public CertificateResponse ApproveCertificate(int trackingId) + { + PatchCertificateRequest oRequest = new PatchCertificateRequest(trackingId); + PatchCertificateRequestBody body = new PatchCertificateRequestBody() + { + Operation = CertificateOperation.APPROVE + }; + ECSResponse oResponse = Request(oRequest, JsonConvert.SerializeObject(body, Newtonsoft.Json.Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore })); + CertificateResponse response; + + if (IsError(oResponse.Response)) + { + ErrorResponse e = JsonConvert.DeserializeObject(oResponse.Response); + Logger.LogError($"Error occurred approving certificate with trackingId {trackingId} from Entrust REST API - {e.Errors.First().Message}"); + throw new Exception(e.Errors.First().Message); + } + else + { + response = JsonConvert.DeserializeObject(oResponse.Response); + } + + return response; + } + + public static ECSClient InitializeClient(ECSConfig config) + { + Logger.MethodEntry(LogLevel.Debug); + X509Certificate2 clientCert = null; + if (!string.IsNullOrEmpty(config.ClientCertificate.Thumbprint)) + { + //Cert auth, cert in Windows store + StoreName sn; + StoreLocation sl; + string thumbprint = config.ClientCertificate.Thumbprint; + + if (string.IsNullOrEmpty(thumbprint) || + !Enum.TryParse(config.ClientCertificate.StoreName, out sn) || + !Enum.TryParse(config.ClientCertificate.StoreLocation, out sl)) + { + throw new Exception("Unable to find client authentication certificate"); + } + + X509Certificate2Collection foundCerts; + using (X509Store currentStore = new X509Store(sn, sl)) + { + Logger.LogTrace($"Search for client auth certificates with Thumprint {thumbprint} in the {sn}{sl} certificate store"); + + currentStore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); + foundCerts = currentStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, true); + Logger.LogTrace($"Found {foundCerts.Count} certificates in the {currentStore.Name} store"); + currentStore.Close(); + } + if (foundCerts.Count > 1) + { + throw new Exception($"Multiple certificates with Thumprint {thumbprint} found in the {sn}{sl} certificate store"); + } + if (foundCerts.Count > 0) + clientCert = foundCerts[0]; + } + else if (!string.IsNullOrEmpty(config.ClientCertificate.CertificatePath)) + { + //Cert auth, cert in pfx file + try + { + X509Certificate2 cert = new X509Certificate2(config.ClientCertificate.CertificatePath, config.ClientCertificate.CertificatePassword); + clientCert = cert; + } + catch (Exception ex) + { + throw new Exception($"Unable to open the client certificate file with the given password. Error: {ex.Message}"); + } + } + + return new ECSClient(config.AuthUsername, config.AuthPassword, clientCert); + } + } +} diff --git a/entrust-ecs-caplugin/Constants.cs b/entrust-ecs-caplugin/Constants.cs new file mode 100644 index 0000000..1a78b75 --- /dev/null +++ b/entrust-ecs-caplugin/Constants.cs @@ -0,0 +1,45 @@ +// Copyright 2024 Keyfactor +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.CAPlugin.Entrust +{ + public class Constants + { + public class Config + { + public const string USERNAME = "AuthUsername"; + public const string PASSWORD = "AuthPassword"; + public const string CLIENTCERT = "ClientCertificate"; + public const string NAME = "Name"; + public const string EMAIL = "Email"; + public const string PHONE = "PhoneNumber"; + public const string IGNOREEXPIRED = "IgnoreExpired"; + public const string ENABLED = "Enabled"; + + public const string LIFETIME = "LifetimeMonths"; + public const string ORGANIZATION = "Organization"; + public const string CERTUSAGE = "CertificateUsage"; + public const string RENEWAL_WINDOW = "RenewalWindowDays"; + + public class ClientCert + { + public const string STORE_NAME = "StoreName"; + public const string STORE_LOC = "StoreLocation"; + public const string THUMBPRINT = "Thumbprint"; + public const string CERT_PATH = "CertificatePath"; + public const string CERT_PASS = "CertificatePassword"; + } + } + } +} diff --git a/entrust-ecs-caplugin/ECSCAPlugin.cs b/entrust-ecs-caplugin/ECSCAPlugin.cs new file mode 100644 index 0000000..43ca38b --- /dev/null +++ b/entrust-ecs-caplugin/ECSCAPlugin.cs @@ -0,0 +1,1001 @@ +// Copyright 2024 Keyfactor +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using Keyfactor.AnyGateway.Extensions; +using Keyfactor.Extensions.CAPlugin.Entrust.API; +using Keyfactor.Extensions.CAPlugin.Entrust.Client; +using Keyfactor.Extensions.CAPlugin.Entrust.Models; +using Keyfactor.Logging; +using Keyfactor.PKI.Enums.EJBCA; + +using Microsoft.Extensions.Logging; + +using Newtonsoft.Json; + +using Org.BouncyCastle.Asn1.X509; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using static Keyfactor.PKI.PKIConstants.Microsoft; + +namespace Keyfactor.Extensions.CAPlugin.Entrust +{ + public class ECSCAPlugin : IAnyCAPlugin + { + private ECSConfig _config; + private readonly ILogger _logger; + private ICertificateDataReader _certificateDataReader; + + public ECSCAPlugin() + { + _logger = LogHandler.GetClassLogger(); + } + + public void Initialize(IAnyCAPluginConfigProvider configProvider, ICertificateDataReader certificateDataReader) + { + _certificateDataReader = certificateDataReader; + string rawConfig = JsonConvert.SerializeObject(configProvider.CAConnectionData); + _config = JsonConvert.DeserializeObject(rawConfig); + } + + /// + /// Enroll for a certificate + /// + /// The CSR for the certificate request + /// The subject string + /// The list of SANs + /// Collection of product information and options. Includes both product-level config options as well as custom enrollment fields. + /// The format of the request + /// The type of enrollment (new, renew, reissue) + /// The result of the enrollment + /// + public async Task Enroll(string csr, string subject, Dictionary san, EnrollmentProductInfo productInfo, RequestFormat requestFormat, EnrollmentType enrollmentType) + { + ECSClient client = ECSClient.InitializeClient(_config); + _logger.LogTrace("Entrust Client Created"); + X509Name subjectParsed = new X509Name(subject); + _logger.LogTrace($"Parsed Subject with {subject}"); + + string underscoreErrorMessage = "Underscore is not allowed in DNSName."; + string requestEmail; + string requestNumber; + string requestName; + string commonName = ""; + string organization = ""; + string checkingSanVariable = ""; + int trackingId = 0; + int clientId = -1; + + // Check tracking ID if we're doing a renewal or reissuance. + if (enrollmentType == EnrollmentType.Reissue || enrollmentType == EnrollmentType.Renew || enrollmentType == EnrollmentType.RenewOrReissue) + { + _logger.LogTrace("This is a renew or reissue"); + trackingId = GetTrackingId(productInfo); + _logger.LogTrace($"With trackingId {trackingId}"); + // Check now if the trackingId is 0 to fail early. + if (trackingId == 0) + { + throw new Exception("The tracking ID of the certificate requested for renewal or reissue is 0. This certificate must be renewed or reissued through the Entrust portal."); + } + } + + try + { + checkingSanVariable = "Common Name"; + string cn = subjectParsed.GetValueList(X509Name.CN).Cast().LastOrDefault(); + if (!string.IsNullOrEmpty(cn)) + { + if (cn.Contains("_")) + { + throw new Exception(underscoreErrorMessage); + } + commonName = cn; + } + + _logger.LogTrace($"Common Name of {commonName}"); + + checkingSanVariable = "Organization"; + string org = subjectParsed.GetValueList(X509Name.O).Cast().LastOrDefault(); + if (productInfo.ProductParameters.ContainsKey(Constants.Config.ORGANIZATION) && !string.IsNullOrEmpty(productInfo.ProductParameters[Constants.Config.ORGANIZATION])) + { + organization = productInfo.ProductParameters[Constants.Config.ORGANIZATION]; + } + else if (!string.IsNullOrEmpty(org)) + { + organization = org; + } + + _logger.LogTrace($"Organization of {organization}"); + + checkingSanVariable = "Email"; + string subjectEmail = subjectParsed.GetValueList(X509Name.EmailAddress).Cast().LastOrDefault(); + if (productInfo.ProductParameters.ContainsKey(Constants.Config.EMAIL) && !string.IsNullOrEmpty(productInfo.ProductParameters[Constants.Config.EMAIL])) + { + requestEmail = productInfo.ProductParameters[Constants.Config.EMAIL]; + } + else if (!string.IsNullOrEmpty(subjectEmail)) + { + requestEmail = subjectEmail; + } + else if (!string.IsNullOrEmpty(_config.Email)) + { + requestEmail = _config.Email; + } + else + { + requestEmail = "email@email.invalid"; + } + + _logger.LogTrace($"Email of {requestEmail}"); + + checkingSanVariable = "Telephone Number"; + if (productInfo.ProductParameters.ContainsKey(Constants.Config.PHONE) && !string.IsNullOrEmpty(productInfo.ProductParameters[Constants.Config.PHONE])) + { + requestNumber = productInfo.ProductParameters[Constants.Config.PHONE]; + } + else if (!string.IsNullOrEmpty(_config.PhoneNumber)) + { + requestNumber = _config.PhoneNumber; + } + else + { + requestNumber = "0000000000"; + } + + _logger.LogTrace($"Telephone Number of {requestNumber}"); + + checkingSanVariable = "Name"; + if (productInfo.ProductParameters.ContainsKey(Constants.Config.NAME) && !string.IsNullOrEmpty(productInfo.ProductParameters[Constants.Config.NAME])) + { + requestName = productInfo.ProductParameters[Constants.Config.NAME]; + } + else if (!string.IsNullOrEmpty(_config.Name)) + { + requestName = _config.Name; + } + else + { + requestName = "TestUser"; + } + + _logger.LogTrace($"Name of {requestName}"); + } + catch (Exception ex) + { + if (ex.Message == underscoreErrorMessage) + { + _logger.LogError($"Error occurred trying to validate the SAN information. {ex.Message}"); + throw new Exception(ex.Message); + } + else + { + _logger.LogError($"Error occurred trying to validate the request information. Required attributes {checkingSanVariable} may be missing."); + throw new Exception("Error occurred trying to validate the request information. Required attributes " + checkingSanVariable + " may be missing."); + } + } + + List dnsNames = new List(); + if (san.ContainsKey("Dns")) + { + dnsNames = new List(san["Dns"]); + _logger.LogTrace($"First DNS SAN: {dnsNames[0]}"); + } + + if (!commonName.Contains('.')) + { + throw new Exception($"Domain cannot be determined from Common Name."); + } + + IEnumerable approvedOrgs = client.GetOrganizations().Where(x => x.VerificationStatus.Equals("APPROVED", StringComparison.OrdinalIgnoreCase)); + if (string.IsNullOrEmpty(organization)) // If the organization is empty, use the default client. + { + clientId = 1; + } + else + { + Organization org = approvedOrgs.FirstOrDefault(x => x.Name.Equals(organization, StringComparison.OrdinalIgnoreCase)); + if (org != null) + { + clientId = org.ClientId; + } + } + + _logger.LogTrace($"ClientId of {clientId}"); + + if (clientId == -1) + { + throw new Exception($"Organization {organization} is not a valid Entrust organization for this account. The following organizations are approved: {string.Join(", ", approvedOrgs.Select(x => x.Name))}."); + } + + string usageType = (productInfo.ProductParameters.ContainsKey("CertificateUsage")) ? productInfo.ProductParameters["CertificateUsage"] : ""; + _logger.LogTrace($"usageType of {usageType}"); + string eku = ""; + if (usageType.Equals("SERVERCLIENT", StringComparison.OrdinalIgnoreCase)) + { + eku = "SERVER_AND_CLIENT_AUTH"; + } + else if (usageType.Equals("SERVER", StringComparison.OrdinalIgnoreCase)) + { + eku = "SERVER_AUTH"; + } + else if (usageType.Equals("CLIENT", StringComparison.OrdinalIgnoreCase)) + { + eku = "CLIENT_AUTH"; + } + else + { + eku = ""; + } + + _logger.LogTrace($"Getting Tracking Info"); + Tracking trackingInfo = new Tracking() + { + TrackingInfo = "", + RequesterEmail = requestEmail, + RequesterName = requestName, + RequesterPhone = requestNumber, + Deactivated = false + }; + _logger.LogTrace($"Got Tracking Info"); + + if (!EntrustCertType.InventoryExists(client, productInfo.ProductID)) + { + _logger.LogError($"Inventory for certificate type '{productInfo.ProductID}' has been used up. To perform the operation, revoke existing certificates or contact Entrust to acquire new inventory."); + throw new Exception($"Inventory for certificate type '{productInfo.ProductID}' has been used up. To perform the operation, revoke existing certificates or contact Entrust to acquire new inventory."); + } + + var months = (productInfo.ProductParameters.ContainsKey("Lifetime")) ? int.Parse(productInfo.ProductParameters["Lifetime"]) : 12; + + _logger.LogTrace($"Months of {months}"); + if (enrollmentType == EnrollmentType.RenewOrReissue) + { + _logger.LogTrace($"Determining if request is a renew or a reissue"); + var priorCertSnString = productInfo.ProductParameters["PriorCertSN"]; + int renewalWindowDays = productInfo.ProductParameters.ContainsKey("RenewalWindowDays") ? int.Parse(productInfo.ProductParameters["RenewalWindowDays"]) : 90; + var reqId = _certificateDataReader.GetRequestIDBySerialNumber(priorCertSnString).Result; + if (string.IsNullOrEmpty(reqId)) + { + throw new Exception($"No certificate with serial number '{priorCertSnString}' could be found."); + } + var expDate = _certificateDataReader.GetExpirationDateByRequestId(reqId); + + var renewCutoff = DateTime.Now.AddDays(renewalWindowDays * -1); + + if (expDate > renewCutoff) + { + _logger.LogTrace($"Certificate with serial number {priorCertSnString} is within renewal window"); + enrollmentType = EnrollmentType.Renew; + } + else + { + _logger.LogTrace($"Certificate with serial number {priorCertSnString} is not within renewal window. Reissuing..."); + enrollmentType = EnrollmentType.Reissue; + } + } + + _logger.LogTrace($"Switch Statement for Enrollment Type of {enrollmentType}"); + CertificateResponse response; + switch (enrollmentType) + { + case EnrollmentType.New: + + _logger.LogTrace($"Csr is {csr}"); + _logger.LogTrace($"ClientId is {clientId}"); + _logger.LogTrace($"Org is {organization}"); + _logger.LogTrace($"CertType is {productInfo.ProductID.ToUpper()}"); + _logger.LogTrace($"CertExpiryDate is {DateTime.Now.AddMonths(months)}"); + _logger.LogTrace($"CertLifetime is {"P" + Math.Round(months / 12.0).ToString() + "Y"}"); + _logger.LogTrace($"Tracking is {trackingInfo}"); + _logger.LogTrace($"QueueForApproval is false"); + _logger.LogTrace($"CertEmail is {requestEmail}"); + _logger.LogTrace($"SubjectAltName is {(dnsNames.Count > 0 ? dnsNames[0] : "empty")}"); + _logger.LogTrace($"Password is ''"); + _logger.LogTrace($"SigningAlg is SHA-2"); + _logger.LogTrace($"Eku is {eku}"); + _logger.LogTrace($"Cn is {commonName}"); + _logger.LogTrace($"Upn is {requestEmail}"); + _logger.LogTrace($"Ou is empty string list"); + _logger.LogTrace($"EndUserKeyStorageAgreement is true"); + _logger.LogTrace($"ValidateOnly is false"); + + NewCertificateRequest request = new NewCertificateRequest() + { + Csr = csr, + ClientId = clientId, + Org = organization, + CertType = productInfo.ProductID.ToUpper(), + CertExpiryDate = DateTime.Now.AddMonths(months), + CertLifetime = "P" + Math.Round(months / 12.0).ToString() + "Y", + Tracking = trackingInfo, + QueueForApproval = false, + CertEmail = requestEmail, + SubjectAltName = dnsNames, + Password = "", + SigningAlg = "SHA-2", + Eku = eku, + Cn = commonName, + //email from userInfo + Upn = requestEmail, + Ou = new List(), + EndUserKeyStorageAgreement = true, + //When true, this causes the api to only validate the submitted info and not actually register a cert. + ValidateOnly = false + }; + _logger.LogTrace($"Before Validation Request: {JsonConvert.SerializeObject(request)}"); + (bool validResponse, string messageResponse) = client.ValidateRequestNewCertificate(request); + _logger.LogTrace($"ValidResponse?: {validResponse}"); + _logger.LogTrace($"messageResponse: {messageResponse}"); + + if (!validResponse) + { + _logger.LogError($"Request validation failed. {messageResponse}"); + throw new Exception($"Request validation failed. {messageResponse}"); + } + + response = client.RequestNewCertificate(request); + _logger.LogTrace($"New Cert Request Response: {JsonConvert.SerializeObject(response)}"); + break; + + case EnrollmentType.Reissue: + + _logger.LogTrace($"Csr is {csr}"); + _logger.LogTrace($"ClientId is {clientId}"); + _logger.LogTrace($"Org is {organization}"); + _logger.LogTrace($"Tracking is {trackingInfo}"); + _logger.LogTrace($"CertEmail is {requestEmail}"); + _logger.LogTrace($"SubjectAltName is {(dnsNames.Count > 0 ? dnsNames[0] : "empty")}"); + _logger.LogTrace($"Password is ''"); + _logger.LogTrace($"SigningAlg is SHA-2"); + _logger.LogTrace($"Eku is {eku}"); + _logger.LogTrace($"Cn is {commonName}"); + _logger.LogTrace($"Upn is {requestEmail}"); + _logger.LogTrace($"Ou is empty string list"); + _logger.LogTrace($"EndUserKeyStorageAgreement is true"); + + ReissueCertificateRequestBody reissueRequest = new ReissueCertificateRequestBody() + { + Csr = csr, + ClientId = clientId, + Org = string.Empty, + Tracking = trackingInfo, + CertEmail = requestEmail, + SubjectAltName = dnsNames, + Password = string.Empty, + SigningAlg = "SHA-2", + Eku = eku, + Cn = commonName, + //email from userInfo + Upn = requestEmail, + Ou = new List(), + EndUserKeyStorageAgreement = true, + }; + _logger.LogTrace($"reissueRequest: {JsonConvert.SerializeObject(reissueRequest)}"); + response = client.ReissueCertificate(reissueRequest, trackingId); + _logger.LogTrace($"reissueResponse: {JsonConvert.SerializeObject(response)}"); + break; + + case EnrollmentType.Renew: + + _logger.LogTrace($"Csr is {csr}"); + _logger.LogTrace($"ClientId is {clientId}"); + _logger.LogTrace($"Org is {organization}"); + _logger.LogTrace($"CertExpiryDate is {DateTime.Now.AddMonths(months)}"); + _logger.LogTrace($"CertLifetime is {"P" + Math.Round(months / 12.0).ToString() + "Y"}"); + _logger.LogTrace($"Tracking is {trackingInfo}"); + _logger.LogTrace($"CertEmail is {requestEmail}"); + _logger.LogTrace($"SubjectAltName is {(dnsNames.Count > 0 ? dnsNames[0] : "empty")}"); + _logger.LogTrace($"Password is ''"); + _logger.LogTrace($"SigningAlg is SHA-2"); + _logger.LogTrace($"Eku is {eku}"); + _logger.LogTrace($"Cn is {commonName}"); + _logger.LogTrace($"Upn is {requestEmail}"); + _logger.LogTrace($"Ou is empty string list"); + _logger.LogTrace($"EndUserKeyStorageAgreement is true"); + + RenewCertificateRequestBody renewRequest = new RenewCertificateRequestBody() + { + Csr = csr, + ClientId = clientId, + Org = "", + CertExpiryDate = DateTime.Now.AddMonths(months), + CertLifetime = "P" + Math.Round(months / 12.0).ToString() + "Y", + Tracking = trackingInfo, + CertEmail = requestEmail, + SubjectAltName = dnsNames, + Password = "", + SigningAlg = "SHA-2", + Eku = eku, + Cn = commonName, + //email from userInfo + Upn = requestEmail, + Ou = new List(), + EndUserKeyStorageAgreement = true, + }; + _logger.LogTrace($"reissueRequest: {JsonConvert.SerializeObject(renewRequest)}"); + //Validation is not supported for Renewals so validateOnly flag does not apply + response = client.RenewCertificate(renewRequest, trackingId); + _logger.LogTrace($"renewResponse: {JsonConvert.SerializeObject(response)}"); + break; + + default: + throw new Exception($"The enrollment type {enrollmentType} is not recognized."); + } + _logger.LogTrace($"Getting Cert By Tracking Id {response.TrackingId}"); + CertificateExt enrolledCert = client.GetCertificateByTrackingId(response.TrackingId); + _logger.LogTrace($"Got Cert By Tracking Id {response.TrackingId} with status of {enrolledCert.Status}"); + int status = ConvertStatus(enrolledCert.Status, response.TrackingId.ToString()); + string statusMessage; + switch (status) + { + case (int)EndEntityStatus.GENERATED: + statusMessage = $"Certificate with trackingId {enrolledCert.TrackingId} issued successfully"; + break; + + case (int)EndEntityStatus.EXTERNALVALIDATION: + // Attempt to approve the cert. If still pending, return External validation + (int statusPending, string statusPendingMessage) statusTuple = ApproveCert(response.TrackingId); + status = statusTuple.statusPending; + statusMessage = statusTuple.statusPendingMessage; + break; + + case (int)EndEntityStatus.FAILED: + statusMessage = $"Certificate with trackingId {enrolledCert.TrackingId} is denied"; + break; + + default: + statusMessage = $"Certificate with trackingId {enrolledCert.TrackingId} has an unknown status"; + break; + } + + _logger.LogTrace($"Returning Result of CARequestId={response.TrackingId}, Certificate={response.EndEntityCert}, Status={status}, StatusMessage={statusMessage}"); + return new EnrollmentResult + { + CARequestID = response.TrackingId.ToString(), + Certificate = response.EndEntityCert, + Status = status, + StatusMessage = statusMessage + }; + + } + + /// + /// Gets the annotations for the CA Connector-level configuration fields + /// + /// + public Dictionary GetCAConnectorAnnotations() + { + return new Dictionary() + { + [Constants.Config.USERNAME] = new PropertyConfigInfo() + { + Comments = "Username for the gateway to authenticate with Entrust", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + [Constants.Config.PASSWORD] = new PropertyConfigInfo() + { + Comments = "Password for the account used to authenticate with Entrust", + Hidden = true, + DefaultValue = "", + Type = "String" + }, + [Constants.Config.CLIENTCERT] = new PropertyConfigInfo() + { + Comments = "The client certificate information used to authenticate with Entrust (if configured to use certificate authentication). This can be either a Windows cert store name and location (e.g. 'My' and 'LocalMachine' for the Local Computer personal cert store) and thumbprint, or a PFX file and password.", + Hidden = false, + DefaultValue = "", + Type = "ClientCertificate" + }, + [Constants.Config.NAME] = new PropertyConfigInfo() + { + Comments = "The default requester name", + Hidden = false, + DefaultValue = "TestUser", + Type = "String" + }, + [Constants.Config.EMAIL] = new PropertyConfigInfo() + { + Comments = "The default requester email address", + Hidden = false, + DefaultValue = "email@email.invalid", + Type = "String" + }, + [Constants.Config.PHONE] = new PropertyConfigInfo() + { + Comments = "The default requester phone number", + Hidden = false, + DefaultValue = "0000000000", + Type = "String" + }, + [Constants.Config.IGNOREEXPIRED] = new PropertyConfigInfo() + { + Comments = "If set to true, will not sync expired certs from Entrust", + Hidden = false, + DefaultValue = false, + Type = "Boolean" + }, + [Constants.Config.ENABLED] = new PropertyConfigInfo() + { + Comments = "Flag to Enable or Disable gateway functionality. Disabling is primarily used to allow creation of the CA prior to configuration information being available.", + Hidden = false, + DefaultValue = true, + Type = "Boolean" + } + }; + } + + public List GetProductIds() + { + try + { + ECSClient client = ECSClient.InitializeClient(_config); + var certTypes = EntrustCertType.GetCustomerAccountTypes(client); + List productIds = new List(); + productIds.AddRange(certTypes.Select(c => c.ProductCode)); + return productIds; + } + catch (Exception ex) + { + _logger.LogError($"Unable to retrieve cert types from Entrust: {ex.Message}"); + return new List(); + } + } + + public async Task GetSingleRecord(string caRequestID) + { + // Get status of cert and the cert itself from Digicert + ECSClient client = ECSClient.InitializeClient(_config); + + // Split string to see what kind of ID we have. + string[] parts = caRequestID.Split('-'); + + // Get the cert by tracking ID or thumbprint. + CertificateExt entrustCert = parts.Length == 1 ? client.GetCertificateByTrackingId(Int32.Parse(caRequestID)) : client.GetCertificateByThumbprint(parts[1]); + int status = ConvertStatus(entrustCert.Status, caRequestID); + if (status == (int)RequestDisposition.PENDING) + { + status = (int)EndEntityStatus.EXTERNALVALIDATION; + } + return new AnyCAPluginCertificate + { + CARequestID = caRequestID, + Certificate = !string.IsNullOrEmpty(entrustCert.EndEntityCert) ? entrustCert.EndEntityCert : null, + Status = status, + ProductID = entrustCert.CertType + }; + } + + public Dictionary GetTemplateParameterAnnotations() + { + return new Dictionary() + { + [Constants.Config.LIFETIME] = new PropertyConfigInfo() + { + Comments = "OPTIONAL: The number of months of validity to use when requesting certs. If not provided, default is 12.", + Hidden = false, + DefaultValue = 12, + Type = "Number" + }, + [Constants.Config.ORGANIZATION] = new PropertyConfigInfo() + { + Comments = "OPTIONAL: For requests that will not have a subject (such as ACME) you can use this field to provide an organization name. Value supplied here will override any CSR values, so do not include this field if you want the organization from the CSR to be used.", + Hidden = false, + DefaultValue = "", + Type = "String" + }, + [Constants.Config.CERTUSAGE] = new PropertyConfigInfo() + { + Comments = "Required for public SSL certificate types. Represents the key usage for the certificates enrolled against this template. Valid values are 'server', 'client', or 'serverclient'. Do not provide a value for cert types that are not public SSL.", + Hidden = false, + DefaultValue = "server", + Type = "String" + }, + [Constants.Config.RENEWAL_WINDOW] = new PropertyConfigInfo() + { + Comments = "OPTIONAL: The number of days from certificate expiration that the gateway should do a renewal rather than a reissue. If not provided, default is 90.", + Hidden = false, + DefaultValue = 90, + Type = "Number" + } + }; + } + + public async Task Ping() + { + try + { + ECSClient client = ECSClient.InitializeClient(_config); + + _logger.LogDebug("Attempting to ping Entrust API."); + + _ = client.GetClients(); + + _logger.LogDebug("Successfully pinged Entrust API."); + } + catch (Exception e) + { + _logger.LogError($"There was an error contacting Entrust: {e.Message}."); + throw new Exception($"Error attempting to ping Entrust: {e.Message}.", e); + } + } + + public async Task Revoke(string caRequestID, string hexSerialNumber, uint revocationReason) + { + _logger.LogTrace("Entered Entrust Revoke method"); + + ECSClient client = ECSClient.InitializeClient(_config); + string reason = RevokeReasonToString(revocationReason); + string comment = $"Revoked by Entrust Gateway for the following reason: {reason}"; + var cert = await GetSingleRecord(caRequestID); + + if (!string.Equals(reason, "keyCompromise")) + { + // Entrust no longer accepts any reason codes other than keyCompromise and unspecified. + reason = "unspecified"; + } + + if (!(cert.Status == (int)EndEntityStatus.GENERATED)) + { + string errorMessage = String.Format("Request {0} was not found in Entrust database or is not in a valid state to perform a revocation", caRequestID); + _logger.LogError(errorMessage); + throw new Exception(errorMessage); + } + client.RevokeCertificate(Int32.Parse(caRequestID), reason, comment); + + return (int)EndEntityStatus.REVOKED; + } + + public async Task Synchronize(BlockingCollection blockingBuffer, DateTime? lastSync, bool fullSync, CancellationToken cancelToken) + { + int deniedCerts = 0; + int totalSkipped = 0; + ECSClient client = ECSClient.InitializeClient(_config); + List allCerts = client.GetAllCertificates(); + bool ignoreExpired = false; + if (_config.IgnoreExpired.HasValue) + { + ignoreExpired = _config.IgnoreExpired.Value; + } + foreach (Certificate entrustCert in allCerts) + { + cancelToken.ThrowIfCancellationRequested(); + + if (entrustCert.ExpiresAfter.GetValueOrDefault() <= DateTime.UtcNow && ignoreExpired) + { + _logger.LogTrace($"The certificate with serial number '{entrustCert.SerialNumber}' is expired and IgnoreExpired is true. Skipping."); + continue; + } + + // Set up request ID. + string caRequestId = entrustCert.TrackingId.ToString(); + + // If the tracking ID is 0, log it and modify the request ID. + if (entrustCert.TrackingId == 0) + { + _logger.LogWarning($"The certificate with serial number '{entrustCert.SerialNumber}' has a tracking ID of 0. Will attempt to sync using thumbprint."); + + string thumbprint = GetThumbprint(entrustCert); + if (string.IsNullOrEmpty(thumbprint)) + { + _logger.LogWarning("The thumbprint could not be found. Skipping certificate."); + ++totalSkipped; + continue; + } + + caRequestId = $"0-{thumbprint}"; + } + + try + { + // Find cert within the database + int dbCertStatus; + try + { + dbCertStatus = await _certificateDataReader.GetStatusByRequestID(caRequestId); + } + catch + { + //Record not found in database + dbCertStatus = -1; + } + + // Get status and check to see if we need to skip it. + int entrustStatus = ConvertStatus(entrustCert.Status, caRequestId); + if (entrustStatus == (int)EndEntityStatus.FAILED) + { + _logger.LogWarning($"Certificate with tracking ID '{entrustCert.TrackingId}' has a status of FAILED and will be skipped, as it has no certificate record."); + ++deniedCerts; + continue; + } + + // If the cert exists, check the status and see if it's different from the cert from Entrust + // If doing a full sync, update the record anyway (in case other fields have changed) + if (dbCertStatus >= 0) + { + if (dbCertStatus != entrustStatus || fullSync) + { + AnyCAPluginCertificate newCert = entrustCert.TrackingId != 0 ? GetRecordByTrackingId(entrustCert.TrackingId) : GetRecordByThumbprint(GetThumbprint(entrustCert)); + blockingBuffer.Add(newCert); + } + } + else + { + AnyCAPluginCertificate newCert = entrustCert.TrackingId != 0 ? GetRecordByTrackingId(entrustCert.TrackingId) : GetRecordByThumbprint(GetThumbprint(entrustCert)); + blockingBuffer.Add(newCert); + } + } + catch (Exception e) + { + _logger.LogError($"An error occurred while processing certificate with tracking ID '{entrustCert.TrackingId}', skipping.", e); + ++totalSkipped; + } + } + + _logger.LogDebug($"Synchronization skipped a total of {deniedCerts} certificates with the 'DECLINED' status."); + } + + public async Task ValidateCAConnectionInfo(Dictionary connectionInfo) + { + _logger.MethodEntry(LogLevel.Trace); + + try + { + if (!(bool)connectionInfo[Constants.Config.ENABLED]) + { + _logger.LogWarning($"The CA is currently in the Disabled state. It must be Enabled to perform operations. Skipping validation..."); + _logger.MethodExit(LogLevel.Trace); + return; + } + } + catch (Exception ex) + { + _logger.LogError($"Exception: {LogHandler.FlattenException(ex)}"); + } + + List errors = new List(); + + _logger.LogTrace("Checking the Username"); + string username = connectionInfo.ContainsKey(Constants.Config.USERNAME) ? (string)connectionInfo[Constants.Config.USERNAME] : string.Empty; + if (string.IsNullOrWhiteSpace(username)) + { + errors.Add("The username is required"); + } + + _logger.LogTrace("Checking the Password"); + string password = connectionInfo.ContainsKey(Constants.Config.PASSWORD) ? (string)connectionInfo[Constants.Config.PASSWORD] : string.Empty; + if (string.IsNullOrWhiteSpace(password)) + { + errors.Add("The password is required"); + } + + _logger.LogTrace("Checking the user information"); + string name = connectionInfo.ContainsKey(Constants.Config.NAME) ? (string)connectionInfo[Constants.Config.NAME] : string.Empty; + if (string.IsNullOrWhiteSpace(name)) + { + errors.Add("The name is required"); + } + + string email = connectionInfo.ContainsKey(Constants.Config.EMAIL) ? (string)connectionInfo[Constants.Config.EMAIL] : string.Empty; + if (string.IsNullOrWhiteSpace(email)) + { + errors.Add("The email is required"); + } + + string number = connectionInfo.ContainsKey(Constants.Config.PHONE) ? (string)connectionInfo[Constants.Config.PHONE] : string.Empty; + if (string.IsNullOrWhiteSpace(number)) + { + errors.Add("The phone number is required"); + } + + ECSConfig tempConfig = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(connectionInfo)); + + ECSClient client = ECSClient.InitializeClient(tempConfig); + try + { + List clients = client.GetClients(); + if (clients.Count <= 0) + { + errors.Add($"Checking clients to determine Entrust connection failed."); + } + } + catch (Exception e) + { + errors.Add($"An error occured when trying to connect to Entrust. {e.Message}"); + } + _logger.LogTrace("Leaving 'ValidateCAConnectionInfo' method."); + + // We cannot proceed if there are any errors. + if (errors.Any()) + { + ThrowValidationException(errors); + } + } + + private void ThrowValidationException(List errors) + { + throw new AnyCAValidationException(string.Join("\n", errors)); + } + + public async Task ValidateProductInfo(EnrollmentProductInfo productInfo, Dictionary connectionInfo) + { + string productId = productInfo.ProductID; + ECSConfig tempConfig = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(connectionInfo)); + + ECSClient client = ECSClient.InitializeClient(tempConfig); + _logger.LogTrace("Checking inventory"); + + bool inventory = EntrustCertType.ProductIDValid(client, productId); + if (!inventory) + { + throw new Exception($"The product ID '{productId}' could not be validated."); + } + else + { + _logger.LogTrace($"Validation for product ID '{productId}' successful"); + } + } + + private int ConvertStatus(string status, string certId) + { + switch (status.ToLower()) + { + case "active": + case "ready": + case "reissued": + case "renewed": + case "expired": + return (int)EndEntityStatus.GENERATED; + + case "pending": + return (int)EndEntityStatus.EXTERNALVALIDATION; + + case "deactivated": + case "suspended": + case "revoked": + return (int)EndEntityStatus.REVOKED; + + case "declined": + return (int)EndEntityStatus.FAILED; + + default: + _logger.LogError($"Order {certId} has unexpected status {status}"); + throw new Exception($"Order {certId} has unknown status {status}"); + } + } + + public static string RevokeReasonToString(UInt32 revokeType) + { + switch (revokeType) + { + case 1: + case 2: // Entrust doesn't accept CA Compromised, since they get to decide that, not us + return "keyCompromise"; + case 3: + return "affiliationChanged"; + case 4: + return "superseded"; + case 5: + case 6: // Entrust doesn't accept Certificate Hold + return "cessationOfOperation"; + default: + return "affiliationChanged"; + } + } + + private string GetThumbprint(Certificate entrustCert) + { + // It seems as if this URL is the only place we can actually get the thumbprint. + if (entrustCert.URI.Contains("/thumbprints/")) + { + string[] parts = entrustCert.URI.Split(new string[] { "/thumbprints/" }, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length > 1) + { + // Trim just in case some URIs come back with trailing slash. + return parts.Last().Trim('/').ToUpper(); + } + } + + // If the URL doesn't contain thumbprint, we return nothing. + return null; + } + + /// + /// Gets a single record by its tracking ID. + /// + /// The Entrust REST API client. + /// The tracking ID of the cert we want. + /// + private AnyCAPluginCertificate GetRecordByTrackingId(int trackingId) + { + ECSClient client = ECSClient.InitializeClient(_config); + CertificateExt entrustCertDetail = client.GetCertificateByTrackingId(trackingId); + string cert = !string.IsNullOrEmpty(entrustCertDetail.EndEntityCert) ? entrustCertDetail.EndEntityCert : null; + int statusCode = ConvertStatus(entrustCertDetail.Status, trackingId.ToString()); + + AnyCAPluginCertificate newCert = new AnyCAPluginCertificate + { + CARequestID = trackingId.ToString(), + Certificate = cert, + Status = statusCode, + CSR = !string.IsNullOrEmpty(entrustCertDetail.Csr) ? entrustCertDetail.Csr : null, + RevocationDate = entrustCertDetail.Tracking.Deactivated ? entrustCertDetail.Tracking.DeactivatedOn ?? DateTime.UtcNow : (DateTime?)null, + ProductID = entrustCertDetail.CertType + }; + return newCert; + } + + /// + /// Gets a single record by its thumbprint. + /// + /// The Entrust REST API client. + /// The thumbprint of the cert we want. + /// + private AnyCAPluginCertificate GetRecordByThumbprint(string thumbprint) + { + ECSClient client = ECSClient.InitializeClient(_config); + CertificateExt entrustCertDetail = client.GetCertificateByThumbprint(thumbprint); + string cert = !string.IsNullOrEmpty(entrustCertDetail.EndEntityCert) ? entrustCertDetail.EndEntityCert : null; + int statusCode = entrustCertDetail.Status.Equals("UNKNOWN", StringComparison.OrdinalIgnoreCase) ? (int)EndEntityStatus.EXTERNALVALIDATION : ConvertStatus(entrustCertDetail.Status, $"0-{thumbprint}"); + AnyCAPluginCertificate newCert = new AnyCAPluginCertificate + { + CARequestID = $"0-{thumbprint}", + Certificate = cert, + Status = statusCode, + CSR = !string.IsNullOrEmpty(entrustCertDetail.Csr) ? entrustCertDetail.Csr : null, + RevocationDate = entrustCertDetail.Tracking.Deactivated ? entrustCertDetail.Tracking.DeactivatedOn ?? DateTime.UtcNow : (DateTime?)null, + ProductID = entrustCertDetail.CertType + }; + return newCert; + } + + private int GetTrackingId(EnrollmentProductInfo enrollmentProductInfo) + { + ECSClient client = ECSClient.InitializeClient(_config); + if (enrollmentProductInfo.ProductParameters.ContainsKey("PriorCertSN")) + { + //get prior cert serial number + string attrPriorCertSN = enrollmentProductInfo.ProductParameters["PriorCertSN"]; + + //requesting certificate by serial number + Certificate priorCertTemp = client.GetCertificateBySerialNumber(attrPriorCertSN); + if (priorCertTemp != null) + { + return priorCertTemp.TrackingId; + } + else + { + _logger.LogTrace($"No certificate found with serial number {enrollmentProductInfo.ProductParameters["PriorCertSN"]}."); + } + } + + throw new Exception($"Reissue requested, but certificate with serial number {enrollmentProductInfo.ProductParameters["PriorCertSN"]} not found."); + } + + private ValueTuple ApproveCert(int trackingId) + { + var client = ECSClient.InitializeClient(_config); + CertificateResponse approveResult = client.ApproveCertificate(trackingId); + CertificateExt changedCert = client.GetCertificateByTrackingId(trackingId); + int newStatus = ConvertStatus(changedCert.Status, trackingId.ToString()); + + if (newStatus == (int)EndEntityStatus.EXTERNALVALIDATION) + { + return ((int)EndEntityStatus.EXTERNALVALIDATION, $"Certificate with trackingId {trackingId} is still pending after approval attempt. External validation is required."); + } + else if (newStatus == (int)EndEntityStatus.GENERATED) + { + return (newStatus, $"Certificate with trackingId {trackingId} has been issued after Entrust returned it with a pending status."); + } + + throw new Exception($"Unable to approve certificate with trackingId {trackingId}. Status is neither issued or pending. "); + } + } +} diff --git a/entrust-ecs-caplugin/ECSConfig.cs b/entrust-ecs-caplugin/ECSConfig.cs new file mode 100644 index 0000000..e5c3b0c --- /dev/null +++ b/entrust-ecs-caplugin/ECSConfig.cs @@ -0,0 +1,37 @@ +// Copyright 2024 Keyfactor +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Transactions; + +namespace Keyfactor.Extensions.CAPlugin.Entrust +{ + public class ECSConfig + { + public string AuthUsername { get; set; } + public string AuthPassword { get; set; } + public AuthCert ClientCertificate { get; set; } + public string Name { get; set; } + public string Email { get; set; } + public string PhoneNumber { get; set; } + public bool? IgnoreExpired { get; set; } + public bool Enabled { get; set; } = true; + } + + public class AuthCert + { + public string StoreName { get; set; } + public string StoreLocation { get; set; } + public string Thumbprint { get; set; } + public string CertificatePath { get; set; } + public string CertificatePassword { get; set; } + } +} diff --git a/entrust-ecs-caplugin/Models/CertTypes.cs b/entrust-ecs-caplugin/Models/CertTypes.cs new file mode 100644 index 0000000..e60a629 --- /dev/null +++ b/entrust-ecs-caplugin/Models/CertTypes.cs @@ -0,0 +1,133 @@ +// Copyright 2024 Keyfactor +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using Keyfactor.Extensions.CAPlugin.Entrust.API; +using Keyfactor.Extensions.CAPlugin.Entrust.Client; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.CAPlugin.Entrust.Models +{ + public class EntrustCertType + { + public string ShortName { get; set; } + public string ProductCode { get; set; } + public string DisplayName { get; set; } + + #region Product Types + + public static EntrustCertType Standard = new EntrustCertType() { ShortName = "STANDARD_SSL", ProductCode = "STANDARD_SSL", DisplayName = "Standard SSL" }; + public static EntrustCertType Advantage = new EntrustCertType() { ShortName = "ADVANTAGE_SSL", ProductCode = "ADVANTAGE_SSL", DisplayName = "Advantage SSL" }; + public static EntrustCertType UC = new EntrustCertType() { ShortName = "UC_SSL", ProductCode = "UC_SSL", DisplayName = "UC SSL" }; + + public static EntrustCertType EV = new EntrustCertType() { ShortName = "EV_SSL", ProductCode = "EV_SSL", DisplayName = "EV SSL" }; + public static EntrustCertType QWAC = new EntrustCertType() { ShortName = "QWAC_SSL", ProductCode = "QWAC_SSL", DisplayName = "QWAC SSL" }; + public static EntrustCertType PSD2 = new EntrustCertType() { ShortName = "PSD2_SSL", ProductCode = "PSD2_SSL", DisplayName = "PSD2 SSL" }; + + public static EntrustCertType Wildcard = new EntrustCertType() { ShortName = "WILDCARD_SSL", ProductCode = "WILDCARD_SSL", DisplayName = "Wildcard SSL" }; + public static EntrustCertType Private = new EntrustCertType() { ShortName = "PRIVATE_SSL", ProductCode = "PRIVATE_SSL", DisplayName = "Private SSL" }; + public static EntrustCertType PD = new EntrustCertType() { ShortName = "PD_SSL", ProductCode = "PD_SSL", DisplayName = "PD SSL" }; + public static EntrustCertType CodeSigning = new EntrustCertType() { ShortName = "CODE_SIGNING", ProductCode = "CODE_SIGNING", DisplayName = "Code Signing" }; + public static EntrustCertType EVCodeSigning = new EntrustCertType() { ShortName = "EV_CODE_SIGNING", ProductCode = "EV_CODE_SIGNING", DisplayName = "EV Code Signing" }; + public static EntrustCertType CDSIndividual = new EntrustCertType() { ShortName = "CDS_INDIVIDUAL", ProductCode = "CDS_INDIVIDUAL", DisplayName = "CDS Individual" }; + public static EntrustCertType CDSGroup = new EntrustCertType() { ShortName = "CDS_GROUP", ProductCode = "CDS_GROUP", DisplayName = "CDS Group" }; + public static EntrustCertType CDSEntLite = new EntrustCertType() { ShortName = "CDS_ENT_LITE", ProductCode = "CDS_ENT_LITE", DisplayName = "CDS Ent Lite" }; + public static EntrustCertType CDSEntPro = new EntrustCertType() { ShortName = "CDS_ENT_PRO", ProductCode = "CDS_ENT_PRO", DisplayName = "CDS Ent Pro" }; + public static EntrustCertType SMIMEEnt = new EntrustCertType() { ShortName = "SMIME_ENT", ProductCode = "SMIME_ENT", DisplayName = "SMIME Ent" }; + + /// + /// Master list of all product types. + /// + public static new List AllTypes = new List() { Standard, Advantage, UC, EV, QWAC, PSD2, Wildcard, Private, PD, CodeSigning, EVCodeSigning, CDSIndividual, CDSGroup, CDSEntLite, CDSEntPro, SMIMEEnt }; + + /// + /// The certificate types that are available through FLEX inventory. + /// + private static List FlexTypes => AllTypes.Where(x => x.ShortName.Contains("SSL") && x.ShortName != "PD_SSL").ToList(); + + #endregion Product Types + + + #region Methods + + /// + /// Checks if inventory exists for the product type provided. + /// + /// The is used to call out to the Entrust API. + /// The product type we seek to check the inventory of. + /// + public static bool InventoryExists(ECSClient client, string productType) + { + // Gets an inventory with all of the product types. + List inventoryItems = client.GetInventories(); + + InventoryItem inventory = inventoryItems.FirstOrDefault(x => x.ProductType.Equals(productType, StringComparison.CurrentCultureIgnoreCase)); + if (inventory == null) + { + inventory = inventoryItems.FirstOrDefault(x => x.ProductType.Equals("FLEX", StringComparison.CurrentCultureIgnoreCase)); + } + + return inventory != null && inventory.RemainingCount > 0; + } + + /// + /// Checks if the product ID exists in Entrust. + /// + /// The is used to call out to the Entrust API. + /// The product type we seek to check the inventory of. + /// + public static bool ProductIDValid(ECSClient client, string productType) + { + // Client should always be valid, but sanity check it anyway. + if (client == null) + { + return false; + } + + // Try to find the product they asked for. + EntrustCertType inventory = GetCustomerAccountTypes(client) + ?.FirstOrDefault(x => x.ProductCode.Equals(productType, StringComparison.CurrentCultureIgnoreCase)); + + return inventory != null; + } + + /// + /// Gets a complete list of product types for a customer's account, including both the base types and the FLEX product types. + /// + /// The making the API call to get the product types. + /// + public static List GetCustomerAccountTypes(ECSClient client) + { + if (client == null) + { + throw new Exception("The client does not have a value, and therefore the customer account types cannot be retrieved."); + } + + // Gets an inventory with all of the product types. + List inventoryItems = client.GetInventories(); + + // Add the base types. + List customerTypes = AllTypes + .Where(x => inventoryItems.Any(y => y.ProductType == x.ProductCode)) + .ToList(); + + // Add any types we get through FLEX inventory. + if (inventoryItems.Any(x => x.ProductType == "FLEX")) + { + customerTypes.AddRange(FlexTypes.Where(x => !customerTypes.Any(y => y.ShortName == x.ShortName))); + } + + return customerTypes; + } + + #endregion Methods + } +} diff --git a/entrust-ecs-caplugin/Models/CertificateOperation.cs b/entrust-ecs-caplugin/Models/CertificateOperation.cs new file mode 100644 index 0000000..1e1a6ea --- /dev/null +++ b/entrust-ecs-caplugin/Models/CertificateOperation.cs @@ -0,0 +1,23 @@ +// Copyright 2024 Keyfactor +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.CAPlugin.Entrust.Models +{ + public class CertificateOperation + { + public static string APPROVE = "APPROVE"; + public static string DECLINE = "DECLINE"; + public static string SUSPEND = "SUSPEND"; + public static string UNSUSPEND = "UNSUSPEND"; + } +} diff --git a/entrust-ecs-caplugin/Models/CertificateRequest.cs b/entrust-ecs-caplugin/Models/CertificateRequest.cs new file mode 100644 index 0000000..b3567bd --- /dev/null +++ b/entrust-ecs-caplugin/Models/CertificateRequest.cs @@ -0,0 +1,78 @@ +// Copyright 2024 Keyfactor +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using Newtonsoft.Json; + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.CAPlugin.Entrust.Models +{ + public class CertificateRequest + { + [JsonProperty("csr")] + public string CSR { get; set; } + + [JsonProperty("subjectAltName")] + public List SubjectAltName { get; set; } + + [JsonProperty("signingAlg")] + public string SigningAlg { get; set; } = "SHA-2"; + + [JsonProperty("eku")] + public string EKU { get; set; } + + [JsonProperty("ctLog")] + public bool? CTLog { get; set; } + + [JsonProperty("cn")] + public string CN { get; set; } + + [JsonProperty("certEmail")] + public string CertEmail { get; set; } + + [JsonProperty("upn")] + public string UPN { get; set; } + + [JsonProperty("clientId")] + public int? ClientId { get; set; } + + [JsonProperty("org")] + public string Org { get; set; } + + [JsonProperty("ou")] + public List OU { get; set; } + + [JsonProperty("password")] + public string Password { get; set; } + + [JsonProperty("tracking")] + public Tracking Tracking { get; set; } + + [JsonProperty("endUserKeyStorageAgreement")] + public bool? EndUserKeyStorageAgreement { get; set; } + + [JsonProperty("queueForApproval")] + public bool? QueueForApproval { get; set; } + + [JsonProperty("certExpiryDate")] + public DateTime? CertExpiryDate { get; set; } + + [JsonProperty("certLifetime")] + public string CertLifetime { get; set; } + + [JsonProperty("validateOnly")] + public bool? ValidateOnly { get; set; } + + [JsonProperty("certType")] + public string CertType { get; set; } + } +} diff --git a/entrust-ecs-caplugin/Models/Tracking.cs b/entrust-ecs-caplugin/Models/Tracking.cs new file mode 100644 index 0000000..d472ea5 --- /dev/null +++ b/entrust-ecs-caplugin/Models/Tracking.cs @@ -0,0 +1,38 @@ +// Copyright 2024 Keyfactor +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions +// and limitations under the License. + +using Newtonsoft.Json; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.CAPlugin.Entrust.Models +{ + public class Tracking + { + [JsonProperty("trackingInfo")] + public string TrackingInfo { get; set; } + + [JsonProperty("requesterName")] + public string RequesterName { get; set; } + + [JsonProperty("requesterEmail")] + public string RequesterEmail { get; set; } + + [JsonProperty("requesterPhone")] + public string RequesterPhone { get; set; } + + [JsonProperty("deactivated")] + public bool Deactivated { get; set; } + + [JsonProperty("deactivatedOn")] + public DateTime? DeactivatedOn { get; set; } + } +} diff --git a/entrust-ecs-caplugin/entrust-ecs-caplugin.csproj b/entrust-ecs-caplugin/entrust-ecs-caplugin.csproj new file mode 100644 index 0000000..ceb2a3c --- /dev/null +++ b/entrust-ecs-caplugin/entrust-ecs-caplugin.csproj @@ -0,0 +1,26 @@ + + + + net6.0 + Keyfactor.Extensions.CAPlugin.Entrust + disable + disable + EntrustECSCAPlugin + + + + + + + + + + + + + Always + + + + + diff --git a/entrust-ecs-caplugin/manifest.json b/entrust-ecs-caplugin/manifest.json new file mode 100644 index 0000000..27b73aa --- /dev/null +++ b/entrust-ecs-caplugin/manifest.json @@ -0,0 +1,10 @@ +{ + "extensions": { + "Keyfactor.AnyGateway.Extensions.IAnyCAPlugin": { + "EntrustECSCAPlugin": { + "assemblypath": "EntrustECSCAPlugin.dll", + "TypeFullName": "Keyfactor.Extensions.CAPlugin.Entrust.ECSCAPlugin" + } + } + } +} diff --git a/integration-manifest.json b/integration-manifest.json index 4beca57..6177375 100644 --- a/integration-manifest.json +++ b/integration-manifest.json @@ -1,12 +1,69 @@ { - "$schema": "https://keyfactor.github.io/integration-manifest-schema.json", - "integration_type": "ca-gateway", - "name": "", - "status": "prototype", - "support_level": "community", - "link_github": false, - "update_catalog": false, - "description": "", - "gateway_framework": "10.x.x", - "release_dir": "UPDATE-THIS-WITH-PATH-TO-BINARY-BUILD-FOLDER" -} + "$schema": "https://keyfactor.github.io/v2/integration-manifest-schema.json", + "integration_type": "anyca-plugin", + "name": "Entrust ECS AnyCA REST Gateway Plugin", + "status": "production", + "support_level": "kf-supported", + "link_github": true, + "update_catalog": true, + "description": "Entrust ECS plugin for the AnyCA REST Gateway framework", + "gateway_framework": "24.2.0", + "release_dir": "entrust-ecs-caplugin/bin/Release/net6.0", + "about": { + "carest": { + "product_ids": [], + "ca_plugin_config": [ + { + "name": "AuthUsername", + "description": "Username for the gateway to authenticate with Entrust" + }, + { + "name": "AuthPassword", + "description": "Password for the account used to authenticate with Entrust" + }, + { + "name": "ClientCertificate", + "description": "The client certificate information used to authenticate with Entrust (if configured to use certificate authentication). This can be either a Windows cert store name and location (e.g. 'My' and 'LocalMachine' for the Local Computer personal cert store) and thumbprint, or a PFX file and password." + }, + { + "name": "Name", + "description": "The default requester name" + }, + { + "name": "Email", + "description": "The default requester email address" + }, + { + "name": "PhoneNumber", + "description": "The default requester phone number" + }, + { + "name": "IgnoreExpired", + "description": "If set to true, will not sync expired certs from Entrust" + }, + { + "name": "Enabled", + "description": "Flag to Enable or Disable gateway functionality. Disabling is primarily used to allow creation of the CA prior to configuration information being available." + } + ], + "enrollment_config": [ + { + "name": "LifetimeMonths", + "description": "OPTIONAL: The number of months of validity to use when requesting certs. If not provided, default is 12." + }, + { + "name": "Organization", + "description": "OPTIONAL: For requests that will not have a subject (such as ACME) you can use this field to provide an organization name. Value supplied here will override any CSR values, so do not include this field if you want the organization from the CSR to be used." + }, + { + "name": "CertificateUsage", + "description": "Required for public SSL certificate types. Represents the key usage for the certificates enrolled against this template. Valid values are 'server', 'client', or 'serverclient'. Do not provide a value for cert types that are not public SSL." + }, + { + "name": "RenewalWindowDays", + "description": "OPTIONAL: The number of days from certificate expiration that the gateway should do a renewal rather than a reissue. If not provided, default is 90." + } + ] + } + } +} \ No newline at end of file