Skip to content

Commit 647b07c

Browse files
committed
Basic tests
1 parent c1f611c commit 647b07c

File tree

7 files changed

+224
-126
lines changed

7 files changed

+224
-126
lines changed

keystore/admin.go

Lines changed: 16 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,12 @@ import (
55
"crypto/ecdsa"
66
"crypto/ed25519"
77
"crypto/rand"
8-
"encoding/json"
98
"fmt"
109
"maps"
1110
"time"
1211

1312
"golang.org/x/crypto/curve25519"
1413

15-
gethkeystore "github.com/ethereum/go-ethereum/accounts/keystore"
1614
"github.com/ethereum/go-ethereum/crypto"
1715
"github.com/smartcontractkit/chainlink-common/pkg/keystore/internal"
1816
)
@@ -45,14 +43,19 @@ type ImportKeyRequest struct {
4543
}
4644

4745
type ImportKeysResponse struct{}
46+
type ExportKeyParam struct {
47+
KeyName string
48+
Enc EncryptionParams
49+
}
50+
4851
type ExportKeysRequest struct {
49-
KeyNames []string
52+
Keys []ExportKeyParam
5053
}
5154
type ExportKeysResponse struct {
5255
Keys []ExportKeyResponse
5356
}
5457
type ExportKeyResponse struct {
55-
KeyInfo KeyInfo
58+
KeyName string
5659
Data []byte
5760
}
5861

