Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,13 @@ Fix for JSON serialization of revocation
1.1.0
Add support for using the cert upload feature to upload auth certs
Switch to .NET 8

1.1.1
Allow for manual specification of enrollment term length
Add Lifetime parameter to allow for manual specification of cert validity
Bugfix - Properly handle syncs of 0 records
Allow for manual specification of enrollment term length

1.1.2
Add Lifetime parameter to allow for manual specification of cert validity
Bugfix - Properly handle syncs of 0 records
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<h1 align="center" style="border-bottom: none">
Sectigo Certificate Manager Gateway AnyCA Gateway REST Plugin
Sectigo Certificate Manager AnyCA Gateway REST Plugin
</h1>

<p align="center">
Expand Down Expand Up @@ -45,10 +45,10 @@ The Sectigo AnyCA Gateway REST plugin extends the capabilities of the Sectigo Ce

## Compatibility

The Sectigo Certificate Manager Gateway AnyCA Gateway REST plugin is compatible with the Keyfactor AnyCA Gateway REST 24.2.0 and later.
The Sectigo Certificate Manager AnyCA Gateway REST plugin is compatible with the Keyfactor AnyCA Gateway REST 24.2.0 and later.

## Support
The Sectigo Certificate Manager 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 Sectigo Certificate Manager AnyCA Gateway REST plugin is supported by Keyfactor for Keyfactor customers. If you have a support issue, please open a support ticket with your Keyfactor representative. If you have a support issue, please open a support ticket via the Keyfactor Support Portal at https://support.keyfactor.com.

> To report a problem or suggest a new feature, use the **[Issues](../../issues)** tab. If you want to contribute actual bug fixes or proposed enhancements, use the **[Pull requests](../../pulls)** tab.

Expand All @@ -61,7 +61,7 @@ In addition, for the admin account you plan to use, make sure it has the API adm

