From 959ed0e9b0e8f5b4cca470a42582e8f4aee2f4bf Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Wed, 29 Nov 2023 11:51:07 +0100 Subject: [PATCH] refactor: add command to coreiface, consistent with others --- client/rpc/key.go | 52 +++++++++++++++++ core/commands/keystore.go | 117 +++++++++++++------------------------- core/coreapi/key.go | 77 +++++++++++++++++++++++++ core/coreiface/key.go | 4 ++ 4 files changed, 174 insertions(+), 76 deletions(-) diff --git a/client/rpc/key.go b/client/rpc/key.go index daddffb2399f..710d9fb06d24 100644 --- a/client/rpc/key.go +++ b/client/rpc/key.go @@ -1,6 +1,7 @@ package rpc import ( + "bytes" "context" "errors" @@ -9,6 +10,7 @@ import ( iface "github.com/ipfs/kubo/core/coreiface" caopts "github.com/ipfs/kubo/core/coreiface/options" "github.com/libp2p/go-libp2p/core/peer" + "github.com/multiformats/go-multibase" ) type KeyAPI HttpApi @@ -141,3 +143,53 @@ func (api *KeyAPI) Remove(ctx context.Context, name string) (iface.Key, error) { func (api *KeyAPI) core() *HttpApi { return (*HttpApi)(api) } + +func (api *KeyAPI) Sign(ctx context.Context, name string, data []byte) (iface.Key, []byte, error) { + var out struct { + Key keyOutput + Signature string + } + + err := api.core().Request("key/sign"). + Option("key", name). + FileBody(bytes.NewReader(data)). + Exec(ctx, &out) + if err != nil { + return nil, nil, err + } + + key, err := newKey(out.Key.Name, out.Key.Id) + if err != nil { + return nil, nil, err + } + + _, signature, err := multibase.Decode(out.Signature) + if err != nil { + return nil, nil, err + } + + return key, signature, nil +} + +func (api *KeyAPI) Verify(ctx context.Context, keyOrName string, signature, data []byte) (iface.Key, bool, error) { + var out struct { + Key keyOutput + SignatureValid bool + } + + err := api.core().Request("key/verify"). + Option("key", keyOrName). + Option("signature", toMultibase(signature)). + FileBody(bytes.NewReader(data)). + Exec(ctx, &out) + if err != nil { + return nil, false, err + } + + key, err := newKey(out.Key.Name, out.Key.Id) + if err != nil { + return nil, false, err + } + + return key, out.SignatureValid, nil +} diff --git a/core/commands/keystore.go b/core/commands/keystore.go index 2b7ef22053fa..ce18a6c646eb 100644 --- a/core/commands/keystore.go +++ b/core/commands/keystore.go @@ -692,7 +692,7 @@ func keyOutputListEncoders() cmds.EncoderFunc { } type KeySignOutput struct { - Key string // CIDv1-Libp2p-Key + Key KeyOutput Signature string } @@ -702,48 +702,57 @@ var keySignCmd = &cmds.Command{ }, Options: []cmds.Option{ cmds.StringOption("key", "k", "The name of the key to use for signing."), + ke.OptionIPNSBase, }, Arguments: []cmds.Argument{ cmds.FileArg("data", true, false, "The data to sign.").EnableStdin(), }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { - sk, err := getKeyForSignVerify(req, env) + api, err := cmdenv.GetApi(env, req) + if err != nil { + return err + } + keyEnc, err := ke.KeyEncoderFromString(req.Options[ke.OptionIPNSBase.Name()].(string)) if err != nil { return err } - pid, err := peer.IDFromPrivateKey(sk) + name, _ := req.Options["key"].(string) + + file, err := cmdenv.GetFileArg(req.Files.Entries()) if err != nil { return err } + defer file.Close() - // Read given data. - data, err := readDataForSignVerify(req) + data, err := io.ReadAll(file) if err != nil { return err } - // Sign it! - sig, err := sk.Sign(data) + key, signature, err := api.Key().Sign(req.Context, name, data) if err != nil { return err } - encoder, err := mbase.EncoderByName("base64url") + encodedSignature, err := mbase.Encode(mbase.Base64url, signature) if err != nil { return err } return res.Emit(&KeySignOutput{ - Key: peer.ToCid(pid).String(), - Signature: encoder.Encode(sig), + Key: KeyOutput{ + Name: key.Name(), + Id: keyEnc.FormatID(key.ID()), + }, + Signature: encodedSignature, }) }, Type: KeySignOutput{}, } type KeyVerifyOutput struct { - Key string // CIDv1-Libp2p-Key + Key KeyOutput SignatureValid bool } @@ -754,100 +763,56 @@ var keyVerifyCmd = &cmds.Command{ Options: []cmds.Option{ cmds.StringOption("key", "k", "The name of the key to use for signing."), cmds.StringOption("signature", "s", "Multibase-encoded signature to verify."), + ke.OptionIPNSBase, }, Arguments: []cmds.Argument{ cmds.FileArg("data", true, false, "The data to verify against the given signature.").EnableStdin(), }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error { - var ( - pk crypto.PubKey - pid peer.ID - ) + api, err := cmdenv.GetApi(env, req) + if err != nil { + return err + } + keyEnc, err := ke.KeyEncoderFromString(req.Options[ke.OptionIPNSBase.Name()].(string)) + if err != nil { + return err + } name, _ := req.Options["key"].(string) - if sk, err := getKeyForSignVerify(req, env); err == nil { - pk = sk.GetPublic() - pid, err = peer.IDFromPublicKey(pk) - if err != nil { - return err - } - } else if pid, err = peer.Decode(name); err == nil { - pk, err = pid.ExtractPublicKey() - if err != nil { - return err - } - } else { + encodedSignature, _ := req.Options["signature"].(string) + + _, signature, err := mbase.Decode(encodedSignature) + if err != nil { return err } - // Read signature - signatureString, _ := req.Options["signature"].(string) - _, signature, err := mbase.Decode(signatureString) + file, err := cmdenv.GetFileArg(req.Files.Entries()) if err != nil { return err } + defer file.Close() - // Read given data. - data, err := readDataForSignVerify(req) + data, err := io.ReadAll(file) if err != nil { return err } - // Verify - valid, err := pk.Verify(data, signature) + key, valid, err := api.Key().Verify(req.Context, name, signature, data) if err != nil { return err } return res.Emit(&KeyVerifyOutput{ - Key: peer.ToCid(pid).String(), + Key: KeyOutput{ + Name: key.Name(), + Id: keyEnc.FormatID(key.ID()), + }, SignatureValid: valid, }) }, Type: KeyVerifyOutput{}, } -func getKeyForSignVerify(req *cmds.Request, env cmds.Environment) (crypto.PrivKey, error) { - name, _ := req.Options["key"].(string) - if name == "" || name == "self" { - node, err := cmdenv.GetNode(env) - if err != nil { - return nil, err - } - - return node.PrivateKey, nil - } else { - cfgRoot, err := cmdenv.GetConfigRoot(env) - if err != nil { - return nil, err - } - - // Signing is read-only: safe to read key without acquiring repo lock - // (this makes sign work when ipfs daemon is already running) - ksp := filepath.Join(cfgRoot, "keystore") - ks, err := keystore.NewFSKeystore(ksp) - if err != nil { - return nil, err - } - - return ks.Get(name) - } -} - -func readDataForSignVerify(req *cmds.Request) ([]byte, error) { - file, err := cmdenv.GetFileArg(req.Files.Entries()) - if err != nil { - return nil, err - } - defer file.Close() - - data, err := io.ReadAll(file) - if err != nil { - return nil, err - } - return append([]byte("libp2p-key signed message:"), data...), nil -} - // DaemonNotRunning checks to see if the ipfs repo is locked, indicating that // the daemon is running, and returns and error if the daemon is running. func DaemonNotRunning(req *cmds.Request, env cmds.Environment) error { diff --git a/core/coreapi/key.go b/core/coreapi/key.go index e78868067c7d..7d8833f07af9 100644 --- a/core/coreapi/key.go +++ b/core/coreapi/key.go @@ -262,3 +262,80 @@ func (api *KeyAPI) Self(ctx context.Context) (coreiface.Key, error) { return newKey("self", api.identity) } + +func (api *KeyAPI) Sign(ctx context.Context, name string, data []byte) (coreiface.Key, []byte, error) { + var ( + sk crypto.PrivKey + err error + ) + if name == "" || name == "self" { + name = "self" + sk = api.privateKey + } else { + sk, err = api.repo.Keystore().Get(name) + } + if err != nil { + return nil, nil, err + } + + pid, err := peer.IDFromPrivateKey(sk) + if err != nil { + return nil, nil, err + } + + key, err := newKey(name, pid) + if err != nil { + return nil, nil, err + } + + data = append([]byte("libp2p-key signed message:"), data...) + + sig, err := sk.Sign(data) + if err != nil { + return nil, nil, err + } + + return key, sig, nil +} + +func (api *KeyAPI) Verify(ctx context.Context, keyOrName string, signature, data []byte) (coreiface.Key, bool, error) { + var ( + name string + pk crypto.PubKey + err error + ) + if keyOrName == "" || keyOrName == "self" { + name = "self" + pk = api.privateKey.GetPublic() + } else if sk, err := api.repo.Keystore().Get(keyOrName); err == nil { + name = keyOrName + pk = sk.GetPublic() + } else if pid, err := peer.Decode(keyOrName); err == nil { + name = "" + pk, err = pid.ExtractPublicKey() + if err != nil { + return nil, false, err + } + } else { + return nil, false, fmt.Errorf("'%q' is not a known key, or a valid peer id", keyOrName) + } + + pid, err := peer.IDFromPublicKey(pk) + if err != nil { + return nil, false, err + } + + key, err := newKey(name, pid) + if err != nil { + return nil, false, err + } + + data = append([]byte("libp2p-key signed message:"), data...) + + valid, err := pk.Verify(data, signature) + if err != nil { + return nil, false, err + } + + return key, valid, nil +} diff --git a/core/coreiface/key.go b/core/coreiface/key.go index 9d61cc95b339..0fd621bdeb11 100644 --- a/core/coreiface/key.go +++ b/core/coreiface/key.go @@ -40,4 +40,8 @@ type KeyAPI interface { // Remove removes keys from keystore. Returns ipns path of the removed key Remove(ctx context.Context, name string) (Key, error) + + Sign(ctx context.Context, name string, data []byte) (Key, []byte, error) + + Verify(ctx context.Context, keyOrName string, signature, data []byte) (Key, bool, error) }