@@ -77,12 +80,12 @@ func (ks *keystore) CreateKeys(ctx context.Context, req CreateKeysRequest) (Crea
7780
ks.mu.Lock()
7881
defer ks.mu.Unlock()
7982

80-
// Clone the keystore.
8183
ksCopy := maps.Clone(ks.keystore)
8284
var responses []CreateKeyResponse
83-
84-
// Create all keys
8585
for _, keyReq := range req.Keys {
86+
if _, ok := ksCopy[keyReq.KeyName]; ok {
87+
return CreateKeysResponse{}, fmt.Errorf("key already exists: %s", keyReq.KeyName)
88+
}
8689
switch keyReq.KeyType {
8790
case Ed25519:
8891
_, privateKey, err := ed25519.GenerateKey(rand.Reader)
@@ -148,7 +151,7 @@ func (ks *keystore) CreateKeys(ctx context.Context, req CreateKeysRequest) (Crea
148151
}
149152

150153
// Persist it to storage.
151-
if err := save(ks.storage, ks.password, ksCopy); err != nil {
154+
if err := save(ks.storage, ks.enc, ksCopy); err != nil {
152155
return CreateKeysResponse{}, fmt.Errorf("failed to save keystore: %w", err)
153156
}
154157
// If we succeed to save, update the in memory keystore.
@@ -162,9 +165,12 @@ func (k *keystore) DeleteKeys(ctx context.Context, req DeleteKeysRequest) (Delet
162165

163166
ksCopy := maps.Clone(k.keystore)
164167
for _, name := range req.KeyNames {
168+
if _, ok := ksCopy[name]; !ok {
169+
return DeleteKeysResponse{}, fmt.Errorf("key not found: %s", name)
170+
}
165171
delete(ksCopy, name)
166172
}
167-
if err := save(k.storage, k.password, ksCopy); err != nil {
173+
if err := save(k.storage, k.enc, ksCopy); err != nil {
168174
return DeleteKeysResponse{}, fmt.Errorf("failed to save keystore: %w", err)
169175
}
170176
k.keystore = ksCopy
@@ -176,55 +182,9 @@ func (k *keystore) ImportKeys(ctx context.Context, req ImportKeysRequest) (Impor
176182
}
177183

178184
func (k *keystore) ExportKeys(ctx context.Context, req ExportKeysRequest) (ExportKeysResponse, error) {
179-
var responses []ExportKeyResponse
180-
181-
for _, name := range req.KeyNames {
182-
key, ok := k.keystore[name]
183-
if !ok {
184-
return ExportKeysResponse{}, fmt.Errorf("key not found: %s", name)
185-
}
186-
exportedKey, err := gethkeystore.EncryptDataV3(internal.Bytes(key.privateKey), []byte(k.password), gethkeystore.StandardScryptN, gethkeystore.StandardScryptP)
187-
if err != nil {
188-
return ExportKeysResponse{}, fmt.Errorf("failed to export key %s: %w", name, err)
189-
}
190-
exportedKeyData, err := json.Marshal(exportedKey)
191-
if err != nil {
192-
return ExportKeysResponse{}, fmt.Errorf("failed to marshal exported key %s: %w", name, err)
193-
}
194-
responses = append(responses, ExportKeyResponse{
195-
KeyInfo: KeyInfo{
196-
Name: name,
197-
KeyType: key.keyType,
198-
PublicKey: key.publicKey,
199-
Metadata: key.metadata,
200-
},
201-
Data: exportedKeyData,
202-
})
203-
}
204-
205-
return ExportKeysResponse{Keys: responses}, nil
185+
return ExportKeysResponse{}, nil
206186
}
207187

208188
func (ks *keystore) SetMetadata(ctx context.Context, req SetMetadataRequest) (SetMetadataResponse, error) {
209-
ks.mu.Lock()
210-
defer ks.mu.Unlock()
211-
212-
ksCopy := maps.Clone(ks.keystore)
213-
214-
for _, update := range req.Updates {
215-
key, ok := ksCopy[update.KeyName]
216-
if !ok {
217-
return SetMetadataResponse{}, fmt.Errorf("key not found: %s", update.KeyName)
218-
}
219-
220-
key.metadata = update.Metadata
221-
ksCopy[update.KeyName] = key
222-
}
223-
224-
if err := save(ks.storage, ks.password, ksCopy); err != nil {
225-
return SetMetadataResponse{}, fmt.Errorf("failed to save keystore: %w", err)
226-
}
227-
228-
ks.keystore = ksCopy
229189
return SetMetadataResponse{}, nil
230190
}

keystore/admin_fuzz_test.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package keystore_test
2+
3+
import (
4+
"context"
5+
"crypto/sha256"
6+
"encoding/binary"
7+
"math/rand"
8+
"testing"
9+
10+
ks "github.com/smartcontractkit/chainlink-common/pkg/keystore"
11+
"github.com/smartcontractkit/chainlink-common/pkg/keystore/storage"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func FuzzKeystore_AdminModel(f *testing.F) {
16+
// Here we fuzz a sequence of operations on the keystore
17+
// and check that the invariants are maintained.
18+
testKeyNames := []string{"test-key-1", "test-key-2", "test-key-3"}
19+
//testMetadata := [][]byte{[]byte("test-metadata-1"), []byte("test-metadata-2")}
20+
type key struct {
21+
keyType ks.KeyType
22+
metadata []byte
23+
}
24+
25+
f.Fuzz(func(t *testing.T, seed []byte) {
26+
t.Parallel()
27+
ctx := context.Background()
28+
mem := storage.NewMemoryStorage()
29+
enc := ks.EncryptionParams{Password: "pw", ScryptParams: ks.FastScryptParams}
30+
kstore, err := ks.NewKeystore(mem, enc)
31+
require.NoError(t, err)
32+
33+
// Generate a random sequence of operations
34+
// from the seed.
35+
h := sha256.Sum256(seed)
36+
r := rand.New(rand.NewSource(int64(binary.LittleEndian.Uint64(h[:]))))
37+
n := 1 + r.Intn(5)
38+
39+
expected := make(map[string]key)
40+
for i := 0; i < n; i++ {
41+
// TODO: extend to other operations.
42+
switch r.Intn(2) {
43+
case 0:
44+
// create keys
45+
numKeys := r.Intn(len(testKeyNames) - 1)
46+
keys := make([]ks.CreateKeyRequest, numKeys)
47+
for i := 0; i < numKeys; i++ {
48+
kIndex := r.Intn(len(ks.AllKeyTypes) - 1)
49+
kType := ks.AllKeyTypes[kIndex]
50+
kNameIndex := r.Intn(len(testKeyNames) - 1)
51+
kName := testKeyNames[kNameIndex]
52+
keys[i] = ks.CreateKeyRequest{KeyName: kName, KeyType: kType}
53+
}
54+
_, _ = kstore.CreateKeys(ctx, ks.CreateKeysRequest{Keys: keys})
55+
for _, k := range keys {
56+
expected[k.KeyName] = key{keyType: k.KeyType, metadata: []byte{}}
57+
}
58+
case 1:
59+
// delete keys
60+
numKeys := r.Intn(len(testKeyNames) - 1)
61+
var req ks.DeleteKeysRequest
62+
for i := 0; i < numKeys; i++ {
63+
kNameIndex := r.Intn(len(testKeyNames) - 1)
64+
kName := testKeyNames[kNameIndex]
65+
req.KeyNames = append(req.KeyNames, kName)
66+
}
67+
_, _ = kstore.DeleteKeys(ctx, req)
68+
for _, name := range req.KeyNames {
69+
delete(expected, name)
70+
}
71+
}
72+
}
73+
74+
// Check that the invariants are maintained:
75+
// 1. No duplicate names.
76+
// 2. Net set keys is as expected (including metadata).
77+
resp, err := kstore.GetKeys(ctx, ks.GetKeysRequest{})
78+
require.NoError(t, err)
79+
80+
have := make(map[string]key)
81+
for _, k := range resp.Keys {
82+
have[k.KeyInfo.Name] = key{
83+
keyType: k.KeyInfo.KeyType,
84+
metadata: k.KeyInfo.Metadata,
85+
}
86+
}
87+
require.Equal(t, len(expected), len(have))
88+
for expectedKeyName, expectedKey := range expected {
89+
haveKey, ok := have[expectedKeyName]
90+
require.True(t, ok)
91+
require.Equal(t, expectedKey.keyType, haveKey.keyType)
92+
require.Equal(t, expectedKey.metadata, haveKey.metadata)
93+
}
94+
})
95+
}

keystore/admin_reader_test.go

Lines changed: 64 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,69 +2,87 @@ package keystore_test
22

33
import (
44
"context"
5+
"fmt"
56
"testing"
67

78
"github.com/smartcontractkit/chainlink-common/pkg/keystore"
89
"github.com/smartcontractkit/chainlink-common/pkg/keystore/storage"
10+
"github.com/stretchr/testify/assert"
911
"github.com/stretchr/testify/require"
1012
)
1113

12-
func TestKeystore_AdminReader(t *testing.T) {
14+
func TestKeystore_AdminCreateDeleteKeys(t *testing.T) {
1315
storage := storage.NewMemoryStorage()
14-
ks, err := keystore.NewKeystore(storage, "test-password")
16+
ks, err := keystore.NewKeystore(storage, keystore.EncryptionParams{
17+
Password: "test-password",
18+
ScryptParams: keystore.FastScryptParams,
19+
})
1520
require.NoError(t, err)
1621
ctx := context.Background()
17-
var (
18-
testKeyEd25519 = "test-ed25519"
19-
testKeyEcdsaSecp256k1 = "test-ecdsa-secp256k1"
20-
testKeyX25519 = "test-x25519"
21-
)
22-
23-
req := keystore.CreateKeysRequest{
24-
Keys: []keystore.CreateKeyRequest{
25-
{KeyName: testKeyEd25519, KeyType: keystore.Ed25519},
26-
{KeyName: testKeyEcdsaSecp256k1, KeyType: keystore.EcdsaSecp256k1},
27-
{KeyName: testKeyX25519, KeyType: keystore.X25519},
28-
},
22+
// Can create keys of all types.
23+
var keys []keystore.GetKeyResponse
24+
for _, kType := range keystore.AllKeyTypes {
25+
name := fmt.Sprintf("test-key-%s", kType)
26+
_, err := ks.CreateKeys(ctx, keystore.CreateKeysRequest{
27+
Keys: []keystore.CreateKeyRequest{
28+
{
29+
KeyName: name,
30+
KeyType: kType,
31+
},
32+
},
33+
})
34+
require.NoError(t, err)
35+
// Should be able to read back the key we created.
36+
resp, err := ks.GetKeys(ctx, keystore.GetKeysRequest{
37+
KeyNames: []string{name},
38+
})
39+
require.NoError(t, err)
40+
require.Equal(t, 1, len(resp.Keys))
41+
require.Equal(t, kType, resp.Keys[0].KeyInfo.KeyType)
42+
require.Equal(t, name, resp.Keys[0].KeyInfo.Name)
43+
require.NotEmpty(t, resp.Keys[0].KeyInfo.PublicKey)
44+
require.Empty(t, resp.Keys[0].KeyInfo.Metadata)
45+
keys = append(keys, resp.Keys[0])
2946
}
30-
31-
resp, err := ks.CreateKeys(ctx, req)
47+
// Get all should return same keys we created.
48+
resp, err := ks.GetKeys(ctx, keystore.GetKeysRequest{})
3249
require.NoError(t, err)
33-
require.Len(t, resp.Keys, 3)
50+
require.Equal(t, len(keystore.AllKeyTypes), len(resp.Keys))
51+
assert.ElementsMatch(t, keys, resp.Keys)
3452

35-
expectedTypes := []keystore.KeyType{keystore.Ed25519, keystore.EcdsaSecp256k1, keystore.X25519}
36-
for i, key := range resp.Keys {
37-
require.Equal(t, expectedTypes[i], key.KeyInfo.KeyType)
38-
require.Equal(t, req.Keys[i].KeyName, key.KeyInfo.Name)
39-
require.NotEmpty(t, key.KeyInfo.PublicKey, "Expected non-empty public key for %s", key.KeyInfo.Name)
53+
// Can't create keys with duplicate names.
54+
for _, kType := range keystore.AllKeyTypes {
55+
_, err := ks.CreateKeys(ctx, keystore.CreateKeysRequest{
56+
Keys: []keystore.CreateKeyRequest{
57+
{
58+
KeyName: fmt.Sprintf("test-key-%s", kType),
59+
KeyType: kType,
60+
},
61+
},
62+
})
63+
require.Error(t, err)
4064
}
4165

42-
getReq := keystore.GetKeysRequest{
43-
KeyNames: []string{testKeyEd25519, testKeyEcdsaSecp256k1},
66+
// Delete each key we created.
67+
for _, k := range keys {
68+
_, err := ks.DeleteKeys(ctx, keystore.DeleteKeysRequest{
69+
KeyNames: []string{k.KeyInfo.Name},
70+
})
71+
require.NoError(t, err)
72+
// Key no longer exists
73+
_, err = ks.GetKeys(ctx, keystore.GetKeysRequest{
74+
KeyNames: []string{k.KeyInfo.Name},
75+
})
76+
require.Error(t, err)
4477
}
45-
46-
getResp, err := ks.GetKeys(ctx, getReq)
78+
// Get all should return no keys.
79+
resp, err = ks.GetKeys(ctx, keystore.GetKeysRequest{})
4780
require.NoError(t, err)
48-
require.Len(t, getResp.Keys, 2)
81+
require.Empty(t, resp.Keys)
4982

50-
allKeysReq := keystore.GetKeysRequest{}
51-
allKeysResp, err := ks.GetKeys(ctx, allKeysReq)
52-
require.NoError(t, err)
53-
require.Len(t, allKeysResp.Keys, 3)
54-
55-
deleteReq := keystore.DeleteKeysRequest{
56-
KeyNames: []string{testKeyX25519},
57-
}
58-
59-
_, err = ks.DeleteKeys(ctx, deleteReq)
60-
require.NoError(t, err)
61-
62-
deleteVerifyReq := keystore.GetKeysRequest{KeyNames: []string{testKeyX25519}}
63-
_, err = ks.GetKeys(ctx, deleteVerifyReq)
83+
// Can't delete keys that don't exist.
84+
_, err = ks.DeleteKeys(ctx, keystore.DeleteKeysRequest{
85+
KeyNames: []string{fmt.Sprintf("test-key-%s", keystore.AllKeyTypes[0])},
86+
})
6487
require.Error(t, err)
65-
66-
finalKeysReq := keystore.GetKeysRequest{}
67-
finalKeysResp, err := ks.GetKeys(ctx, finalKeysReq)
68-
require.NoError(t, err)
69-
require.Len(t, finalKeysResp.Keys, 2)
7088
}

keystore/encryptor.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,6 @@ import (
44
"context"
55
)
66

7-
const (
8-
X25519 KeyType = "X25519"
9-
// TODO: Support P256 for DKG.
10-
)
11-
127
type EncryptRequest struct {
138
KeyName string
149
Data []byte

0 commit comments

Comments
 (0)