Skip to content

Commit c01f0ce

Browse files
authored
Merge pull request #42 from Keyfactor/duplicates
add duplicate support
2 parents c3a719f + 1d8ade4 commit c01f0ce

7 files changed

Lines changed: 181 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,7 @@
2020
### 2.1.2
2121
* Hotfix for incremental sync to default to a 6 day window if no previous incremental sync has run
2222
* Workaround for DigiCert API issue where retrieving the PEM data of multiple certificates in the same order can occasionally return duplicate data rather than the correct cert
23-
* Remove caching of product ID lookups from DigiCert account
23+
* Remove caching of product ID lookups from DigiCert account
24+
25+
### 2.2.0
26+
* Add support for duplicating certs

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ An API Key within your Digicert account that has the necessary permissions to en
116116
* **UsageDesignation** - Required for secure_email_* types, ignored otherwise. The primary usage of the certificate. Valid values are: signing, key_management, dual_use
117117

118118

119+
## Certificate Duplicates
120+
121+
DigiCert supports the ability to duplicate existing certificate orders. To take advantage of this functionality, in Keyfactor Command, under the enrollment pattern you're using, create an Enrollment Field named 'Duplicate' of type Multiple Choice, and the values 'False', 'True'. When performing a renew operation against that enrollment pattern, set the value to True to tell the gateway to duplicate instead of renew. The field will be ignored on new enrollments.
122+
119123
120124
## License
121125
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using Keyfactor.Extensions.CAPlugin.DigiCert.Models;
2+
using Newtonsoft.Json;
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
10+
namespace Keyfactor.Extensions.CAPlugin.DigiCert.API
11+
{
12+
[Serializable]
13+
public class DuplicateRequest : CertCentralBaseRequest
14+
{
15+
public DuplicateRequest(uint orderId)
16+
{
17+
Method = "POST";
18+
OrderId = orderId;
19+
Resource = $"services/v2/order/certificate/{OrderId}/duplicate";
20+
Certificate = new CertificateDuplicateRequest();
21+
}
22+
23+
[JsonProperty("certificate")]
24+
public CertificateDuplicateRequest Certificate { get; set; }
25+
26+
[JsonProperty("order_id")]
27+
public uint OrderId { get; set; }
28+
29+
[JsonProperty("skip_approval")]
30+
public bool SkipApproval { get; set; }
31+
}
32+
33+
public class CertificateDuplicateRequest
34+
{
35+
[JsonProperty("common_name")]
36+
public string CommonName { get; set; }
37+
38+
[JsonProperty("dns_names")]
39+
public List<string> DnsNames { get; set; }
40+
41+
[JsonProperty("csr")]
42+
public string CSR { get; set; }
43+
44+
[JsonProperty("server_platform")]
45+
public Server_platform ServerPlatform { get; set; }
46+
47+
[JsonProperty("signature_hash")]
48+
public string SignatureHash { get; set; }
49+
50+
[JsonProperty("ca_cert_id")]
51+
public string CACertID { get; set; }
52+
}
53+
54+
public class DuplicateResponse : CertCentralBaseResponse
55+
{
56+
public DuplicateResponse()
57+
{
58+
Requests = new List<Requests>();
59+
}
60+
61+
[JsonProperty("id")]
62+
public int OrderId { get; set; }
63+
64+
[JsonProperty("requests")]
65+
public List<Requests> Requests { get; set; }
66+
67+
[JsonProperty("certificate_chain")]
68+
public List<CertificateChainElement> CertificateChain { get; set; }
69+
}
70+
}

digicert-certcentral-caplugin/CertCentralCAPlugin.cs

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using Newtonsoft.Json;
1717

1818
using Org.BouncyCastle.Asn1.X509;
19+
using Org.BouncyCastle.Pqc.Crypto.Falcon;
1920

2021
using System.Collections.Concurrent;
2122
using System.Runtime.InteropServices;
@@ -300,33 +301,56 @@ public async Task<EnrollmentResult> Enroll(string csr, string subject, Dictionar
300301
_logger.LogWarning($"{CertCentralConstants.Config.INCLUDE_CLIENT_AUTH}: Ability to include client auth EKU in SSL certs is currently planned to cease in May 2026. Make sure any workflows that depend on this feature are updated before then to avoid interruptions.");
301302
}
302303

