@@ -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
5959func 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 {
8484func 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 {
11998func 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 {
201158func 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 {
237172func 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 {
265186func 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+
293338func 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