Skip to content

Commit 39a3be0

Browse files
authored
Merge pull request #1726 from smartcontractkit/remaining-cli
Add remaining functions to CLI
2 parents 9720931 + 8401fce commit 39a3be0

File tree

7 files changed

+276
-130
lines changed

7 files changed

+276
-130
lines changed

keystore/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
[![Go Reference](https://pkg.go.dev/badge/github.com/smartcontractkit/chainlink-common/keystore.svg)](https://pkg.go.dev/github.com/smartcontractkit/chainlink-common/keystore)
2+
13
WARNING: In development do not use in production.
24

35
# Keystore

keystore/admin.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ func (ks *keystore) CreateKeys(ctx context.Context, req CreateKeysRequest) (Crea
232232
}
233233
ksCopy[keyReq.KeyName] = newKey(keyReq.KeyType, internal.NewRaw(privateKey.Bytes()), publicKey, time.Now(), []byte{})
234234
default:
235-
return CreateKeysResponse{}, fmt.Errorf("%w: %s", ErrUnsupportedKeyType, keyReq.KeyType)
235+
return CreateKeysResponse{}, fmt.Errorf("%w: %s, available key types: %s", ErrUnsupportedKeyType, keyReq.KeyType, AllKeyTypes.String())
236236
}
237237

238238
created := ksCopy[keyReq.KeyName].createdAt

keystore/cli/cli.go

Lines changed: 160 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,14 @@ KEYSTORE_PASSWORD is the password used to encrypt the key material before storag
5252
cmd.PersistentFlags().String("keystore-db-url", "", "Overrides KEYSTORE_DB_URL environment variable")
5353
cmd.PersistentFlags().String("keystore-password", "", "Overrides KEYSTORE_PASSWORD environment variable. Not recommended as will leave shell traces.")
5454

55-
cmd.AddCommand(NewListCmd(), NewGetCmd(), NewCreateCmd(), NewDeleteCmd(), NewExportCmd(), NewImportCmd(), NewSetMetadataCmd())
55+
cmd.AddCommand(NewListCmd(), NewGetCmd(), NewCreateCmd(), NewDeleteCmd(), NewExportCmd(), NewImportCmd(), NewSetMetadataCmd(), NewSignCmd(), NewVerifyCmd(), NewEncryptCmd(), NewDecryptCmd())
5656
return cmd
5757
}
5858

