Skip to content

Commit 6b0d6ed

Browse files
committed
Add ECDSA SignerVerifier
Signed-off-by: Aditya Sirish <[email protected]>
1 parent 0581e17 commit 6b0d6ed

File tree

7 files changed

+229
-7
lines changed

7 files changed

+229
-7
lines changed

signerverifier/ecdsa.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package signerverifier
2+
3+
import (
4+
"context"
5+
"crypto"
6+
"crypto/ecdsa"
7+
"crypto/rand"
8+
"crypto/sha256"
9+
"crypto/sha512"
10+
"os"
11+
)
12+
13+
const ECDSAKeyType = "ecdsa"
14+
15+
type ECDSASignerVerifier struct {
16+
keyID string
17+
curveSize int
18+
private *ecdsa.PrivateKey
19+
public *ecdsa.PublicKey
20+
}
21+
22+
func NewECDSASignerVerifierFromSSLibKey(key *SSLibKey) (*ECDSASignerVerifier, error) {
23+
_, publicParsedKey, err := decodeAndParsePEM([]byte(key.KeyVal.Public))
24+
if err != nil {
25+
return nil, err
26+
}
27+
28+
sv := &ECDSASignerVerifier{
29+
keyID: key.KeyID(),
30+
curveSize: publicParsedKey.(*ecdsa.PublicKey).Params().BitSize,
31+
public: publicParsedKey.(*ecdsa.PublicKey),
32+
private: nil,
33+
}
34+
35+
if len(key.KeyVal.Private) > 0 {
36+
_, privateParsedKey, err := decodeAndParsePEM([]byte(key.KeyVal.Private))
37+
if err != nil {
38+
return nil, err
39+
}
40+
41+
sv.private = privateParsedKey.(*ecdsa.PrivateKey)
42+
}
43+
44+
return sv, nil
45+
}
46+
47+
func (sv *ECDSASignerVerifier) Sign(ctx context.Context, data []byte) ([]byte, error) {
48+
if sv.private == nil {
49+
return nil, ErrNotPrivateKey
50+
}
51+
52+
hashedData := getECDSAHashedData(data, sv.curveSize)
53+
54+
return ecdsa.SignASN1(rand.Reader, sv.private, hashedData)
55+
}
56+
57+
func (sv *ECDSASignerVerifier) Verify(ctx context.Context, data []byte, sig []byte) error {
58+
hashedData := getECDSAHashedData(data, sv.curveSize)
59+
60+
if ok := ecdsa.VerifyASN1(sv.public, hashedData, sig); !ok {
61+
return ErrSignatureVerificationFailed
62+
}
63+
64+
return nil
65+
}
66+
67+
func (sv *ECDSASignerVerifier) KeyID() (string, error) {
68+
return sv.keyID, nil
69+
}
70+
71+
func (sv *ECDSASignerVerifier) Public() crypto.PublicKey {
72+
return sv.public
73+
}
74+
75+
func LoadECDSAKeyFromFile(path string) (*SSLibKey, error) {
76+
contents, err := os.ReadFile(path)
77+
if err != nil {
78+
return nil, err
79+
}
80+
81+
return loadKeyFromSSLibBytes(contents)
82+
}
83+
84+
func getECDSAHashedData(data []byte, curveSize int) []byte {
85+
switch {
86+
case curveSize <= 256:
87+
return hashBeforeSigning(data, sha256.New())
88+
case 256 < curveSize && curveSize <= 384:
89+
return hashBeforeSigning(data, sha512.New384())
90+
case curveSize > 384:
91+
return hashBeforeSigning(data, sha512.New())
92+
}
93+
return []byte{}
94+
}

