From f2f97ab589bd4df79f91e4c5ac329c13860ee298 Mon Sep 17 00:00:00 2001 From: Aditya Sirish Date: Tue, 2 Jan 2024 16:56:36 -0500 Subject: [PATCH] Support PEM encoding for all key types This change matches python-securesystemslib by retiring the custom serialization format. With this change, RSA, ED25519, and ECDSA keys can be loaded from standard PEM encoding, meaning custom tooling isn't needed to generate the keys. This commit adds deprecation warnings to prior Load methods that expected the custom format. Signed-off-by: Aditya Sirish --- signerverifier/ecdsa.go | 10 +- signerverifier/ecdsa_test.go | 91 +++++++++++++++ signerverifier/ed25519.go | 5 + signerverifier/ed25519_test.go | 91 +++++++++++++++ signerverifier/rsa.go | 15 ++- signerverifier/rsa_test.go | 6 +- signerverifier/signerverifier.go | 110 ++++++++++++++++++ signerverifier/signerverifier_test.go | 103 ++++++++++++++++ signerverifier/test-data/ecdsa-test-key-pem | 5 + .../test-data/ecdsa-test-key-pem.pub | 4 + signerverifier/test-data/ed25519-test-key-pem | 3 + .../test-data/ed25519-test-key-pem.pub | 3 + signerverifier/utils.go | 3 + 13 files changed, 442 insertions(+), 7 deletions(-) create mode 100644 signerverifier/signerverifier_test.go create mode 100644 signerverifier/test-data/ecdsa-test-key-pem create mode 100644 signerverifier/test-data/ecdsa-test-key-pem.pub create mode 100644 signerverifier/test-data/ed25519-test-key-pem create mode 100644 signerverifier/test-data/ed25519-test-key-pem.pub diff --git a/signerverifier/ecdsa.go b/signerverifier/ecdsa.go index f3e6c20..691091a 100644 --- a/signerverifier/ecdsa.go +++ b/signerverifier/ecdsa.go @@ -11,7 +11,10 @@ import ( "os" ) -const ECDSAKeyType = "ecdsa" +const ( + ECDSAKeyType = "ecdsa" + ECDSAKeyScheme = "ecdsa-sha2-nistp256" +) // ECDSASignerVerifier is a dsse.SignerVerifier compliant interface to sign and // verify signatures using ECDSA keys. @@ -89,6 +92,11 @@ func (sv *ECDSASignerVerifier) Public() crypto.PublicKey { // LoadECDSAKeyFromFile returns an SSLibKey instance for an ECDSA key stored in // a file in the custom securesystemslib format. +// +// Deprecated: use LoadKey(). The custom serialization format has been +// deprecated. Use +// https://github.com/secure-systems-lab/securesystemslib/blob/main/docs/migrate_key.py +// to convert your key. func LoadECDSAKeyFromFile(path string) (*SSLibKey, error) { contents, err := os.ReadFile(path) if err != nil { diff --git a/signerverifier/ecdsa_test.go b/signerverifier/ecdsa_test.go index 71163dc..d695412 100644 --- a/signerverifier/ecdsa_test.go +++ b/signerverifier/ecdsa_test.go @@ -148,6 +148,55 @@ func TestECDSASignerVerifierWithDSSEEnvelope(t *testing.T) { assert.Equal(t, "98adf38602c48c5479e9a991ee3f8cbf541ee4f985e00f7a5fc4148d9a45b704", acceptedKeys[0].KeyID) } +func TestECDSASignerVerifierWithDSSEEnvelopeAndPEMKey(t *testing.T) { + key, err := LoadKey(ecdsaPrivateKey) + if err != nil { + t.Fatal(err) + } + + sv, err := NewECDSASignerVerifierFromSSLibKey(key) + if err != nil { + t.Fatal(err) + } + + payloadType := "application/vnd.dsse+json" + payload := []byte("test message") + + es, err := dsse.NewEnvelopeSigner(sv) + if err != nil { + t.Error(err) + } + + env, err := es.SignPayload(context.Background(), payloadType, payload) + if err != nil { + t.Error(err) + } + + assert.Equal(t, "98adf38602c48c5479e9a991ee3f8cbf541ee4f985e00f7a5fc4148d9a45b704", env.Signatures[0].KeyID) + envPayload, err := env.DecodeB64Payload() + assert.Equal(t, payload, envPayload) + assert.Nil(t, err) + + key, err = LoadKey(ecdsaPublicKey) + if err != nil { + t.Fatal(err) + } + + sv, err = NewECDSASignerVerifierFromSSLibKey(key) + if err != nil { + t.Fatal(err) + } + + ev, err := dsse.NewEnvelopeVerifier(sv) + if err != nil { + t.Error(err) + } + + acceptedKeys, err := ev.Verify(context.Background(), env) + assert.Nil(t, err) + assert.Equal(t, "98adf38602c48c5479e9a991ee3f8cbf541ee4f985e00f7a5fc4148d9a45b704", acceptedKeys[0].KeyID) +} + func TestECDSASignerVerifierWithMetablockFile(t *testing.T) { key, err := LoadECDSAKeyFromFile(filepath.Join("test-data", "ecdsa-test-key.pub")) if err != nil { @@ -189,3 +238,45 @@ func TestECDSASignerVerifierWithMetablockFile(t *testing.T) { err = sv.Verify(context.Background(), encodedBytes, decodedSig) assert.Nil(t, err) } + +func TestECDSASignerVerifierWithMetablockFileAndPEMKey(t *testing.T) { + key, err := LoadKey(ecdsaPublicKey) + if err != nil { + t.Fatal(err) + } + + sv, err := NewECDSASignerVerifierFromSSLibKey(key) + if err != nil { + t.Fatal(err) + } + + metadataBytes, err := os.ReadFile(filepath.Join("test-data", "test-ecdsa.98adf386.link")) + if err != nil { + t.Fatal(err) + } + + mb := struct { + Signatures []struct { + KeyID string `json:"keyid"` + Sig string `json:"sig"` + } `json:"signatures"` + Signed any `json:"signed"` + }{} + + if err := json.Unmarshal(metadataBytes, &mb); err != nil { + t.Fatal(err) + } + + assert.Equal(t, "304502201fbb03c0937504182a48c66f9218bdcb2e99a07ada273e92e5e543867f98c8d7022100dbfa7bbf74fd76d76c1d08676419cba85bbd81dfb000f3ac6a786693ddc508f5", mb.Signatures[0].Sig) + assert.Equal(t, sv.keyID, mb.Signatures[0].KeyID) + + encodedBytes, err := cjson.EncodeCanonical(mb.Signed) + if err != nil { + t.Fatal(err) + } + + decodedSig := hexDecode(t, mb.Signatures[0].Sig) + + err = sv.Verify(context.Background(), encodedBytes, decodedSig) + assert.Nil(t, err) +} diff --git a/signerverifier/ed25519.go b/signerverifier/ed25519.go index 0a2210c..d954e14 100644 --- a/signerverifier/ed25519.go +++ b/signerverifier/ed25519.go @@ -88,6 +88,11 @@ func (sv *ED25519SignerVerifier) Public() crypto.PublicKey { // LoadED25519KeyFromFile returns an SSLibKey instance for an ED25519 key stored // in a file in the custom securesystemslib format. +// +// Deprecated: use LoadKey(). The custom serialization format has been +// deprecated. Use +// https://github.com/secure-systems-lab/securesystemslib/blob/main/docs/migrate_key.py +// to convert your key. func LoadED25519KeyFromFile(path string) (*SSLibKey, error) { contents, err := os.ReadFile(path) if err != nil { diff --git a/signerverifier/ed25519_test.go b/signerverifier/ed25519_test.go index 19cf91e..5b1973f 100644 --- a/signerverifier/ed25519_test.go +++ b/signerverifier/ed25519_test.go @@ -164,6 +164,55 @@ func TestED25519SignerVerifierWithDSSEEnvelope(t *testing.T) { assert.Equal(t, "52e3b8e73279d6ebdd62a5016e2725ff284f569665eb92ccb145d83817a02997", acceptedKeys[0].KeyID) } +func TestED25519SignerVerifierWithDSSEEnvelopeAndPEMKey(t *testing.T) { + key, err := LoadKey(ed25519PrivateKey) + if err != nil { + t.Fatal(err) + } + + sv, err := NewED25519SignerVerifierFromSSLibKey(key) + if err != nil { + t.Fatal(err) + } + + payloadType := "application/vnd.dsse+json" + payload := []byte("test message") + + es, err := dsse.NewEnvelopeSigner(sv) + if err != nil { + t.Error(err) + } + + env, err := es.SignPayload(context.Background(), payloadType, payload) + if err != nil { + t.Error(err) + } + + assert.Equal(t, "52e3b8e73279d6ebdd62a5016e2725ff284f569665eb92ccb145d83817a02997", env.Signatures[0].KeyID) + envPayload, err := env.DecodeB64Payload() + assert.Equal(t, payload, envPayload) + assert.Nil(t, err) + + key, err = LoadKey(ed25519PublicKey) + if err != nil { + t.Fatal(err) + } + + sv, err = NewED25519SignerVerifierFromSSLibKey(key) + if err != nil { + t.Fatal(err) + } + + ev, err := dsse.NewEnvelopeVerifier(sv) + if err != nil { + t.Error(err) + } + + acceptedKeys, err := ev.Verify(context.Background(), env) + assert.Nil(t, err) + assert.Equal(t, "52e3b8e73279d6ebdd62a5016e2725ff284f569665eb92ccb145d83817a02997", acceptedKeys[0].KeyID) +} + func TestED25519SignerVerifierWithMetablockFile(t *testing.T) { key, err := LoadED25519KeyFromFile(filepath.Join("test-data", "ed25519-test-key.pub")) if err != nil { @@ -205,3 +254,45 @@ func TestED25519SignerVerifierWithMetablockFile(t *testing.T) { err = sv.Verify(context.Background(), encodedBytes, decodedSig) assert.Nil(t, err) } + +func TestED25519SignerVerifierWithMetablockFileAndPEMKey(t *testing.T) { + key, err := LoadKey(ed25519PublicKey) + if err != nil { + t.Fatal(err) + } + + sv, err := NewED25519SignerVerifierFromSSLibKey(key) + if err != nil { + t.Fatal(err) + } + + metadataBytes, err := os.ReadFile(filepath.Join("test-data", "test-ed25519.52e3b8e7.link")) + if err != nil { + t.Fatal(err) + } + + mb := struct { + Signatures []struct { + KeyID string `json:"keyid"` + Sig string `json:"sig"` + } `json:"signatures"` + Signed any `json:"signed"` + }{} + + if err := json.Unmarshal(metadataBytes, &mb); err != nil { + t.Fatal(err) + } + + assert.Equal(t, "4c8b7605a9195d4ddba54493bbb5257a9836c1d16056a027fd77e97b95a4f3e36f8bc3c9c9960387d68187760b3072a30c44f992c5bf8f7497c303a3b0a32403", mb.Signatures[0].Sig) + assert.Equal(t, sv.keyID, mb.Signatures[0].KeyID) + + encodedBytes, err := cjson.EncodeCanonical(mb.Signed) + if err != nil { + t.Fatal(err) + } + + decodedSig := hexDecode(t, mb.Signatures[0].Sig) + + err = sv.Verify(context.Background(), encodedBytes, decodedSig) + assert.Nil(t, err) +} diff --git a/signerverifier/rsa.go b/signerverifier/rsa.go index b039659..2abfcb2 100644 --- a/signerverifier/rsa.go +++ b/signerverifier/rsa.go @@ -94,6 +94,11 @@ func (sv *RSAPSSSignerVerifier) Public() crypto.PublicKey { // LoadRSAPSSKeyFromFile returns an SSLibKey instance for an RSA key stored in a // file. +// +// Deprecated: use LoadKey(). The custom serialization format has been +// deprecated. Use +// https://github.com/secure-systems-lab/securesystemslib/blob/main/docs/migrate_key.py +// to convert your key. func LoadRSAPSSKeyFromFile(path string) (*SSLibKey, error) { contents, err := os.ReadFile(path) if err != nil { @@ -103,9 +108,13 @@ func LoadRSAPSSKeyFromFile(path string) (*SSLibKey, error) { return LoadRSAPSSKeyFromBytes(contents) } -// LoadRSAPSSKeyFromBytes is a function that takes a byte array as input. This byte array should represent a PEM encoded RSA key, as PEM encoding is required. -// The function returns an SSLibKey instance, which is a struct that holds the key data. - +// LoadRSAPSSKeyFromBytes is a function that takes a byte array as input. This +// byte array should represent a PEM encoded RSA key, as PEM encoding is +// required. The function returns an SSLibKey instance, which is a struct that +// holds the key data. +// +// Deprecated: use LoadKey() for all key types, RSA is no longer the only key +// that uses PEM serialization. func LoadRSAPSSKeyFromBytes(contents []byte) (*SSLibKey, error) { pemData, keyObj, err := decodeAndParsePEM(contents) if err != nil { diff --git a/signerverifier/rsa_test.go b/signerverifier/rsa_test.go index b8711d8..1ca3412 100644 --- a/signerverifier/rsa_test.go +++ b/signerverifier/rsa_test.go @@ -102,7 +102,7 @@ func TestRSAPSSSignerVerifierSignAndVerify(t *testing.T) { } func TestRSAPSSSignerVerifierWithDSSEEnvelope(t *testing.T) { - key, err := LoadRSAPSSKeyFromFile(filepath.Join("test-data", "rsa-test-key")) + key, err := LoadKey(rsaPrivateKey) if err != nil { t.Fatal(err) } @@ -130,7 +130,7 @@ func TestRSAPSSSignerVerifierWithDSSEEnvelope(t *testing.T) { assert.Equal(t, payload, envPayload) assert.Nil(t, err) - key, err = LoadRSAPSSKeyFromFile(filepath.Join("test-data", "rsa-test-key.pub")) + key, err = LoadKey(rsaPublicKey) if err != nil { t.Fatal(err) } @@ -151,7 +151,7 @@ func TestRSAPSSSignerVerifierWithDSSEEnvelope(t *testing.T) { } func TestRSAPSSSignerVerifierWithMetablockFile(t *testing.T) { - key, err := LoadRSAPSSKeyFromFile(filepath.Join("test-data", "rsa-test-key.pub")) + key, err := LoadKey(rsaPublicKey) if err != nil { t.Fatal(err) } diff --git a/signerverifier/signerverifier.go b/signerverifier/signerverifier.go index 85cae65..67d1722 100644 --- a/signerverifier/signerverifier.go +++ b/signerverifier/signerverifier.go @@ -1,7 +1,13 @@ package signerverifier import ( + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "crypto/x509" + "encoding/hex" "errors" + "strings" ) var KeyIDHashAlgorithms = []string{"sha256", "sha512"} @@ -12,6 +18,7 @@ var ( ErrUnknownKeyType = errors.New("unknown key type") ErrInvalidThreshold = errors.New("threshold is either less than 1 or greater than number of provided public keys") ErrInvalidKey = errors.New("key object has no value") + ErrInvalidPEM = errors.New("unable to parse PEM block") ) const ( @@ -34,3 +41,106 @@ type KeyVal struct { Identity string `json:"identity,omitempty"` Issuer string `json:"issuer,omitempty"` } + +// LoadKey returns an SSLibKey object when provided a PEM encoded key. +// Currently, RSA, ED25519, and ECDSA keys are supported. +func LoadKey(keyBytes []byte) (*SSLibKey, error) { + pemBlock, rawKey, err := decodeAndParsePEM(keyBytes) + if err != nil { + return nil, err + } + + var key *SSLibKey + switch k := rawKey.(type) { + case *rsa.PublicKey: + pubKeyBytes, err := x509.MarshalPKIXPublicKey(k) + if err != nil { + return nil, err + } + key = &SSLibKey{ + KeyIDHashAlgorithms: KeyIDHashAlgorithms, + KeyType: RSAKeyType, + KeyVal: KeyVal{ + Public: strings.TrimSpace(string(generatePEMBlock(pubKeyBytes, PublicKeyPEM))), + }, + Scheme: RSAKeyScheme, + } + + case *rsa.PrivateKey: + pubKeyBytes, err := x509.MarshalPKIXPublicKey(k.Public()) + if err != nil { + return nil, err + } + key = &SSLibKey{ + KeyIDHashAlgorithms: KeyIDHashAlgorithms, + KeyType: RSAKeyType, + KeyVal: KeyVal{ + Public: strings.TrimSpace(string(generatePEMBlock(pubKeyBytes, PublicKeyPEM))), + Private: strings.TrimSpace(string(generatePEMBlock(pemBlock.Bytes, RSAPrivateKeyPEM))), + }, + Scheme: RSAKeyScheme, + } + + case ed25519.PublicKey: + key = &SSLibKey{ + KeyIDHashAlgorithms: KeyIDHashAlgorithms, + KeyType: ED25519KeyType, + KeyVal: KeyVal{ + Public: strings.TrimSpace(hex.EncodeToString(k)), + }, + Scheme: ED25519KeyType, + } + + case ed25519.PrivateKey: + pubKeyBytes := k.Public() + key = &SSLibKey{ + KeyIDHashAlgorithms: KeyIDHashAlgorithms, + KeyType: ED25519KeyType, + KeyVal: KeyVal{ + Public: strings.TrimSpace(hex.EncodeToString(pubKeyBytes.(ed25519.PublicKey))), + Private: strings.TrimSpace(hex.EncodeToString(k)), + }, + Scheme: ED25519KeyType, + } + + case *ecdsa.PublicKey: + pubKeyBytes, err := x509.MarshalPKIXPublicKey(k) + if err != nil { + return nil, err + } + key = &SSLibKey{ + KeyIDHashAlgorithms: KeyIDHashAlgorithms, + KeyType: ECDSAKeyType, + KeyVal: KeyVal{ + Public: strings.TrimSpace(string(generatePEMBlock(pubKeyBytes, PublicKeyPEM))), + }, + Scheme: ECDSAKeyScheme, + } + + case *ecdsa.PrivateKey: + pubKeyBytes, err := x509.MarshalPKIXPublicKey(k.Public()) + if err != nil { + return nil, err + } + key = &SSLibKey{ + KeyIDHashAlgorithms: KeyIDHashAlgorithms, + KeyType: ECDSAKeyType, + KeyVal: KeyVal{ + Public: strings.TrimSpace(string(generatePEMBlock(pubKeyBytes, PublicKeyPEM))), + Private: strings.TrimSpace(string(generatePEMBlock(pemBlock.Bytes, PrivateKeyPEM))), + }, + Scheme: ECDSAKeyScheme, + } + + default: + return nil, ErrUnknownKeyType + } + + keyID, err := calculateKeyID(key) + if err != nil { + return nil, err + } + key.KeyID = keyID + + return key, nil +} diff --git a/signerverifier/signerverifier_test.go b/signerverifier/signerverifier_test.go new file mode 100644 index 0000000..71b1175 --- /dev/null +++ b/signerverifier/signerverifier_test.go @@ -0,0 +1,103 @@ +package signerverifier + +import ( + _ "embed" + "testing" + + "github.com/stretchr/testify/assert" +) + +//go:embed test-data/rsa-test-key +var rsaPrivateKey []byte + +//go:embed test-data/rsa-test-key.pub +var rsaPublicKey []byte + +//go:embed test-data/ed25519-test-key-pem +var ed25519PrivateKey []byte + +//go:embed test-data/ed25519-test-key-pem.pub +var ed25519PublicKey []byte + +//go:embed test-data/ecdsa-test-key-pem +var ecdsaPrivateKey []byte + +//go:embed test-data/ecdsa-test-key-pem.pub +var ecdsaPublicKey []byte + +func TestLoadKey(t *testing.T) { + // RSA expected values + expectedRSAPrivateKey := "-----BEGIN RSA PRIVATE KEY-----\n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\n-----END RSA PRIVATE KEY-----" + expectedRSAPublicKey := "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA04egZRic+dZMVtiQc56D\nejU4FF1q3aOkUKnD+Q4lTbj1zp6ODKJTcktupmrad68jqtMiSGG8he6ELFs377q8\nbbgEUMWgAf+06Q8oFvUSfOXzZNFI7H5SMPOJY5aDWIMIEZ8DlcO7TfkA7D3iAEJX\nxxTOVS3UAIk5umO7Y7t7yXr8O/C4u78krGazCnoblcekMLJZV4O/5BloWNAe/B1c\nvZdaZUf3brD4ZZrxEtXw/tefhn1aHsSUajVW2wwjSpKhqj7Z0XS3bDS3T95/3xsN\n6+hlS6A7rJfiWpKIRHj0vh2SXLDmmhQl1In8TD/aiycTUyWcBRHVPlYFgYPt6SaT\nVQSgMzSxC43/2fINb2fyt8SbUHJ3Ct+mzRzd/1AQikWhBdstJLxInewzjYE/sb+c\n2CmCxMPQG2BwmAWXaaumeJcXVPBlMgAcjMatM8bPByTbXpKDnQslOE7g/gswDIwn\nEm53T13mZzYUvbLJ0q3aljZVLIC3IZn3ZwA2yCWchBkVAgMBAAE=\n-----END PUBLIC KEY-----" + expectedRSAKeyID := "4e8d20af09fcaed6c388a186427f94a5f7ff5591ec295f4aab2cff49ffe39e9b" + + // ED25519 expected values + expectedED25519PrivateKey := "66f6ebad4aeb949b91c84c9cfd6ee351fc4fd544744bab6e30fb400ba13c6e9a3f586ce67329419fb0081bd995914e866a7205da463d593b3b490eab2b27fd3f" + expectedED25519PublicKey := "3f586ce67329419fb0081bd995914e866a7205da463d593b3b490eab2b27fd3f" + expectedED25519KeyID := "52e3b8e73279d6ebdd62a5016e2725ff284f569665eb92ccb145d83817a02997" + + // ECDSA expected values + expectedECDSAPrivateKey := "-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgCjoPFeWCpjL5OS+h\nwg7JaWoDcpW2np2VJjuVLeSR4QyhRANCAAS74cSqqlctrjyVcf2uRHKCx+wIqrVc\nzfqhfKYn3DGkzrycKqStkJWdn2WQR4LAPypZhM3EPnJ9ZfAmMWH4ruot\n-----END PRIVATE KEY-----" + expectedECDSAPublicKey := "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu+HEqqpXLa48lXH9rkRygsfsCKq1\nXM36oXymJ9wxpM68nCqkrZCVnZ9lkEeCwD8qWYTNxD5yfWXwJjFh+K7qLQ==\n-----END PUBLIC KEY-----" + expectedECDSAKeyID := "98adf38602c48c5479e9a991ee3f8cbf541ee4f985e00f7a5fc4148d9a45b704" + + t.Run("RSA private key", func(t *testing.T) { + key, err := LoadKey(rsaPrivateKey) + assert.Nil(t, err) + assert.Equal(t, expectedRSAKeyID, key.KeyID) + assert.Equal(t, expectedRSAPublicKey, key.KeyVal.Public) + assert.Equal(t, expectedRSAPrivateKey, key.KeyVal.Private) + assert.Equal(t, RSAKeyScheme, key.Scheme) + assert.Equal(t, RSAKeyType, key.KeyType) + }) + + t.Run("RSA public key", func(t *testing.T) { + key, err := LoadKey(rsaPublicKey) + assert.Nil(t, err) + assert.Equal(t, expectedRSAKeyID, key.KeyID) + assert.Equal(t, expectedRSAPublicKey, key.KeyVal.Public) + assert.Equal(t, "", key.KeyVal.Private) + assert.Equal(t, RSAKeyScheme, key.Scheme) + assert.Equal(t, RSAKeyType, key.KeyType) + }) + + t.Run("ED25519 private key", func(t *testing.T) { + key, err := LoadKey(ed25519PrivateKey) + assert.Nil(t, err) + assert.Equal(t, expectedED25519KeyID, key.KeyID) + assert.Equal(t, expectedED25519PublicKey, key.KeyVal.Public) + assert.Equal(t, expectedED25519PrivateKey, key.KeyVal.Private) + assert.Equal(t, ED25519KeyType, key.Scheme) + assert.Equal(t, ED25519KeyType, key.KeyType) + }) + + t.Run("ED25519 public key", func(t *testing.T) { + key, err := LoadKey(ed25519PublicKey) + assert.Nil(t, err) + assert.Equal(t, expectedED25519KeyID, key.KeyID) + assert.Equal(t, expectedED25519PublicKey, key.KeyVal.Public) + assert.Equal(t, "", key.KeyVal.Private) + assert.Equal(t, ED25519KeyType, key.Scheme) + assert.Equal(t, ED25519KeyType, key.KeyType) + }) + + t.Run("ECDSA private key", func(t *testing.T) { + key, err := LoadKey(ecdsaPrivateKey) + assert.Nil(t, err) + assert.Equal(t, expectedECDSAKeyID, key.KeyID) + assert.Equal(t, expectedECDSAPublicKey, key.KeyVal.Public) + assert.Equal(t, expectedECDSAPrivateKey, key.KeyVal.Private) + assert.Equal(t, ECDSAKeyScheme, key.Scheme) + assert.Equal(t, ECDSAKeyType, key.KeyType) + }) + + t.Run("ECDSA public key", func(t *testing.T) { + key, err := LoadKey(ecdsaPublicKey) + assert.Nil(t, err) + assert.Equal(t, expectedECDSAKeyID, key.KeyID) + assert.Equal(t, expectedECDSAPublicKey, key.KeyVal.Public) + assert.Equal(t, "", key.KeyVal.Private) + assert.Equal(t, ECDSAKeyScheme, key.Scheme) + assert.Equal(t, ECDSAKeyType, key.KeyType) + }) +} diff --git a/signerverifier/test-data/ecdsa-test-key-pem b/signerverifier/test-data/ecdsa-test-key-pem new file mode 100644 index 0000000..9a9dc36 --- /dev/null +++ b/signerverifier/test-data/ecdsa-test-key-pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgCjoPFeWCpjL5OS+h +wg7JaWoDcpW2np2VJjuVLeSR4QyhRANCAAS74cSqqlctrjyVcf2uRHKCx+wIqrVc +zfqhfKYn3DGkzrycKqStkJWdn2WQR4LAPypZhM3EPnJ9ZfAmMWH4ruot +-----END PRIVATE KEY----- diff --git a/signerverifier/test-data/ecdsa-test-key-pem.pub b/signerverifier/test-data/ecdsa-test-key-pem.pub new file mode 100644 index 0000000..fa97dc5 --- /dev/null +++ b/signerverifier/test-data/ecdsa-test-key-pem.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu+HEqqpXLa48lXH9rkRygsfsCKq1 +XM36oXymJ9wxpM68nCqkrZCVnZ9lkEeCwD8qWYTNxD5yfWXwJjFh+K7qLQ== +-----END PUBLIC KEY----- diff --git a/signerverifier/test-data/ed25519-test-key-pem b/signerverifier/test-data/ed25519-test-key-pem new file mode 100644 index 0000000..019197d --- /dev/null +++ b/signerverifier/test-data/ed25519-test-key-pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIGb2661K65SbkchMnP1u41H8T9VEdEurbjD7QAuhPG6a +-----END PRIVATE KEY----- diff --git a/signerverifier/test-data/ed25519-test-key-pem.pub b/signerverifier/test-data/ed25519-test-key-pem.pub new file mode 100644 index 0000000..fc74cbc --- /dev/null +++ b/signerverifier/test-data/ed25519-test-key-pem.pub @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAP1hs5nMpQZ+wCBvZlZFOhmpyBdpGPVk7O0kOqysn/T8= +-----END PUBLIC KEY----- diff --git a/signerverifier/utils.go b/signerverifier/utils.go index e77e07f..e8a30b5 100644 --- a/signerverifier/utils.go +++ b/signerverifier/utils.go @@ -27,6 +27,9 @@ var ( // LoadKeyFromSSLibBytes returns a pointer to a Key instance created from the // contents of the bytes. The key contents are expected to be in the custom // securesystemslib format. +// +// Deprecated: use LoadKey() for all key types, RSA is no longer the only key +// that uses PEM serialization. func LoadKeyFromSSLibBytes(contents []byte) (*SSLibKey, error) { var key *SSLibKey if err := json.Unmarshal(contents, &key); err != nil {