Skip to content

Commit

Permalink
refactor: add command to coreiface, consistent with others
Browse files Browse the repository at this point in the history
  • Loading branch information
hacdias committed Nov 29, 2023
1 parent 3ef2748 commit 051bca8
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 76 deletions.
52 changes: 52 additions & 0 deletions client/rpc/key.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package rpc

import (
"bytes"
"context"
"errors"

Expand All @@ -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
Expand Down Expand Up @@ -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
}
117 changes: 41 additions & 76 deletions core/commands/keystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,7 @@ func keyOutputListEncoders() cmds.EncoderFunc {
}

type KeySignOutput struct {
Key string // CIDv1-Libp2p-Key
Key KeyOutput
Signature string
}

Expand All @@ -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
}

Expand All @@ -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 {
Expand Down
77 changes: 77 additions & 0 deletions core/coreapi/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
4 changes: 4 additions & 0 deletions core/coreiface/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

0 comments on commit 051bca8

Please sign in to comment.