signerverifier/ecdsa_test.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package signerverifier
2+
3+
import (
4+
"context"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/secure-systems-lab/go-securesystemslib/dsse"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestNewECDSASignerVerifierFromSSLibKey(t *testing.T) {
13+
key, err := LoadECDSAKeyFromFile(filepath.Join("test-data", "ecdsa-test-key.pub"))
14+
if err != nil {
15+
t.Fatal(err)
16+
}
17+
18+
sv, err := NewECDSASignerVerifierFromSSLibKey(key)
19+
if err != nil {
20+
t.Fatal(err)
21+
}
22+
23+
expectedPublicString := "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu+HEqqpXLa48lXH9rkRygsfsCKq1\nXM36oXymJ9wxpM68nCqkrZCVnZ9lkEeCwD8qWYTNxD5yfWXwJjFh+K7qLQ==\n-----END PUBLIC KEY-----"
24+
_, expectedPublicKey, err := decodeAndParsePEM([]byte(expectedPublicString))
25+
assert.Nil(t, err)
26+
27+
assert.Equal(t, "98adf38602c48c5479e9a991ee3f8cbf541ee4f985e00f7a5fc4148d9a45b704", sv.keyID)
28+
assert.Equal(t, expectedPublicKey, sv.public)
29+
assert.Nil(t, sv.private)
30+
}
31+
32+
func TestECDSASignerVerifierSign(t *testing.T) {
33+
t.Run("using valid key", func(t *testing.T) {
34+
key, err := LoadECDSAKeyFromFile(filepath.Join("test-data", "ecdsa-test-key"))
35+
if err != nil {
36+
t.Fatal(err)
37+
}
38+
39+
sv, err := NewECDSASignerVerifierFromSSLibKey(key)
40+
if err != nil {
41+
t.Fatal(err)
42+
}
43+
44+
message := []byte("test message")
45+
46+
signature, err := sv.Sign(context.Background(), message)
47+
assert.Nil(t, err)
48+
49+
err = sv.Verify(context.Background(), message, signature)
50+
assert.Nil(t, err)
51+
})
52+
53+
t.Run("using invalid key", func(t *testing.T) {
54+
key, err := LoadECDSAKeyFromFile(filepath.Join("test-data", "ecdsa-test-key.pub"))
55+
if err != nil {
56+
t.Fatal(err)
57+
}
58+
59+
sv, err := NewECDSASignerVerifierFromSSLibKey(key)
60+
if err != nil {
61+
t.Fatal(err)
62+
}
63+
64+
message := []byte("test message")
65+
66+
_, err = sv.Sign(context.Background(), message)
67+
assert.ErrorIs(t, err, ErrNotPrivateKey)
68+
})
69+
}
70+
71+
func TestECDSASignerVerifierWithDSSEEnvelope(t *testing.T) {
72+
key, err := LoadECDSAKeyFromFile(filepath.Join("test-data", "ecdsa-test-key"))
73+
if err != nil {
74+
t.Fatal(err)
75+
}
76+
77+
sv, err := NewECDSASignerVerifierFromSSLibKey(key)
78+
if err != nil {
79+
t.Fatal(err)
80+
}
81+
82+
payloadType := "application/vnd.dsse+json"
83+
payload := []byte("test message")
84+
85+
es, err := dsse.NewEnvelopeSigner(sv)
86+
if err != nil {
87+
t.Error(err)
88+
}
89+
90+
env, err := es.SignPayload(context.Background(), payloadType, payload)
91+
if err != nil {
92+
t.Error(err)
93+
}
94+
95+
assert.Equal(t, "98adf38602c48c5479e9a991ee3f8cbf541ee4f985e00f7a5fc4148d9a45b704", env.Signatures[0].KeyID)
96+
envPayload, err := env.DecodeB64Payload()
97+
assert.Equal(t, payload, envPayload)
98+
assert.Nil(t, err)
99+
100+
key, err = LoadECDSAKeyFromFile(filepath.Join("test-data", "ecdsa-test-key.pub"))
101+
if err != nil {
102+
t.Fatal(err)
103+
}
104+
105+
sv, err = NewECDSASignerVerifierFromSSLibKey(key)
106+
if err != nil {
107+
t.Fatal(err)
108+
}
109+
110+
ev, err := dsse.NewEnvelopeVerifier(sv)
111+
if err != nil {
112+
t.Error(err)
113+
}
114+
115+
acceptedKeys, err := ev.Verify(context.Background(), env)
116+
assert.Nil(t, err)
117+
assert.Equal(t, "98adf38602c48c5479e9a991ee3f8cbf541ee4f985e00f7a5fc4148d9a45b704", acceptedKeys[0].KeyID)
118+
}

signerverifier/rsa.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ var ErrFailedPEMParsing = errors.New("failed parsing the PEM block: unsupported
2020
const (
2121
RSAKeyType = "rsa"
2222
RSAKeyScheme = "rsassa-pss-sha256"
23-
RSAPublicKeyPEM = "PUBLIC KEY"
2423
RSAPrivateKeyPEM = "RSA PRIVATE KEY"
2524
)
2625

@@ -61,13 +60,13 @@ func (sv *RSAPSSSignerVerifier) Sign(ctx context.Context, data []byte) ([]byte,
6160
return nil, ErrNotPrivateKey
6261
}
6362

64-
hashedData := hashBeforeSigning(data)
63+
hashedData := hashBeforeSigning(data, sha256.New())
6564

6665
return rsa.SignPSS(rand.Reader, sv.private, crypto.SHA256, hashedData, &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256})
6766
}
6867

