Skip to content

Commit e6e1a9e

Browse files
authored
Merge pull request #43 from adityasaky/add-signers
Add signerverifiers for RSAPSS, ECDSA, ED25519
2 parents 83ae237 + abf9e07 commit e6e1a9e

19 files changed

+1232
-2
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ jobs:
44
test:
55
strategy:
66
matrix:
7-
go-version: [1.17.x, 1.18.x, 1.19.x, 1.20.x]
7+
go-version: [1.18.x, 1.19.x, 1.20.x]
88
os: [ubuntu-latest, macos-latest, windows-latest]
99
runs-on: ${{ matrix.os }}
1010
steps:

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/secure-systems-lab/go-securesystemslib
22

3-
go 1.17
3+
go 1.20
44

55
require (
66
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb

signerverifier/ecdsa.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package signerverifier
2+
3+
import (
4+
"context"
5+
"crypto"
6+
"crypto/ecdsa"
7+
"crypto/rand"
8+
"crypto/sha256"
9+
"crypto/sha512"
10+
"fmt"
11+
"os"
12+
)
13+
14+
const ECDSAKeyType = "ecdsa"
15+
16+
// ECDSASignerVerifier is a dsse.SignerVerifier compliant interface to sign and
17+
// verify signatures using ECDSA keys.
18+
type ECDSASignerVerifier struct {
19+
keyID string
20+
curveSize int
21+
private *ecdsa.PrivateKey
22+
public *ecdsa.PublicKey
23+
}
24+
25+
// NewECDSASignerVerifierFromSSLibKey creates an ECDSASignerVerifier from an
26+
// SSLibKey.
27+
func NewECDSASignerVerifierFromSSLibKey(key *SSLibKey) (*ECDSASignerVerifier, error) {
28+
if len(key.KeyVal.Public) == 0 {
29+
return nil, ErrInvalidKey
30+
}
31+
32+
_, publicParsedKey, err := decodeAndParsePEM([]byte(key.KeyVal.Public))
33+
if err != nil {
34+
return nil, fmt.Errorf("unable to create ECDSA signerverifier: %w", err)
35+
}
36+
37+
sv := &ECDSASignerVerifier{
38+
keyID: key.KeyID,
39+
curveSize: publicParsedKey.(*ecdsa.PublicKey).Params().BitSize,
40+
public: publicParsedKey.(*ecdsa.PublicKey),
41+
private: nil,
42+
}
43+
44+
if len(key.KeyVal.Private) > 0 {
45+
_, privateParsedKey, err := decodeAndParsePEM([]byte(key.KeyVal.Private))
46+
if err != nil {
47+
return nil, fmt.Errorf("unable to create ECDSA signerverifier: %w", err)
48+
}
49+
50+
sv.private = privateParsedKey.(*ecdsa.PrivateKey)
51+
}
52+
53+
return sv, nil
54+
}
55+
56+
// Sign creates a signature for `data`.
57+
func (sv *ECDSASignerVerifier) Sign(ctx context.Context, data []byte) ([]byte, error) {
58+
if sv.private == nil {
59+
return nil, ErrNotPrivateKey
60+
}
61+
62+
hashedData := getECDSAHashedData(data, sv.curveSize)
63+
64+
return ecdsa.SignASN1(rand.Reader, sv.private, hashedData)
65+
}
66+
67+
// Verify verifies the `sig` value passed in against `data`.
68+
func (sv *ECDSASignerVerifier) Verify(ctx context.Context, data []byte, sig []byte) error {
69+
hashedData := getECDSAHashedData(data, sv.curveSize)
70+
71+
if ok := ecdsa.VerifyASN1(sv.public, hashedData, sig); !ok {
72+
return ErrSignatureVerificationFailed
73+
}
74+
75+
return nil
76+
}
77+
78+
// KeyID returns the identifier of the key used to create the
79+
// ECDSASignerVerifier instance.
80+
func (sv *ECDSASignerVerifier) KeyID() (string, error) {
81+
return sv.keyID, nil
82+
}
83+
84+
// Public returns the public portion of the key used to create the
85+
// ECDSASignerVerifier instance.
86+
func (sv *ECDSASignerVerifier) Public() crypto.PublicKey {
87+
return sv.public
88+
}
89+
90+
// LoadECDSAKeyFromFile returns an SSLibKey instance for an ECDSA key stored in
91+
// a file in the custom securesystemslib format.
92+
func LoadECDSAKeyFromFile(path string) (*SSLibKey, error) {
93+
contents, err := os.ReadFile(path)
94+
if err != nil {
95+
return nil, fmt.Errorf("unable to load ECDSA key from file: %w", err)
96+
}
97+
98+
return loadKeyFromSSLibBytes(contents)
99+
}
100+
101+
func getECDSAHashedData(data []byte, curveSize int) []byte {
102+
switch {
103+
case curveSize <= 256:
104+
return hashBeforeSigning(data, sha256.New())
105+
case 256 < curveSize && curveSize <= 384:
106+
return hashBeforeSigning(data, sha512.New384())
107+
case curveSize > 384:
108+
return hashBeforeSigning(data, sha512.New())
109+
}
110+
return []byte{}
111+
}

signerverifier/ecdsa_test.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package signerverifier
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"os"
7+
"path/filepath"
8+
"testing"
9+
10+
"github.com/secure-systems-lab/go-securesystemslib/cjson"
11+
"github.com/secure-systems-lab/go-securesystemslib/dsse"
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
func TestNewECDSASignerVerifierFromSSLibKey(t *testing.T) {
16+
key, err := LoadECDSAKeyFromFile(filepath.Join("test-data", "ecdsa-test-key.pub"))
17+
if err != nil {
18+
t.Fatal(err)
19+
}
20+
21+
sv, err := NewECDSASignerVerifierFromSSLibKey(key)
22+
if err != nil {
23+
t.Fatal(err)
24+
}
25+
26+
expectedPublicString := "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu+HEqqpXLa48lXH9rkRygsfsCKq1\nXM36oXymJ9wxpM68nCqkrZCVnZ9lkEeCwD8qWYTNxD5yfWXwJjFh+K7qLQ==\n-----END PUBLIC KEY-----"
27+
_, expectedPublicKey, err := decodeAndParsePEM([]byte(expectedPublicString))
28+
assert.Nil(t, err)
29+
30+
assert.Equal(t, "98adf38602c48c5479e9a991ee3f8cbf541ee4f985e00f7a5fc4148d9a45b704", sv.keyID)
31+
assert.Equal(t, expectedPublicKey, sv.public)
32+
assert.Nil(t, sv.private)
33+
}
34+
35+
func TestLoadECDSAKeyFromFile(t *testing.T) {
36+
t.Run("ecdsa public key", func(t *testing.T) {
37+
key, err := LoadECDSAKeyFromFile(filepath.Join("test-data", "ecdsa-test-key.pub"))
38+
assert.Nil(t, err)
39+
40+
assert.Equal(t, "98adf38602c48c5479e9a991ee3f8cbf541ee4f985e00f7a5fc4148d9a45b704", key.KeyID)
41+
assert.Equal(t, "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu+HEqqpXLa48lXH9rkRygsfsCKq1\nXM36oXymJ9wxpM68nCqkrZCVnZ9lkEeCwD8qWYTNxD5yfWXwJjFh+K7qLQ==\n-----END PUBLIC KEY-----", key.KeyVal.Public)
42+
assert.Equal(t, "ecdsa-sha2-nistp256", key.Scheme)
43+
assert.Equal(t, ECDSAKeyType, key.KeyType)
44+
})
45+
46+
t.Run("ecdsa private key", func(t *testing.T) {
47+
key, err := LoadECDSAKeyFromFile(filepath.Join("test-data", "ecdsa-test-key"))
48+
assert.Nil(t, err)
49+
50+
assert.Equal(t, "98adf38602c48c5479e9a991ee3f8cbf541ee4f985e00f7a5fc4148d9a45b704", key.KeyID)
51+
assert.Equal(t, "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu+HEqqpXLa48lXH9rkRygsfsCKq1\nXM36oXymJ9wxpM68nCqkrZCVnZ9lkEeCwD8qWYTNxD5yfWXwJjFh+K7qLQ==\n-----END PUBLIC KEY-----", key.KeyVal.Public)
52+
assert.Equal(t, "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIAo6DxXlgqYy+TkvocIOyWlqA3KVtp6dlSY7lS3kkeEMoAoGCCqGSM49\nAwEHoUQDQgAEu+HEqqpXLa48lXH9rkRygsfsCKq1XM36oXymJ9wxpM68nCqkrZCV\nnZ9lkEeCwD8qWYTNxD5yfWXwJjFh+K7qLQ==\n-----END EC PRIVATE KEY-----", key.KeyVal.Private)
53+
assert.Equal(t, "ecdsa-sha2-nistp256", key.Scheme)
54+
assert.Equal(t, ECDSAKeyType, key.KeyType)
55+
})
56+
57+
t.Run("invalid path", func(t *testing.T) {
58+
_, err := LoadECDSAKeyFromFile(filepath.Join("test-data", "invalid"))
59+
assert.ErrorContains(t, err, "unable to load ECDSA key from file")
60+
})
61+
}
62+
63+
func TestECDSASignerVerifierSign(t *testing.T) {
64+
t.Run("using valid key", func(t *testing.T) {
65+
key, err := LoadECDSAKeyFromFile(filepath.Join("test-data", "ecdsa-test-key"))
66+
if err != nil {
67+
t.Fatal(err)
68+
}
69+
70+
sv, err := NewECDSASignerVerifierFromSSLibKey(key)
71+
if err != nil {
72+
t.Fatal(err)
73+
}
74+
75+
message := []byte("test message")
76+
77+
signature, err := sv.Sign(context.Background(), message)
78+
assert.Nil(t, err)
79+
80+
err = sv.Verify(context.Background(), message, signature)
81+
assert.Nil(t, err)
82+
})
83+
84+
t.Run("using invalid key", func(t *testing.T) {
85+
key, err := LoadECDSAKeyFromFile(filepath.Join("test-data", "ecdsa-test-key.pub"))
86+
if err != nil {
87+
t.Fatal(err)
88+
}
89+
90+
sv, err := NewECDSASignerVerifierFromSSLibKey(key)
91+
if err != nil {
92+
t.Fatal(err)
93+
}
94+
95+
message := []byte("test message")
96+
97+
_, err = sv.Sign(context.Background(), message)
98+
assert.ErrorIs(t, err, ErrNotPrivateKey)
99+
})
100+
}
101+
102+
func TestECDSASignerVerifierWithDSSEEnvelope(t *testing.T) {
103+
key, err := LoadECDSAKeyFromFile(filepath.Join("test-data", "ecdsa-test-key"))
104+
if err != nil {
105+
t.Fatal(err)
106+
}
107+
108+
sv, err := NewECDSASignerVerifierFromSSLibKey(key)
109+
if err != nil {
110+
t.Fatal(err)
111+
}
112+
113+
payloadType := "application/vnd.dsse+json"
114+
payload := []byte("test message")
115+
116+
es, err := dsse.NewEnvelopeSigner(sv)
117+
if err != nil {
118+
t.Error(err)
119+
}
120+
121+
env, err := es.SignPayload(context.Background(), payloadType, payload)
122+
if err != nil {
123+
t.Error(err)
124+
}
125+
126+
assert.Equal(t, "98adf38602c48c5479e9a991ee3f8cbf541ee4f985e00f7a5fc4148d9a45b704", env.Signatures[0].KeyID)
127+
envPayload, err := env.DecodeB64Payload()
128+
assert.Equal(t, payload, envPayload)
129+
assert.Nil(t, err)
130+
131+
key, err = LoadECDSAKeyFromFile(filepath.Join("test-data", "ecdsa-test-key.pub"))
132+
if err != nil {
133+
t.Fatal(err)
134+
}
135+
136+
sv, err = NewECDSASignerVerifierFromSSLibKey(key)
137+
if err != nil {
138+
t.Fatal(err)
139+
}
140+
141+
ev, err := dsse.NewEnvelopeVerifier(sv)
142+
if err != nil {
143+
t.Error(err)
144+
}
145+
146+
acceptedKeys, err := ev.Verify(context.Background(), env)
147+
assert.Nil(t, err)
148+
assert.Equal(t, "98adf38602c48c5479e9a991ee3f8cbf541ee4f985e00f7a5fc4148d9a45b704", acceptedKeys[0].KeyID)
149+
}
150+
151+
func TestECDSASignerVerifierWithMetablockFile(t *testing.T) {
152+
key, err := LoadECDSAKeyFromFile(filepath.Join("test-data", "ecdsa-test-key.pub"))
153+
if err != nil {
154+
t.Fatal(err)
155+
}
156+
157+
sv, err := NewECDSASignerVerifierFromSSLibKey(key)
158+
if err != nil {
159+
t.Fatal(err)
160+
}
161+
162+
metadataBytes, err := os.ReadFile(filepath.Join("test-data", "test-ecdsa.98adf386.link"))
163+
if err != nil {
164+
t.Fatal(err)
165+
}
166+
167+
mb := struct {
168+
Signatures []struct {
169+
KeyID string `json:"keyid"`
170+
Sig string `json:"sig"`
171+
} `json:"signatures"`
172+
Signed any `json:"signed"`
173+
}{}
174+
175+
if err := json.Unmarshal(metadataBytes, &mb); err != nil {
176+
t.Fatal(err)
177+
}
178+
179+
assert.Equal(t, "304502201fbb03c0937504182a48c66f9218bdcb2e99a07ada273e92e5e543867f98c8d7022100dbfa7bbf74fd76d76c1d08676419cba85bbd81dfb000f3ac6a786693ddc508f5", mb.Signatures[0].Sig)
180+
assert.Equal(t, sv.keyID, mb.Signatures[0].KeyID)
181+
182+
encodedBytes, err := cjson.EncodeCanonical(mb.Signed)
183+
if err != nil {
184+
t.Fatal(err)
185+
}
186+
187+
decodedSig := hexDecode(t, mb.Signatures[0].Sig)
188+
189+
err = sv.Verify(context.Background(), encodedBytes, decodedSig)
190+
assert.Nil(t, err)
191+
}

0 commit comments

Comments
 (0)