Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f289620
filter by product id
dgaley Jul 3, 2025
0da4b14
don't rebuild request objects
dgaley Sep 4, 2025
73aab26
workflow and manifest
dgaley Oct 16, 2025
12ced82
manifest
dgaley Oct 16, 2025
bba0dea
docs
dgaley Oct 16, 2025
d68e05f
.
dgaley Oct 16, 2025
b75eb13
Merge pull request #42 from Keyfactor/syncfilter
dgaley Oct 16, 2025
a4ebf1c
add ability to specify MSSLProfileID manually
dgaley Oct 16, 2025
111b491
Merge pull request #44 from Keyfactor/domainid
dgaley Oct 16, 2025
e2eb921
Update keyfactor-starter-workflow.yml
dgaley Oct 16, 2025
b453873
fix(ci): Fix secret variable for APPROVE_README_PUSH
spbsoluble Oct 16, 2025
6709b2e
Update generated README
Oct 16, 2025
d7f0586
Update CHANGELOG.md
dgaley Oct 16, 2025
36ef6e6
treat SANs that are the base domain of a wildcard CN as identical
dgaley Oct 30, 2025
bc0c317
changelog
dgaley Oct 30, 2025
d2a7b11
add check to both new and renew
dgaley Oct 30, 2025
a07a6dd
Merge pull request #46 from Keyfactor/sandupecheck
dgaley Oct 30, 2025
9c3b7ec
better handling of wildcard/www prefixes
dgaley Nov 11, 2025
724ca9a
Merge pull request #47 from Keyfactor/wildcard
dgaley Nov 11, 2025
e25e436
allow san/cn to be an exact match for the domain name
dgaley Dec 4, 2025
5ab5b53
Merge pull request #48 from Keyfactor/domain
dgaley Dec 4, 2025
987dcda
Better CN/SAN matching
dgaley Dec 23, 2025
36e8906
Update GlobalSignEnrollRequest.cs
dgaley Dec 23, 2025
4fb4b3d
Update GlobalSignEnrollRequest.cs
dgaley Dec 23, 2025
75b979d
Move exception condition for failed domain match
dgaley Jan 20, 2026
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
4 changes: 2 additions & 2 deletions .github/workflows/keyfactor-starter-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
call-starter-workflow:
uses: keyfactor/actions/.github/workflows/starter.yml@v2
secrets:
token: ${{ secrets.V2BUILDTOKEN}}
APPROVE_README_PUSH: ${{ secrets.APPROVE_README_PUSH}}
token: ${{ secrets.V2BUILDTOKEN }}
APPROVE_README_PUSH: ${{ secrets.V2BUILDTOKEN }}
gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }}
gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }}
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,9 @@ Hotfixes for BaseOption flag for Renewal workflow
Hotfix for domain lookup

1.1.2
Hotfix for renewal workflow
Hotfix for renewal workflow

1.2.0
Add SyncProducts config to filter certificate sync by product ID
Add ability to manually specify MSSLProfileID per template to use for domain lookup
Bugfix: Treat SANs that match the base domain of a wildcard CN as identical for the purpose of removing duplicates
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,18 @@ The following sections will breakdown the required configurations for the AnyGat
## Templates
The Template section will map the CA's SSL profile to an AD template. The Lifetime parameter is required and represents the certificate duration in months.
* ```ContactName```
The name to pass to GlobalSign as the contact name for enrollments. OPTIONAL if Active Directory authentication is used in Keyfactor Command, in that case it can look up the name of the requesting user. Value provided in this config field overrides AD lookups.
The name to pass to GlobalSign as the contact name for enrollments. OPTIONAL if Active Directory authentication is used in Keyfactor Command, in that case it can look up the name of the requesting user. Value provided in this config field overrides AD lookups.
* ```MSSLProfileID```
OPTIONAL: If specified, enrollments will use that profile ID for domain lookups. If not provided, domain lookup will be done based on the Common Name or first DNS SAN. Useful if your GlobalSign account has multiple domain objects with the same domain string, or subdomains (e.g. sub.test.com vs test.com).