1. Install the AnyCA Gateway REST per the [official Keyfactor documentation](https://software.keyfactor.com/Guides/AnyCAGatewayREST/Content/AnyCAGatewayREST/InstallIntroduction.htm).

2. On the server hosting the AnyCA Gateway REST, download and unzip the latest [Sectigo Certificate Manager Gateway AnyCA Gateway REST plugin](https://github.com/Keyfactor/sectigo-scm-caplugin/releases/latest) from GitHub.
2. On the server hosting the AnyCA Gateway REST, download and unzip the latest [Sectigo Certificate Manager AnyCA Gateway REST plugin](https://github.com/Keyfactor/sectigo-scm-caplugin/releases/latest) from GitHub.

3. Copy the unzipped directory (usually called `net6.0` or `net8.0`) to the Extensions directory:

Expand All @@ -72,11 +72,11 @@ In addition, for the admin account you plan to use, make sure it has the API adm
Program Files\Keyfactor\AnyCA Gateway\AnyGatewayREST\net8.0\Extensions
```

> The directory containing the Sectigo Certificate Manager Gateway AnyCA Gateway REST plugin DLLs (`net6.0` or `net8.0`) can be named anything, as long as it is unique within the `Extensions` directory.
> The directory containing the Sectigo Certificate Manager AnyCA Gateway REST plugin DLLs (`net6.0` or `net8.0`) can be named anything, as long as it is unique within the `Extensions` directory.

4. Restart the AnyCA Gateway REST service.

5. Navigate to the AnyCA Gateway REST portal and verify that the Gateway recognizes the Sectigo Certificate Manager Gateway plugin by hovering over the ⓘ symbol to the right of the Gateway on the top left of the portal.
5. Navigate to the AnyCA Gateway REST portal and verify that the Gateway recognizes the Sectigo Certificate Manager plugin by hovering over the ⓘ symbol to the right of the Gateway on the top left of the portal.

## Configuration

Expand Down Expand Up @@ -113,6 +113,7 @@ In addition, for the admin account you plan to use, make sure it has the API adm
* **MultiDomain** - This flag lets Keyfactor know if the certificate can contain multiple domain names. Depending on the setting, the SAN entries of the request will change to support Sectigo requirements.
* **Organization** - If the organization name is provided here, the Sectigo gateway will use that organization name in requests instead of whatever is in the O= field in the request subject.
* **Department** - If your Sectigo account is using department-level products, put the appropriate department name here. Previously, this was alternatively supplied in the OU= subject field, which is now deprecated.
* **Lifetime** - OPTIONAL: The term length (in days) to use for enrollment. If not provided, the default is the first value available in the profile definition in your Sectigo account.



Expand Down
4 changes: 4 additions & 0 deletions integration-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@
{
"name": "Department",
"description": "If your Sectigo account is using department-level products, put the appropriate department name here. Previously, this was alternatively supplied in the OU= subject field, which is now deprecated."
},
{
"name": "Lifetime",
"description": "OPTIONAL: The term length (in days) to use for enrollment. If not provided, the default is the first value available in the profile definition in your Sectigo account."
}
]
}
Expand Down
81 changes: 28 additions & 53 deletions sectigo-scm-caplugin/Client/SectigoClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

using Org.BouncyCastle.Asn1.Ocsp;

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
Expand All @@ -19,6 +21,8 @@
using System.Threading;
using System.Threading.Tasks;

using Error = Keyfactor.Extensions.CAPlugin.Sectigo.API.Error;

namespace Keyfactor.Extensions.CAPlugin.Sectigo.Client
{
public class SectigoClient
Expand All @@ -34,7 +38,9 @@ public SectigoClient(HttpClient client)

public async Task<Certificate> GetCertificate(int sslId)
{
var response = await RestClient.GetAsync($"api/ssl/v1/{sslId}");
string url = $"api/ssl/v1/{sslId}";
Logger.LogTrace($"API Request: GET {url}");
var response = await RestClient.GetAsync(url);
return await ProcessResponse<Certificate>(response);
}

Expand All @@ -61,7 +67,10 @@ public async Task CertificateListProducer(BlockingCollection<Certificate> certs,
Logger.LogInformation($"Request Certificates at Position {certIndex} with Page Size {pageSize}");
certificatePageToProcess = await PageCertificates(certIndex, pageSize, filter);
Logger.LogDebug($"Found {certificatePageToProcess.Count} certificate to process");

if (certificatePageToProcess.Count == 0)
{
return;
}
//Processing Loop will add and retry adding to queue until all certificates have been processed for a page
batchCount = 0;
blockedCount = 0;
Expand Down Expand Up @@ -136,7 +145,7 @@ public async Task CertificateListProducer(BlockingCollection<Certificate> certs,
public async Task<List<Certificate>> PageCertificates(int position = 0, int size = 25, string filter = "")
{
string filterQueryString = string.IsNullOrEmpty(filter) ? string.Empty : $"&{filter}";
Logger.LogTrace($"API Request: api/ssl/v1?position={position}&size={size}{filterQueryString}".TrimEnd());
Logger.LogTrace($"API Request: GET api/ssl/v1?position={position}&size={size}{filterQueryString}".TrimEnd());
var response = await RestClient.GetAsync($"api/ssl/v1?position={position}&size={size}{filterQueryString}".TrimEnd());
return await ProcessResponse<List<Certificate>>(response);
}
Expand All @@ -148,37 +157,26 @@ public async Task<bool> RevokeSslCertificateById(int sslId, int revcode, string
reasonCode = revcode,
reason = revreason
};
Logger.LogTrace($"API Request: POST api/ssl/v1/revoke/{sslId}\nParameters: {JsonConvert.SerializeObject(data, Formatting.Indented)}");
var response = await RestClient.PostAsJsonAsync($"api/ssl/v1/revoke/{sslId}", data);
if (response.IsSuccessStatusCode)
{
return true;
}
var failedResp = ProcessResponse<RevocationResponse>(response).Result;
return failedResp.IsSuccess;//Should throw an exception with error message from API
var resp = ProcessResponse<RevocationResponse>(response).Result;

return true;//Should throw an exception with error message from API, should only hit this if success
}

public async Task<ListOrganizationsResponse> ListOrganizations()
{
Logger.LogTrace($"API Request: GET api/organization/v1");
var response = await RestClient.GetAsync("api/organization/v1");
if (response.IsSuccessStatusCode)
{
string responseContent = await response.Content.ReadAsStringAsync();
Logger.LogTrace($"Raw Response: {responseContent}");
}
var orgsResponse = await ProcessResponse<List<Organization>>(response);

return new ListOrganizationsResponse { Organizations = orgsResponse };
}

public async Task<OrganizationDetailsResponse> GetOrganizationDetails(int orgId)
{
Logger.LogTrace($"API Request: GET api/organization/v1/{orgId}");
var response = await RestClient.GetAsync($"api/organization/v1/{orgId}");
if (response.IsSuccessStatusCode)
{
string responseContent = await response.Content.ReadAsStringAsync();
Logger.LogTrace($"Raw Response: {responseContent}");
}

var orgDetailsResponse = await ProcessResponse<OrganizationDetailsResponse>(response);
return orgDetailsResponse;
}
Expand All @@ -200,6 +198,7 @@ public async Task<ListPersonsResponse> ListPersons(int orgId)

public async Task<ListCustomFieldsResponse> ListCustomFields()
{
Logger.LogTrace($"API Request: GET api/ssl/v1/customFields");
var response = await RestClient.GetAsync("api/ssl/v1/customFields");
return new ListCustomFieldsResponse { CustomFields = await ProcessResponse<List<CustomField>>(response) };
}
Expand All @@ -211,13 +210,14 @@ public async Task<ListSslProfilesResponse> ListSslProfiles(int? orgId = null)
{
urlSuffix = $"?organizationId={orgId}";
}

Logger.LogTrace($"API Request: GET api/ssl/v1/types{urlSuffix}");
var response = await RestClient.GetAsync($"api/ssl/v1/types{urlSuffix}");
return new ListSslProfilesResponse { SslProfiles = await ProcessResponse<List<Profile>>(response) };
}

public async Task<List<Person>> PagePersons(int orgId, int position = 0, int size = 25)
{
Logger.LogTrace($"API Request: GET api/person/v1?position={position}&size={size}&organizationId={orgId}");
var response = await RestClient.GetAsync($"api/person/v1?position={position}&size={size}&organizationId={orgId}");
return await ProcessResponse<List<Person>>(response);
}
Expand All @@ -226,6 +226,7 @@ public async Task<int> Enroll(EnrollRequest request)
{
try
{
Logger.LogTrace($"API Request: POST api/ssl/v1/enroll\nParameters: {JsonConvert.SerializeObject(request, Formatting.Indented)}");
var response = await RestClient.PostAsJsonAsync("api/ssl/v1/enroll", request);
var enrollResponse = await ProcessResponse<EnrollResponse>(response);

Expand All @@ -245,35 +246,14 @@ public async Task<int> Enroll(EnrollRequest request)
}
}

public async Task<int> Renew(int sslId)
{
try
{
var response = await RestClient.PostAsJsonAsync($"api/ssl/v1/renewById/{sslId}", "");
var renewResponse = await ProcessResponse<EnrollResponse>(response);

return renewResponse.sslId;
}
catch (InvalidOperationException invalidOp)
{
throw new Exception($"Invalid Operation. {invalidOp.Message}|{invalidOp.StackTrace}");
}
catch (HttpRequestException httpEx)
{
throw new Exception($"HttpRequestException. {httpEx.Message}|{httpEx.StackTrace}");
}
catch (Exception)
{
throw;
}
}

public async Task<X509Certificate2> PickupCertificate(int sslId, string subject)
{
Logger.LogTrace($"API Request: GET api/ssl/v1/collect/{sslId}/x509C0");
var response = await RestClient.GetAsync($"api/ssl/v1/collect/{sslId}/x509CO");

if (response.IsSuccessStatusCode && response.Content.Headers.ContentLength > 0)
{
Logger.LogTrace($"Raw response: {response.Content.ReadAsStringAsync()}");
string pemChain = await response.Content.ReadAsStringAsync();

string[] splitChain = pemChain.Replace("\r\n", string.Empty).Split(new string[] { "-----" }, StringSplitOptions.RemoveEmptyEntries);
Expand All @@ -284,24 +264,19 @@ public async Task<X509Certificate2> PickupCertificate(int sslId, string subject)
//return new X509Certificate2();
}

public async Task Reissue(ReissueRequest request, int sslId)
{
var response = await RestClient.PostAsJsonAsync($"api/ssl/v1/replace/{sslId}", request);
response.EnsureSuccessStatusCode();
}

#region Static Methods

private static async Task<T> ProcessResponse<T>(HttpResponseMessage response)
{
string responseContent = await response.Content.ReadAsStringAsync();
Logger.LogDebug($"Raw API response: {responseContent}");
if (response.IsSuccessStatusCode)
{
string responseContent = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(responseContent);
}
else
{
var error = JsonConvert.DeserializeObject<Error>(await response.Content.ReadAsStringAsync());
var error = JsonConvert.DeserializeObject<Error>(responseContent);
throw new Exception($"{error.Code} | {error.Description}");
}
}
Expand Down
1 change: 1 addition & 0 deletions sectigo-scm-caplugin/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class Config
public const string MULTIDOMAIN = "MultiDomain";
public const string ORGANIZATION = "Organization";
public const string DEPARTMENT = "Department";
public const string LIFETIME = "Lifetime";
}

//headers for API client
Expand Down
Loading
Loading