Skip to content

Commit 9d3a5d3

Browse files
committed
pki: add unit tests for configurable PKI feature
Add tests covering PKI validation, feature gate enforcement, certificate generation with PKI configs, and cross-algorithm certificate signing. Key additions: - Test ValidatePKIConfig catches invalid configs and empty PKI - Test ConfigurablePKI feature gate with TechPreview and CustomNoUpgrade - Test ValidateInstallConfig catches invalid PKI with field paths - Test SelfSignedCertKey.Generate() with non-nil PKI configs - Test ECDSA CA signing RSA leaf certificate with chain verification - Test RSA/ECDSA key generation, KeyUsage flags, signature algorithm detection - Test PEM encode/decode roundtrip for RSA and ECDSA keys Assisted-by: Claude Code (Opus 4.6)
1 parent 8d61880 commit 9d3a5d3

7 files changed

Lines changed: 773 additions & 2 deletions

File tree

pkg/asset/tls/certkey_test.go

Lines changed: 130 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@ package tls
22

33
import (
44
"context"
5+
"crypto/ecdsa"
6+
"crypto/rsa"
57
"crypto/x509"
68
"crypto/x509/pkix"
79
"net"
810
"testing"
911

1012
"github.com/stretchr/testify/assert"
13+
14+
configv1alpha1 "github.com/openshift/api/config/v1alpha1"
15+
"github.com/openshift/installer/pkg/types"
1116
)
1217

