diff --git a/pkg/asset/tls/utils.go b/pkg/asset/tls/utils.go index ff11207288f..cf79a539cde 100644 --- a/pkg/asset/tls/utils.go +++ b/pkg/asset/tls/utils.go @@ -63,31 +63,53 @@ func PublicKeyToPem(key *rsa.PublicKey) ([]byte, error) { } // PemToPrivateKey converts a PEM data block to a private key (RSA or ECDSA). +// It iterates through all PEM blocks to tolerate non-key blocks such as +// EC PARAMETERS that OpenSSL may prepend to EC private keys. func PemToPrivateKey(data []byte) (crypto.PrivateKey, error) { - block, _ := pem.Decode(data) - if block == nil { - return nil, errors.Errorf("could not find a PEM block in the private key") - } - - switch block.Type { - case "RSA PRIVATE KEY": - return x509.ParsePKCS1PrivateKey(block.Bytes) - case "EC PRIVATE KEY": - return x509.ParseECPrivateKey(block.Bytes) - case "PRIVATE KEY": - key, err := x509.ParsePKCS8PrivateKey(block.Bytes) - if err != nil { - return nil, err + rest := data + var parseErr error + for { + var block *pem.Block + block, rest = pem.Decode(rest) + if block == nil { + break } - switch key.(type) { - case *rsa.PrivateKey, *ecdsa.PrivateKey: + + switch block.Type { + case "RSA PRIVATE KEY": + key, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + parseErr = err + continue + } + return key, nil + case "EC PRIVATE KEY": + key, err := x509.ParseECPrivateKey(block.Bytes) + if err != nil { + parseErr = err + continue + } return key, nil + case "PRIVATE KEY": + key, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + parseErr = err + continue + } + switch key.(type) { + case *rsa.PrivateKey, *ecdsa.PrivateKey: + return key, nil + default: + return nil, fmt.Errorf("unsupported PKCS#8 key type: %T", key) + } default: - return nil, fmt.Errorf("unsupported PKCS#8 key type: %T", key) + continue } - default: - return nil, fmt.Errorf("unsupported PEM block type: %s", block.Type) } + if parseErr != nil { + return nil, parseErr + } + return nil, errors.Errorf("could not find a PEM block with a supported private key type") } // PemToPublicKey converts a data block to rsa.PublicKey. diff --git a/pkg/asset/tls/utils_test.go b/pkg/asset/tls/utils_test.go index 8eb13c9ccef..84ae612fb79 100644 --- a/pkg/asset/tls/utils_test.go +++ b/pkg/asset/tls/utils_test.go @@ -107,4 +107,57 @@ func TestPemToPrivateKeyFormats(t *testing.T) { _, ok := decoded.(*rsa.PrivateKey) assert.True(t, ok, "expected *rsa.PrivateKey") }) + + t.Run("EC key with EC PARAMETERS prefix", func(t *testing.T) { + ecKey, err := GenerateECDSAPrivateKey(types.ECDSACurveP256) + assert.NoError(t, err) + ecDER, err := x509.MarshalECPrivateKey(ecKey) + assert.NoError(t, err) + + paramBlock := pem.EncodeToMemory(&pem.Block{ + Type: "EC PARAMETERS", + Bytes: []byte{0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07}, + }) + keyBlock := pem.EncodeToMemory(&pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: ecDER, + }) + combined := make([]byte, 0, len(paramBlock)+len(keyBlock)) + combined = append(combined, paramBlock...) + combined = append(combined, keyBlock...) + + decoded, err := PemToPrivateKey(combined) + assert.NoError(t, err) + _, ok := decoded.(*ecdsa.PrivateKey) + assert.True(t, ok, "expected *ecdsa.PrivateKey") + }) + + t.Run("unsupported block type only", func(t *testing.T) { + pemBytes := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: []byte("not a key"), + }) + _, err := PemToPrivateKey(pemBytes) + assert.Error(t, err) + assert.Contains(t, err.Error(), "could not find a PEM block with a supported private key type") + }) + + t.Run("malformed key block followed by valid key", func(t *testing.T) { + badBlock := pem.EncodeToMemory(&pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: []byte("malformed key data"), + }) + key, err := GenerateECDSAPrivateKey(types.ECDSACurveP256) + assert.NoError(t, err) + goodPem, err := PrivateKeyToPem(key) + assert.NoError(t, err) + combined := make([]byte, 0, len(badBlock)+len(goodPem)) + combined = append(combined, badBlock...) + combined = append(combined, goodPem...) + + decoded, err := PemToPrivateKey(combined) + assert.NoError(t, err) + _, ok := decoded.(*ecdsa.PrivateKey) + assert.True(t, ok, "expected *ecdsa.PrivateKey") + }) }