Skip to content

Commit 1d08138

Browse files
committed
Added a special per template setting of SignatureAlgorithm. Added appropriate docs.
1 parent ee1dc5a commit 1d08138

10 files changed

Lines changed: 340 additions & 19 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,4 +360,5 @@ MigrationBackup/
360360
.ionide/
361361

362362
# Fody - auto-generated XML schema
363-
FodyWeavers.xsd
363+
FodyWeavers.xsd
364+
/README.md

README.md

Lines changed: 50 additions & 4 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/aws-pca-caplugin/releases"><img src="https://img.shields.io/github/v/release/Keyfactor/aws-pca-caplugin?style=flat-square" alt="Release" /></a>
9-
<img src="https://img.shields.io/github/issues/Keyfactor/aws-pca-caplugin?style=flat-square" alt="Issues" />
10-
<img src="https://img.shields.io/github/downloads/Keyfactor/aws-pca-caplugin/total?style=flat-square&label=downloads&color=28B905" alt="GitHub Downloads (all assets, all releases)" />
8+
<a href="https://github.com/Keyfactor/aws-pca-caplugin-dev/releases"><img src="https://img.shields.io/github/v/release/Keyfactor/aws-pca-caplugin-dev?style=flat-square" alt="Release" /></a>
9+
<img src="https://img.shields.io/github/issues/Keyfactor/aws-pca-caplugin-dev?style=flat-square" alt="Issues" />
10+
<img src="https://img.shields.io/github/downloads/Keyfactor/aws-pca-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.4 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 [AWSPCA CA Gateway AnyCA Gateway REST plugin](https://github.com/Keyfactor/aws-pca-caplugin/releases/latest) from GitHub.
56+
2. On the server hosting the AnyCA Gateway REST, download and unzip the latest [AWSPCA CA Gateway AnyCA Gateway REST plugin](https://github.com/Keyfactor/aws-pca-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

@@ -107,6 +107,11 @@ This integration is tested and confirmed as working for Anygateway REST 24.4 and
107107
108108
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.
109109
110+
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:
111+
112+
* **LifetimeDays** - OPTIONAL: The number of days of validity to use when requesting certs. If not provided, default is 365
113+
* **SigningAlgorithm** - Required: Signing Algorithm to use with the PCA.
114+
110115
111116
## Authentication (Access Key + Secret)
112117
@@ -291,6 +296,47 @@ The following examples are intended as **copy/adapt templates**.
291296
]
292297
}
293298
```
299+
---
300+
301+
## Signing algorithm selection (ACM PCA)
302+
303+
The gateway supports an optional CAConnection setting `SigningAlgorithm` that controls the **certificate signature algorithm**
304+
passed to AWS ACM PCA `IssueCertificate`.
305+
306+
- If **not set**, the plugin will **auto-select** a compatible default based on the CA `KeyAlgorithm` returned by
307+
`DescribeCertificateAuthority`.
308+
- If **set**, the plugin validates the value and **rejects incompatible combinations** before calling AWS.
309+
310+
### Valid `SigningAlgorithm` values (AWS PCA)
311+
312+
- RSA family: `SHA256WITHRSA`, `SHA384WITHRSA`, `SHA512WITHRSA`
313+
- ECDSA family: `SHA256WITHECDSA`, `SHA384WITHECDSA`, `SHA512WITHECDSA`
314+
- SM2: `SM3WITHSM2`
315+
- ML-DSA (post-quantum): `ML_DSA_44`, `ML_DSA_65`, `ML_DSA_87`
316+
317+
### Allowed CA key algorithm and signing algorithm combinations
318+
319+
The CA key algorithm is the PCA CA **KeyAlgorithm** (not the subject key in the CSR). The signing algorithm must match the CA key family.
320+
321+
| CA KeyAlgorithm | Allowed SigningAlgorithm values |
322+
|---|---|
323+
| `RSA_2048`, `RSA_3072`, `RSA_4096` | `SHA256WITHRSA`, `SHA384WITHRSA`, `SHA512WITHRSA` |
324+
| `EC_prime256v1`, `EC_secp384r1`, `EC_secp521r1` | `SHA256WITHECDSA`, `SHA384WITHECDSA`, `SHA512WITHECDSA` |
325+
| `SM2` | `SM3WITHSM2` |
326+
| `ML_DSA_44` | `ML_DSA_44` |
327+
| `ML_DSA_65` | `ML_DSA_65` |
328+
| `ML_DSA_87` | `ML_DSA_87` |
329+
330+
### Auto-selection defaults
331+
332+
When `SigningAlgorithm` is omitted, the plugin selects:
333+
334+
- RSA CAs -> `SHA256WITHRSA`
335+
- EC P-256 -> `SHA256WITHECDSA`
336+
- EC P-384 -> `SHA384WITHECDSA`
337+
- EC P-521 -> `SHA512WITHECDSA`
338+
- SM2 -> `SM3WITHSM2`
339+
- ML-DSA -> exact-match (`ML_DSA_44/65/87`)
294340
295341
296342
## License

aws-pca-caplugin/AWSPCACAPlugin.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,15 @@ public async Task<EnrollmentResult> Enroll(
358358
parsed > 0)
359359
days = parsed;
360360

361+
362+
// Optional signing algorithm override (template parameter)
363+
string? signingAlgorithm = null;
364+
if (productInfo.ProductParameters != null &&
365+
productInfo.ProductParameters.TryGetValue(EnrollmentConfigConstants.SigningAlgorithm,
366+
out var algoStr) &&
367+
!string.IsNullOrWhiteSpace(algoStr))
368+
signingAlgorithm = algoStr.Trim();
369+
361370
// Normalize CSR to PEM (keeps your existing behavior)
362371
csr = PemUtilities.DERToPEM(PemUtilities.PEMToDER(csr), PemUtilities.PemObjectType.CertRequest);
363372

@@ -369,6 +378,7 @@ public async Task<EnrollmentResult> Enroll(
369378
csr,
370379
productInfo.ProductID,
371380
days,
381+
signingAlgorithm,
372382
"Certificate Issued")
373383
.ConfigureAwait(false);
374384
}
@@ -627,6 +637,15 @@ public Dictionary<string, PropertyConfigInfo> GetTemplateParameterAnnotations()
627637
Hidden = false,
628638
DefaultValue = 365,
629639
Type = "Number"
640+
},
641+
// this is used to passdown csr details/prefill. will be overridden by commmand if not present.
642+
[EnrollmentConfigConstants.SigningAlgorithm] = new()
643+
{
644+
Comments =
645+
"Required: AWS ACM PCA certificate signature algorithm to use when issuing certificates. Value is an AWS PCA SigningAlgorithm enum name (case-insensitive), e.g. SHA256WITHRSA, SHA384WITHRSA, SHA256WITHECDSA. If omitted, the plugin selects a default compatible with the CA key algorithm.",
646+
Hidden = false,
647+
DefaultValue = "SHA256WITHRSA",
648+
Type = "String"
630649
}
631650
};
632651
}
@@ -641,6 +660,7 @@ private async Task<EnrollmentResult> IssueAndFetchAsync(
641660
string csrPem,
642661
string productId,
643662
int validityDays,
663+
string? signingAlgorithm,
644664
string statusMessageOnSuccess,
645665
string? idempotencyToken = null)
646666
{
@@ -650,6 +670,7 @@ private async Task<EnrollmentResult> IssueAndFetchAsync(
650670
CsrPem = csrPem,
651671
ProductId = productId,
652672
ValidityDays = validityDays,
673+
SigningAlgorithm = signingAlgorithm,
653674
IdempotencyToken = idempotencyToken ?? Guid.NewGuid().ToString("N")
654675
};
655676

aws-pca-caplugin/AWSPCACAPlugin.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313

1414

1515
<Target Name="CustomPostBuild" AfterTargets="PostBuildEvent">
16-
<Exec Condition="'$(Configuration)'=='DebugAndPush'" Command="PowerShell -ExecutionPolicy Bypass -File &quot;C:\Users\mkachkaev\source\repos\scripts\SyncScriptAWS_GT.ps1&quot;&#xA;" />
16+
<Exec Condition="'$(Configuration)'=='DebugAndPush'"
17+
Command="PowerShell -ExecutionPolicy Bypass -File &quot;C:\Users\mkachkaev\source\repos\scripts\SyncScriptAWS_GT.ps1&quot;&#xA;" />
1718
</Target>
1819

1920

aws-pca-caplugin/CHANGELOG.md

Lines changed: 0 additions & 9 deletions
This file was deleted.

aws-pca-caplugin/Client/ACMPCAClient.cs

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public sealed class AwsPcaClient : IAwsPcaClient
3939
private const string ENHANCED_KEY_USAGE_OID = "2.5.29.37";
4040
private const string SERVER_AUTH_OID = "1.3.6.1.5.5.7.3.1";
4141
private const string CLIENT_AUTH_OID = "1.3.6.1.5.5.7.3.2";
42+
43+
private readonly SemaphoreSlim _caInfoLock = new(1, 1);
4244
private readonly AWSCredentials AwsCredentials;
4345
private readonly string CaArn;
4446
private readonly ILogger Logger;
@@ -47,6 +49,7 @@ public sealed class AwsPcaClient : IAwsPcaClient
4749
private readonly RegionEndpoint Region;
4850
private readonly string RoleArn;
4951
private readonly string S3Bucket;
52+
private string? _caKeyAlgorithmName;
5053
private IAmazonS3? S3Client;
5154

5255
public AwsPcaClient(IAnyCAPluginConfigProvider configProvider)
@@ -127,11 +130,18 @@ public async Task<IssueCertificateResponse> SubmitIssueCertificateAsync(
127130
var csrBytes = PemUtilities.DERToPEM(PemUtilities.PEMToDER(request.CsrPem),
128131
PemUtilities.PemObjectType.CertRequest);
129132

133+
134+
var signingAlgoRes = await ResolveSigningAlgorithmAsync(request.SigningAlgorithm, cancellationToken)
135+
.ConfigureAwait(false);
136+
if (signingAlgoRes.Error != null)
137+
return new IssueCertificateResponse { RegistrationError = signingAlgoRes.Error };
138+
139+
var signingAlgorithm = signingAlgoRes.Value!;
130140
var issueReq = new Amazon.ACMPCA.Model.IssueCertificateRequest
131141
{
132142
CertificateAuthorityArn = CaArn,
133143
Csr = new MemoryStream(Encoding.ASCII.GetBytes(csrBytes)),
134-
SigningAlgorithm = SigningAlgorithm.SHA256WITHRSA,
144+
SigningAlgorithm = signingAlgorithm,
135145
IdempotencyToken = request.IdempotencyToken ?? Guid.NewGuid().ToString("N"),
136146
Validity = new Validity
137147
{
@@ -578,6 +588,163 @@ public static string InferAndValidateTemplateTypeKey(X509Certificate2 cert)
578588
return Constants.TemplateARNs.ContainsKey(key) ? key : "Unknown";
579589
}
580590

591+
592+
private async Task<(string? Value, RegistrationError? Error)> GetCaKeyAlgorithmAsync(
593+
CancellationToken cancellationToken)
594+
{
595+
if (!string.IsNullOrWhiteSpace(_caKeyAlgorithmName))
596+
return (_caKeyAlgorithmName, null);
597+
598+
await _caInfoLock.WaitAsync(cancellationToken).ConfigureAwait(false);
599+
try
600+
{
601+
if (!string.IsNullOrWhiteSpace(_caKeyAlgorithmName))
602+
return (_caKeyAlgorithmName, null);
603+
604+
try
605+
{
606+
var resp = await PcaClient.DescribeCertificateAuthorityAsync(
607+
new DescribeCertificateAuthorityRequest { CertificateAuthorityArn = CaArn },
608+
cancellationToken).ConfigureAwait(false);
609+
610+
// KeyAlgorithm is represented as a string in the service model; some SDK surfaces expose it as a string.
611+
// Use reflection to remain tolerant across AWSSDK.ACMPCA versions.
612+
var cfg = resp?.CertificateAuthority?.CertificateAuthorityConfiguration;
613+
if (cfg == null)
614+
return (null,
615+
new RegistrationError
616+
{
617+
ErrorCode = "AwsDescribeCaFailed",
618+
Description = "DescribeCertificateAuthority returned no configuration."
619+
});
620+
621+
var prop = cfg.GetType().GetProperty("KeyAlgorithm");
622+
var val = prop?.GetValue(cfg)?.ToString();
623+
if (string.IsNullOrWhiteSpace(val))
624+
return (null,
625+
new RegistrationError
626+
{
627+
ErrorCode = "AwsDescribeCaFailed",
628+
Description = "Unable to read CA KeyAlgorithm from DescribeCertificateAuthority response."
629+
});
630+
631+
_caKeyAlgorithmName = val.Trim();
632+
return (_caKeyAlgorithmName, null);
633+
}
634+
catch (Exception ex)
635+
{
636+
return (null,
637+
new RegistrationError
638+
{ ErrorCode = "AwsDescribeCaFailed", Description = "Failed to query CA KeyAlgorithm." });
639+
}
640+
}
641+
finally
642+
{
643+
_caInfoLock.Release();
644+
}
645+
}
646+
647+
private static bool IsKnownSigningAlgorithm(SigningAlgorithm alg)
648+
{
649+
var v = alg.Value;
650+
return v == SigningAlgorithm.SHA256WITHRSA.Value
651+
|| v == SigningAlgorithm.SHA384WITHRSA.Value
652+
|| v == SigningAlgorithm.SHA512WITHRSA.Value
653+
|| v == SigningAlgorithm.SHA256WITHECDSA.Value
654+
|| v == SigningAlgorithm.SHA384WITHECDSA.Value
655+
|| v == SigningAlgorithm.SHA512WITHECDSA.Value
656+
|| v == SigningAlgorithm.SM3WITHSM2.Value
657+
|| v == SigningAlgorithm.ML_DSA_44.Value
658+
|| v == SigningAlgorithm.ML_DSA_65.Value
659+
|| v == SigningAlgorithm.ML_DSA_87.Value;
660+
}
661+
662+
private static bool IsSigningAlgorithmCompatible(string caKeyAlgorithmName, SigningAlgorithm signingAlgorithm)
663+
{
664+
var ka = caKeyAlgorithmName.Trim();
665+
var sig = signingAlgorithm.Value;
666+
667+
if (ka.StartsWith("RSA_", StringComparison.OrdinalIgnoreCase))
668+
return sig.EndsWith("WITHRSA", StringComparison.OrdinalIgnoreCase);
669+
670+
if (ka.StartsWith("EC_", StringComparison.OrdinalIgnoreCase))
671+
return sig.EndsWith("WITHECDSA", StringComparison.OrdinalIgnoreCase);
672+
673+
if (ka.Equals("SM2", StringComparison.OrdinalIgnoreCase))
674+
return sig.Equals(SigningAlgorithm.SM3WITHSM2.Value, StringComparison.OrdinalIgnoreCase);
675+
676+
if (ka.StartsWith("ML_DSA_", StringComparison.OrdinalIgnoreCase))
677+
return sig.Equals(ka, StringComparison.OrdinalIgnoreCase);
678+
679+
return false;
680+
}
681+
682+
private static SigningAlgorithm DefaultSigningAlgorithmForCaKey(string caKeyAlgorithmName)
683+
{
684+
var ka = caKeyAlgorithmName.Trim();
685+
686+
if (ka.StartsWith("RSA_", StringComparison.OrdinalIgnoreCase))
687+
return SigningAlgorithm.SHA256WITHRSA;
688+
689+
if (ka.StartsWith("EC_", StringComparison.OrdinalIgnoreCase))
690+
{
691+
if (ka.Equals("EC_secp384r1", StringComparison.OrdinalIgnoreCase))
692+
return SigningAlgorithm.SHA384WITHECDSA;
693+
694+
if (ka.Equals("EC_secp521r1", StringComparison.OrdinalIgnoreCase))
695+
return SigningAlgorithm.SHA512WITHECDSA;
696+
697+
return SigningAlgorithm.SHA256WITHECDSA;
698+
}
699+
700+
if (ka.Equals("SM2", StringComparison.OrdinalIgnoreCase))
701+
return SigningAlgorithm.SM3WITHSM2;
702+
703+
if (ka.Equals("ML_DSA_44", StringComparison.OrdinalIgnoreCase))
704+
return SigningAlgorithm.ML_DSA_44;
705+
if (ka.Equals("ML_DSA_65", StringComparison.OrdinalIgnoreCase))
706+
return SigningAlgorithm.ML_DSA_65;
707+
if (ka.Equals("ML_DSA_87", StringComparison.OrdinalIgnoreCase))
708+
return SigningAlgorithm.ML_DSA_87;
709+
710+
// Fallback
711+
return SigningAlgorithm.SHA256WITHRSA;
712+
}
713+
714+
private async Task<(SigningAlgorithm? Value, RegistrationError? Error)> ResolveSigningAlgorithmAsync(
715+
string? requestedSigningAlgorithm,
716+
CancellationToken cancellationToken)
717+
{
718+
var caKeyRes = await GetCaKeyAlgorithmAsync(cancellationToken).ConfigureAwait(false);
719+
if (caKeyRes.Error != null)
720+
return (null, caKeyRes.Error);
721+
722+
var caKey = caKeyRes.Value!;
723+
if (!string.IsNullOrWhiteSpace(requestedSigningAlgorithm))
724+
{
725+
var resolved = SigningAlgorithm.FindValue(requestedSigningAlgorithm.Trim());
726+
if (!IsKnownSigningAlgorithm(resolved))
727+
return (null, new RegistrationError
728+
{
729+
ErrorCode = "InvalidConfiguration",
730+
Description =
731+
$"SigningAlgorithm '{requestedSigningAlgorithm}' is not a supported AWS ACM PCA SigningAlgorithm value."
732+
});
733+
734+
if (!IsSigningAlgorithmCompatible(caKey, resolved))
735+
return (null, new RegistrationError
736+
{
737+
ErrorCode = "InvalidConfiguration",
738+
Description =
739+
$"SigningAlgorithm '{requestedSigningAlgorithm}' is not compatible with CA KeyAlgorithm '{caKey}'."
740+
});
741+
742+
return (resolved, null);
743+
}
744+
745+
return (DefaultSigningAlgorithmForCaKey(caKey), null);
746+
}
747+
581748
private static class ConfigKeys
582749
{
583750
public const string CaArn = "CAArn";

aws-pca-caplugin/Constants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,5 @@ public static List<string> GetTemplateTypes()
7272
public class EnrollmentConfigConstants
7373
{
7474
public const string LifetimeDays = "LifetimeDays";
75+
public const string SigningAlgorithm = "SigningAlgorithm";
7576
}

aws-pca-caplugin/Models/Models.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,19 @@ public sealed class IssueCertificateRequest
4242
/// <summary>Optional desired term in days. If null, defaults to 365.</summary>
4343
public int? ValidityDays { get; set; }
4444

45+
/// <summary>
46+
/// Optional override for ACM PCA IssueCertificate.SigningAlgorithm.
47+
/// If null/empty, the client auto-selects a compatible default based on the CA KeyAlgorithm.
48+
/// </summary>
49+
public string? SigningAlgorithm { get; set; }
50+
4551
/// <summary>Optional idempotency token.</summary>
4652
public string? IdempotencyToken { get; set; }
4753
}
4854

4955
/// <summary>
50-
/// Mirrors the CSC plugin expectations: contains the certificate payload + a Keyfactor-accepted numeric status.
56+
/// Response wrapper that carries the issued certificate payload and status.: contains the certificate payload + a
57+
/// Keyfactor-accepted numeric status.
5158
/// </summary>
5259
public sealed class CertificateResponse
5360
{

0 commit comments

Comments
 (0)