44
55using System ;
66using System . Collections . Generic ;
7- using System . Linq ;
87using System . Threading . Tasks ;
98using FluentAssertions ;
109using Keyfactor . AnyGateway . Extensions ;
11- using Org . BouncyCastle . Asn1 ;
12- using Org . BouncyCastle . Asn1 . Sec ;
13- using Org . BouncyCastle . Asn1 . X509 ;
14- using Org . BouncyCastle . Crypto ;
15- using Org . BouncyCastle . Crypto . Generators ;
1610using Org . BouncyCastle . Crypto . Parameters ;
1711using Org . BouncyCastle . Pkcs ;
18- using Org . BouncyCastle . Security ;
1912using Xunit ;
2013using Xunit . Abstractions ;
2114
2215namespace Keyfactor . Extensions . CAPlugin . CERTInext . IntegrationTests
2316{
2417 /// <summary>
2518 /// Key-algorithm coverage matrix: RSA 2048/3072/4096/6144/8192, ECDSA P-256/P-384/P-521,
26- /// Ed25519, and Ed448.
19+ /// Ed25519, and Ed448 (see <see cref="KeyAlgorithms"/>) .
2720 ///
28- /// Motivation: every other test in the suite hardcodes an RSA-2048 CSR, so only RSA-2048
21+ /// Motivation: every other test in the suite hardcoded an RSA-2048 CSR, so only RSA-2048
2922 /// certificates were ever exercised end-to-end (and that is all that showed up in Command).
3023 /// The plugin takes the CSR as enrollment input and submits it verbatim, so the key
31- /// algorithm is entirely determined by the CSR. These tests parameterise CSR generation
32- /// (BouncyCastle — never BCL crypto) across the full matrix.
24+ /// algorithm is entirely determined by the CSR.
3325 ///
34- /// Two layers, matching the agreed scope (submission / CSR-validity only — no DCV, no issuance):
26+ /// This file is the offline / submission- only layer ( no DCV, no issuance):
3527 /// 1. <see cref="Csr_RoundTripsKeyAlgorithm"/> — deterministic, no API, always runs. Proves we
3628 /// emit a structurally valid, self-consistent PKCS#10 CSR for each algorithm (the public key
3729 /// type/size round-trips and the request signature verifies).
3830 /// 2. <see cref="Enroll_AcceptsKeyAlgorithm"/> — opt-in (creates real sandbox orders). Proves
39- /// whether CERTInext *accepts* each algorithm at order submission. A CA-side rejection
40- /// (e.g. "algorithm not supported") is reported as an explicit Skip carrying the CA's own
41- /// message, so the suite documents real CA support rather than failing on a CA limitation.
31+ /// whether CERTInext *accepts* each algorithm at order submission. A CA-side rejection is
32+ /// reported as an explicit Skip carrying the CA's own message.
4233 ///
43- /// Caveat: "accepted at submission" is weaker than "will issue". A public CA may accept the
44- /// order and only reject an exotic key (Ed25519/Ed448, very large RSA) at issuance, after DCV.
45- /// End-to-end issuance per algorithm would require the DCV build + a Cloudflare round per order .
34+ /// The end-to-end "does CERTInext actually issue this algorithm" matrix (DCV on, one real
35+ /// scrup.org cert per type) lives in <c>DcvLifecycleTests.EnrollWithDcvOn_IssuesPerKeyAlgorithm</c>
36+ /// and only exists on the DCV build.
4637 /// </summary>
4738 public class AlgorithmMatrixTests : IClassFixture < IntegrationTestFixture >
4839 {
@@ -58,99 +49,7 @@ public AlgorithmMatrixTests(IntegrationTestFixture fixture, ITestOutputHelper ou
5849 _output = output ;
5950 }
6051
61- // ---------------------------------------------------------------------------
62- // Key-algorithm specifications
63- // ---------------------------------------------------------------------------
64-
65- private enum KeyKind { Rsa , Ecdsa , Ed25519 , Ed448 }
66-
67- private sealed class KeySpec
68- {
69- public string Tag ; // stable, human-readable id ("RSA-2048", "ECDSA-P256", ...)
70- public KeyKind Kind ;
71- public int Strength ; // RSA modulus bits, or EC field size in bits (informational for Ed)
72- public string SignatureAlgorithm ; // BouncyCastle signature-algorithm name for the CSR
73- public DerObjectIdentifier CurveOid ; // EC named-curve OID (null for non-EC)
74- }
75-
76- // CA/Baseline-Requirements hash pairing: P-256→SHA256, P-384→SHA384, P-521→SHA512.
77- private static readonly KeySpec [ ] Specs =
78- {
79- new ( ) { Tag = "RSA-2048" , Kind = KeyKind . Rsa , Strength = 2048 , SignatureAlgorithm = "SHA256withRSA" } ,
80- new ( ) { Tag = "RSA-3072" , Kind = KeyKind . Rsa , Strength = 3072 , SignatureAlgorithm = "SHA256withRSA" } ,
81- new ( ) { Tag = "RSA-4096" , Kind = KeyKind . Rsa , Strength = 4096 , SignatureAlgorithm = "SHA256withRSA" } ,
82- new ( ) { Tag = "RSA-6144" , Kind = KeyKind . Rsa , Strength = 6144 , SignatureAlgorithm = "SHA256withRSA" } ,
83- new ( ) { Tag = "RSA-8192" , Kind = KeyKind . Rsa , Strength = 8192 , SignatureAlgorithm = "SHA256withRSA" } ,
84- new ( ) { Tag = "ECDSA-P256" , Kind = KeyKind . Ecdsa , Strength = 256 , SignatureAlgorithm = "SHA256withECDSA" , CurveOid = SecObjectIdentifiers . SecP256r1 } ,
85- new ( ) { Tag = "ECDSA-P384" , Kind = KeyKind . Ecdsa , Strength = 384 , SignatureAlgorithm = "SHA384withECDSA" , CurveOid = SecObjectIdentifiers . SecP384r1 } ,
86- new ( ) { Tag = "ECDSA-P521" , Kind = KeyKind . Ecdsa , Strength = 521 , SignatureAlgorithm = "SHA512withECDSA" , CurveOid = SecObjectIdentifiers . SecP521r1 } ,
87- new ( ) { Tag = "Ed25519" , Kind = KeyKind . Ed25519 , Strength = 256 , SignatureAlgorithm = "Ed25519" } ,
88- new ( ) { Tag = "Ed448" , Kind = KeyKind . Ed448 , Strength = 448 , SignatureAlgorithm = "Ed448" } ,
89- } ;
90-
91- private static KeySpec SpecFor ( string tag ) => Specs . Single ( s => s . Tag == tag ) ;
92-
93- /// <summary>xUnit member-data source — one row per key type, keyed by its stable tag.</summary>
94- public static IEnumerable < object [ ] > KeyTypes => Specs . Select ( s => new object [ ] { s . Tag } ) ;
95-
96- // ---------------------------------------------------------------------------
97- // CSR generation (BouncyCastle)
98- // ---------------------------------------------------------------------------
99-
100- private static AsymmetricCipherKeyPair GenerateKeyPair ( KeySpec spec )
101- {
102- switch ( spec . Kind )
103- {
104- case KeyKind . Rsa :
105- {
106- var gen = new RsaKeyPairGenerator ( ) ;
107- gen . Init ( new KeyGenerationParameters ( new SecureRandom ( ) , spec . Strength ) ) ;
108- return gen . GenerateKeyPair ( ) ;
109- }
110- case KeyKind . Ecdsa :
111- {
112- var gen = new ECKeyPairGenerator ( "ECDSA" ) ;
113- gen . Init ( new ECKeyGenerationParameters ( spec . CurveOid , new SecureRandom ( ) ) ) ;
114- return gen . GenerateKeyPair ( ) ;
115- }
116- case KeyKind . Ed25519 :
117- {
118- var gen = new Ed25519KeyPairGenerator ( ) ;
119- gen . Init ( new Ed25519KeyGenerationParameters ( new SecureRandom ( ) ) ) ;
120- return gen . GenerateKeyPair ( ) ;
121- }
122- case KeyKind . Ed448 :
123- {
124- var gen = new Ed448KeyPairGenerator ( ) ;
125- gen . Init ( new Ed448KeyGenerationParameters ( new SecureRandom ( ) ) ) ;
126- return gen . GenerateKeyPair ( ) ;
127- }
128- default :
129- throw new ArgumentOutOfRangeException ( nameof ( spec ) , spec . Kind , "unhandled key kind" ) ;
130- }
131- }
132-
133- private static string GenerateCsrPem ( string commonName , KeySpec spec )
134- {
135- var keyPair = GenerateKeyPair ( spec ) ;
136- var subject = new X509Name ( $ "CN={ commonName } ") ;
137- var csr = new Pkcs10CertificationRequest ( spec . SignatureAlgorithm , subject , keyPair . Public , null , keyPair . Private ) ;
138-
139- return "-----BEGIN CERTIFICATE REQUEST-----\n "
140- + Convert . ToBase64String ( csr . GetEncoded ( ) , Base64FormattingOptions . InsertLineBreaks )
141- + "\n -----END CERTIFICATE REQUEST-----" ;
142- }
143-
144- private static byte [ ] DerFromPem ( string pem )
145- {
146- var b64 = pem
147- . Replace ( "-----BEGIN CERTIFICATE REQUEST-----" , string . Empty )
148- . Replace ( "-----END CERTIFICATE REQUEST-----" , string . Empty )
149- . Replace ( "\r " , string . Empty )
150- . Replace ( "\n " , string . Empty )
151- . Trim ( ) ;
152- return Convert . FromBase64String ( b64 ) ;
153- }
52+ public static IEnumerable < object [ ] > KeyTypes => KeyAlgorithms . AsMemberData ;
15453
15554 // ---------------------------------------------------------------------------
15655 // Layer 1 — deterministic CSR-validity round-trip (no API, always runs)
@@ -167,11 +66,11 @@ private static byte[] DerFromPem(string pem)
16766 [ MemberData ( nameof ( KeyTypes ) ) ]
16867 public void Csr_RoundTripsKeyAlgorithm ( string tag )
16968 {
170- var spec = SpecFor ( tag ) ;
69+ var spec = KeyAlgorithms . For ( tag ) ;
17170
172- string pem = GenerateCsrPem ( $ "algo-{ tag . ToLowerInvariant ( ) . Replace ( "-" , string . Empty ) } .example.com", spec ) ;
71+ string pem = KeyAlgorithms . GenerateCsrPem ( $ "algo-{ KeyAlgorithms . Slug ( tag ) } .example.com", spec ) ;
17372
174- var request = new Pkcs10CertificationRequest ( DerFromPem ( pem ) ) ;
73+ var request = new Pkcs10CertificationRequest ( KeyAlgorithms . DerFromPem ( pem ) ) ;
17574
17675 request . Verify ( ) . Should ( ) . BeTrue ( $ "the { tag } CSR must be self-signed with a verifiable signature") ;
17776
@@ -216,7 +115,9 @@ public void Csr_RoundTripsKeyAlgorithm(string tag)
216115 ///
217116 /// Opt-in: requires <c>CERTINEXT_ALGO_MATRIX=1</c> because each run creates a real (pending,
218117 /// non-issued) DV order on the sandbox account. No DCV is performed, so the orders park at
219- /// EXTERNALVALIDATION and are not cleaned up here.
118+ /// EXTERNALVALIDATION and are not cleaned up here. "Accepted at submission" is weaker than
119+ /// "will issue" — see <c>DcvLifecycleTests.EnrollWithDcvOn_IssuesPerKeyAlgorithm</c> for the
120+ /// end-to-end issuance matrix.
220121 /// </summary>
221122 [ SkippableTheory ]
222123 [ MemberData ( nameof ( KeyTypes ) ) ]
@@ -227,9 +128,9 @@ public async Task Enroll_AcceptsKeyAlgorithm(string tag)
227128 Environment . GetEnvironmentVariable ( OptInFlag ) == "1" ,
228129 $ "Set { OptInFlag } =1 to run the live algorithm-submission matrix (creates real sandbox orders).") ;
229130
230- var spec = SpecFor ( tag ) ;
231- string cn = $ "algo-{ tag . ToLowerInvariant ( ) . Replace ( "-" , string . Empty ) } .example.com";
232- string csrPem = GenerateCsrPem ( cn , spec ) ;
131+ var spec = KeyAlgorithms . For ( tag ) ;
132+ string cn = $ "algo-{ KeyAlgorithms . Slug ( tag ) } .example.com";
133+ string csrPem = KeyAlgorithms . GenerateCsrPem ( cn , spec ) ;
233134
234135 var productInfo = new EnrollmentProductInfo
235136 {
0 commit comments