master
go 857 lines 23.4 KB
Raw
1 package commands
2
3 import (
4 "bytes"
5 "crypto/ed25519"
6 "crypto/x509"
7 "encoding/pem"
8 "errors"
9 "fmt"
10 "io"
11 "os"
12 "path/filepath"
13 "strings"
14 "text/tabwriter"
15
16 keystore "github.com/ipfs/boxo/keystore"
17 cmds "github.com/ipfs/go-ipfs-cmds"
18 oldcmds "github.com/ipfs/kubo/commands"
19 config "github.com/ipfs/kubo/config"
20 cmdenv "github.com/ipfs/kubo/core/commands/cmdenv"
21 "github.com/ipfs/kubo/core/commands/e"
22 ke "github.com/ipfs/kubo/core/commands/keyencode"
23 options "github.com/ipfs/kubo/core/coreiface/options"
24 fsrepo "github.com/ipfs/kubo/repo/fsrepo"
25 migrations "github.com/ipfs/kubo/repo/fsrepo/migrations"
26 "github.com/libp2p/go-libp2p/core/crypto"
27 peer "github.com/libp2p/go-libp2p/core/peer"
28 mbase "github.com/multiformats/go-multibase"
29 )
30
31 var KeyCmd = &cmds.Command{
32 Helptext: cmds.HelpText{
33 Tagline: "Create and list IPNS name keypairs",
34 ShortDescription: `
35 'ipfs key gen' generates a new keypair for usage with IPNS and 'ipfs name
36 publish'.
37
38 > ipfs key gen --type=rsa --size=2048 mykey
39 > ipfs name publish --key=mykey QmSomeHash
40
41 'ipfs key ls' lists the available keys.
42
43 > ipfs key ls
44 self
45 mykey
46 `,
47 },
48 Subcommands: map[string]*cmds.Command{
49 "gen": keyGenCmd,
50 "export": keyExportCmd,
51 "import": keyImportCmd,
52 "list": keyListDeprecatedCmd,
53 "ls": keyListCmd,
54 "rename": keyRenameCmd,
55 "rm": keyRmCmd,
56 "rotate": keyRotateCmd,
57 "sign": keySignCmd,
58 "verify": keyVerifyCmd,
59 },
60 }
61
62 type KeyOutput struct {
63 Name string
64 Id string //nolint
65 }
66
67 type KeyOutputList struct {
68 Keys []KeyOutput
69 }
70
71 // KeyRenameOutput define the output type of keyRenameCmd
72 type KeyRenameOutput struct {
73 Was string
74 Now string
75 Id string //nolint
76 Overwrite bool
77 }
78
79 const (
80 keyStoreAlgorithmDefault = options.Ed25519Key
81 keyStoreTypeOptionName = "type"
82 keyStoreSizeOptionName = "size"
83 oldKeyOptionName = "oldkey"
84 )
85
86 var keyGenCmd = &cmds.Command{
87 Helptext: cmds.HelpText{
88 Tagline: "Create a new keypair",
89 },
90 Options: []cmds.Option{
91 cmds.StringOption(keyStoreTypeOptionName, "t", "type of the key to create: rsa, ed25519").WithDefault(keyStoreAlgorithmDefault),
92 cmds.IntOption(keyStoreSizeOptionName, "s", "size of the key to generate"),
93 ke.OptionIPNSBase,
94 },
95 Arguments: []cmds.Argument{
96 cmds.StringArg("name", true, false, "name of key to create"),
97 },
98 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
99 api, err := cmdenv.GetApi(env, req)
100 if err != nil {
101 return err
102 }
103
104 typ, f := req.Options[keyStoreTypeOptionName].(string)
105 if !f {
106 return errors.New("please specify a key type with --type")
107 }
108
109 name := req.Arguments[0]
110 if name == "self" {
111 return errors.New("cannot create key with name 'self'")
112 }
113
114 opts := []options.KeyGenerateOption{options.Key.Type(typ)}
115
116 size, sizefound := req.Options[keyStoreSizeOptionName].(int)
117 if sizefound {
118 opts = append(opts, options.Key.Size(size))
119 }
120 keyEnc, err := ke.KeyEncoderFromString(req.Options[ke.OptionIPNSBase.Name()].(string))
121 if err != nil {
122 return err
123 }
124
125 key, err := api.Key().Generate(req.Context, name, opts...)
126 if err != nil {
127 return err
128 }
129
130 return cmds.EmitOnce(res, &KeyOutput{
131 Name: name,
132 Id: keyEnc.FormatID(key.ID()),
133 })
134 },
135 Encoders: cmds.EncoderMap{
136 cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, ko *KeyOutput) error {
137 _, err := w.Write([]byte(ko.Id + "\n"))
138 return err
139 }),
140 },
141 Type: KeyOutput{},
142 }
143
144 const (
145 // Key format options used both for importing and exporting.
146 keyFormatOptionName = "format"
147 keyFormatPemCleartextOption = "pem-pkcs8-cleartext"
148 keyFormatLibp2pCleartextOption = "libp2p-protobuf-cleartext"
149 keyAllowAnyTypeOptionName = "allow-any-key-type"
150 )
151
152 var keyExportCmd = &cmds.Command{
153 Helptext: cmds.HelpText{
154 Tagline: "Export a keypair",
155 ShortDescription: `
156 Exports a named libp2p key to disk.
157
158 By default, the output will be stored at './<key-name>.key', but an alternate
159 path can be specified with '--output=<path>' or '-o=<path>'.
160
161 It is possible to export a private key to interoperable PEM PKCS8 format by explicitly
162 passing '--format=pem-pkcs8-cleartext'. The resulting PEM file can then be consumed
163 elsewhere. For example, using openssl to get a PEM with public key:
164
165 $ ipfs key export testkey --format=pem-pkcs8-cleartext -o privkey.pem
166 $ openssl pkey -in privkey.pem -pubout > pubkey.pem
167 `,
168 },
169 Arguments: []cmds.Argument{
170 cmds.StringArg("name", true, false, "name of key to export").EnableStdin(),
171 },
172 Options: []cmds.Option{
173 cmds.StringOption(outputOptionName, "o", "The path where the output should be stored."),
174 cmds.StringOption(keyFormatOptionName, "f", "The format of the exported private key, libp2p-protobuf-cleartext or pem-pkcs8-cleartext.").WithDefault(keyFormatLibp2pCleartextOption),
175 },
176 NoRemote: true,
177 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
178 name := req.Arguments[0]
179
180 if name == "self" {
181 return fmt.Errorf("cannot export key with name 'self'")
182 }
183
184 cfgRoot, err := cmdenv.GetConfigRoot(env)
185 if err != nil {
186 return err
187 }
188
189 // Check repo version, and error out if not matching
190 ver, err := migrations.RepoVersion(cfgRoot)
191 if err != nil {
192 return err
193 }
194 if ver != fsrepo.RepoVersion {
195 return fmt.Errorf("key export expects repo version (%d) but found (%d)", fsrepo.RepoVersion, ver)
196 }
197
198 // Export is read-only: safe to read it without acquiring repo lock
199 // (this makes export work when ipfs daemon is already running)
200 ksp := filepath.Join(cfgRoot, "keystore")
201 ks, err := keystore.NewFSKeystore(ksp)
202 if err != nil {
203 return err
204 }
205
206 sk, err := ks.Get(name)
207 if err != nil {
208 return fmt.Errorf("key with name '%s' doesn't exist", name)
209 }
210
211 exportFormat, _ := req.Options[keyFormatOptionName].(string)
212 var formattedKey []byte
213 switch exportFormat {
214 case keyFormatPemCleartextOption:
215 stdKey, err := crypto.PrivKeyToStdKey(sk)
216 if err != nil {
217 return fmt.Errorf("converting libp2p private key to std Go key: %w", err)
218 }
219 // For some reason the ed25519.PrivateKey does not use pointer
220 // receivers, so we need to convert it for MarshalPKCS8PrivateKey.
221 // (We should probably change this upstream in PrivKeyToStdKey).
222 if ed25519KeyPointer, ok := stdKey.(*ed25519.PrivateKey); ok {
223 stdKey = *ed25519KeyPointer
224 }
225 // This function supports a restricted list of public key algorithms,
226 // but we generate and use only the RSA and ed25519 types that are on that list.
227 formattedKey, err = x509.MarshalPKCS8PrivateKey(stdKey)
228 if err != nil {
229 return fmt.Errorf("marshalling key to PKCS8 format: %w", err)
230 }
231
232 case keyFormatLibp2pCleartextOption:
233 formattedKey, err = crypto.MarshalPrivateKey(sk)
234 if err != nil {
235 return err
236 }
237 default:
238 return fmt.Errorf("unrecognized export format: %s", exportFormat)
239 }
240
241 return res.Emit(bytes.NewReader(formattedKey))
242 },
243 PostRun: cmds.PostRunMap{
244 cmds.CLI: func(res cmds.Response, re cmds.ResponseEmitter) error {
245 req := res.Request()
246
247 v, err := res.Next()
248 if err != nil {
249 return err
250 }
251
252 outReader, ok := v.(io.Reader)
253 if !ok {
254 return e.New(e.TypeErr(outReader, v))
255 }
256
257 outPath, _ := req.Options[outputOptionName].(string)
258 exportFormat, _ := req.Options[keyFormatOptionName].(string)
259 if outPath == "" {
260 var fileExtension string
261 switch exportFormat {
262 case keyFormatPemCleartextOption:
263 fileExtension = "pem"
264 case keyFormatLibp2pCleartextOption:
265 fileExtension = "key"
266 }
267 trimmed := strings.TrimRight(fmt.Sprintf("%s.%s", req.Arguments[0], fileExtension), "/")
268 _, outPath = filepath.Split(trimmed)
269 outPath = filepath.Clean(outPath)
270 }
271
272 // create file with owner-only permissions to protect private key material
273 file, err := os.OpenFile(outPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o600)
274 if err != nil {
275 return err
276 }
277 defer file.Close()
278
279 switch exportFormat {
280 case keyFormatPemCleartextOption:
281 privKeyBytes, err := io.ReadAll(outReader)
282 if err != nil {
283 return err
284 }
285
286 err = pem.Encode(file, &pem.Block{
287 Type: "PRIVATE KEY",
288 Bytes: privKeyBytes,
289 })
290 if err != nil {
291 return fmt.Errorf("encoding PEM block: %w", err)
292 }
293
294 case keyFormatLibp2pCleartextOption:
295 _, err = io.Copy(file, outReader)
296 if err != nil {
297 return err
298 }
299 }
300
301 return nil
302 },
303 },
304 }
305
306 var keyImportCmd = &cmds.Command{
307 Helptext: cmds.HelpText{
308 Tagline: "Import a key and prints imported key id",
309 ShortDescription: `
310 Imports a key and stores it under the provided name.
311
312 By default, the key is assumed to be in 'libp2p-protobuf-cleartext' format,
313 however it is possible to import private keys wrapped in interoperable PEM PKCS8
314 by passing '--format=pem-pkcs8-cleartext'.
315
316 The PEM format allows for key generation outside of the IPFS node:
317
318 $ openssl genpkey -algorithm ED25519 > ed25519.pem
319 $ ipfs key import test-openssl -f pem-pkcs8-cleartext ed25519.pem
320 `,
321 },
322 Options: []cmds.Option{
323 ke.OptionIPNSBase,
324 cmds.StringOption(keyFormatOptionName, "f", "The format of the private key to import, libp2p-protobuf-cleartext or pem-pkcs8-cleartext.").WithDefault(keyFormatLibp2pCleartextOption),
325 cmds.BoolOption(keyAllowAnyTypeOptionName, "Allow importing any key type.").WithDefault(false),
326 },
327 Arguments: []cmds.Argument{
328 cmds.StringArg("name", true, false, "name to associate with key in keychain"),
329 cmds.FileArg("key", true, false, "key provided by generate or export"),
330 },
331 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
332 name := req.Arguments[0]
333
334 if name == "self" {
335 return fmt.Errorf("cannot import key with name 'self'")
336 }
337
338 keyEnc, err := ke.KeyEncoderFromString(req.Options[ke.OptionIPNSBase.Name()].(string))
339 if err != nil {
340 return err
341 }
342
343 file, err := cmdenv.GetFileArg(req.Files.Entries())
344 if err != nil {
345 return err
346 }
347 defer file.Close()
348
349 data, err := io.ReadAll(file)
350 if err != nil {
351 return err
352 }
353
354 importFormat, _ := req.Options[keyFormatOptionName].(string)
355 var sk crypto.PrivKey
356 switch importFormat {
357 case keyFormatPemCleartextOption:
358 pemBlock, rest := pem.Decode(data)
359 if pemBlock == nil {
360 return fmt.Errorf("PEM block not found in input data:\n%s", rest)
361 }
362
363 if pemBlock.Type != "PRIVATE KEY" {
364 return fmt.Errorf("expected PRIVATE KEY type in PEM block but got: %s", pemBlock.Type)
365 }
366
367 stdKey, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes)
368 if err != nil {
369 return fmt.Errorf("parsing PKCS8 format: %w", err)
370 }
371
372 // In case ed25519.PrivateKey is returned we need the pointer for
373 // conversion to libp2p (see export command for more details).
374 if ed25519KeyPointer, ok := stdKey.(ed25519.PrivateKey); ok {
375 stdKey = &ed25519KeyPointer
376 }
377
378 sk, _, err = crypto.KeyPairFromStdKey(stdKey)
379 if err != nil {
380 return fmt.Errorf("converting std Go key to libp2p key: %w", err)
381 }
382 case keyFormatLibp2pCleartextOption:
383 sk, err = crypto.UnmarshalPrivateKey(data)
384 if err != nil {
385 // check if data is PEM, if so, provide user with hint
386 pemBlock, _ := pem.Decode(data)
387 if pemBlock != nil {
388 return fmt.Errorf("unexpected PEM block for format=%s: try again with format=%s", keyFormatLibp2pCleartextOption, keyFormatPemCleartextOption)
389 }
390 return fmt.Errorf("unable to unmarshall format=%s: %w", keyFormatLibp2pCleartextOption, err)
391 }
392
393 default:
394 return fmt.Errorf("unrecognized import format: %s", importFormat)
395 }
396
397 // We only allow importing keys of the same type we generate (see list in
398 // https://github.com/ipfs/interface-go-ipfs-core/blob/1c3d8fc/options/key.go#L58-L60),
399 // unless explicitly stated by the user.
400 allowAnyKeyType, _ := req.Options[keyAllowAnyTypeOptionName].(bool)
401 if !allowAnyKeyType {
402 switch t := sk.(type) {
403 case *crypto.RsaPrivateKey, *crypto.Ed25519PrivateKey:
404 default:
405 return fmt.Errorf("key type %T is not allowed to be imported, only RSA or Ed25519;"+
406 " use flag --%s if you are sure of what you're doing",
407 t, keyAllowAnyTypeOptionName)
408 }
409 }
410
411 cfgRoot, err := cmdenv.GetConfigRoot(env)
412 if err != nil {
413 return err
414 }
415
416 r, err := fsrepo.Open(cfgRoot)
417 if err != nil {
418 return err
419 }
420 defer r.Close()
421
422 _, err = r.Keystore().Get(name)
423 if err == nil {
424 return fmt.Errorf("key with name '%s' already exists", name)
425 }
426
427 err = r.Keystore().Put(name, sk)
428 if err != nil {
429 return err
430 }
431
432 pid, err := peer.IDFromPrivateKey(sk)
433 if err != nil {
434 return err
435 }
436
437 return cmds.EmitOnce(res, &KeyOutput{
438 Name: name,
439 Id: keyEnc.FormatID(pid),
440 })
441 },
442 Encoders: cmds.EncoderMap{
443 cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, ko *KeyOutput) error {
444 _, err := w.Write([]byte(ko.Id + "\n"))
445 return err
446 }),
447 },
448 Type: KeyOutput{},
449 }
450
451 var keyListCmd = &cmds.Command{
452 Helptext: cmds.HelpText{
453 Tagline: "List all local keypairs.",
454 },
455 Options: []cmds.Option{
456 cmds.BoolOption("l", "Show extra information about keys."),
457 ke.OptionIPNSBase,
458 },
459 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
460 keyEnc, err := ke.KeyEncoderFromString(req.Options[ke.OptionIPNSBase.Name()].(string))
461 if err != nil {
462 return fmt.Errorf("cannot get key encoder: %w", err)
463 }
464
465 api, err := cmdenv.GetApi(env, req)
466 if err != nil {
467 return err
468 }
469
470 keys, err := api.Key().List(req.Context)
471 if err != nil {
472 return fmt.Errorf("listing keys failed: %w", err)
473 }
474
475 list := make([]KeyOutput, 0, len(keys))
476
477 for _, key := range keys {
478 list = append(list, KeyOutput{
479 Name: key.Name(),
480 Id: keyEnc.FormatID(key.ID()),
481 })
482 }
483
484 return cmds.EmitOnce(res, &KeyOutputList{list})
485 },
486 Encoders: cmds.EncoderMap{
487 cmds.Text: keyOutputListEncoders(),
488 },
489 Type: KeyOutputList{},
490 }
491
492 var keyListDeprecatedCmd = &cmds.Command{
493 Status: cmds.Deprecated,
494 Helptext: cmds.HelpText{
495 Tagline: "Deprecated: use 'ipfs key ls' instead.",
496 },
497 Options: keyListCmd.Options,
498 Run: keyListCmd.Run,
499 Encoders: keyListCmd.Encoders,
500 Type: keyListCmd.Type,
501 }
502
503 const (
504 keyStoreForceOptionName = "force"
505 )
506
507 var keyRenameCmd = &cmds.Command{
508 Helptext: cmds.HelpText{
509 Tagline: "Rename a keypair.",
510 },
511 Arguments: []cmds.Argument{
512 cmds.StringArg("name", true, false, "name of key to rename"),
513 cmds.StringArg("newName", true, false, "new name of the key"),
514 },
515 Options: []cmds.Option{
516 cmds.BoolOption(keyStoreForceOptionName, "f", "Allow to overwrite an existing key."),
517 ke.OptionIPNSBase,
518 },
519 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
520 api, err := cmdenv.GetApi(env, req)
521 if err != nil {
522 return err
523 }
524 keyEnc, err := ke.KeyEncoderFromString(req.Options[ke.OptionIPNSBase.Name()].(string))
525 if err != nil {
526 return err
527 }
528
529 name := req.Arguments[0]
530 newName := req.Arguments[1]
531 force, _ := req.Options[keyStoreForceOptionName].(bool)
532
533 key, overwritten, err := api.Key().Rename(req.Context, name, newName, options.Key.Force(force))
534 if err != nil {
535 return err
536 }
537
538 return cmds.EmitOnce(res, &KeyRenameOutput{
539 Was: name,
540 Now: newName,
541 Id: keyEnc.FormatID(key.ID()),
542 Overwrite: overwritten,
543 })
544 },
545 Encoders: cmds.EncoderMap{
546 cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, kro *KeyRenameOutput) error {
547 if kro.Overwrite {
548 fmt.Fprintf(w, "Key %s renamed to %s with overwriting\n", kro.Id, cmdenv.EscNonPrint(kro.Now))
549 } else {
550 fmt.Fprintf(w, "Key %s renamed to %s\n", kro.Id, cmdenv.EscNonPrint(kro.Now))
551 }
552 return nil
553 }),
554 },
555 Type: KeyRenameOutput{},
556 }
557
558 var keyRmCmd = &cmds.Command{
559 Helptext: cmds.HelpText{
560 Tagline: "Remove a keypair.",
561 },
562 Arguments: []cmds.Argument{
563 cmds.StringArg("name", true, true, "names of keys to remove").EnableStdin(),
564 },
565 Options: []cmds.Option{
566 cmds.BoolOption("l", "Show extra information about keys."),
567 ke.OptionIPNSBase,
568 },
569 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
570 api, err := cmdenv.GetApi(env, req)
571 if err != nil {
572 return err
573 }
574 keyEnc, err := ke.KeyEncoderFromString(req.Options[ke.OptionIPNSBase.Name()].(string))
575 if err != nil {
576 return err
577 }
578
579 names := req.Arguments
580
581 list := make([]KeyOutput, 0, len(names))
582 for _, name := range names {
583 key, err := api.Key().Remove(req.Context, name)
584 if err != nil {
585 return err
586 }
587
588 list = append(list, KeyOutput{
589 Name: name,
590 Id: keyEnc.FormatID(key.ID()),
591 })
592 }
593
594 return cmds.EmitOnce(res, &KeyOutputList{list})
595 },
596 Encoders: cmds.EncoderMap{
597 cmds.Text: keyOutputListEncoders(),
598 },
599 Type: KeyOutputList{},
600 }
601
602 var keyRotateCmd = &cmds.Command{
603 Helptext: cmds.HelpText{
604 Tagline: "Rotates the IPFS identity.",
605 ShortDescription: `
606 Generates a new ipfs identity and saves it to the ipfs config file.
607 Your existing identity key will be backed up in the Keystore.
608 The daemon must not be running when calling this command.
609
610 ipfs uses a repository in the local file system. By default, the repo is
611 located at ~/.ipfs. To change the repo location, set the $IPFS_PATH
612 environment variable:
613
614 export IPFS_PATH=/path/to/ipfsrepo
615 `,
616 },
617 Arguments: []cmds.Argument{},
618 Options: []cmds.Option{
619 cmds.StringOption(oldKeyOptionName, "o", "Keystore name to use for backing up your existing identity"),
620 cmds.StringOption(keyStoreTypeOptionName, "t", "type of the key to create: rsa, ed25519").WithDefault(keyStoreAlgorithmDefault),
621 cmds.IntOption(keyStoreSizeOptionName, "s", "size of the key to generate"),
622 },
623 NoRemote: true,
624 PreRun: DaemonNotRunning,
625 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
626 cctx := env.(*oldcmds.Context)
627 nBitsForKeypair, nBitsGiven := req.Options[keyStoreSizeOptionName].(int)
628 algorithm, _ := req.Options[keyStoreTypeOptionName].(string)
629 oldKey, ok := req.Options[oldKeyOptionName].(string)
630 if !ok {
631 return fmt.Errorf("keystore name for backing up old key must be provided")
632 }
633 if oldKey == "self" {
634 return fmt.Errorf("keystore name for back up cannot be named 'self'")
635 }
636 return doRotate(os.Stdout, cctx.ConfigRoot, oldKey, algorithm, nBitsForKeypair, nBitsGiven)
637 },
638 }
639
640 func doRotate(out io.Writer, repoRoot string, oldKey string, algorithm string, nBitsForKeypair int, nBitsGiven bool) error {
641 // Open repo
642 repo, err := fsrepo.Open(repoRoot)
643 if err != nil {
644 return fmt.Errorf("opening repo (%v)", err)
645 }
646 defer repo.Close()
647
648 // Read config file from repo
649 cfg, err := repo.Config()
650 if err != nil {
651 return fmt.Errorf("reading config from repo (%v)", err)
652 }
653
654 // Generate new identity
655 var identity config.Identity
656 if nBitsGiven {
657 identity, err = config.CreateIdentity(out, []options.KeyGenerateOption{
658 options.Key.Size(nBitsForKeypair),
659 options.Key.Type(algorithm),
660 })
661 } else {
662 identity, err = config.CreateIdentity(out, []options.KeyGenerateOption{
663 options.Key.Type(algorithm),
664 })
665 }
666 if err != nil {
667 return fmt.Errorf("creating identity (%v)", err)
668 }
669
670 // Save old identity to keystore
671 oldPrivKey, err := cfg.Identity.DecodePrivateKey("")
672 if err != nil {
673 return fmt.Errorf("decoding old private key (%v)", err)
674 }
675 keystore := repo.Keystore()
676 if err := keystore.Put(oldKey, oldPrivKey); err != nil {
677 return fmt.Errorf("saving old key in keystore (%v)", err)
678 }
679
680 // Update identity
681 cfg.Identity = identity
682
683 // Write config file to repo
684 if err = repo.SetConfig(cfg); err != nil {
685 return fmt.Errorf("saving new key to config (%v)", err)
686 }
687 return nil
688 }
689
690 func keyOutputListEncoders() cmds.EncoderFunc {
691 return cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, list *KeyOutputList) error {
692 withID, _ := req.Options["l"].(bool)
693
694 tw := tabwriter.NewWriter(w, 1, 2, 1, ' ', 0)
695 for _, s := range list.Keys {
696 if withID {
697 fmt.Fprintf(tw, "%s\t%s\t\n", s.Id, cmdenv.EscNonPrint(s.Name))
698 } else {
699 fmt.Fprintf(tw, "%s\n", cmdenv.EscNonPrint(s.Name))
700 }
701 }
702 tw.Flush()
703 return nil
704 })
705 }
706
707 type KeySignOutput struct {
708 Key KeyOutput
709 Signature string
710 }
711
712 var keySignCmd = &cmds.Command{
713 Status: cmds.Experimental,
714 Helptext: cmds.HelpText{
715 Tagline: "Generates a signature for the given data with a specified key. Useful for proving the key ownership.",
716 LongDescription: `
717 Sign arbitrary bytes, such as to prove ownership of a Peer ID or an IPNS Name.
718 To avoid signature reuse, the signed payload is always prefixed with
719 "libp2p-key signed message:".
720 `,
721 },
722 Options: []cmds.Option{
723 cmds.StringOption("key", "k", "The name of the key to use for signing."),
724 ke.OptionIPNSBase,
725 },
726 Arguments: []cmds.Argument{
727 cmds.FileArg("data", true, false, "The data to sign.").EnableStdin(),
728 },
729 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
730 api, err := cmdenv.GetApi(env, req)
731 if err != nil {
732 return err
733 }
734 keyEnc, err := ke.KeyEncoderFromString(req.Options[ke.OptionIPNSBase.Name()].(string))
735 if err != nil {
736 return err
737 }
738
739 name, _ := req.Options["key"].(string)
740
741 file, err := cmdenv.GetFileArg(req.Files.Entries())
742 if err != nil {
743 return err
744 }
745 defer file.Close()
746
747 data, err := io.ReadAll(file)
748 if err != nil {
749 return err
750 }
751
752 key, signature, err := api.Key().Sign(req.Context, name, data)
753 if err != nil {
754 return err
755 }
756
757 encodedSignature, err := mbase.Encode(mbase.Base64url, signature)
758 if err != nil {
759 return err
760 }
761
762 return res.Emit(&KeySignOutput{
763 Key: KeyOutput{
764 Name: key.Name(),
765 Id: keyEnc.FormatID(key.ID()),
766 },
767 Signature: encodedSignature,
768 })
769 },
770 Type: KeySignOutput{},
771 }
772
773 type KeyVerifyOutput struct {
774 Key KeyOutput
775 SignatureValid bool
776 }
777
778 var keyVerifyCmd = &cmds.Command{
779 Status: cmds.Experimental,
780 Helptext: cmds.HelpText{
781 Tagline: "Verify that the given data and signature match.",
782 LongDescription: `
783 Verify if the given data and signatures match. To avoid the signature reuse,
784 the signed payload is always prefixed with "libp2p-key signed message:".
785 `,
786 },
787 Options: []cmds.Option{
788 cmds.StringOption("key", "k", "The name of the key to use for verifying."),
789 cmds.StringOption("signature", "s", "Multibase-encoded signature to verify."),
790 ke.OptionIPNSBase,
791 },
792 Arguments: []cmds.Argument{
793 cmds.FileArg("data", true, false, "The data to verify against the given signature.").EnableStdin(),
794 },
795 Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
796 api, err := cmdenv.GetApi(env, req)
797 if err != nil {
798 return err
799 }
800 keyEnc, err := ke.KeyEncoderFromString(req.Options[ke.OptionIPNSBase.Name()].(string))
801 if err != nil {
802 return err
803 }
804
805 name, _ := req.Options["key"].(string)
806 encodedSignature, _ := req.Options["signature"].(string)
807
808 _, signature, err := mbase.Decode(encodedSignature)
809 if err != nil {
810 return err
811 }
812
813 file, err := cmdenv.GetFileArg(req.Files.Entries())
814 if err != nil {
815 return err
816 }
817 defer file.Close()
818
819 data, err := io.ReadAll(file)
820 if err != nil {
821 return err
822 }
823
824 key, valid, err := api.Key().Verify(req.Context, name, signature, data)
825 if err != nil {
826 return err
827 }
828
829 return res.Emit(&KeyVerifyOutput{
830 Key: KeyOutput{
831 Name: key.Name(),
832 Id: keyEnc.FormatID(key.ID()),
833 },
834 SignatureValid: valid,
835 })
836 },
837 Type: KeyVerifyOutput{},
838 }
839
840 // DaemonNotRunning checks to see if the ipfs repo is locked, indicating that
841 // the daemon is running, and returns and error if the daemon is running.
842 func DaemonNotRunning(req *cmds.Request, env cmds.Environment) error {
843 cctx := env.(*oldcmds.Context)
844 daemonLocked, err := fsrepo.LockedByOtherProcess(cctx.ConfigRoot)
845 if err != nil {
846 return err
847 }
848
849 log.Info("checking if daemon is running...")
850 if daemonLocked {
851 log.Debug("ipfs daemon is running")
852 e := "ipfs daemon is running. please stop it to run this command"
853 return cmds.ClientError(e)
854 }
855
856 return nil
857 }