Skip to content

Commit 26c60d9

Browse files
committed
Minor.
1 parent c2968ea commit 26c60d9

File tree

3 files changed

+146
-6
lines changed

3 files changed

+146
-6
lines changed

keystore/admin.go

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,19 @@ import (
66
"crypto/ecdsa"
77
"crypto/ed25519"
88
"crypto/rand"
9+
"encoding/json"
910
"fmt"
1011
"maps"
1112
"time"
1213

14+
gethkeystore "github.com/ethereum/go-ethereum/accounts/keystore"
1315
"golang.org/x/crypto/curve25519"
16+
"google.golang.org/protobuf/proto"
1417

1518
"github.com/ethereum/go-ethereum/crypto"
19+
1620
"github.com/smartcontractkit/chainlink-common/keystore/internal"
21+
"github.com/smartcontractkit/chainlink-common/keystore/serialization"
1722
)
1823

1924
var (
@@ -52,8 +57,8 @@ type ImportKeysRequest struct {
5257

5358
type ImportKeyRequest struct {
5459
KeyName string
55-
KeyType KeyType
5660
Data []byte
61+
Enc EncryptionParams
5762
}
5863

5964
type ImportKeysResponse struct{}
@@ -227,12 +232,91 @@ func (k *keystore) DeleteKeys(ctx context.Context, req DeleteKeysRequest) (Delet
227232
return DeleteKeysResponse{}, nil
228233
}
229234

230-
func (k *keystore) ImportKeys(ctx context.Context, req ImportKeysRequest) (ImportKeysResponse, error) {
235+
func (ks *keystore) ImportKeys(ctx context.Context, req ImportKeysRequest) (ImportKeysResponse, error) {
236+
ks.mu.Lock()
237+
defer ks.mu.Unlock()
238+
239+
ksCopy := maps.Clone(ks.keystore)
240+
for _, keyReq := range req.Keys {
241+
if err := ValidKeyName(keyReq.KeyName); err != nil {
242+
return ImportKeysResponse{}, fmt.Errorf("%w: %s", ErrInvalidKeyName, err)
243+
}
244+
if _, ok := ksCopy[keyReq.KeyName]; ok {
245+
return ImportKeysResponse{}, fmt.Errorf("%w: %s", ErrKeyAlreadyExists, keyReq.KeyName)
246+
}
247+
if err := ValidKeyName(keyReq.KeyName); err != nil {
248+
return ImportKeysResponse{}, fmt.Errorf("%w: %s", ErrInvalidKeyName, err)
249+
}
250+
encData := gethkeystore.CryptoJSON{}
251+
err := json.Unmarshal(keyReq.Data, &encData)
252+
if err != nil {
253+
return ImportKeysResponse{}, fmt.Errorf("key = %s, failed to unmarshal encrypted import data: %w", keyReq.KeyName, err)
254+
}
255+
decData, err := gethkeystore.DecryptDataV3(encData, keyReq.Enc.Password)
256+
if err != nil {
257+
return ImportKeysResponse{}, fmt.Errorf("key = %s, failed to decrypt key: %w", keyReq.KeyName, err)
258+
}
259+
keypb := &serialization.Key{}
260+
err = proto.Unmarshal(decData, keypb)
261+
if err != nil {
262+
return ImportKeysResponse{}, fmt.Errorf("key = %s, failed to unmarshal key: %w", keyReq.KeyName, err)
263+
}
264+
pkRaw := internal.NewRaw(keypb.PrivateKey)
265+
keyType := KeyType(keypb.KeyType)
266+
publicKey, err := publicKeyFromPrivateKey(pkRaw, keyType)
267+
if err != nil {
268+
return ImportKeysResponse{}, fmt.Errorf("key = %s, failed to get public key from private key: %w", keyReq.KeyName, err)
269+
}
270+
metadata := keypb.Metadata
271+
if metadata == nil {
272+
metadata = []byte{}
273+
}
274+
ksCopy[keyReq.KeyName] = newKey(keyType, pkRaw, publicKey, time.Unix(keypb.CreatedAt, 0), metadata)
275+
}
276+
// Persist it to storage.
277+
if err := ks.save(ctx, ksCopy); err != nil {
278+
return ImportKeysResponse{}, fmt.Errorf("failed to save keystore: %w", err)
279+
}
280+
// If we succeed to save, update the in memory keystore.
281+
ks.keystore = ksCopy
231282
return ImportKeysResponse{}, nil
232283
}
233284

234-
func (k *keystore) ExportKeys(ctx context.Context, req ExportKeysRequest) (ExportKeysResponse, error) {
235-
return ExportKeysResponse{}, nil
285+
func (ks *keystore) ExportKeys(_ context.Context, req ExportKeysRequest) (ExportKeysResponse, error) {
286+
ks.mu.Lock()
287+
defer ks.mu.Unlock()
288+
289+
result := ExportKeysResponse{}
290+
for _, keyReq := range req.Keys {
291+
if key, ok := ks.keystore[keyReq.KeyName]; !ok {
292+
return ExportKeysResponse{}, fmt.Errorf("%w: %s", ErrKeyNotFound, keyReq.KeyName)
293+
} else {
294+
keypb := &serialization.Key{
295+
Name: keyReq.KeyName,
296+
KeyType: string(key.keyType),
297+
PrivateKey: internal.Bytes(key.privateKey),
298+
CreatedAt: key.createdAt.Unix(),
299+
Metadata: key.metadata,
300+
}
301+
serialized, err := proto.Marshal(keypb)
302+
if err != nil {
303+
return ExportKeysResponse{}, fmt.Errorf("key = %s, failed to marshal key: %w", keyReq.KeyName, err)
304+
}
305+
encData, err := gethkeystore.EncryptDataV3(serialized, []byte(keyReq.Enc.Password), keyReq.Enc.ScryptParams.N, keyReq.Enc.ScryptParams.P)
306+
if err != nil {
307+
return ExportKeysResponse{}, fmt.Errorf("key = %s, failed to encrypt key: %w", keyReq.KeyName, err)
308+
}
309+
encDataBytes, err := json.Marshal(encData)
310+
if err != nil {
311+
return ExportKeysResponse{}, fmt.Errorf("key = %s, failed to marshal encrypted key: %w", keyReq.KeyName, err)
312+
}
313+
result.Keys = append(result.Keys, ExportKeyResponse{
314+
KeyName: keyReq.KeyName,
315+
Data: encDataBytes,
316+
})
317+
}
318+
}
319+
return result, nil
236320
}
237321

238322
func (ks *keystore) SetMetadata(ctx context.Context, req SetMetadataRequest) (SetMetadataResponse, error) {

keystore/admin_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,3 +210,59 @@ func TestKeystore_ConcurrentCreateAndRead(t *testing.T) {
210210
require.NoError(t, err)
211211
require.Equal(t, numWriters*keysPerWriter, len(resp.Keys))
212212
}
213+
214+
func TestKeystore_ExportImport(t *testing.T) {
215+
t.Parallel()
216+
217+
ks1, err := keystore.LoadKeystore(t.Context(), keystore.NewMemoryStorage(), keystore.EncryptionParams{
218+
Password: "ks1",
219+
ScryptParams: keystore.FastScryptParams,
220+
})
221+
require.NoError(t, err)
222+
_, err = ks1.CreateKeys(t.Context(), keystore.CreateKeysRequest{
223+
Keys: []keystore.CreateKeyRequest{
224+
{KeyName: "key1", KeyType: keystore.Ed25519},
225+
},
226+
})
227+
require.NoError(t, err)
228+
exportParams := keystore.EncryptionParams{
229+
Password: "export-pass",
230+
ScryptParams: keystore.FastScryptParams,
231+
}
232+
exportResponse, err := ks1.ExportKeys(t.Context(), keystore.ExportKeysRequest{
233+
Keys: []keystore.ExportKeyParam{
234+
{KeyName: "key1", Enc: exportParams},
235+
},
236+
})
237+
require.Len(t, exportResponse.Keys, 1)
238+
ks2, err := keystore.LoadKeystore(t.Context(), keystore.NewMemoryStorage(), keystore.EncryptionParams{
239+
Password: "ks2",
240+
ScryptParams: keystore.FastScryptParams,
241+
})
242+
_, err = ks2.ImportKeys(t.Context(), keystore.ImportKeysRequest{
243+
Keys: []keystore.ImportKeyRequest{
244+
{KeyName: "key1", Enc: exportParams, Data: exportResponse.Keys[0].Data},
245+
},
246+
})
247+
require.NoError(t, err)
248+
249+
n1, err := ks1.GetKeys(t.Context(), keystore.GetKeysRequest{KeyNames: []string{"key1"}})
250+
require.NoError(t, err)
251+
n2, err := ks2.GetKeys(t.Context(), keystore.GetKeysRequest{KeyNames: []string{"key1"}})
252+
require.Equal(t, n1, n2)
253+
254+
testData := []byte("hello world")
255+
signature, err := ks2.Sign(t.Context(), keystore.SignRequest{
256+
KeyName: "key1",
257+
Data: testData,
258+
})
259+
require.NoError(t, err)
260+
verifyResp, err := ks1.Verify(t.Context(), keystore.VerifyRequest{
261+
KeyType: keystore.Ed25519,
262+
PublicKey: n1.Keys[0].KeyInfo.PublicKey,
263+
Data: testData,
264+
Signature: signature.Signature,
265+
})
266+
require.NoError(t, err)
267+
require.True(t, verifyResp.Valid)
268+
}

keystore/keystore.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,9 @@ func newKey(keyType KeyType, privateKey internal.Raw, publicKey []byte, createdA
143143
}
144144
}
145145

146-
// EncryptionParams controls password-based encryption cost.
147-
// N and P are scrypt parameters; higher values increase CPU/memory cost.
146+
// EncryptionParams controls password-based encryption.
148147
// Password is the secret used to derive the encryption key.
148+
// ScryptParams control CPU/memory cost.
149149
type EncryptionParams struct {
150150
Password string
151151
ScryptParams ScryptParams

0 commit comments

Comments
 (0)