303-
// Current gateway core leaves it up to the integration to determine if it is a renewal or a reissue
304+
bool dupe = false;
305+
// Current gateway core leaves it up to the integration to determine if it is a renewal, a reissue, or a duplicate
304306
if (enrollmentType == EnrollmentType.RenewOrReissue)
305307
{
306-
//// Determine if we're going to do a renew or a reissue.
308+
//// Determine if we're going to do a renew, reissue, or duplicate.
307309
priorCertSnString = productInfo.ProductParameters["PriorCertSN"];
308310
_logger.LogTrace($"Attempting to retrieve the certificate with serial number {priorCertSnString}.");
309-
var reqId = _certificateDataReader.GetRequestIDBySerialNumber(priorCertSnString).Result;
310-
if (string.IsNullOrEmpty(reqId))
311+
priorCertReqID = await _certificateDataReader.GetRequestIDBySerialNumber(priorCertSnString);
312+
if (string.IsNullOrEmpty(priorCertReqID))
311313
{
312314
throw new Exception($"No certificate with serial number '{priorCertSnString}' could be found.");
313315
}
314-
var expDate = _certificateDataReader.GetExpirationDateByRequestId(reqId);
315316

316-
var renewCutoff = DateTime.Now.AddDays(renewWindow * -1);
317-
318-
if (expDate > renewCutoff)
317+
if (productInfo.ProductParameters.ContainsKey(CertCentralConstants.Config.DUPLICATE))
318+
{
319+
string dupStr = productInfo.ProductParameters[CertCentralConstants.Config.DUPLICATE].ToString();
320+
if (!bool.TryParse(dupStr, out dupe))
321+
{
322+
_logger.LogError($"Could not parse 'Duplicate' field as true or false. Check configuration. Value: {dupStr}");
323+
throw new Exception($"Could not parse 'Duplicate' field as true or false. Check configuration");
324+
}
325+
}
326+
if (!dupe)
319327
{
320-
_logger.LogTrace($"Certificate with serial number {priorCertSnString} is within renewal window");
321-
enrollmentType = EnrollmentType.Renew;
328+
var expDate = _certificateDataReader.GetExpirationDateByRequestId(priorCertReqID);
329+
330+
var renewCutoff = DateTime.Now.AddDays(renewWindow * -1);
331+
332+
if (expDate > renewCutoff)
333+
{
334+
_logger.LogTrace($"Certificate with serial number {priorCertSnString} is within renewal window");
335+
enrollmentType = EnrollmentType.Renew;
336+
}
337+
else
338+
{
339+
_logger.LogTrace($"Certificate with serial number {priorCertSnString} is not within renewal window. Reissuing...");
340+
enrollmentType = EnrollmentType.Reissue;
341+
}
322342
}
323343
else
324344
{
325-
_logger.LogTrace($"Certificate with serial number {priorCertSnString} is not within renewal window. Reissuing...");
326-
enrollmentType = EnrollmentType.Reissue;
345+
_logger.LogTrace($"'Duplicate' flag set, performing duplication");
327346
}
328347
}
329348

