Skip to content

Commit 4f20f40

Browse files
Merge pull request #3119 from jrvaldes/WINC-1406
WINC-1406: Adds validation logic in the signer.go to trigger a warning if a weak public key is used
2 parents 96fe49f + c6484be commit 4f20f40

File tree

3 files changed

+156
-1
lines changed

3 files changed

+156
-1
lines changed

controllers/secret_controller.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,16 @@ func (r *SecretReconciler) reconcileUserDataSecret(ctx context.Context) error {
183183
}
184184
return fmt.Errorf("unable to get secret %s: %w", secrets.PrivateKeySecret, err)
185185
}
186+
187+
// get the public key from the signer
188+
publicKey := keySigner.PublicKey()
189+
if err := signer.ValidatePublicKey(publicKey); err != nil {
190+
r.log.Info("Warning: A weak private key is being used for userdata generation. "+
191+
"It is strongly recommended to use a more secure key.", "details", err)
192+
}
193+
186194
// Generate expected userData based on the existing private key
187-
validUserData, err := secrets.GenerateUserData(r.platform, keySigner.PublicKey())
195+
validUserData, err := secrets.GenerateUserData(r.platform, publicKey)
188196
if err != nil {
189197
return fmt.Errorf("error generating %s secret: %w", secrets.UserDataSecret, err)
190198
}

pkg/signer/signer.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ package signer
22