6968
func (sv RSAPSSSignerVerifier) Verify(ctx context.Context, data []byte, sig []byte) error {
70-
hashedData := hashBeforeSigning(data)
69+
hashedData := hashBeforeSigning(data, sha256.New())
7170

7271
if err := rsa.VerifyPSS(sv.public, crypto.SHA256, hashedData, sig, &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}); err != nil {
7372
return ErrSignatureVerificationFailed
@@ -108,14 +107,14 @@ func LoadRSAPSSKeyFromFile(path string) (*SSLibKey, error) {
108107
if err != nil {
109108
return nil, err
110109
}
111-
key.KeyVal.Public = string(generatePEMBlock(pubKeyBytes, RSAPublicKeyPEM))
110+
key.KeyVal.Public = string(generatePEMBlock(pubKeyBytes, PublicKeyPEM))
112111

113112
case *rsa.PrivateKey:
114113
pubKeyBytes, err := x509.MarshalPKIXPublicKey(k.Public())
115114
if err != nil {
116115
return nil, err
117116
}
118-
key.KeyVal.Public = string(generatePEMBlock(pubKeyBytes, RSAPublicKeyPEM))
117+
key.KeyVal.Public = string(generatePEMBlock(pubKeyBytes, PublicKeyPEM))
119118
key.KeyVal.Private = string(generatePEMBlock(pemData.Bytes, RSAPrivateKeyPEM))
120119
}
121120

signerverifier/signerverifier.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ var (
1313
ErrInvalidThreshold = errors.New("threshold is either less than 1 or greater than number of provided public keys")
1414
)
1515

16+
const (
17+
PublicKeyPEM = "PUBLIC KEY"
18+
PrivateKeyPEM = "PRIVATE KEY"
19+
)
20+
1621
type SSLibKey struct {
1722
KeyIDHashAlgorithms []string `json:"keyid_hash_algorithms"`
1823
KeyType string `json:"keytype"`
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"keytype": "ecdsa", "scheme": "ecdsa-sha2-nistp256", "keyid": "98adf38602c48c5479e9a991ee3f8cbf541ee4f985e00f7a5fc4148d9a45b704", "keyval": {"public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu+HEqqpXLa48lXH9rkRygsfsCKq1\nXM36oXymJ9wxpM68nCqkrZCVnZ9lkEeCwD8qWYTNxD5yfWXwJjFh+K7qLQ==\n-----END PUBLIC KEY-----", "private": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIAo6DxXlgqYy+TkvocIOyWlqA3KVtp6dlSY7lS3kkeEMoAoGCCqGSM49\nAwEHoUQDQgAEu+HEqqpXLa48lXH9rkRygsfsCKq1XM36oXymJ9wxpM68nCqkrZCV\nnZ9lkEeCwD8qWYTNxD5yfWXwJjFh+K7qLQ==\n-----END EC PRIVATE KEY-----"}, "keyid_hash_algorithms": ["sha256", "sha512"]}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"keytype": "ecdsa", "scheme": "ecdsa-sha2-nistp256", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu+HEqqpXLa48lXH9rkRygsfsCKq1\nXM36oXymJ9wxpM68nCqkrZCVnZ9lkEeCwD8qWYTNxD5yfWXwJjFh+K7qLQ==\n-----END PUBLIC KEY-----"}}

signerverifier/utils.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/hex"
77
"encoding/json"
88
"encoding/pem"
9+
"hash"
910

1011
"github.com/secure-systems-lab/go-securesystemslib/cjson"
1112
)
@@ -112,11 +113,14 @@ func parsePEMKey(data []byte) (any, error) {
112113
if err == nil {
113114
return key, nil
114115
}
116+
key, err = x509.ParseECPrivateKey(data)
117+
if err == nil {
118+
return key, nil
119+
}
115120
return nil, ErrFailedPEMParsing
116121
}
117122

118-
func hashBeforeSigning(data []byte) []byte {
119-
h := sha256.New()
123+
func hashBeforeSigning(data []byte, h hash.Hash) []byte {
120124
h.Write(data)
121125
return h.Sum(nil)
122126
}

0 commit comments

Comments
 (0)