349+
if (dupe)
350+
{
351+
return await Duplicate(client, productInfo, priorCertReqID, commonName, csr, dnsNames, signatureHash, caCertId);
352+
}
353+
330354
// Check if the order has more validity in it (multi-year cert). If so, do a reissue instead of a renew
331355
if (enrollmentType == EnrollmentType.Renew)
332356
{
@@ -1459,6 +1483,46 @@ private async Task<EnrollmentResult> Reissue(CertCentralClient client, Enrollmen
14591483
return await ExtractEnrollmentResult(client, client.ReissueCertificate(reissueRequest), commonName);
14601484
}
14611485

1486+
/// <summary>
1487+
/// Duplicates a certificate.
1488+
/// </summary>
1489+
/// <param name="client">The client used to contact DigiCert.</param>
1490+
/// <param name="request">The <see cref="OrderRequest"/>.</param>
1491+
/// <param name="enrollmentProductInfo">Information about the DigiCert product this certificate uses.</param>
1492+
/// <returns></returns>
1493+
private async Task<EnrollmentResult> Duplicate(CertCentralClient client, EnrollmentProductInfo enrollmentProductInfo, string caRequestId, string commonName, string csr, List<string> dnsNames, string signatureHash, string caCertId)
1494+
{
1495+
CheckProductExistence(enrollmentProductInfo.ProductID);
1496+
1497+
// Get order ID
1498+
_logger.LogTrace("Attempting to parse the order ID from the AnyGateway certificate.");
1499+
uint orderId = 0;
1500+
try
1501+
{
1502+
orderId = uint.Parse(caRequestId.Split('-').First());
1503+
}
1504+
catch (Exception e)
1505+
{
1506+
throw new Exception($"There was an error parsing the order ID from the certificate: {e.Message}", e);
1507+
}
1508+
1509+
// Duplicate certificate.
1510+
DuplicateRequest duplicateRequest = new DuplicateRequest(orderId)
1511+
{
1512+
Certificate = new CertificateDuplicateRequest
1513+
{
1514+
CommonName = commonName,
1515+
CSR = csr,
1516+
DnsNames = dnsNames,
1517+
SignatureHash = signatureHash,
1518+
CACertID = caCertId
1519+
}
1520+
};
1521+
1522+
_logger.LogTrace("Attempting to duplicate certificate.");
1523+
return await ExtractEnrollmentResult(client, client.DuplicateCertificate(duplicateRequest), commonName);
1524+
}
1525+
14621526
/// <summary>
14631527
/// Verify that the given product ID is valid
14641528
/// </summary>

digicert-certcentral-caplugin/Client/CertCentralClient.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,28 @@ public OrderResponse ReissueCertificate(ReissueRequest request)
357357
return reissueResponse;
358358
}
359359

360+
public OrderResponse DuplicateCertificate(DuplicateRequest request)
361+
{
362+
string jsonRequest = JsonConvert.SerializeObject(request, Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
363+
Logger.LogTrace($"Duplicate request:\n{jsonRequest}");
364+
365+
CertCentralResponse response = Request(request, jsonRequest);
366+
367+
OrderResponse duplicateResponse = new OrderResponse();
368+
if (!response.Success)
369+
{
370+
Errors errors = JsonConvert.DeserializeObject<Errors>(response.Response);
371+
duplicateResponse.Status = CertCentralBaseResponse.StatusType.ERROR;
372+
duplicateResponse.Errors = errors.errors;
373+
}
374+
else
375+
{
376+
duplicateResponse = JsonConvert.DeserializeObject<OrderResponse>(response.Response);
377+
}
378+
379+
return duplicateResponse;
380+
}
381+
360382
public RevokeCertificateResponse RevokeCertificate(RevokeCertificateRequest request)
361383
{
362384
CertCentralResponse response = Request(request, JsonConvert.SerializeObject(request, Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }));

digicert-certcentral-caplugin/Constants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public class Config
2525
public const string LIFETIME = "LifetimeDays";
2626
public const string CA_CERT_ID = "CACertId";
2727
public const string RENEWAL_WINDOW = "RenewalWindowDays";
28+
public const string DUPLICATE = "Duplicate";
2829
public const string REVOKE_CERT = "RevokeCertificateOnly";
2930
public const string ENABLED = "Enabled";
3031
public const string SYNC_CA_FILTER = "SyncCAFilter";

docsource/configuration.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,7 @@ In order to enroll for certificates the Keyfactor Command server must trust the
1818

1919
Note for SMIME product types (Secure Email types): The template configuration fields provided for those are not required to be filled out in the gateway config. Many of those values would change on a per-enrollment basis. The way to handle that is to create Enrollment fields in Command with the same name (for example: CommonNameIndicator) and then any values populated in those fields will override any static values provided in the configuration.
2020

21+
## Certificate Duplicates
22+
23+
DigiCert supports the ability to duplicate existing certificate orders. To take advantage of this functionality, in Keyfactor Command, under the enrollment pattern you're using, create an Enrollment Field named 'Duplicate' of type Multiple Choice, and the values 'False', 'True'. When performing a renew operation against that enrollment pattern, set the value to True to tell the gateway to duplicate instead of renew. The field will be ignored on new enrollments.
24+

0 commit comments

Comments
 (0)