```json
"Templates": {
"WebServer": {
"ProductID": "PV_SHA2",
"Parameters": {
"Lifetime":"12",
"ContactName":"John Doe"
"ContactName":"John Doe",
"MSSLProfileID":"123456"
}
}
}
Expand Down Expand Up @@ -194,14 +197,19 @@ This is the password that will be used to connect to the GlobalSign API
OPTIONAL: If provided, full syncs will start at the specified date.
* ```SyncIntervalDays```
OPTIONAL: Required if SyncStartDate is used. Specifies how to page the certificate sync. Should be a value such that no interval of that length contains > 500 certificate enrollments.
* ```SyncProducts```
OPTIONAL: If provided as a comma-separated list of product IDs, will limit the certificate sync to only certificates of those products. If blank or not provided, will sync all certs.

```json
"CAConnection": {
"IsTest":"false",
"PickupRetries":5,
"PickupDelay":150,
"Username":"PAR12344_apiuser",
"Password":"password"
"Password":"password",
"SyncStartDate":"2020-01-01",
"SyncIntervalDays":30,
"SyncProducts":"PV_SHA2, PEV_SHA2"
},
```
## GatewayRegistration
Expand Down
7 changes: 3 additions & 4 deletions globalsign-mssl-cagateway.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31129.286
# Visual Studio Version 17
VisualStudioVersion = 17.10.35122.118
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GlobalSignCAProxy", "src\GlobalSignCAProxy\GlobalSignCAProxy.csproj", "{8A26FA6A-22CC-4BD0-9AAC-CDF95A85011D}"
EndProject
Expand All @@ -13,8 +13,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
CHANGELOG.md = CHANGELOG.md
integration-manifest.json = integration-manifest.json
.github\workflows\keyfactor-extension-generate-readme.yml = .github\workflows\keyfactor-extension-generate-readme.yml
.github\workflows\keyfactor-extension-release.yml = .github\workflows\keyfactor-extension-release.yml
.github\workflows\keyfactor-starter-workflow.yml = .github\workflows\keyfactor-starter-workflow.yml
README.md.tpl = README.md.tpl
readme_source.md = readme_source.md
EndProjectSection
Expand Down
3 changes: 2 additions & 1 deletion integration-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"status": "production",
"update_catalog": true,
"link_github": true,
"release_dir": "src\\GlobalSignCAProxy\\bin\\Release",
"release_dir": "src/GlobalSignCAProxy/bin/Release",
"release_project": "src/GlobalSignCAProxy/GlobalSignCAProxy.csproj",
"support_level": "kf-supported",
"description": "This integration allows for the Synchronization, Enrollment, and Revocation of TLS Certificates from the GlobalSign Certificate Center."
}
14 changes: 11 additions & 3 deletions readme_source.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,18 @@ The following sections will breakdown the required configurations for the AnyGat
## Templates
The Template section will map the CA's SSL profile to an AD template. The Lifetime parameter is required and represents the certificate duration in months.
* ```ContactName```
The name to pass to GlobalSign as the contact name for enrollments. OPTIONAL if Active Directory authentication is used in Keyfactor Command, in that case it can look up the name of the requesting user. Value provided in this config field overrides AD lookups.
The name to pass to GlobalSign as the contact name for enrollments. OPTIONAL if Active Directory authentication is used in Keyfactor Command, in that case it can look up the name of the requesting user. Value provided in this config field overrides AD lookups.
* ```MSSLProfileID```
OPTIONAL: If specified, enrollments will use that profile ID for domain lookups. If not provided, domain lookup will be done based on the Common Name or first DNS SAN. Useful if your GlobalSign account has multiple domain objects with the same domain string, or subdomains (e.g. sub.test.com vs test.com).

```json
"Templates": {
"WebServer": {
"ProductID": "PV_SHA2",
"Parameters": {
"Lifetime":"12",
"ContactName":"John Doe"
"ContactName":"John Doe",
"MSSLProfileID":"123456"
}
}
}
Expand Down Expand Up @@ -152,14 +155,19 @@ This is the password that will be used to connect to the GlobalSign API
OPTIONAL: If provided, full syncs will start at the specified date.
* ```SyncIntervalDays```
OPTIONAL: Required if SyncStartDate is used. Specifies how to page the certificate sync. Should be a value such that no interval of that length contains > 500 certificate enrollments.
* ```SyncProducts```
OPTIONAL: If provided as a comma-separated list of product IDs, will limit the certificate sync to only certificates of those products. If blank or not provided, will sync all certs.

```json
"CAConnection": {
"IsTest":"false",
"PickupRetries":5,
"PickupDelay":150,
"Username":"PAR12344_apiuser",
"Password":"password"
"Password":"password",
"SyncStartDate":"2020-01-01",
"SyncIntervalDays":30,
"SyncProducts":"PV_SHA2, PEV_SHA2"
},
```
## GatewayRegistration
Expand Down
46 changes: 36 additions & 10 deletions src/GlobalSignCAProxy/Api/GlobalSignEnrollRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@

using CSS.Common.Logging;

using Keyfactor.Extensions.AnyGateway.GlobalSign.Services.Order;
using Keyfactor.Extensions.AnyGateway.GlobalSign.Services.Order;

