diff --git a/pkg/cryptoutils/privatekey.go b/pkg/cryptoutils/privatekey.go index d97bf36bf..b1a0dad05 100644 --- a/pkg/cryptoutils/privatekey.go +++ b/pkg/cryptoutils/privatekey.go @@ -31,7 +31,11 @@ import ( const ( // PrivateKeyPEMType is the string "PRIVATE KEY" to be used during PEM encoding and decoding - PrivateKeyPEMType PEMType = "PRIVATE KEY" + PrivateKeyPEMType PEMType = "PRIVATE KEY" + // ECPrivateKeyPEMType is the string "EC PRIVATE KEY" used to parse SEC 1 EC private keys + ECPrivateKeyPEMType PEMType = "EC PRIVATE KEY" + // PKCS1PrivateKeyPEMType is the string "RSA PRIVATE KEY" used to parse PKCS#1-encoded private keys + PKCS1PrivateKeyPEMType PEMType = "RSA PRIVATE KEY" encryptedCosignPrivateKeyPEMType PEMType = "ENCRYPTED COSIGN PRIVATE KEY" // EncryptedSigstorePrivateKeyPEMType is the string "ENCRYPTED SIGSTORE PRIVATE KEY" to be used during PEM encoding and decoding EncryptedSigstorePrivateKeyPEMType PEMType = "ENCRYPTED SIGSTORE PRIVATE KEY" @@ -106,6 +110,10 @@ func UnmarshalPEMToPrivateKey(pemBytes []byte, pf PassFunc) (crypto.PrivateKey, switch derBlock.Type { case string(PrivateKeyPEMType): return x509.ParsePKCS8PrivateKey(derBlock.Bytes) + case string(PKCS1PrivateKeyPEMType): + return x509.ParsePKCS1PrivateKey(derBlock.Bytes) + case string(ECPrivateKeyPEMType): + return x509.ParseECPrivateKey(derBlock.Bytes) case string(EncryptedSigstorePrivateKeyPEMType), string(encryptedCosignPrivateKeyPEMType): derBytes := derBlock.Bytes if pf != nil { @@ -123,7 +131,7 @@ func UnmarshalPEMToPrivateKey(pemBytes []byte, pf PassFunc) (crypto.PrivateKey, return x509.ParsePKCS8PrivateKey(derBytes) } - return nil, fmt.Errorf("unknown PEM file type: %v", derBlock.Type) + return nil, fmt.Errorf("unknown private key PEM file type: %v", derBlock.Type) } // MarshalPrivateKeyToDER converts a crypto.PrivateKey into a PKCS8 ASN.1 DER byte slice @@ -134,7 +142,7 @@ func MarshalPrivateKeyToDER(priv crypto.PrivateKey) ([]byte, error) { return x509.MarshalPKCS8PrivateKey(priv) } -// MarshalPrivateKeyToPEM converts a crypto.PrivateKey into a PEM-encoded byte slice +// MarshalPrivateKeyToPEM converts a crypto.PrivateKey into a PKCS#8 PEM-encoded byte slice func MarshalPrivateKeyToPEM(priv crypto.PrivateKey) ([]byte, error) { derBytes, err := MarshalPrivateKeyToDER(priv) if err != nil { diff --git a/pkg/cryptoutils/privatekey_test.go b/pkg/cryptoutils/privatekey_test.go index bf6abf3ff..7489ba20f 100644 --- a/pkg/cryptoutils/privatekey_test.go +++ b/pkg/cryptoutils/privatekey_test.go @@ -21,6 +21,9 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/rsa" + "crypto/x509" + "encoding/pem" + "strings" "testing" "github.com/google/go-cmp/cmp" @@ -209,3 +212,85 @@ func TestRSAPrivateKeyPEMRoundtrip(t *testing.T) { } verifyPrivateKeyPEMRoundtrip(t, priv) } + +func TestUnmarshalPEMToPrivateKey(t *testing.T) { + // test PKCS#8 PEM-encoded private keys + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("rsa.GenerateKey failed: %v", err) + } + pkcs8PrivateKey, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + t.Fatalf("x509.MarshalPKCS8PrivateKey failed: %v", err) + } + pkcs8PEMBlock := pem.EncodeToMemory(&pem.Block{ + Type: "PRIVATE KEY", + Bytes: pkcs8PrivateKey, + }) + k, err := UnmarshalPEMToPrivateKey(pkcs8PEMBlock, nil) + if err != nil { + t.Fatalf("UnmarshalPEMToPrivateKey for PKCS#8 failed: %v", err) + } + if !priv.Equal(k) { + t.Fatalf("private keys for PKCS#8 are not equal") + } + + // test PKCS#1 PEM-encoded RSA private keys + priv, err = rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("rsa.GenerateKey failed: %v", err) + } + rsaPrivKey := x509.MarshalPKCS1PrivateKey(priv) + pkcs1PEMBlock := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: rsaPrivKey, + }) + k, err = UnmarshalPEMToPrivateKey(pkcs1PEMBlock, nil) + if err != nil { + t.Fatalf("UnmarshalPEMToPrivateKey for PKCS#1 failed: %v", err) + } + if !priv.Equal(k) { + t.Fatalf("private keys for PKCS1 are not equal") + } + + // test SEC 1 EC private keys + ecdsaKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("ecdsa.GenerateKey failed: %v", err) + } + ecPrivKey, err := x509.MarshalECPrivateKey(ecdsaKey) + if err != nil { + t.Fatalf("x509.MarshalECPrivateKey failed: %v", err) + } + ecPEMBlock := pem.EncodeToMemory(&pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: ecPrivKey, + }) + k, err = UnmarshalPEMToPrivateKey(ecPEMBlock, nil) + if err != nil { + t.Fatalf("UnmarshalPEMToPrivateKey for SEC 1 failed: %v", err) + } + if !ecdsaKey.Equal(k) { + t.Fatalf("private keys for SEC 1 (EC) are not equal") + } + + // test Sigstore formatted private keys + privSigstorePEM, _, err := GeneratePEMEncodedECDSAKeyPair(elliptic.P256(), StaticPasswordFunc([]byte("pw"))) + if err != nil { + t.Fatalf("GeneratePEMEncodedECDSAKeyPair failed: %v", err) + } + _, err = UnmarshalPEMToPrivateKey(privSigstorePEM, StaticPasswordFunc([]byte("pw"))) + if err != nil { + t.Fatalf("UnmarshalPEMToPrivateKey for Sigstore encoded key failed: %v", err) + } + + // test other PEM formats return an error + invalidPEMBlock := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: rsaPrivKey, + }) + _, err = UnmarshalPEMToPrivateKey(invalidPEMBlock, nil) + if err == nil || !strings.Contains(err.Error(), "unknown private key PEM file type") { + t.Fatalf("expected error unmarshalling invalid PEM block, got: %v", err) + } +} diff --git a/pkg/cryptoutils/publickey.go b/pkg/cryptoutils/publickey.go index e9f48decb..d2b94d4d9 100644 --- a/pkg/cryptoutils/publickey.go +++ b/pkg/cryptoutils/publickey.go @@ -37,6 +37,8 @@ import ( const ( // PublicKeyPEMType is the string "PUBLIC KEY" to be used during PEM encoding and decoding PublicKeyPEMType PEMType = "PUBLIC KEY" + // PKCS1PublicKeyPEMType is the string "RSA PUBLIC KEY" used to parse PKCS#1-encoded public keys + PKCS1PublicKeyPEMType PEMType = "RSA PUBLIC KEY" ) // subjectPublicKeyInfo is used to construct a subject key ID. @@ -55,6 +57,8 @@ func UnmarshalPEMToPublicKey(pemBytes []byte) (crypto.PublicKey, error) { switch derBytes.Type { case string(PublicKeyPEMType): return x509.ParsePKIXPublicKey(derBytes.Bytes) + case string(PKCS1PublicKeyPEMType): + return x509.ParsePKCS1PublicKey(derBytes.Bytes) default: return nil, fmt.Errorf("unknown Public key PEM file type: %v. Are you passing the correct public key?", derBytes.Type) diff --git a/pkg/cryptoutils/publickey_test.go b/pkg/cryptoutils/publickey_test.go index a399999cd..e9b6cb243 100644 --- a/pkg/cryptoutils/publickey_test.go +++ b/pkg/cryptoutils/publickey_test.go @@ -21,6 +21,8 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/rsa" + "crypto/x509" + "encoding/pem" "strings" "testing" @@ -252,3 +254,54 @@ func TestValidatePubKeyEd25519(t *testing.T) { } // Only success, ED25519 keys do not support customization } + +func TestUnmarshalPEMToPublicKey(t *testing.T) { + // test PKIX PEM-encoded public keys + priv, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("rsa.GenerateKey failed: %v", err) + } + pkixPubKey, err := x509.MarshalPKIXPublicKey(priv.Public()) + if err != nil { + t.Fatalf("x509.MarshalPKIXPublicKey failed: %v", err) + } + pkixPEMBlock := pem.EncodeToMemory(&pem.Block{ + Type: "PUBLIC KEY", + Bytes: pkixPubKey, + }) + k, err := UnmarshalPEMToPublicKey(pkixPEMBlock) + if err != nil { + t.Fatalf("UnmarshalPEMToPublicKey for PKIX failed: %v", err) + } + if EqualKeys(priv.Public(), k) != nil { + t.Fatalf("public keys for PKIX are not equal") + } + + // test PKCS#1 PEM-encoded RSA public keys + priv, err = rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("rsa.GenerateKey failed: %v", err) + } + rsaPubKey := x509.MarshalPKCS1PublicKey(&priv.PublicKey) + pkcs1PEMBlock := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: rsaPubKey, + }) + k, err = UnmarshalPEMToPublicKey(pkcs1PEMBlock) + if err != nil { + t.Fatalf("UnmarshalPEMToPublicKey for PKCS#1 failed: %v", err) + } + if EqualKeys(priv.Public(), k) != nil { + t.Fatalf("public keys for PKCS1 are not equal") + } + + // test other PEM formats return an error + invalidPEMBlock := pem.EncodeToMemory(&pem.Block{ + Type: "EC PUBLIC KEY", + Bytes: rsaPubKey, + }) + _, err = UnmarshalPEMToPublicKey(invalidPEMBlock) + if err == nil || !strings.Contains(err.Error(), "unknown Public key PEM file type") { + t.Fatalf("expected error unmarshalling invalid PEM block, got: %v", err) + } +}