33
import (
44
"context"
5+
"crypto/dsa"
6+
"crypto/ecdsa"
7+
"crypto/ed25519"
8+
"crypto/rsa"
59
"fmt"
610

711
"golang.org/x/crypto/ssh"
@@ -11,6 +15,9 @@ import (
1115
"github.com/openshift/windows-machine-config-operator/pkg/secrets"
1216
)
1317

18+
// minRSABitLen is the minimum RSA key size recommended for security.
19+
const minRSABitLen = 2048
20+
1421
// Create creates a signer using the private key data
1522
func Create(ctx context.Context, secret kubeTypes.NamespacedName, c client.Client) (ssh.Signer, error) {
1623
privateKey, err := secrets.GetPrivateKey(ctx, secret, c)
@@ -23,3 +30,50 @@ func Create(ctx context.Context, secret kubeTypes.NamespacedName, c client.Clien
2330
}
2431
return signer, nil
2532
}
33+
34+
// ValidatePublicKey checks if the given public key meets security standards.
35+
// It returns an error if the key is weak.
36+
func ValidatePublicKey(pubKey ssh.PublicKey) error {
37+
return validate(pubKey)
38+
}
39+
40+
// validate checks the provided ssh.PublicKey for cryptographic strength and compliance with modern security standards.
41+
// It performs the following checks:
42+
// 1. Ensures the key implements ssh.CryptoPublicKey, which exposes the underlying crypto.PublicKey.
43+
// 2. For RSA keys: verifies the modulus bit length is at least minRSABitLen (2048 bits), rejecting weak keys.
44+
// 3. For DSA keys: rejects all, as DSA is deprecated and considered insecure.
45+
// 4. For ECDSA keys: checks the curve used; specifically rejects P-224 as too weak
46+
// 5. For Ed25519 keys: accepts as secure.
47+
// 6. For unknown or unsupported key types: rejects with an error.
48+
//
49+
// Returns nil if the key is considered secure, or an error describing the weakness otherwise.
50+
func validate(pubKey ssh.PublicKey) error {
51+
cryptoPubKey, ok := pubKey.(ssh.CryptoPublicKey)
52+
if !ok {
53+
// This case should ideally not be hit with standard SSH keys.
54+
return fmt.Errorf("invalid key type: %s", pubKey.Type())
55+
}
56+
57+
switch key := cryptoPubKey.CryptoPublicKey().(type) {
58+
case *rsa.PublicKey:
59+
if key.N.BitLen() < minRSABitLen {
60+
return fmt.Errorf("RSA key size is %d bits, which is considered weak. Use %d or greater",
61+
key.N.BitLen(), minRSABitLen)
62+
}
63+
case *dsa.PublicKey:
64+
return fmt.Errorf("DSA keys are deprecated and considered weak. Please use RSA, ECDSA, or Ed25519")
65+
case *ecdsa.PublicKey:
66+
curveName := key.Curve.Params().Name
67+
// P‑224 is deprecated, too small (~112‑bit) for modern standards and should be phased out by 2030
68+
if curveName == "P‑224" {
69+
return fmt.Errorf("found ECDSA key with small curve %s. Use P-256, P-384, P-521 or larger", curveName)
70+
}
71+
case ed25519.PublicKey:
72+
// Ed25519 is a secure algorithm
73+
default:
74+
return fmt.Errorf("unknown or unsupported public key type: %T", key)
75+
}
76+
77+
// the key is not weak
78+
return nil
79+
}

pkg/signer/signer_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package signer
2+
3+
import (
4+
"crypto/dsa"
5+
"crypto/ecdsa"
6+
"crypto/ed25519"
7+
"crypto/elliptic"
8+
"crypto/rand"
9+
"crypto/rsa"
10+
"testing"
11+
12+
"golang.org/x/crypto/ssh"
13+
)
14+
15+
func TestValidatePublicKey(t *testing.T) {
16+
// Strong RSA (2048-bit)
17+
strongRSAKey, err := rsa.GenerateKey(rand.Reader, 2048)
18+
if err != nil {
19+
t.Fatalf("Failed to generate strong RSA key: %v", err)
20+
}
21+
strongRSAPub, err := ssh.NewPublicKey(&strongRSAKey.PublicKey)
22+
if err != nil {
23+
t.Fatalf("Failed to create SSH public key from strong RSA key: %v", err)
24+
}
25+
26+
// Weak RSA (1024-bit)
27+
weakRSAKey, err := rsa.GenerateKey(rand.Reader, 1024)
28+
if err != nil {
29+
t.Fatalf("Failed to generate weak RSA key: %v", err)
30+
}
31+
weak1024RSAPub, err := ssh.NewPublicKey(&weakRSAKey.PublicKey)
32+
if err != nil {
33+
t.Fatalf("Failed to create SSH public key from weak RSA key: %v", err)
34+
}
35+
36+
// Strong curve ECDSA (P-256)
37+
strongECDSAKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
38+
if err != nil {
39+
t.Fatalf("Failed to generate strong ECDSA key: %v", err)
40+
}
41+
strongECDSAPub, err := ssh.NewPublicKey(&strongECDSAKey.PublicKey)
42+
if err != nil {
43+
t.Fatalf("Failed to create SSH public key from strong ECDSA key: %v", err)
44+
}
45+
46+
// Strong Ed25519
47+
ed25519Pub, ed25519Priv, err := ed25519.GenerateKey(rand.Reader)
48+
if err != nil {
49+
t.Fatalf("Failed to generate Ed25519 key: %v", err)
50+
}
51+
ed25519SSHPub, err := ssh.NewPublicKey(ed25519Pub)
52+
if err != nil {
53+
t.Fatalf("Failed to create SSH public key from Ed25519 key: %v", err)
54+
}
55+
_ = ed25519Priv // Suppress unused variable warning
56+
57+
// Weak DSA
58+
params := new(dsa.Parameters)
59+
if err := dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160); err != nil {
60+
t.Fatalf("Failed to generate DSA params: %v", err)
61+
}
62+
dsaKey := new(dsa.PrivateKey)
63+
dsaKey.Parameters = *params
64+
if err := dsa.GenerateKey(dsaKey, rand.Reader); err != nil {
65+
t.Fatalf("Failed to generate DSA key: %v", err)
66+
}
67+
dsaPub, err := ssh.NewPublicKey(&dsaKey.PublicKey)
68+
if err != nil {
69+
t.Fatalf("Failed to create SSH public key from DSA key: %v", err)
70+
}
71+
72+
testCases := []struct {
73+
name string
74+
key ssh.PublicKey
75+
wantErr bool
76+
}{
77+
{"Strong RSA", strongRSAPub, false},
78+
{"Weak RSA", weak1024RSAPub, true},
79+
{"Strong ECDSA", strongECDSAPub, false},
80+
{"Ed25519", ed25519SSHPub, false},
81+
{"DSA", dsaPub, true},
82+
}
83+
84+
for _, tc := range testCases {
85+
t.Run(tc.name, func(t *testing.T) {
86+
err := ValidatePublicKey(tc.key)
87+
if (err != nil) != tc.wantErr {
88+
t.Errorf("ValidatePublicKey() error = %v, wantErr %v", err, tc.wantErr)
89+
return
90+
}
91+
})
92+
}
93+
}

0 commit comments

Comments
 (0)