From 32c91befffe736e61c83580bd22c03df6bec0085 Mon Sep 17 00:00:00 2001 From: Ross Golder Date: Fri, 24 Jul 2026 16:44:15 +0700 Subject: [PATCH] fix(crypto): add PKCS8 private key support (#1249) Extend ParsePrivateKeyBytes to support PKCS8 private key format in addition to PKCS1 and EC. This enables compatibility with certificates generated by various tools. Add unit tests for ParsePrivateKeyBytes covering PKCS1, PKCS8, EC, and invalid key formats. --- internal/crypto/crypto.go | 14 ++++ internal/crypto/crypto_test.go | 122 +++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 internal/crypto/crypto_test.go diff --git a/internal/crypto/crypto.go b/internal/crypto/crypto.go index 6ae8ae3d..761d2dcd 100644 --- a/internal/crypto/crypto.go +++ b/internal/crypto/crypto.go @@ -129,6 +129,20 @@ func ParsePrivateKeyBytes(content []byte) (crypto.Signer, error) { return privateKey, nil } + if pemContent.Type == "PRIVATE KEY" { + privateKey, err := x509.ParsePKCS8PrivateKey(pemContent.Bytes) + if err != nil { + return nil, fmt.Errorf("cannot parse PKCS8 Private Key: %w", err) + } + + signer, ok := privateKey.(crypto.Signer) + if !ok { + return nil, fmt.Errorf("PKCS8 private key is not a signer") + } + + return signer, nil + } + privateKey, err := x509.ParsePKCS1PrivateKey(pemContent.Bytes) if err != nil { return nil, fmt.Errorf("cannot parse PKCS1 Private Key: %w", err) diff --git a/internal/crypto/crypto_test.go b/internal/crypto/crypto_test.go new file mode 100644 index 00000000..39e620c8 --- /dev/null +++ b/internal/crypto/crypto_test.go @@ -0,0 +1,122 @@ +// Copyright 2022 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package crypto + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "testing" + "time" +) + +func TestParsePrivateKeyBytes(t *testing.T) { + tests := []struct { + name string + keyType string + keyGen func() []byte + wantError bool + }{ + { + name: "PKCS1 RSA key", + keyType: "PKCS1", + keyGen: func() []byte { + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("failed to generate RSA key: %v", err) + } + keyBytes := x509.MarshalPKCS1PrivateKey(privateKey) + + return pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes}) + }, + wantError: false, + }, + { + name: "PKCS8 RSA key", + keyType: "PKCS8", + keyGen: func() []byte { + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("failed to generate RSA key: %v", err) + } + pkcs8Bytes, err := x509.MarshalPKCS8PrivateKey(privateKey) + if err != nil { + t.Fatalf("failed to marshal PKCS8: %v", err) + } + + return pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: pkcs8Bytes}) + }, + wantError: false, + }, + { + name: "EC key", + keyType: "EC", + keyGen: func() []byte { + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("failed to generate EC key: %v", err) + } + ecBytes, err := x509.MarshalECPrivateKey(privateKey) + if err != nil { + t.Fatalf("failed to marshal EC: %v", err) + } + + return pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: ecBytes}) + }, + wantError: false, + }, + { + name: "invalid key", + keyType: "invalid", + keyGen: func() []byte { + return pem.EncodeToMemory(&pem.Block{Type: "UNKNOWN", Bytes: []byte("not a valid key")}) + }, + wantError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + keyBytes := tt.keyGen() + _, err := ParsePrivateKeyBytes(keyBytes) + if (err != nil) != tt.wantError { + t.Errorf("ParsePrivateKeyBytes() error = %v, wantError %v", err, tt.wantError) + } + }) + } +} + +func GenerateSelfSignedCA() ([]byte, []byte, error) { + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return nil, nil, err + } + + template := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: "test-ca", + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(365 * 24 * time.Hour), + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + BasicConstraintsValid: true, + IsCA: true, + } + + certDER, err := x509.CreateCertificate(rand.Reader, template, template, &privateKey.PublicKey, privateKey) + if err != nil { + return nil, nil, err + } + + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}) + + return certPEM, keyPEM, nil +}