1318
func TestSignedCertKeyGenerate(t *testing.T) {
@@ -46,8 +51,14 @@ func TestSignedCertKeyGenerate(t *testing.T) {
4651

4752
for _, tt := range tests {
4853
t.Run(tt.name, func(t *testing.T) {
49-
rootCA := &RootCA{}
50-
err := rootCA.Generate(context.Background(), nil)
54+
rootCA := &SelfSignedCertKey{}
55+
rootCACfg := &CertCfg{
56+
Subject: pkix.Name{CommonName: "test-root-ca", OrganizationalUnit: []string{"openshift"}},
57+
KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
58+
Validity: ValidityTenYears(),
59+
IsCA: true,
60+
}
61+
err := rootCA.Generate(context.Background(), rootCACfg, "test-root-ca", nil)
5162
assert.NoError(t, err, "failed to generate root CA")
5263

5364
certKey := &SignedCertKey{}
@@ -90,3 +101,120 @@ func TestSignedCertKeyGenerate(t *testing.T) {
90101
})
91102
}
92103
}
104+
105+
func TestSelfSignedCertKeyGenerateWithPKIConfig(t *testing.T) {
106+
cases := []struct {
107+
name string
108+
pkiConfig *types.PKIConfig
109+
expectKeyType interface{}
110+
expectPubKeyAlg x509.PublicKeyAlgorithm
111+
}{
112+
{
113+
name: "RSA 4096",
114+
pkiConfig: &types.PKIConfig{
115+
SignerCertificates: configv1alpha1.CertificateConfig{
116+
Key: configv1alpha1.KeyConfig{
117+
Algorithm: configv1alpha1.KeyAlgorithmRSA,
118+
RSA: configv1alpha1.RSAKeyConfig{KeySize: 4096},
119+
},
120+
},
121+
},
122+
expectKeyType: &rsa.PrivateKey{},
123+
expectPubKeyAlg: x509.RSA,
124+
},
125+
{
126+
name: "ECDSA P384",
127+
pkiConfig: &types.PKIConfig{
128+
SignerCertificates: configv1alpha1.CertificateConfig{
129+
Key: configv1alpha1.KeyConfig{
130+
Algorithm: configv1alpha1.KeyAlgorithmECDSA,
131+
ECDSA: configv1alpha1.ECDSAKeyConfig{Curve: configv1alpha1.ECDSACurveP384},
132+
},
133+
},
134+
},
135+
expectKeyType: &ecdsa.PrivateKey{},
136+
expectPubKeyAlg: x509.ECDSA,
137+
},
138+
}
139+
140+
for _, tc := range cases {
141+
t.Run(tc.name, func(t *testing.T) {
142+
cfg := &CertCfg{
143+
Subject: pkix.Name{CommonName: "test-pki-ca", OrganizationalUnit: []string{"openshift"}},
144+
Validity: ValidityTenYears(),
145+
IsCA: true,
146+
}
147+
148+
ca := &SelfSignedCertKey{}
149+
err := ca.Generate(context.Background(), cfg, "test-pki-ca", tc.pkiConfig)
150+
assert.NoError(t, err)
151+
152+
key, err := PemToPrivateKey(ca.Key())
153+
assert.NoError(t, err)
154+
assert.IsType(t, tc.expectKeyType, key)
155+
156+
cert, err := PemToCertificate(ca.Cert())
157+
assert.NoError(t, err)
158+
assert.Equal(t, tc.expectPubKeyAlg, cert.PublicKeyAlgorithm)
159+
assert.True(t, cert.IsCA)
160+
})
161+
}
162+
}
163+
164+
func TestCrossAlgorithmCertificateSigning(t *testing.T) {
165+
// Generate ECDSA P384 CA
166+
ecdsaPKI := &types.PKIConfig{
167+
SignerCertificates: configv1alpha1.CertificateConfig{
168+
Key: configv1alpha1.KeyConfig{
169+
Algorithm: configv1alpha1.KeyAlgorithmECDSA,
170+
ECDSA: configv1alpha1.ECDSAKeyConfig{Curve: configv1alpha1.ECDSACurveP384},
171+
},
172+
},
173+
}
174+
rootCA := &SelfSignedCertKey{}
175+
rootCACfg := &CertCfg{
176+
Subject: pkix.Name{CommonName: "ecdsa-ca", OrganizationalUnit: []string{"openshift"}},
177+
Validity: ValidityTenYears(),
178+
IsCA: true,
179+
}
180+
err := rootCA.Generate(context.Background(), rootCACfg, "ecdsa-ca", ecdsaPKI)
181+
assert.NoError(t, err)
182+
183+
// Verify CA key is ECDSA
184+
caKey, err := PemToPrivateKey(rootCA.Key())
185+
assert.NoError(t, err)
186+
assert.IsType(t, &ecdsa.PrivateKey{}, caKey)
187+
188+
// Generate RSA leaf signed by ECDSA CA
189+
leafCfg := &CertCfg{
190+
Subject: pkix.Name{CommonName: "leaf-cert", OrganizationalUnit: []string{"openshift"}},
191+
KeyUsages: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
192+
Validity: ValidityTenYears(),
193+
DNSNames: []string{"test.openshift.io"},
194+
}
195+
certKey := &SignedCertKey{}
196+
err = certKey.Generate(context.Background(), leafCfg, rootCA, "cross-algo-leaf", DoNotAppendParent)
197+
assert.NoError(t, err)
198+
199+
// Verify leaf key is RSA (SignedCertKey always generates RSA leaf keys)
200+
leafKey, err := PemToPrivateKey(certKey.Key())
201+
assert.NoError(t, err)
202+
assert.IsType(t, &rsa.PrivateKey{}, leafKey)
203+
204+
// Verify the leaf cert was signed by the ECDSA CA
205+
leafCert, err := PemToCertificate(certKey.Cert())
206+
assert.NoError(t, err)
207+
assert.Equal(t, x509.ECDSAWithSHA384, leafCert.SignatureAlgorithm)
208+
209+
// Verify cert chain: leaf validates against CA
210+
caCert, err := PemToCertificate(rootCA.Cert())
211+
assert.NoError(t, err)
212+
certPool := x509.NewCertPool()
213+
certPool.AddCert(caCert)
214+
_, err = leafCert.Verify(x509.VerifyOptions{
215+
Roots: certPool,
216+
DNSName: "test.openshift.io",
217+
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
218+
})
219+
assert.NoError(t, err, "leaf cert should validate against ECDSA CA")
220+
}

pkg/asset/tls/tls_test.go

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
package tls
22

33
import (
4+
"crypto/ecdsa"
5+
"crypto/elliptic"
46
"crypto/rand"
7+
"crypto/rsa"
58
"crypto/x509"
69
"crypto/x509/pkix"
710
"testing"
811
"time"
12+
13+
"github.com/stretchr/testify/assert"
14+
15+
configv1alpha1 "github.com/openshift/api/config/v1alpha1"
916
)
1017

1118
func TestSelfSignedCertificate(t *testing.T) {
@@ -105,3 +112,197 @@ func TestSignedCertificate(t *testing.T) {
105112
}
106113
}
107114
}
115+
116+
func TestGenerateRSAPrivateKey(t *testing.T) {
117+
cases := []struct {
118+
name string
119+
keySize int32
120+
}{
121+
{"RSA 2048", 2048},
122+
{"RSA 4096", 4096},
123+
}
124+
125+
for _, tc := range cases {
126+
t.Run(tc.name, func(t *testing.T) {
127+
key, err := GenerateRSAPrivateKey(tc.keySize)
128+
assert.NoError(t, err)
129+
assert.IsType(t, &rsa.PrivateKey{}, key)
130+
assert.Equal(t, int(tc.keySize), key.N.BitLen())
131+
})
132+
}
133+
}
134+
135+
func TestGenerateECDSAPrivateKey(t *testing.T) {
136+
cases := []struct {
137+
name string
138+
curve configv1alpha1.ECDSACurve
139+
expected elliptic.Curve
140+
expectErr bool
141+
}{
142+
{"P256", configv1alpha1.ECDSACurveP256, elliptic.P256(), false},
143+
{"P384", configv1alpha1.ECDSACurveP384, elliptic.P384(), false},
144+
{"P521", configv1alpha1.ECDSACurveP521, elliptic.P521(), false},
145+
{"invalid", "P224", nil, true},
146+
}
147+
148+
for _, tc := range cases {
149+
t.Run(tc.name, func(t *testing.T) {
150+
key, err := GenerateECDSAPrivateKey(tc.curve)
151+
if tc.expectErr {
152+
assert.Error(t, err)
153+
return
154+
}
155+
assert.NoError(t, err)
156+
assert.IsType(t, &ecdsa.PrivateKey{}, key)
157+
assert.Equal(t, tc.expected, key.Curve)
158+
})
159+
}
160+
}
161+
162+
func TestGenerateSelfSignedCertificateWithParams(t *testing.T) {
163+
cases := []struct {
164+
name string
165+
params PrivateKeyParams
166+
expectKeyType interface{}
167+
expectPubKeyAlg x509.PublicKeyAlgorithm
168+
}{
169+
{
170+
name: "RSA 4096 CA",
171+
params: PrivateKeyParams{
172+
Algorithm: configv1alpha1.KeyAlgorithmRSA,
173+
RSAKeySize: 4096,
174+
},
175+
expectKeyType: &rsa.PrivateKey{},
176+
expectPubKeyAlg: x509.RSA,
177+
},
178+
{
179+
name: "ECDSA P384 CA",
180+
params: PrivateKeyParams{
181+
Algorithm: configv1alpha1.KeyAlgorithmECDSA,
182+
ECDSACurve: configv1alpha1.ECDSACurveP384,
183+
},
184+
expectKeyType: &ecdsa.PrivateKey{},
185+
expectPubKeyAlg: x509.ECDSA,
186+
},
187+
{
188+
name: "RSA 2048 CA (default)",
189+
params: PrivateKeyParams{
190+
Algorithm: configv1alpha1.KeyAlgorithmRSA,
191+
RSAKeySize: 2048,
192+
},
193+
expectKeyType: &rsa.PrivateKey{},
194+
expectPubKeyAlg: x509.RSA,
195+
},
196+
}
197+
198+
for _, tc := range cases {
199+
t.Run(tc.name, func(t *testing.T) {
200+
cfg := &CertCfg{
201+
Subject: pkix.Name{CommonName: "test-ca", OrganizationalUnit: []string{"openshift"}},
202+
Validity: time.Hour,
203+
IsCA: true,
204+
}
205+
key, cert, err := GenerateSelfSignedCertificate(cfg, tc.params)
206+
assert.NoError(t, err)
207+
assert.IsType(t, tc.expectKeyType, key)
208+
assert.Equal(t, tc.expectPubKeyAlg, cert.PublicKeyAlgorithm)
209+
assert.True(t, cert.IsCA)
210+
})
211+
}
212+
}
213+
214+
func TestKeyUsageForAlgorithm(t *testing.T) {
215+
cases := []struct {
216+
name string
217+
params PrivateKeyParams
218+
isCA bool
219+
wantUsage x509.KeyUsage
220+
notUsage x509.KeyUsage
221+
}{
222+
{
223+
name: "RSA signer",
224+
params: PrivateKeyParams{Algorithm: configv1alpha1.KeyAlgorithmRSA, RSAKeySize: 2048},
225+
isCA: true,
226+
wantUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign,
227+
},
228+
{
229+
name: "ECDSA signer",
230+
params: PrivateKeyParams{Algorithm: configv1alpha1.KeyAlgorithmECDSA, ECDSACurve: configv1alpha1.ECDSACurveP256},
231+
isCA: true,
232+
wantUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
233+
notUsage: x509.KeyUsageKeyEncipherment,
234+
},
235+
{
236+
name: "RSA non-CA",
237+
params: PrivateKeyParams{Algorithm: configv1alpha1.KeyAlgorithmRSA, RSAKeySize: 2048},
238+
isCA: false,
239+
wantUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
240+
notUsage: x509.KeyUsageCertSign,
241+
},
242+
{
243+
name: "ECDSA non-CA",
244+
params: PrivateKeyParams{Algorithm: configv1alpha1.KeyAlgorithmECDSA, ECDSACurve: configv1alpha1.ECDSACurveP384},
245+
isCA: false,
246+
wantUsage: x509.KeyUsageDigitalSignature,
247+
notUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign,
248+
},
249+
}
250+
251+
for _, tc := range cases {
252+
t.Run(tc.name, func(t *testing.T) {
253+
cfg := &CertCfg{
254+
Subject: pkix.Name{CommonName: "test", OrganizationalUnit: []string{"openshift"}},
255+
Validity: time.Hour,
256+
IsCA: tc.isCA,
257+
}
258+
_, cert, err := GenerateSelfSignedCertificate(cfg, tc.params)
259+
assert.NoError(t, err)
260+
assert.Equal(t, tc.wantUsage, cert.KeyUsage, "KeyUsage mismatch")
261+
if tc.notUsage != 0 {
262+
assert.Zero(t, cert.KeyUsage&tc.notUsage, "unexpected KeyUsage bits set")
263+
}
264+
})
265+
}
266+
}
267+
268+
func TestSignatureAlgorithmAutoDetection(t *testing.T) {
269+
cases := []struct {
270+
name string
271+
params PrivateKeyParams
272+
expected x509.SignatureAlgorithm
273+
}{
274+
{
275+
name: "RSA",
276+
params: PrivateKeyParams{Algorithm: configv1alpha1.KeyAlgorithmRSA, RSAKeySize: 2048},
277+
expected: x509.SHA256WithRSA,
278+
},
279+
{
280+
name: "ECDSA P256",
281+
params: PrivateKeyParams{Algorithm: configv1alpha1.KeyAlgorithmECDSA, ECDSACurve: configv1alpha1.ECDSACurveP256},
282+
expected: x509.ECDSAWithSHA256,
283+
},
284+
{
285+
name: "ECDSA P384",
286+
params: PrivateKeyParams{Algorithm: configv1alpha1.KeyAlgorithmECDSA, ECDSACurve: configv1alpha1.ECDSACurveP384},
287+
expected: x509.ECDSAWithSHA384,
288+
},
289+
{
290+
name: "ECDSA P521",
291+
params: PrivateKeyParams{Algorithm: configv1alpha1.KeyAlgorithmECDSA, ECDSACurve: configv1alpha1.ECDSACurveP521},
292+
expected: x509.ECDSAWithSHA512,
293+
},
294+
}
295+
296+
for _, tc := range cases {
297+
t.Run(tc.name, func(t *testing.T) {
298+
cfg := &CertCfg{
299+
Subject: pkix.Name{CommonName: "test-sig", OrganizationalUnit: []string{"openshift"}},
300+
Validity: time.Hour,
301+
IsCA: true,
302+
}
303+
_, cert, err := GenerateSelfSignedCertificate(cfg, tc.params)
304+
assert.NoError(t, err)
305+
assert.Equal(t, tc.expected, cert.SignatureAlgorithm)
306+
})
307+
}
308+
}

0 commit comments

Comments
 (0)