using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Keyfactor.Extensions.AnyGateway.GlobalSign.Api
{
using System.Text;
namespace Keyfactor.Extensions.AnyGateway.GlobalSign.Api
{
public class GlobalSignEnrollRequest : LoggingClientBase
{
internal GlobalSignCAConfig Config;

public GlobalSignEnrollRequest(GlobalSignCAConfig config)
{
Config = config;
Expand Down Expand Up @@ -90,6 +90,29 @@ public BmV2PvOrderRequest Request
Logger.Info($"SAN Entry {item} matches CN, removing from request");
continue;
}
string trimCN = CommonName, trimItem = item;
if (CommonName.StartsWith("*."))
{
trimCN = CommonName.Substring(2).ToLower();
trimItem = item.ToLower();
List<string> equivs = new List<string> { $"*.{trimCN}", $"www.{trimCN}", $"{trimCN}" };
if (equivs.Contains(trimItem))
{
Logger.Info($"SAN Entry {item} is equivalent to CN ignoring wildcards or www prefix, removing from request");
continue;
}
}
else if (CommonName.StartsWith("www."))
{
trimCN = CommonName.Substring(4).ToLower();
trimItem = item.ToLower();
List<string> equivs = new List<string> { $"www.{trimCN}", $"{trimCN}" };
if (equivs.Contains(trimItem))
{
Logger.Info($"SAN Entry {item} is equivalent to CN ignoring wildcards or www prefix, removing from request");
continue;
}
}
SANEntry entry = new SANEntry();
entry.SubjectAltName = item;
StringBuilder sb = new StringBuilder();
Expand Down Expand Up @@ -134,9 +157,12 @@ public BmV2PvOrderRequest Request
{
request.OrderRequestParameter.BaseOption = BaseOption;
}

return request;
}
}
}
}

}
}


