Skip to content

Commit 6afaec6

Browse files
committed
Added template parameter configuration via REST gateway. Fixed bug with email used for verification. Changed docs and enrollment field/template parameter names. See changelog.
1 parent 3ade825 commit 6afaec6

File tree

10 files changed

+278
-91
lines changed

10 files changed

+278
-91
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
v.1.0.2
2+
- Warning: enrollment field/template parameter with the name "CN DCV Email ([email protected])" has been renamed to "CN DCV Email" to make it compatible with the REST gateway. "Aplicant Pgone (+nn.nnnnnnnn)" has also been renamed to "Applicant Phone".
3+
- Updated dependencies.
4+
- Added support for default values via enrollment parameters configured in the AnyGateway REST certificate template.
5+
- Fixed issue with non-ASCII characters breaking the gateway.
6+
17
v1.0.1
28
- Fixed issue with SANs not being read correctly.
39

README.md

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
<p align="center">
66
<!-- Badges -->
77
<img src="https://img.shields.io/badge/integration_status-pilot-3D1973?style=flat-square" alt="Integration Status: pilot" />
8-
<a href="https://github.com/Keyfactor/cscglobal-caplugin/releases"><img src="https://img.shields.io/github/v/release/Keyfactor/cscglobal-caplugin?style=flat-square" alt="Release" /></a>
9-
<img src="https://img.shields.io/github/issues/Keyfactor/cscglobal-caplugin?style=flat-square" alt="Issues" />
10-
<img src="https://img.shields.io/github/downloads/Keyfactor/cscglobal-caplugin/total?style=flat-square&label=downloads&color=28B905" alt="GitHub Downloads (all assets, all releases)" />
8+
<a href="https://github.com/Keyfactor/cscglobal-caplugin-dev/releases"><img src="https://img.shields.io/github/v/release/Keyfactor/cscglobal-caplugin-dev?style=flat-square" alt="Release" /></a>
9+
<img src="https://img.shields.io/github/issues/Keyfactor/cscglobal-caplugin-dev?style=flat-square" alt="Issues" />
10+
<img src="https://img.shields.io/github/downloads/Keyfactor/cscglobal-caplugin-dev/total?style=flat-square&label=downloads&color=28B905" alt="GitHub Downloads (all assets, all releases)" />
1111
</p>
1212

1313
<p align="center">
@@ -53,7 +53,7 @@ This integration is tested and confirmed as working for Anygateway REST 24.2 and
5353

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

