Skip to content

Commit

Permalink
Add support for parsing PKCS#1 priv/pub keys, SEC1 priv keys (#638)
Browse files Browse the repository at this point in the history
This adds support for parsing RSA public and private keys, and SEC 1 EC
private keys. This should reduce unexpected format errors for library
users.

Signed-off-by: Hayden Blauzvern <hblauzvern@google.com>
  • Loading branch information
haydentherapper committed Aug 23, 2022
1 parent bf4a91d commit 6450063
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 3 deletions.
14 changes: 11 additions & 3 deletions pkg/cryptoutils/privatekey.go
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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 {
Expand Down
85 changes: 85 additions & 0 deletions pkg/cryptoutils/privatekey_test.go
Expand Up @@ -21,6 +21,9 @@ import (
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"strings"
"testing"

"github.com/google/go-cmp/cmp"
Expand Down Expand Up @@ -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)
}
}
4 changes: 4 additions & 0 deletions pkg/cryptoutils/publickey.go
Expand Up @@ -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.
Expand All @@ -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)
Expand Down
53 changes: 53 additions & 0 deletions pkg/cryptoutils/publickey_test.go
Expand Up @@ -21,6 +21,8 @@ import (
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"strings"
"testing"

Expand Down Expand Up @@ -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)
}
}

0 comments on commit 6450063

Please sign in to comment.