@cryptotaxi247 / kubo / commits / 8ab2de5ff

feat: ipfs key sign|verify (#10235)

Henrique Dias committed Dec 4, 2023 at 09:51 UTC 8ab2de5ff05e1427a22be868b31efbcd58a6ee13
7 files changed +498 -322
client/rpc/key.go
+52
@@ -1,6 +1,7 @@
1 package rpc
2
3 import (
4 + "bytes"
5 "context"
6 "errors"
7
@@ -9,6 +10,7 @@ import (
10 iface "github.com/ipfs/kubo/core/coreiface"
11 caopts "github.com/ipfs/kubo/core/coreiface/options"
12 "github.com/libp2p/go-libp2p/core/peer"
13 + "github.com/multiformats/go-multibase"
14 )
15
16 type KeyAPI HttpApi
@@ -141,3 +143,53 @@ func (api *KeyAPI) Remove(ctx context.Context, name string) (iface.Key, error) {
143 func (api *KeyAPI) core() *HttpApi {
144 return (*HttpApi)(api)
145 }
146 +
147 +func (api *KeyAPI) Sign(ctx context.Context, name string, data []byte) (iface.Key, []byte, error) {
148 + var out struct {
149 + Key keyOutput
150 + Signature string
151 + }
152 +
153 + err := api.core().Request("key/sign").
154 + Option("key", name).
155 + FileBody(bytes.NewReader(data)).
156 + Exec(ctx, &out)
157 + if err != nil {
158 + return nil, nil, err
159 + }
160 +
161 + key, err := newKey(out.Key.Name, out.Key.Id)
162 + if err != nil {
163 + return nil, nil, err
164 + }
165 +
166 + _, signature, err := multibase.Decode(out.Signature)
167 + if err != nil {
168 + return nil, nil, err
169 + }
170 +
171 + return key, signature, nil
172 +}
173 +
174 +func (api *KeyAPI) Verify(ctx context.Context, keyOrName string, signature, data []byte) (iface.Key, bool, error) {
175 + var out struct {
176 + Key keyOutput
177 + SignatureValid bool
178 + }
179 +
180 + err := api.core().Request("key/verify").
181 + Option("key", keyOrName).
182 + Option("signature", toMultibase(signature)).
183 + FileBody(bytes.NewReader(data)).
184 + Exec(ctx, &out)
185 + if err != nil {
186 + return nil, false, err
187 + }
188 +
189 + key, err := newKey(out.Key.Name, out.Key.Id)
190 + if err != nil {
191 + return nil, false, err
192 + }
193 +
194 + return key, out.SignatureValid, nil
195 +}
core/commands/commands_test.go
+2
@@ -164,6 +164,8 @@ func TestCommands(t *testing.T) {
164 "/key/rename",
165 "/key/rm",
166 "/key/rotate",
167 + "/key/sign",
168 + "/key/verify",
169 "/log",
170 "/log/level",
171 "/log/ls",
core/commands/keystore.go
+136
@@ -24,6 +24,7 @@ import (
24 migrations "github.com/ipfs/kubo/repo/fsrepo/migrations"
25 "github.com/libp2p/go-libp2p/core/crypto"
26 peer "github.com/libp2p/go-libp2p/core/peer"
27 + mbase "github.com/multiformats/go-multibase"
28 )
29
30 var KeyCmd = &cmds.Command{
@@ -51,6 +52,8 @@ publish'.
52 "rename": keyRenameCmd,
53 "rm": keyRmCmd,
54 "rotate": keyRotateCmd,
55 + "sign": keySignCmd,
56 + "verify": keyVerifyCmd,
57 },
58 }
59
@@ -688,6 +691,139 @@ func keyOutputListEncoders() cmds.EncoderFunc {
691 })
692 }
693
694 +type KeySignOutput struct {
695 + Key KeyOutput
696 + Signature string
697 +}
698 +
699 +var keySignCmd = &cmds.Command{
700 + Status: cmds.Experimental,
701 + Helptext: cmds.HelpText{
702 + Tagline: "Generates a signature for the given data with a specified key. Useful for proving the key ownership.",
703 + LongDescription: `
704 +Sign arbitrary bytes, such as to prove ownership of a Peer ID or an IPNS Name.
705 +To avoid signature reuse, the signed payload is always prefixed with
706 +"libp2p-key signed message:".
707 +`,
708 + },
709 + Options: []cmds.Option{
710 + cmds.StringOption("key", "k", "The name of the key to use for signing."),
711 + ke.OptionIPNSBase,
712 + },
713 + Arguments: []cmds.Argument{
714 + cmds.FileArg("data", true, false, "The data to sign.").EnableStdin(),
715 + },
716 + Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
717 + api, err := cmdenv.GetApi(env, req)
718 + if err != nil {
719 + return err
720 + }
721 + keyEnc, err := ke.KeyEncoderFromString(req.Options[ke.OptionIPNSBase.Name()].(string))
722 + if err != nil {
723 + return err
724 + }
725 +
726 + name, _ := req.Options["key"].(string)
727 +
728 + file, err := cmdenv.GetFileArg(req.Files.Entries())
729 + if err != nil {
730 + return err
731 + }
732 + defer file.Close()
733 +
734 + data, err := io.ReadAll(file)
735 + if err != nil {
736 + return err
737 + }
738 +
739 + key, signature, err := api.Key().Sign(req.Context, name, data)
740 + if err != nil {
741 + return err
742 + }
743 +
744 + encodedSignature, err := mbase.Encode(mbase.Base64url, signature)
745 + if err != nil {
746 + return err
747 + }
748 +
749 + return res.Emit(&KeySignOutput{
750 + Key: KeyOutput{
751 + Name: key.Name(),
752 + Id: keyEnc.FormatID(key.ID()),
753 + },
754 + Signature: encodedSignature,
755 + })
756 + },
757 + Type: KeySignOutput{},
758 +}
759 +
760 +type KeyVerifyOutput struct {
761 + Key KeyOutput
762 + SignatureValid bool
763 +}
764 +
765 +var keyVerifyCmd = &cmds.Command{
766 + Status: cmds.Experimental,
767 + Helptext: cmds.HelpText{
768 + Tagline: "Verify that the given data and signature match.",
769 + LongDescription: `
770 +Verify if the given data and signatures match. To avoid the signature reuse,
771 +the signed payload is always prefixed with "libp2p-key signed message:".
772 +`,
773 + },
774 + Options: []cmds.Option{
775 + cmds.StringOption("key", "k", "The name of the key to use for signing."),
776 + cmds.StringOption("signature", "s", "Multibase-encoded signature to verify."),
777 + ke.OptionIPNSBase,
778 + },
779 + Arguments: []cmds.Argument{
780 + cmds.FileArg("data", true, false, "The data to verify against the given signature.").EnableStdin(),
781 + },
782 + Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
783 + api, err := cmdenv.GetApi(env, req)
784 + if err != nil {
785 + return err
786 + }
787 + keyEnc, err := ke.KeyEncoderFromString(req.Options[ke.OptionIPNSBase.Name()].(string))
788 + if err != nil {
789 + return err
790 + }
791 +
792 + name, _ := req.Options["key"].(string)
793 + encodedSignature, _ := req.Options["signature"].(string)
794 +
795 + _, signature, err := mbase.Decode(encodedSignature)
796 + if err != nil {
797 + return err
798 + }
799 +
800 + file, err := cmdenv.GetFileArg(req.Files.Entries())
801 + if err != nil {
802 + return err
803 + }
804 + defer file.Close()
805 +
806 + data, err := io.ReadAll(file)
807 + if err != nil {
808 + return err
809 + }
810 +
811 + key, valid, err := api.Key().Verify(req.Context, name, signature, data)
812 + if err != nil {
813 + return err
814 + }
815 +
816 + return res.Emit(&KeyVerifyOutput{
817 + Key: KeyOutput{
818 + Name: key.Name(),
819 + Id: keyEnc.FormatID(key.ID()),
820 + },
821 + SignatureValid: valid,
822 + })
823 + },
824 + Type: KeyVerifyOutput{},
825 +}
826 +
827 // DaemonNotRunning checks to see if the ipfs repo is locked, indicating that
828 // the daemon is running, and returns and error if the daemon is running.
829 func DaemonNotRunning(req *cmds.Request, env cmds.Environment) error {
core/coreapi/key.go
+80
@@ -262,3 +262,83 @@ func (api *KeyAPI) Self(ctx context.Context) (coreiface.Key, error) {
262
263 return newKey("self", api.identity)
264 }
265 +
266 +const signedMessagePrefix = "libp2p-key signed message:"
267 +
268 +func (api *KeyAPI) Sign(ctx context.Context, name string, data []byte) (coreiface.Key, []byte, error) {
269 + var (
270 + sk crypto.PrivKey
271 + err error
272 + )
273 + if name == "" || name == "self" {
274 + name = "self"
275 + sk = api.privateKey
276 + } else {
277 + sk, err = api.repo.Keystore().Get(name)
278 + }
279 + if err != nil {
280 + return nil, nil, err
281 + }
282 +
283 + pid, err := peer.IDFromPrivateKey(sk)
284 + if err != nil {
285 + return nil, nil, err
286 + }
287 +
288 + key, err := newKey(name, pid)
289 + if err != nil {
290 + return nil, nil, err
291 + }
292 +
293 + data = append([]byte(signedMessagePrefix), data...)
294 +
295 + sig, err := sk.Sign(data)
296 + if err != nil {
297 + return nil, nil, err
298 + }
299 +
300 + return key, sig, nil
301 +}
302 +
303 +func (api *KeyAPI) Verify(ctx context.Context, keyOrName string, signature, data []byte) (coreiface.Key, bool, error) {
304 + var (
305 + name string
306 + pk crypto.PubKey
307 + err error
308 + )
309 + if keyOrName == "" || keyOrName == "self" {
310 + name = "self"
311 + pk = api.privateKey.GetPublic()
312 + } else if sk, err := api.repo.Keystore().Get(keyOrName); err == nil {
313 + name = keyOrName
314 + pk = sk.GetPublic()
315 + } else if ipnsName, err := ipns.NameFromString(keyOrName); err == nil {
316 + // This works for both IPNS names and Peer IDs.
317 + name = ""
318 + pk, err = ipnsName.Peer().ExtractPublicKey()
319 + if err != nil {
320 + return nil, false, err
321 + }
322 + } else {
323 + return nil, false, fmt.Errorf("'%q' is not a known key, an IPNS Name, or a valid PeerID", keyOrName)
324 + }
325 +
326 + pid, err := peer.IDFromPublicKey(pk)
327 + if err != nil {
328 + return nil, false, err
329 + }
330 +
331 + key, err := newKey(name, pid)
332 + if err != nil {
333 + return nil, false, err
334 + }
335 +
336 + data = append([]byte(signedMessagePrefix), data...)
337 +
338 + valid, err := pk.Verify(data, signature)
339 + if err != nil {
340 + return nil, false, err
341 + }
342 +
343 + return key, valid, nil
344 +}
core/coreiface/key.go
+8
@@ -40,4 +40,12 @@ type KeyAPI interface {
40
41 // Remove removes keys from keystore. Returns ipns path of the removed key
42 Remove(ctx context.Context, name string) (Key, error)
43 +
44 + // Sign signs the given data with the key named name. Returns the key used
45 + // for signing, the signature, and an error.
46 + Sign(ctx context.Context, name string, data []byte) (Key, []byte, error)
47 +
48 + // Verify verifies if the given data and signatures match. Returns the key used
49 + // for verification, whether signature and data match, and an error.
50 + Verify(ctx context.Context, keyOrName string, signature, data []byte) (Key, bool, error)
51 }
core/coreiface/tests/key.go
+211 -322
@@ -5,10 +5,14 @@ import (
5 "strings"
6 "testing"
7
8 + "github.com/ipfs/boxo/ipns"
9 "github.com/ipfs/go-cid"
10 iface "github.com/ipfs/kubo/core/coreiface"
11 opt "github.com/ipfs/kubo/core/coreiface/options"
12 + "github.com/libp2p/go-libp2p/core/peer"
13 mbase "github.com/multiformats/go-multibase"
14 + "github.com/stretchr/testify/assert"
15 + "github.com/stretchr/testify/require"
16 )
17
18 func (tp *TestSuite) TestKey(t *testing.T) {
@@ -34,151 +38,90 @@ func (tp *TestSuite) TestKey(t *testing.T) {
38 t.Run("TestRenameOverwrite", tp.TestRenameOverwrite)
39 t.Run("TestRenameSameNameNoForce", tp.TestRenameSameNameNoForce)
40 t.Run("TestRenameSameName", tp.TestRenameSameName)
37 - t.Run("TestRemove", tp.TestRemove)
41 + t.Run("TestSign", tp.TestSign)
42 + t.Run("TestVerify", tp.TestVerify)
43 }
44
45 func (tp *TestSuite) TestListSelf(t *testing.T) {
46 ctx, cancel := context.WithCancel(context.Background())
47 defer cancel()
48 +
49 api, err := tp.makeAPI(t, ctx)
44 - if err != nil {
45 - t.Fatal(err)
46 - return
47 - }
50 + require.NoError(t, err)
51
52 self, err := api.Key().Self(ctx)
50 - if err != nil {
51 - t.Fatal(err)
52 - }
53 + require.NoError(t, err)
54
55 keys, err := api.Key().List(ctx)
55 - if err != nil {
56 - t.Fatalf("failed to list keys: %s", err)
57 - return
58 - }
59 -
60 - if len(keys) != 1 {
61 - t.Fatalf("there should be 1 key (self), got %d", len(keys))
62 - return
63 - }
64 -
65 - if keys[0].Name() != "self" {
66 - t.Errorf("expected the key to be called 'self', got '%s'", keys[0].Name())
67 - }
68 -
69 - if keys[0].Path().String() != "/ipns/"+iface.FormatKeyID(self.ID()) {
70 - t.Errorf("expected the key to have path '/ipns/%s', got '%s'", iface.FormatKeyID(self.ID()), keys[0].Path().String())
71 - }
56 + require.NoError(t, err)
57 + require.Len(t, keys, 1)
58 + assert.Equal(t, "self", keys[0].Name())
59 + assert.Equal(t, "/ipns/"+iface.FormatKeyID(self.ID()), keys[0].Path().String())
60 }
61
62 func (tp *TestSuite) TestRenameSelf(t *testing.T) {
63 ctx, cancel := context.WithCancel(context.Background())
64 defer cancel()
65 +
66 api, err := tp.makeAPI(t, ctx)
78 - if err != nil {
79 - t.Fatal(err)
80 - return
81 - }
67 + require.NoError(t, err)
68
69 _, _, err = api.Key().Rename(ctx, "self", "foo")
84 - if err == nil {
85 - t.Error("expected error to not be nil")
86 - } else {
87 - if !strings.Contains(err.Error(), "cannot rename key with name 'self'") {
88 - t.Fatalf("expected error 'cannot rename key with name 'self'', got '%s'", err.Error())
89 - }
90 - }
70 + require.ErrorContains(t, err, "cannot rename key with name 'self'")
71
72 _, _, err = api.Key().Rename(ctx, "self", "foo", opt.Key.Force(true))
93 - if err == nil {
94 - t.Error("expected error to not be nil")
95 - } else {
96 - if !strings.Contains(err.Error(), "cannot rename key with name 'self'") {
97 - t.Fatalf("expected error 'cannot rename key with name 'self'', got '%s'", err.Error())
98 - }
99 - }
73 + require.ErrorContains(t, err, "cannot rename key with name 'self'")
74 }
75
76 func (tp *TestSuite) TestRemoveSelf(t *testing.T) {
77 ctx, cancel := context.WithCancel(context.Background())
78 defer cancel()
79 +
80 api, err := tp.makeAPI(t, ctx)
106 - if err != nil {
107 - t.Fatal(err)
108 - return
109 - }
81 + require.NoError(t, err)
82
83 _, err = api.Key().Remove(ctx, "self")
112 - if err == nil {
113 - t.Error("expected error to not be nil")
114 - } else {
115 - if !strings.Contains(err.Error(), "cannot remove key with name 'self'") {
116 - t.Fatalf("expected error 'cannot remove key with name 'self'', got '%s'", err.Error())
117 - }
118 - }
84 + require.ErrorContains(t, err, "cannot remove key with name 'self'")
85 }
86
87 func (tp *TestSuite) TestGenerate(t *testing.T) {
88 ctx, cancel := context.WithCancel(context.Background())
89 defer cancel()
90 +
91 api, err := tp.makeAPI(t, ctx)
125 - if err != nil {
126 - t.Fatal(err)
127 - }
92 + require.NoError(t, err)
93
94 k, err := api.Key().Generate(ctx, "foo")
130 - if err != nil {
131 - t.Fatal(err)
132 - return
133 - }
134 -
135 - if k.Name() != "foo" {
136 - t.Errorf("expected the key to be called 'foo', got '%s'", k.Name())
137 - }
95 + require.NoError(t, err)
96 + require.Equal(t, "foo", k.Name())
97
98 verifyIPNSPath(t, k.Path().String())
99 }
100
142 -func verifyIPNSPath(t *testing.T, p string) bool {
101 +func verifyIPNSPath(t *testing.T, p string) {
102 t.Helper()
144 - if !strings.HasPrefix(p, "/ipns/") {
145 - t.Errorf("path %q does not look like an IPNS path", p)
146 - return false
147 - }
103 +
104 + require.True(t, strings.HasPrefix(p, "/ipns/"))
105 +
106 k := p[len("/ipns/"):]
107 c, err := cid.Decode(k)
150 - if err != nil {
151 - t.Errorf("failed to decode IPNS key %q (%v)", k, err)
152 - return false
153 - }
108 + require.NoError(t, err)
109 +
110 b36, err := c.StringOfBase(mbase.Base36)
155 - if err != nil {
156 - t.Fatalf("cid cannot format itself in b36")
157 - return false
158 - }
159 - if b36 != k {
160 - t.Errorf("IPNS key is not base36")
161 - }
162 - return true
111 + require.NoError(t, err)
112 + require.Equal(t, k, b36)
113 }
114
115 func (tp *TestSuite) TestGenerateSize(t *testing.T) {
116 ctx, cancel := context.WithCancel(context.Background())
117 defer cancel()
118 +
119 api, err := tp.makeAPI(t, ctx)
169 - if err != nil {
170 - t.Fatal(err)
171 - }
120 + require.NoError(t, err)
121
122 k, err := api.Key().Generate(ctx, "foo", opt.Key.Size(2048))
174 - if err != nil {
175 - t.Fatal(err)
176 - return
177 - }
178 -
179 - if k.Name() != "foo" {
180 - t.Errorf("expected the key to be called 'foo', got '%s'", k.Name())
181 - }
123 + require.NoError(t, err)
124 + require.Equal(t, "foo", k.Name())
125
126 verifyIPNSPath(t, k.Path().String())
127 }
@@ -190,93 +133,47 @@ func (tp *TestSuite) TestGenerateType(t *testing.T) {
133 defer cancel()
134
135 api, err := tp.makeAPI(t, ctx)
193 - if err != nil {
194 - t.Fatal(err)
195 - }
136 + require.NoError(t, err)
137
138 k, err := api.Key().Generate(ctx, "bar", opt.Key.Type(opt.Ed25519Key))
198 - if err != nil {
199 - t.Fatal(err)
200 - return
201 - }
202 -
203 - if k.Name() != "bar" {
204 - t.Errorf("expected the key to be called 'foo', got '%s'", k.Name())
205 - }
206 -
139 + require.NoError(t, err)
140 + require.Equal(t, "bar", k.Name())
141 // Expected to be an inlined identity hash.
208 - if !strings.HasPrefix(k.Path().String(), "/ipns/12") {
209 - t.Errorf("expected the key to be prefixed with '/ipns/12', got '%s'", k.Path().String())
210 - }
142 + require.True(t, strings.HasPrefix(k.Path().String(), "/ipns/12"))
143 }
144
145 func (tp *TestSuite) TestGenerateExisting(t *testing.T) {
146 ctx, cancel := context.WithCancel(context.Background())
147 defer cancel()
148 +
149 api, err := tp.makeAPI(t, ctx)
217 - if err != nil {
218 - t.Fatal(err)
219 - }
150 + require.NoError(t, err)
151
152 _, err = api.Key().Generate(ctx, "foo")
222 - if err != nil {
223 - t.Fatal(err)
224 - return
225 - }
153 + require.NoError(t, err)
154
155 _, err = api.Key().Generate(ctx, "foo")
228 - if err == nil {
229 - t.Error("expected error to not be nil")
230 - } else {
231 - if !strings.Contains(err.Error(), "key with name 'foo' already exists") {
232 - t.Fatalf("expected error 'key with name 'foo' already exists', got '%s'", err.Error())
233 - }
234 - }
156 + require.ErrorContains(t, err, "key with name 'foo' already exists")
157
158 _, err = api.Key().Generate(ctx, "self")
237 - if err == nil {
238 - t.Error("expected error to not be nil")
239 - } else {
240 - if !strings.Contains(err.Error(), "cannot create key with name 'self'") {
241 - t.Fatalf("expected error 'cannot create key with name 'self'', got '%s'", err.Error())
242 - }
243 - }
159 + require.ErrorContains(t, err, "cannot create key with name 'self'")
160 }
161
162 func (tp *TestSuite) TestList(t *testing.T) {
163 ctx, cancel := context.WithCancel(context.Background())
164 defer cancel()
165 +
166 api, err := tp.makeAPI(t, ctx)
250 - if err != nil {
251 - t.Fatal(err)
252 - }
167 + require.NoError(t, err)
168
169 _, err = api.Key().Generate(ctx, "foo")
255 - if err != nil {
256 - t.Fatal(err)
257 - return
258 - }
170 + require.NoError(t, err)
171
172 l, err := api.Key().List(ctx)
261 - if err != nil {
262 - t.Fatal(err)
263 - return
264 - }
265 -
266 - if len(l) != 2 {
267 - t.Fatalf("expected to get 2 keys, got %d", len(l))
268 - return
269 - }
270 -
271 - if l[0].Name() != "self" {
272 - t.Fatalf("expected key 0 to be called 'self', got '%s'", l[0].Name())
273 - return
274 - }
275 -
276 - if l[1].Name() != "foo" {
277 - t.Fatalf("expected key 1 to be called 'foo', got '%s'", l[1].Name())
278 - return
279 - }
173 + require.NoError(t, err)
174 + require.Len(t, l, 2)
175 + require.Equal(t, "self", l[0].Name())
176 + require.Equal(t, "foo", l[1].Name())
177
178 verifyIPNSPath(t, l[0].Path().String())
179 verifyIPNSPath(t, l[1].Path().String())
@@ -285,254 +182,246 @@ func (tp *TestSuite) TestList(t *testing.T) {
182 func (tp *TestSuite) TestRename(t *testing.T) {
183 ctx, cancel := context.WithCancel(context.Background())
184 defer cancel()
185 +
186 api, err := tp.makeAPI(t, ctx)
289 - if err != nil {
290 - t.Fatal(err)
291 - }
187 + require.NoError(t, err)
188
189 _, err = api.Key().Generate(ctx, "foo")
294 - if err != nil {
295 - t.Fatal(err)
296 - return
297 - }
190 + require.NoError(t, err)
191
192 k, overwrote, err := api.Key().Rename(ctx, "foo", "bar")
300 - if err != nil {
301 - t.Fatal(err)
302 - return
303 - }
304 -
305 - if overwrote {
306 - t.Error("overwrote should be false")
307 - }
308 -
309 - if k.Name() != "bar" {
310 - t.Errorf("returned key should be called 'bar', got '%s'", k.Name())
311 - }
193 + require.NoError(t, err)
194 + assert.False(t, overwrote)
195 + assert.Equal(t, "bar", k.Name())
196 }
197
198 func (tp *TestSuite) TestRenameToSelf(t *testing.T) {
199 ctx, cancel := context.WithCancel(context.Background())
200 defer cancel()
201 +
202 api, err := tp.makeAPI(t, ctx)
318 - if err != nil {
319 - t.Fatal(err)
320 - }
203 + require.NoError(t, err)
204
205 _, err = api.Key().Generate(ctx, "foo")
323 - if err != nil {
324 - t.Fatal(err)
325 - return
326 - }
206 + require.NoError(t, err)
207
208 _, _, err = api.Key().Rename(ctx, "foo", "self")
329 - if err == nil {
330 - t.Error("expected error to not be nil")
331 - } else {
332 - if !strings.Contains(err.Error(), "cannot overwrite key with name 'self'") {
333 - t.Fatalf("expected error 'cannot overwrite key with name 'self'', got '%s'", err.Error())
334 - }
335 - }
209 + require.ErrorContains(t, err, "cannot overwrite key with name 'self'")
210 }
211
212 func (tp *TestSuite) TestRenameToSelfForce(t *testing.T) {
213 ctx, cancel := context.WithCancel(context.Background())
214 defer cancel()
215 +
216 api, err := tp.makeAPI(t, ctx)
342 - if err != nil {
343 - t.Fatal(err)
344 - }
217 + require.NoError(t, err)
218
219 _, err = api.Key().Generate(ctx, "foo")
347 - if err != nil {
348 - t.Fatal(err)
349 - return
350 - }
220 + require.NoError(t, err)
221
222 _, _, err = api.Key().Rename(ctx, "foo", "self", opt.Key.Force(true))
353 - if err == nil {
354 - t.Error("expected error to not be nil")
355 - } else {
356 - if !strings.Contains(err.Error(), "cannot overwrite key with name 'self'") {
357 - t.Fatalf("expected error 'cannot overwrite key with name 'self'', got '%s'", err.Error())
358 - }
359 - }
223 + require.ErrorContains(t, err, "cannot overwrite key with name 'self'")
224 }
225
226 func (tp *TestSuite) TestRenameOverwriteNoForce(t *testing.T) {
227 ctx, cancel := context.WithCancel(context.Background())
228 defer cancel()
229 +
230 api, err := tp.makeAPI(t, ctx)
366 - if err != nil {
367 - t.Fatal(err)
368 - }
231 + require.NoError(t, err)
232
233 _, err = api.Key().Generate(ctx, "foo")
371 - if err != nil {
372 - t.Fatal(err)
373 - return
374 - }
234 + require.NoError(t, err)
235
236 _, err = api.Key().Generate(ctx, "bar")
377 - if err != nil {
378 - t.Fatal(err)
379 - return
380 - }
237 + require.NoError(t, err)
238
239 _, _, err = api.Key().Rename(ctx, "foo", "bar")
383 - if err == nil {
384 - t.Error("expected error to not be nil")
385 - } else {
386 - if !strings.Contains(err.Error(), "key by that name already exists, refusing to overwrite") {
387 - t.Fatalf("expected error 'key by that name already exists, refusing to overwrite', got '%s'", err.Error())
388 - }
389 - }
240 + require.ErrorContains(t, err, "key by that name already exists, refusing to overwrite")
241 }
242
243 func (tp *TestSuite) TestRenameOverwrite(t *testing.T) {
244 ctx, cancel := context.WithCancel(context.Background())
245 defer cancel()
246 +
247 api, err := tp.makeAPI(t, ctx)
396 - if err != nil {
397 - t.Fatal(err)
398 - }
248 + require.NoError(t, err)
249
250 kfoo, err := api.Key().Generate(ctx, "foo")
401 - if err != nil {
402 - t.Fatal(err)
403 - return
404 - }
251 + require.NoError(t, err)
252
253 _, err = api.Key().Generate(ctx, "bar")
407 - if err != nil {
408 - t.Fatal(err)
409 - return
410 - }
254 + require.NoError(t, err)
255
256 k, overwrote, err := api.Key().Rename(ctx, "foo", "bar", opt.Key.Force(true))
413 - if err != nil {
414 - t.Fatal(err)
415 - return
416 - }
417 -
418 - if !overwrote {
419 - t.Error("overwrote should be true")
420 - }
421 -
422 - if k.Name() != "bar" {
423 - t.Errorf("returned key should be called 'bar', got '%s'", k.Name())
424 - }
425 -
426 - if k.Path().String() != kfoo.Path().String() {
427 - t.Errorf("k and kfoo should have equal paths, '%s'!='%s'", k.Path().String(), kfoo.Path().String())
428 - }
257 + require.NoError(t, err)
258 + require.True(t, overwrote)
259 + assert.Equal(t, "bar", k.Name())
260 + assert.Equal(t, kfoo.Path().String(), k.Path().String())
261 }
262
263 func (tp *TestSuite) TestRenameSameNameNoForce(t *testing.T) {
264 ctx, cancel := context.WithCancel(context.Background())
265 defer cancel()
266 +
267 api, err := tp.makeAPI(t, ctx)
435 - if err != nil {
436 - t.Fatal(err)
437 - }
268 + require.NoError(t, err)
269
270 _, err = api.Key().Generate(ctx, "foo")
440 - if err != nil {
441 - t.Fatal(err)
442 - return
443 - }
271 + require.NoError(t, err)
272
273 k, overwrote, err := api.Key().Rename(ctx, "foo", "foo")
446 - if err != nil {
447 - t.Fatal(err)
448 - return
449 - }
450 -
451 - if overwrote {
452 - t.Error("overwrote should be false")
453 - }
454 -
455 - if k.Name() != "foo" {
456 - t.Errorf("returned key should be called 'foo', got '%s'", k.Name())
457 - }
274 + require.NoError(t, err)
275 + assert.False(t, overwrote)
276 + assert.Equal(t, "foo", k.Name())
277 }
278
279 func (tp *TestSuite) TestRenameSameName(t *testing.T) {
280 ctx, cancel := context.WithCancel(context.Background())
281 defer cancel()
282 +
283 api, err := tp.makeAPI(t, ctx)
464 - if err != nil {
465 - t.Fatal(err)
466 - }
284 + require.NoError(t, err)
285
286 _, err = api.Key().Generate(ctx, "foo")
469 - if err != nil {
470 - t.Fatal(err)
471 - return
472 - }
287 + require.NoError(t, err)
288
289 k, overwrote, err := api.Key().Rename(ctx, "foo", "foo", opt.Key.Force(true))
475 - if err != nil {
476 - t.Fatal(err)
477 - return
478 - }
479 -
480 - if overwrote {
481 - t.Error("overwrote should be false")
482 - }
483 -
484 - if k.Name() != "foo" {
485 - t.Errorf("returned key should be called 'foo', got '%s'", k.Name())
486 - }
290 + require.NoError(t, err)
291 + assert.False(t, overwrote)
292 + assert.Equal(t, "foo", k.Name())
293 }
294
295 func (tp *TestSuite) TestRemove(t *testing.T) {
296 ctx, cancel := context.WithCancel(context.Background())
297 defer cancel()
298 +
299 api, err := tp.makeAPI(t, ctx)
493 - if err != nil {
494 - t.Fatal(err)
495 - }
300 + require.NoError(t, err)
301
302 k, err := api.Key().Generate(ctx, "foo")
498 - if err != nil {
499 - t.Fatal(err)
500 - return
501 - }
303 + require.NoError(t, err)
304
305 l, err := api.Key().List(ctx)
504 - if err != nil {
505 - t.Fatal(err)
506 - return
507 - }
508 -
509 - if len(l) != 2 {
510 - t.Fatalf("expected to get 2 keys, got %d", len(l))
511 - return
512 - }
306 + require.NoError(t, err)
307 + require.Len(t, l, 2)
308
309 p, err := api.Key().Remove(ctx, "foo")
515 - if err != nil {
516 - t.Fatal(err)
517 - return
518 - }
519 -
520 - if k.Path().String() != p.Path().String() {
521 - t.Errorf("k and p should have equal paths, '%s'!='%s'", k.Path().String(), p.Path().String())
522 - }
310 + require.NoError(t, err)
311 + assert.Equal(t, p.Path().String(), k.Path().String())
312
313 l, err = api.Key().List(ctx)
525 - if err != nil {
526 - t.Fatal(err)
527 - return
528 - }
529 -
530 - if len(l) != 1 {
531 - t.Fatalf("expected to get 1 key, got %d", len(l))
532 - return
533 - }
534 -
535 - if l[0].Name() != "self" {
536 - t.Errorf("expected the key to be called 'self', got '%s'", l[0].Name())
537 - }
314 + require.NoError(t, err)
315 + require.Len(t, l, 1)
316 + assert.Equal(t, "self", l[0].Name())
317 +}
318 +
319 +func (tp *TestSuite) TestSign(t *testing.T) {
320 + ctx, cancel := context.WithCancel(context.Background())
321 + defer cancel()
322 +
323 + api, err := tp.makeAPI(t, ctx)
324 + require.NoError(t, err)
325 +
326 + key1, err := api.Key().Generate(ctx, "foo", opt.Key.Type(opt.Ed25519Key))
327 + require.NoError(t, err)
328 +
329 + data := []byte("hello world")
330 +
331 + key2, signature, err := api.Key().Sign(ctx, "foo", data)
332 + require.NoError(t, err)
333 +
334 + require.Equal(t, key1.Name(), key2.Name())
335 + require.Equal(t, key1.ID(), key2.ID())
336 +
337 + pk, err := key1.ID().ExtractPublicKey()
338 + require.NoError(t, err)
339 +
340 + valid, err := pk.Verify(append([]byte("libp2p-key signed message:"), data...), signature)
341 + require.NoError(t, err)
342 + require.True(t, valid)
343 +}
344 +
345 +func (tp *TestSuite) TestVerify(t *testing.T) {
346 + t.Parallel()
347 +
348 + t.Run("Verify Own Key", func(t *testing.T) {
349 + t.Parallel()
350 +
351 + ctx, cancel := context.WithCancel(context.Background())
352 + defer cancel()
353 +
354 + api, err := tp.makeAPI(t, ctx)
355 + require.NoError(t, err)
356 +
357 + _, err = api.Key().Generate(ctx, "foo", opt.Key.Type(opt.Ed25519Key))
358 + require.NoError(t, err)
359 +
360 + data := []byte("hello world")
361 +
362 + _, signature, err := api.Key().Sign(ctx, "foo", data)
363 + require.NoError(t, err)
364 +
365 + _, valid, err := api.Key().Verify(ctx, "foo", signature, data)
366 + require.NoError(t, err)
367 + require.True(t, valid)
368 + })
369 +
370 + t.Run("Verify Self", func(t *testing.T) {
371 + t.Parallel()
372 +
373 + ctx, cancel := context.WithCancel(context.Background())
374 + defer cancel()
375 +
376 + api, err := tp.makeAPIWithIdentityAndOffline(t, ctx)
377 + require.NoError(t, err)
378 +
379 + data := []byte("hello world")
380 +
381 + _, signature, err := api.Key().Sign(ctx, "", data)
382 + require.NoError(t, err)
383 +
384 + _, valid, err := api.Key().Verify(ctx, "", signature, data)
385 + require.NoError(t, err)
386 + require.True(t, valid)
387 + })
388 +
389 + t.Run("Verify With Key In Different Formats", func(t *testing.T) {
390 + t.Parallel()
391 +
392 + // Spin some node and get signature out.
393 + ctx, cancel := context.WithCancel(context.Background())
394 + defer cancel()
395 +
396 + api, err := tp.makeAPI(t, ctx)
397 + require.NoError(t, err)
398 +
399 + key, err := api.Key().Generate(ctx, "foo", opt.Key.Type(opt.Ed25519Key))
400 + require.NoError(t, err)
401 +
402 + data := []byte("hello world")
403 +
404 + _, signature, err := api.Key().Sign(ctx, "foo", data)
405 + require.NoError(t, err)
406 +
407 + for _, testCase := range [][]string{
408 + {"Base58 Encoded Peer ID", key.ID().String()},
409 + {"CIDv1 Encoded Peer ID", peer.ToCid(key.ID()).String()},
410 + {"CIDv1 Encoded IPNS Name", ipns.NameFromPeer(key.ID()).String()},
411 + {"Prefixed IPNS Path", ipns.NameFromPeer(key.ID()).AsPath().String()},
412 + } {
413 + t.Run(testCase[0], func(t *testing.T) {
414 + ctx, cancel := context.WithCancel(context.Background())
415 + defer cancel()
416 +
417 + // Spin new node.
418 + api, err := tp.makeAPI(t, ctx)
419 + require.NoError(t, err)
420 +
421 + _, valid, err := api.Key().Verify(ctx, testCase[1], signature, data)
422 + require.NoError(t, err)
423 + require.True(t, valid)
424 + })
425 + }
426 + })
427 }
docs/changelogs/v0.25.md
+9
@@ -10,6 +10,7 @@
10 - [RPC `API.Authorizations`](#rpc-apiauthorizations)
11 - [MPLEX Removal](#mplex-removal)
12 - [Graphsync Experiment Removal](#graphsync-experiment-removal)
13 + - [Commands `ipfs key sign` and `ipfs key verify`](#commands-ipfs-key-sign-and-ipfs-key-verify)
14 - [📝 Changelog](#-changelog)
15 - [👨‍👩‍👧‍👦 Contributors](#-contributors)
16
@@ -55,6 +56,14 @@ to update Kubo because some dependency changed and it fails to build anymore.
56
57 For more information see https://github.com/ipfs/kubo/pull/9747.
58
59 +##### Commands `ipfs key sign` and `ipfs key verify`
60 +
61 +This allows the Kubo node to sign arbitrary bytes to prove ownership of a PeerID or an IPNS Name. To avoid signature reuse, the signed payload is always prefixed with `libp2p-key signed message:`.
62 +
63 +These commands are also both available through the RPC client and implemented in `client/rpc`.
64 +
65 +For more information see https://github.com/ipfs/kubo/issues/10230.
66 +
67 ### 📝 Changelog
68
69 ### 👨‍👩‍👧‍👦 Contributors