56-
2. On the server hosting the AnyCA Gateway REST, download and unzip the latest [CSCGlobal CA Gateway AnyCA Gateway REST plugin](https://github.com/Keyfactor/cscglobal-caplugin/releases/latest) from GitHub.
56+
2. On the server hosting the AnyCA Gateway REST, download and unzip the latest [CSCGlobal CA Gateway AnyCA Gateway REST plugin](https://github.com/Keyfactor/cscglobal-caplugin-dev/releases/latest) from GitHub.
5757

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

@@ -91,7 +91,8 @@ This integration is tested and confirmed as working for Anygateway REST 24.2 and
9191
2. PLEASE NOTE, AT THIS TIME THE RAPID_SSL TEMPLATE IS NOT SUPPORTED BY THE CSC API AND WILL NOT WORK WITH THIS INTEGRATION
9292

9393
The following certificate templates are supported. Please set up the key sizes accordingly in the Certificate Profile menu of Anygateway REST, then enter the remaining details
94-
and the Enrollment Fields for each Template accordingly using the Certificate Templates section in Command:
94+
and the Enrollment Fields for each Template accordingly using the Certificate Templates section in Command. If you would like to set up default values for enrollment parameters, you can do so the in the Certificate Template Menu of Anygateway REST.
95+
If a field value is specified as both an Enrollment Field in Command and in the Certificate Template Menu in the REST Gateway, the value in the Enrollment Field will take precedence.
9596

9697
CONFIG ELEMENT | DESCRIPTION
9798
----------------------------|------------------
@@ -175,7 +176,7 @@ This integration is tested and confirmed as working for Anygateway REST 24.2 and
175176
Business Unit | Multiple Choice | Get From CSC Differs For Clients
176177
Notification Email(s) Comma Separated | String | N/A
177178
CN DCV Email ([email protected]) | String | N/A
178-
Addtl Sans Comma Separated DVC Emails | String | N/A
179+
Addtl Sans Comma Separated DCV Emails | String | N/A
179180

180181

181182
**CSC TrustedSecure Premium Wildcard Certificate - Details Tab**
@@ -289,10 +290,25 @@ This integration is tested and confirmed as working for Anygateway REST 24.2 and
289290
Business Unit | Multiple Choice | Get From CSC Differs For Clients
290291
Notification Email(s) Comma Separated | String | N/A
291292
CN DCV Email ([email protected]) | String | N/A
292-
Addtl Sans Comma Separated DVC Emails | String | N/A
293+
Addtl Sans Comma Separated DCV Emails | String | N/A
293294

294295
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.
295296

297+
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:
298+
299+
* **Term** - OPTIONAL: Certificate term (e.g. 12 or 24 months)
300+
* **Applicant First Name** - OPTIONAL: Applicant First Name
301+
* **Applicant Last Name** - OPTIONAL: Applicant Last Name
302+
* **Applicant Email Address** - OPTIONAL: Applicant Email Address
303+
* **Applicant Phone** - OPTIONAL: Applicant Phone (+nn.nnnnnnnn)
304+
* **Domain Control Validation Method** - OPTIONAL: Domain Control Validation Method (e.g. EMAIL)
305+
* **Organization Contact** - OPTIONAL: Organization Contact (selected from CSC configuration)
306+
* **Business Unit** - OPTIONAL: Business Unit (selected from CSC configuration)
307+
* **Notification Email(s) Comma Separated** - OPTIONAL: Notification Email(s), comma separated
308+
* **CN DCV Email** - OPTIONAL: CN DCV Email (e.g. [email protected])
309+
* **Organization Country** - OPTIONAL: Organization Country
310+
* **Addtl Sans Comma Separated DCV Emails** - OPTIONAL: Additional SANs DCV Emails, comma separated
311+
296312

297313

298314
## License

cscglobal-caplugin.sln

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio Version 17
4-
VisualStudioVersion = 17.11.35327.3
3+
# Visual Studio Version 18
4+
VisualStudioVersion = 18.0.11217.181 d18.0
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSCGlobalCAPlugin", "cscglobal-caplugin\CSCGlobalCAPlugin.csproj", "{01DDFD6F-275D-46E7-B522-E0C965D1BF9C}"
77
EndProject
88
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}"
99
ProjectSection(SolutionItems) = preProject
1010
CHANGELOG.md = CHANGELOG.md
11+
docsource\configuration.md = docsource\configuration.md
1112
integration-manifest.json = integration-manifest.json
1213
EndProjectSection
1314
EndProject

cscglobal-caplugin/CSCGlobalCAPlugin.cs

Lines changed: 132 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,19 @@
55
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
66
// and limitations under the License.
77

8+
using System.Collections.Concurrent;
9+
using System.Security.Cryptography;
10+
using System.Security.Cryptography.X509Certificates;
11+
using System.Text;
12+
using System.Text.RegularExpressions;
813
using Keyfactor.AnyGateway.Extensions;
914
using Keyfactor.Extensions.CAPlugin.CSCGlobal.Client;
1015
using Keyfactor.Extensions.CAPlugin.CSCGlobal.Client.Models;
1116
using Keyfactor.Extensions.CAPlugin.CSCGlobal.Interfaces;
1217
using Keyfactor.Logging;
1318
using Keyfactor.PKI.Enums.EJBCA;
14-
using Keyfactor.PKI.X509;
1519
using Microsoft.Extensions.Logging;
1620
using Newtonsoft.Json;
17-
using System.Collections.Concurrent;
18-
using System.Security.Cryptography;
19-
using System.Security.Cryptography.X509Certificates;
20-
using System.Text;
21-
using System.Text.RegularExpressions;
2221

2322
namespace Keyfactor.Extensions.CAPlugin.CSCGlobal;
2423

@@ -324,6 +323,13 @@ public async Task ValidateCAConnectionInfo(Dictionary<string, object> connection
324323
public async Task ValidateProductInfo(EnrollmentProductInfo productInfo,
325324
Dictionary<string, object> connectionInfo)
326325
{
326+
var certType = ProductIDs.productIds.Find(x =>
327+
x.Equals(productInfo.ProductID, StringComparison.InvariantCultureIgnoreCase));
328+
329+
if (certType == null) throw new ArgumentException($"Cannot find {productInfo.ProductID}", "ProductId");
330+
331+
Logger.LogInformation($"Validated {certType} ({certType})configured for AnyGateway");
332+
327333
}
328334

329335
//done
@@ -372,23 +378,111 @@ public Dictionary<string, PropertyConfigInfo> GetCAConnectorAnnotations()
372378
//done
373379
public Dictionary<string, PropertyConfigInfo> GetTemplateParameterAnnotations()
374380
{
375-
return new Dictionary<string, PropertyConfigInfo>();
381+
return new Dictionary<string, PropertyConfigInfo>
382+
{
383+
[EnrollmentConfigConstants.Term] = new()
384+
{
385+
Comments = "OPTIONAL: Certificate term (e.g. 12 or 24 months)",
386+
Hidden = false,
387+
DefaultValue = string.Empty,
388+
Type = "Number"
389+
},
390+
391+
[EnrollmentConfigConstants.ApplicantFirstName] = new()
392+
{
393+
Comments = "OPTIONAL: Applicant First Name",
394+
Hidden = false,
395+
DefaultValue = string.Empty,
396+
Type = "String"
397+
},
398+
399+
[EnrollmentConfigConstants.ApplicantLastName] = new()
400+
{
401+
Comments = "OPTIONAL: Applicant Last Name",
402+
Hidden = false,
403+
DefaultValue = string.Empty,
404+
Type = "String"
405+
},
406+
407+
[EnrollmentConfigConstants.ApplicantEmailAddress] = new()
408+
{
409+
Comments = "OPTIONAL: Applicant Email Address",
410+
Hidden = false,
411+
DefaultValue = string.Empty,
412+
Type = "String"
413+
},
414+
415+
[EnrollmentConfigConstants.ApplicantPhone] = new()
416+
{
417+
Comments = "OPTIONAL: Applicant Phone (+nn.nnnnnnnn)",
418+
Hidden = false,
419+
DefaultValue = string.Empty,
420+
Type = "String"
421+
},
422+
423+
[EnrollmentConfigConstants.DomainControlValidationMethod] = new()
424+
{
425+
Comments = "OPTIONAL: Domain Control Validation Method (e.g. EMAIL)",
426+
Hidden = false,
427+
DefaultValue = string.Empty,
428+
Type = "String"
429+
},
430+
431+
[EnrollmentConfigConstants.OrganizationContact] = new()
432+
{
433+
Comments = "OPTIONAL: Organization Contact (selected from CSC configuration)",
434+
Hidden = false,
435+
DefaultValue = string.Empty,
436+
Type = "String"
437+
},
438+
439+
[EnrollmentConfigConstants.BusinessUnit] = new()
440+
{
441+
Comments = "OPTIONAL: Business Unit (selected from CSC configuration)",
442+
Hidden = false,
443+
DefaultValue = string.Empty,
444+
Type = "String"
445+
},
446+
447+
[EnrollmentConfigConstants.NotificationEmailsCommaSeparated] = new()
448+
{
449+
Comments = "OPTIONAL: Notification Email(s), comma separated",
450+
Hidden = false,
451+
DefaultValue = string.Empty,
452+
Type = "String"
453+
},
454+
455+
[EnrollmentConfigConstants.CnDcvEmail] = new()
456+
{
457+
Comments = "OPTIONAL: CN DCV Email (e.g. [email protected])",
458+
Hidden = false,
459+
DefaultValue = string.Empty,
460+
Type = "String"
461+
},
462+
463+
[EnrollmentConfigConstants.OrganizationCountry] = new()
464+
{
465+
Comments = "OPTIONAL: Organization Country",
466+
Hidden = false,
467+
DefaultValue = string.Empty,
468+
Type = "String"
469+
},
470+
471+
[EnrollmentConfigConstants.AdditionalSansCommaSeparatedDcvEmails] = new()
472+
{
473+
Comments = "OPTIONAL: Additional SANs DCV Emails, comma separated",
474+
Hidden = false,
475+
DefaultValue = string.Empty,
476+
Type = "String"
477+
}
478+
};
376479
}
377480

378481
//done
379482
public List<string> GetProductIds()
380483
{
381-
var ProductIDs = new List<string>
382-
{
383-
"CSC TrustedSecure Premium Certificate",
384-
"CSC TrustedSecure EV Certificate",
385-
"CSC TrustedSecure UC Certificate",
386-
"CSC TrustedSecure Premium Wildcard Certificate",
387-
"CSC TrustedSecure Domain Validated SSL",
388-
"CSC TrustedSecure Domain Validated Wildcard SSL",
389-
"CSC TrustedSecure Domain Validated UC Certificate"
390-
};
391-
return ProductIDs;
484+
485+
return ProductIDs.productIds;
392486
}
393487

394488
#region PRIVATE
@@ -401,7 +495,7 @@ public List<string> GetProductIds()
401495
private static readonly Regex Ws = new("\\s+", RegexOptions.Compiled);
402496

403497
/// <summary>
404-
/// Returns the end-entity certificate as Base64 DER (no PEM headers), or "" if none could be found.
498+
/// Returns the end-entity certificate as Base64 DER (no PEM headers), or "" if none could be found.
405499
/// </summary>
406500
public string GetEndEntityCertificate(string pemChain)
407501
{
@@ -430,8 +524,8 @@ public string GetEndEntityCertificate(string pemChain)
430524
try
431525
{
432526
// 3) Export to DER and Base64 (no headers).
433-
byte[] der = leaf.Export(X509ContentType.Cert);
434-
string b64 = Convert.ToBase64String(der);
527+
var der = leaf.Export(X509ContentType.Cert);
528+
var b64 = Convert.ToBase64String(der);
435529
Logger.LogTrace("End-entity certificate exported successfully.");
436530
return b64;
437531
}
@@ -453,7 +547,7 @@ private List<X509Certificate2> ExtractCertificates(string pem)
453547

454548
foreach (Match m in PemBlock.Matches(pem))
455549
{
456-
string b64 = m.Groups["b64"].Value;
550+
var b64 = m.Groups["b64"].Value;
457551
if (string.IsNullOrWhiteSpace(b64))
458552
{
459553
Logger.LogTrace("Skipping empty PEM block.");
@@ -467,14 +561,15 @@ private List<X509Certificate2> ExtractCertificates(string pem)
467561
try
468562
{
469563
// Convert.TryFromBase64String is fast and avoids temporary arrays when possible
470-
if (!Convert.TryFromBase64String(b64, new Span<byte>(new byte[GetDecodedLength(b64)]), out int bytesWritten))
564+
if (!Convert.TryFromBase64String(b64, new Span<byte>(new byte[GetDecodedLength(b64)]),
565+
out var bytesWritten))
471566
{
472567
// Fallback to FromBase64String to trigger a clear exception path
473568
var discard = Convert.FromBase64String(b64);
474569
bytesWritten = discard.Length; // unreachable if invalid
475570
}
476571

477-
byte[] der = Convert.FromBase64String(b64);
572+
var der = Convert.FromBase64String(b64);
478573
var cert = new X509Certificate2(der);
479574
results.Add(cert);
480575
Logger.LogTrace($"Imported certificate: Subject='{cert.Subject}', Issuer='{cert.Issuer}'");
@@ -514,23 +609,26 @@ bool IsCa(X509Certificate2 c)
514609
if (bc is X509BasicConstraintsExtension bce)
515610
return bce.CertificateAuthority;
516611
}
517-
catch { /* ignore and treat as unknown */ }
612+
catch
613+
{
614+
/* ignore and treat as unknown */
615+
}
616+
518617
return false; // if unknown, bias towards non-CA for end-entity picking
519618
}
520619

521620
// Candidates that do not issue others (their Subject is not an Issuer of any other).
522621
var nonIssuers = certs.Where(c =>
523-
!certs.Any(o => !ReferenceEquals(o, c) && string.Equals(o.Issuer, c.Subject, StringComparison.OrdinalIgnoreCase))
622+
!certs.Any(o =>
623+
!ReferenceEquals(o, c) && string.Equals(o.Issuer, c.Subject, StringComparison.OrdinalIgnoreCase))
524624
).ToList();
525625

526626
// Prefer non-CA among non-issuers
527627
var nonIssuerNonCa = nonIssuers.Where(c => !IsCa(c)).ToList();
528628
if (nonIssuerNonCa.Count == 1) return nonIssuerNonCa[0];
529629
if (nonIssuerNonCa.Count > 1)
530-
{
531630
// If multiple, pick the one whose subject appears least as an issuer (tie-breaker unnecessary here since nonIssuers already exclude issuers).
532631
return nonIssuerNonCa[0];
533-
}
534632

535633
// If that failed, pick any non-CA that is not an issuer in the set of all issuers
536634
var anyNonCa = certs.Where(c => !IsCa(c)).ToList();
@@ -554,14 +652,15 @@ bool IsCa(X509Certificate2 c)
554652
private static int GetDecodedLength(string b64)
555653
{
556654
// Approximate decoded length: 3/4 of input, minus padding effect
557-
int len = b64.Length;
558-
int padding = 0;
655+
var len = b64.Length;
656+
var padding = 0;
559657
if (len >= 2)
560658
{
561659
if (b64[^1] == '=') padding++;
562660
if (b64[^2] == '=') padding++;
563661
}
564-
return Math.Max(0, (len / 4) * 3 - padding);
662+
663+
return Math.Max(0, len / 4 * 3 - padding);
565664
}
566665

567666
private string ExportCollectionToPem(X509Certificate2Collection collection)
@@ -577,7 +676,8 @@ private string ExportCollectionToPem(X509Certificate2Collection collection)
577676

578677
return pemBuilder.ToString();
579678
}
580-
private static readonly Encoding Utf8Strict = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
679+
680+
private static readonly Encoding Utf8Strict = new UTF8Encoding(false, true);
581681
private static readonly Encoding Latin1 = Encoding.GetEncoding("ISO-8859-1");
582682

583683
private string PreparePemTextFromApi(string? base64)
@@ -621,6 +721,5 @@ private string PreparePemTextFromApi(string? base64)
621721
return text;
622722
}
623723

624-
625724
#endregion
626725
}

0 commit comments

Comments
 (0)