5959
func NewListCmd() *cobra.Command {
6060
cmd := cobra.Command{
6161
Use: "list", Short: "List keys",
62-
RunE: func(cmd *cobra.Command, _ []string) error {
62+
RunE: func(cmd *cobra.Command, args []string) error {
6363
ctx, cancel := context.WithTimeout(cmd.Context(), KeystoreLoadTimeout)
6464
defer cancel()
6565
k, err := loadKeystore(ctx, cmd)
@@ -84,31 +84,10 @@ func NewListCmd() *cobra.Command {
8484
func NewGetCmd() *cobra.Command {
8585
cmd := cobra.Command{
8686
Use: "get", Short: "Get keys",
87-
RunE: func(cmd *cobra.Command, _ []string) error {
88-
jsonBytes, err := readJSONInput(cmd)
89-
if err != nil {
90-
return err
91-
}
92-
var req ks.GetKeysRequest
93-
if err := json.Unmarshal(jsonBytes, &req); err != nil {
94-
return fmt.Errorf("invalid JSON request: %w", err)
95-
}
96-
ctx, cancel := context.WithTimeout(cmd.Context(), KeystoreLoadTimeout)
97-
defer cancel()
98-
k, err := loadKeystore(ctx, cmd)
99-
if err != nil {
100-
return err
101-
}
102-
resp, err := k.GetKeys(ctx, req)
103-
if err != nil {
104-
return err
105-
}
106-
jsonBytes, err = json.Marshal(resp)
107-
if err != nil {
108-
return err
109-
}
110-
_, err = cmd.OutOrStdout().Write(jsonBytes)
111-
return err
87+
RunE: func(cmd *cobra.Command, args []string) error {
88+
return runKeystoreCommand[ks.GetKeysRequest, ks.GetKeysResponse](cmd, args, func(ctx context.Context, k ks.Keystore, req ks.GetKeysRequest) (ks.GetKeysResponse, error) {
89+
return k.GetKeys(ctx, req)
90+
})
11291
},
11392
}
11493
cmd.Flags().StringP("file", "f", "", "input file path (use \"-\" for stdin)")
@@ -119,32 +98,10 @@ func NewGetCmd() *cobra.Command {
11998
func NewCreateCmd() *cobra.Command {
12099
cmd := cobra.Command{
121100
Use: "create", Short: "Create a key",
122-
RunE: func(cmd *cobra.Command, _ []string) error {
123-
jsonBytesIn, err := readJSONInput(cmd)
124-
if err != nil {
125-
return err
126-
}
127-
var req ks.CreateKeysRequest
128-
err = json.Unmarshal(jsonBytesIn, &req)
129-
if err != nil {
130-
return err
131-
}
132-
ctx, cancel := context.WithTimeout(cmd.Context(), KeystoreLoadTimeout)
133-
defer cancel()
134-
k, err := loadKeystore(ctx, cmd)
135-
if err != nil {
136-
return err
137-
}
138-
resp, err := k.CreateKeys(ctx, req)
139-
if err != nil {
140-
return err
141-
}
142-
jsonBytes, err := json.Marshal(resp)
143-
if err != nil {
144-
return err
145-
}
146-
_, err = cmd.OutOrStdout().Write(jsonBytes)
147-
return err
101+
RunE: func(cmd *cobra.Command, args []string) error {
102+
return runKeystoreCommand[ks.CreateKeysRequest, ks.CreateKeysResponse](cmd, args, func(ctx context.Context, k ks.Keystore, req ks.CreateKeysRequest) (ks.CreateKeysResponse, error) {
103+
return k.CreateKeys(ctx, req)
104+
})
148105
},
149106
}
150107
cmd.Flags().StringP("file", "f", "", "input file path (use \"-\" for stdin)")
@@ -201,32 +158,10 @@ func NewDeleteCmd() *cobra.Command {
201158
func NewExportCmd() *cobra.Command {
202159
cmd := cobra.Command{
203160
Use: "export", Short: "Export a key to an encrypted JSON file",
204-
RunE: func(cmd *cobra.Command, _ []string) error {
205-
jsonBytesIn, err := readJSONInput(cmd)
206-
if err != nil {
207-
return err
208-
}
209-
var req ks.ExportKeysRequest
210-
err = json.Unmarshal(jsonBytesIn, &req)
211-
if err != nil {
212-
return err
213-
}
214-
ctx, cancel := context.WithTimeout(cmd.Context(), KeystoreLoadTimeout)
215-
defer cancel()
216-
k, err := loadKeystore(ctx, cmd)
217-
if err != nil {
218-
return err
219-
}
220-
resp, err := k.ExportKeys(ctx, req)
221-
if err != nil {
222-
return err
223-
}
224-
jsonBytesOut, err := json.Marshal(resp)
225-
if err != nil {
226-
return err
227-
}
228-
_, err = cmd.OutOrStdout().Write(jsonBytesOut)
229-
return err
161+
RunE: func(cmd *cobra.Command, args []string) error {
162+
return runKeystoreCommand[ks.ExportKeysRequest, ks.ExportKeysResponse](cmd, args, func(ctx context.Context, k ks.Keystore, req ks.ExportKeysRequest) (ks.ExportKeysResponse, error) {
163+
return k.ExportKeys(ctx, req)
164+
})
230165
},
231166
}
232167
cmd.Flags().StringP("file", "f", "", "input file path (use \"-\" for stdin)")
@@ -237,24 +172,10 @@ func NewExportCmd() *cobra.Command {
237172
func NewImportCmd() *cobra.Command {
238173
cmd := cobra.Command{
239174
Use: "import", Short: "Import an encrypted key JSON file",
240-
RunE: func(cmd *cobra.Command, _ []string) error {
241-
jsonBytes, err := readJSONInput(cmd)
242-
if err != nil {
243-
return err
244-
}
245-
var req ks.ImportKeysRequest
246-
err = json.Unmarshal(jsonBytes, &req)
247-
if err != nil {
248-
return err
249-
}
250-
ctx, cancel := context.WithTimeout(cmd.Context(), KeystoreLoadTimeout)
251-
defer cancel()
252-
k, err := loadKeystore(ctx, cmd)
253-
if err != nil {
254-
return err
255-
}
256-
_, err = k.ImportKeys(ctx, req)
257-
return err
175+
RunE: func(cmd *cobra.Command, args []string) error {
176+
return runKeystoreCommand[ks.ImportKeysRequest, ks.ImportKeysResponse](cmd, args, func(ctx context.Context, k ks.Keystore, req ks.ImportKeysRequest) (ks.ImportKeysResponse, error) {
177+
return k.ImportKeys(ctx, req)
178+
})
258179
},
259180
}
260181
cmd.Flags().StringP("file", "f", "", "input file path (use \"-\" for stdin)")
@@ -265,31 +186,155 @@ func NewImportCmd() *cobra.Command {
265186
func NewSetMetadataCmd() *cobra.Command {
266187
cmd := cobra.Command{
267188
Use: "set-metadata", Short: "Set metadata for keys",
268-
RunE: func(cmd *cobra.Command, _ []string) error {
269-
jsonBytes, err := readJSONInput(cmd)
270-
if err != nil {
271-
return err
272-
}
273-
var req ks.SetMetadataRequest
274-
err = json.Unmarshal(jsonBytes, &req)
275-
if err != nil {
276-
return err
277-
}
278-
ctx, cancel := context.WithTimeout(cmd.Context(), KeystoreLoadTimeout)
279-
defer cancel()
280-
k, err := loadKeystore(ctx, cmd)
281-
if err != nil {
282-
return err
283-
}
284-
_, err = k.SetMetadata(ctx, req)
285-
return err
189+
RunE: func(cmd *cobra.Command, args []string) error {
190+
return runKeystoreCommand[ks.SetMetadataRequest, ks.SetMetadataResponse](cmd, args, func(ctx context.Context, k ks.Keystore, req ks.SetMetadataRequest) (ks.SetMetadataResponse, error) {
191+
return k.SetMetadata(ctx, req)
192+
})
286193
},
287194
}
288195
cmd.Flags().StringP("file", "f", "", "input file path (use \"-\" for stdin)")
289196
cmd.Flags().StringP("data", "d", "", "inline JSON request, e.g. '{\"Updates\": [{\"KeyName\": \"key1\", \"Metadata\": \"base64-encoded-metadata\"}]}'")
290197
return &cmd
291198
}
292199

200+
func runKeystoreCommand[Req any, Resp any](cmd *cobra.Command, args []string, fn func(ctx context.Context, k ks.Keystore,
201+
req Req) (Resp, error)) error {
202+
jsonBytes, err := readJSONInput(cmd)
203+
if err != nil {
204+
return err
205+
}
206+
var req Req
207+
err = json.Unmarshal(jsonBytes, &req)
208+
if err != nil {
209+
return err
210+
}
211+
ctx, cancel := context.WithTimeout(cmd.Context(), KeystoreLoadTimeout)
212+
defer cancel()
213+
k, err := loadKeystore(ctx, cmd)
214+
if err != nil {
215+
return err
216+
}
217+
resp, err := fn(ctx, k, req)
218+
if err != nil {
219+
return err
220+
}
221+
jsonBytesOut, err := json.Marshal(resp)
222+
if err != nil {
223+
return err
224+
}
225+
_, err = cmd.OutOrStdout().Write(jsonBytesOut)
226+
if err != nil {
227+
return err
228+
}
229+
return nil
230+
}
231+
232+
func NewSignCmd() *cobra.Command {
233+
cmd := cobra.Command{
234+
Use: "sign", Short: "Sign data with a key",
235+
RunE: func(cmd *cobra.Command, args []string) error {
236+
return runKeystoreCommand[ks.SignRequest, ks.SignResponse](cmd, args, func(ctx context.Context, k ks.Keystore, req ks.SignRequest) (ks.SignResponse, error) {
237+
return k.Sign(ctx, req)
238+
})
239+
},
240+
}
241+
cmd.Flags().StringP("file", "f", "", "input file path (use \"-\" for stdin)")
242+
cmd.Flags().StringP("data", "d", "", `
243+
Inline JSON request. Data is base64-encoded.
244+
Example:
245+
echo -n 'hello' | base64
246+
aGVsbG8=
247+
248+
./keystore list | jq
249+
{
250+
"Keys": [
251+
{
252+
"KeyName": "mykey",
253+
"KeyType": "Ed25519",
254+
"CreatedAt": "2025-01-01T00:00:00Z",
255+
"PublicKey": "GJnS+erQbyuEm1byCjXy+6JqyX5hrGLE8oUuHSb9DFc="
256+
}
257+
]
258+
}
259+
./keystore sign -d '{"KeyName": "mykey", "Data": "aGVsbG8="}' | jq
260+
{
261+
"Signature": "OVPaQIwQAZycQtiGjhwxZ3KmAdXOHczwi3LpwQTCbtMHfy5mmrp0KusICSO0lzCMeQvxJcd5y6f3siQsohQeCg=="
262+
}
263+
./keystore verify -d '{"KeyType": "Ed25519", "PublicKey": "GJnS+erQbyuEm1byCjXy+6JqyX5hrGLE8oUuHSb9DFc=", "Data": "aGVsbG8=", "Signature": "OVPaQIwQAZycQtiGjhwxZ3KmAdXOHczwi3LpwQTCbtMHfy5mmrp0KusICSO0lzCMeQvxJcd5y6f3siQsohQeCg=="}' | jq
264+
{
265+
"Valid": true
266+
}
267+
`)
268+
return &cmd
269+
}
270+
271+
func NewVerifyCmd() *cobra.Command {
272+
cmd := cobra.Command{
273+
Use: "verify", Short: "Verify a signature",
274+
RunE: func(cmd *cobra.Command, args []string) error {
275+
return runKeystoreCommand[ks.VerifyRequest, ks.VerifyResponse](cmd, args, func(ctx context.Context, k ks.Keystore, req ks.VerifyRequest) (ks.VerifyResponse, error) {
276+
return k.Verify(ctx, req)
277+
})
278+
},
279+
}
280+
cmd.Flags().StringP("file", "f", "", "input file path (use \"-\" for stdin)")
281+
cmd.Flags().StringP("data", "d", "", `inline JSON request. All byte fields are base64-encoded. Example: '{"KeyType": "Ed25519", "PublicKey": "<base64>", "Data": "aGVsbG8=", "Signature": "<base64>"}'`)
282+
return &cmd
283+
}
284+
285+
func NewEncryptCmd() *cobra.Command {
286+
cmd := cobra.Command{
287+
Use: "encrypt", Short: "Encrypt data to a remote public key",
288+
RunE: func(cmd *cobra.Command, args []string) error {
289+
return runKeystoreCommand[ks.EncryptRequest, ks.EncryptResponse](cmd, args, func(ctx context.Context, k ks.Keystore, req ks.EncryptRequest) (ks.EncryptResponse, error) {
290+
return k.Encrypt(ctx, req)
291+
})
292+
},
293+
}
294+
cmd.Flags().StringP("file", "f", "", "input file path (use \"-\" for stdin)")
295+
cmd.Flags().StringP("data", "d", "", `
296+
Inline JSON request. Data/RemotePubKey are base64-encoded.
297+
Example:
298+
echo -n 'hello' | base64
299+
aGVsbG8=
300+
301+
./keystore list | jq
302+
{
303+
"Keys": [
304+
{
305+
"KeyName": "x25519key",
306+
"KeyType": "X25519",
307+
"CreatedAt": "2025-01-01T00:00:00Z",
308+
"PublicKey": "GJnS+erQbyuEm1byCjXy+6JqyX5hrGLE8oUuHSb9DFc="
309+
}
310+
]
311+
}
312+
./keystore encrypt -d '{"RemoteKeyType": "X25519", "RemotePubKey": "GJnS+erQbyuEm1byCjXy+6JqyX5hrGLE8oUuHSb9DFc=", "Data": "aGVsbG8="}' | jq
313+
{
314+
"EncryptedData": "ZGVjb3JhdGVkRGF0YQ=="
315+
}
316+
./keystore decrypt -d '{"KeyName": "x25519key", "EncryptedData": "ZGVjb3JhdGVkRGF0YQ=="}' | jq
317+
{
318+
"Data": "aGVsbG8="
319+
}
320+
`)
321+
return &cmd
322+
}
323+
324+
func NewDecryptCmd() *cobra.Command {
325+
cmd := cobra.Command{
326+
Use: "decrypt", Short: "Decrypt data with a key",
327+
RunE: func(cmd *cobra.Command, args []string) error {
328+
return runKeystoreCommand[ks.DecryptRequest, ks.DecryptResponse](cmd, args, func(ctx context.Context, k ks.Keystore, req ks.DecryptRequest) (ks.DecryptResponse, error) {
329+
return k.Decrypt(ctx, req)
330+
})
331+
},
332+
}
333+
cmd.Flags().StringP("file", "f", "", "input file path (use \"-\" for stdin)")
334+
cmd.Flags().StringP("data", "d", "", `inline JSON request. EncryptedData is base64-encoded. Example: '{"KeyName": "mykey", "EncryptedData": "<base64>"}'`)
335+
return &cmd
336+
}
337+
293338
func loadKeystore(ctx context.Context, cmd *cobra.Command) (ks.Keystore, error) {
294339
root := cmd.Root()
295340
filePath, err := root.Flags().GetString("keystore-file-path")

0 commit comments

Comments
 (0)