22 changes: 22 additions & 0 deletions src/GlobalSignCAProxy/Api/GlobalSignRenewRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,28 @@ public GlobalSignRenewRequest(GlobalSignCAConfig config) : base(config) { }
Logger.Info($"SAN Entry {item} matches CN, removing from request");
continue;
}
string trimCN = CommonName, trimItem = item;
if (trimCN.StartsWith("*."))
{
trimCN = trimCN.Substring(2);
}
else if (trimCN.StartsWith("www."))
{
trimCN = trimCN.Substring(4);
}
if (trimItem.StartsWith("*."))
{
trimItem = trimItem.Substring(2);
}
else if (trimItem.StartsWith("www."))
{
trimItem = trimItem.Substring(4);
}
if (string.Equals(trimCN, trimItem, System.StringComparison.OrdinalIgnoreCase))
{
Logger.Info($"SAN Entry {item} is equivalent to CN ignoring wildcards or www prefix, removing from request");
continue;
}
SANEntry entry = new SANEntry();
entry.SubjectAltName = item;
StringBuilder sb = new StringBuilder();
Expand Down
4 changes: 2 additions & 2 deletions src/GlobalSignCAProxy/Client/GlobalSignApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ public EnrollmentResult Enroll(GlobalSignEnrollRequest enrollRequest)
{
Logger.Trace($"Order Base Option: {rawRequest.OrderRequestParameter.BaseOption}");
}
var response = OrderService.PVOrder(enrollRequest.Request);
var response = OrderService.PVOrder(rawRequest);
if (response.OrderResponseHeader.SuccessCode == 0)
{
Logger.Debug($"Enrollment request successfully submitted");
Expand Down Expand Up @@ -365,7 +365,7 @@ public EnrollmentResult Renew(GlobalSignRenewRequest renewRequest)
Logger.Trace($"Order Base Option: {rawRequest.OrderRequestParameter.BaseOption}");
}
Logger.Trace($"Renewal Target: {rawRequest.OrderRequestParameter.RenewalTargetOrderID}");
var response = OrderService.PVOrder(renewRequest.Request);
var response = OrderService.PVOrder(rawRequest);
if (response.OrderResponseHeader.SuccessCode == 0)
{
Logger.Debug($"Renewal request successfully submitted");
Expand Down
1 change: 1 addition & 0 deletions src/GlobalSignCAProxy/GlobalSignCAConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class GlobalSignCAConfig

public string SyncStartDate { get; set; }
public int SyncIntervalDays { get; set; }
public string SyncProducts { get; set; }

public string GetUrl(GlobalSignServiceType queryType)
{
Expand Down
73 changes: 59 additions & 14 deletions src/GlobalSignCAProxy/GlobalSignCAProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

using Newtonsoft.Json;

using Org.BouncyCastle.Asn1.IsisMtt.X509;
using Org.BouncyCastle.Crypto.Tls;

using System;
Expand All @@ -27,6 +28,7 @@
using System.Linq;
using System.Security.Policy;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web.Services.Configuration;

Expand Down Expand Up @@ -97,6 +99,7 @@ public override EnrollmentResult Enroll(ICertificateDataReader certificateDataRe
// If no CN is found, go through the DNS Name SANs in order, and find a domain that maches the end of one of those SANs
// If a match is found, set the common name to that SAN (GlobalSign API requires the CommonName field be populated)
string commonName = null;
List<DomainDetail> matchedDomains = null;
DomainDetail domain = null;
var allDomains = apiClient.GetDomains();
// Only acccept domains that are able to issue certificates
Expand Down Expand Up @@ -131,28 +134,46 @@ public override EnrollmentResult Enroll(ICertificateDataReader certificateDataRe

var sanDict = new Dictionary<string, string[]>(san, StringComparer.OrdinalIgnoreCase);
Logger.Trace($"DNS SAN Count: {sanDict["dns"].Count()}");

if (commonName == null)
{
foreach (string dnsSan in sanDict["dns"])
if (sanDict["dns"].Count() > 0)
{
var tempDomain = validDomains.Where(d => dnsSan.EndsWith($".{d.DomainName}", StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
if (tempDomain != null)
{
Logger.Debug($"SAN Domain match found for SAN: {dnsSan}");
domain = tempDomain;
commonName = dnsSan;
break;
}
string dnsSan = sanDict["dns"][0];
matchedDomains = validDomains.Where(d => dnsSan.Equals(d.DomainName, StringComparison.OrdinalIgnoreCase)
|| dnsSan.EndsWith($".{d.DomainName}", StringComparison.OrdinalIgnoreCase)).ToList();
commonName = dnsSan;
}
}
else
{
domain = validDomains.Where(d => commonName.EndsWith($".{d.DomainName}", StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
matchedDomains = validDomains.Where(d => commonName.Equals(d.DomainName, StringComparison.OrdinalIgnoreCase)
|| commonName.EndsWith($".{d.DomainName}", StringComparison.OrdinalIgnoreCase)).ToList();
}

if (domain == null)
if (matchedDomains.Count == 1)
{
throw new Exception("Unable to determine GlobalSign domain");
domain = matchedDomains[0];
}
else
{
if (productInfo.ProductParameters.ContainsKey("MSSLProfileID"))
{
var profID = productInfo.ProductParameters["MSSLProfileID"];
var tempDomain = matchedDomains.Where(d => d.MSSLProfileID.Equals(profID, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
if (tempDomain != null)
{
domain = tempDomain;
}
else
{
throw new Exception($"No domain matching common name {commonName} has provided MSSLProfileID of {profID}. Check configuration.");
}
}
else
{
throw new Exception("Unable to determine GlobalSign domain, and no MSSLProfileID provided.");
}
}

Logger.Debug($"Domain info:\nDomain Name: {domain?.DomainName}\nMsslDomainId: {domain?.DomainID}\nMsslProfileId: {domain?.MSSLProfileID}");
Expand Down Expand Up @@ -272,8 +293,31 @@ public override void Synchronize(ICertificateDataReader certificateDataReader, B
DateTime? syncFrom = certificateAuthoritySyncInfo.DoFullSync ? fullSyncFrom : certificateAuthoritySyncInfo.OverallLastSync;
var certs = apiClient.GetCertificatesForSync(certificateAuthoritySyncInfo.DoFullSync, syncFrom, fullSyncFrom, Config.SyncIntervalDays);

foreach (var c in certs)
bool productFilter = false;
List<string> products = null;
if (!string.IsNullOrEmpty(Config.SyncProducts))
{
products = Config.SyncProducts.Split(',').ToList();
products.ForEach(p => p.ToUpper());
productFilter = true;
}

foreach (var c in certs)
{
if (productFilter)
{
bool prodMatch = false;
if (c.OrderInfo?.ProductCode != null && products.Contains(c.OrderInfo.ProductCode.ToUpper()))
{
prodMatch = true;
}
if (!prodMatch)
{
Logger.Info($"Found certificate with product code {c.OrderInfo?.ProductCode}, which does not match the filter criteria. Skipping.");
continue;
}
}

GlobalSignOrderStatus orderStatus = (GlobalSignOrderStatus)Enum.Parse(typeof(GlobalSignOrderStatus), c.CertificateInfo.CertificateStatus);
DateTime? subDate = DateTime.TryParse(c.OrderInfo?.OrderDate, out DateTime orderDate) ? orderDate : (DateTime?)null;
DateTime? resDate = DateTime.TryParse(c.OrderInfo?.OrderCompleteDate, out DateTime completeDate) ? completeDate : (DateTime?)null;
Expand Down Expand Up @@ -433,4 +477,5 @@ private static string ParseSubject(string subject, string rdn)

#endregion Private Methods
}
}

}
Loading