From 027f498bc9faa37434c285b0293f5c9ba15aafb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Sun, 10 Dec 2017 23:15:37 +0100 Subject: [PATCH 1/7] coreapi: Name API proposal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit License: MIT Signed-off-by: Łukasz Magiera --- core/coreapi/coreapi.go | 4 + core/coreapi/interface/interface.go | 19 ++++ core/coreapi/name.go | 148 ++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+) create mode 100644 core/coreapi/name.go diff --git a/core/coreapi/coreapi.go b/core/coreapi/coreapi.go index 5c0326b51e8..fa0a1d6f9c9 100644 --- a/core/coreapi/coreapi.go +++ b/core/coreapi/coreapi.go @@ -29,6 +29,10 @@ func (api *CoreAPI) Dag() coreiface.DagAPI { return &DagAPI{api, nil} } +func (api *CoreAPI) Name() coreiface.NameAPI { + return (*NameAPI)(api) +} + func (api *CoreAPI) ResolveNode(ctx context.Context, p coreiface.Path) (coreiface.Node, error) { p, err := api.ResolvePath(ctx, p) if err != nil { diff --git a/core/coreapi/interface/interface.go b/core/coreapi/interface/interface.go index e51888e6012..3fcf7374e1b 100644 --- a/core/coreapi/interface/interface.go +++ b/core/coreapi/interface/interface.go @@ -6,6 +6,7 @@ import ( "context" "errors" "io" + "time" options "github.com/ipfs/go-ipfs/core/coreapi/interface/options" @@ -27,6 +28,11 @@ type Path interface { type Node ipld.Node type Link ipld.Link +type IpnsEntry struct { + Name string + Value Path +} + type Reader interface { io.ReadSeeker io.Closer @@ -37,6 +43,7 @@ type CoreAPI interface { // Unixfs returns an implementation of Unixfs API Unixfs() UnixfsAPI Dag() DagAPI + Name() NameAPI // ResolvePath resolves the path using Unixfs resolver ResolvePath(context.Context, Path) (Path, error) @@ -90,6 +97,18 @@ type DagAPI interface { WithDepth(depth int) options.DagTreeOption } +type NameAPI interface { + Publish(ctx context.Context, path Path, validTime time.Duration, key string) (*IpnsEntry, error) + Resolve(ctx context.Context, name string, recursive bool, local bool, nocache bool) (Path, error) +} + +type KeyApi interface { + Generate(ctx context.Context, name string, algorithm string, size int) error + List(ctx context.Context) (map[string]string, error) //TODO: better key type? + Rename(ctx context.Context, oldName string, newName string) error + Remove(ctx context.Context, name string) error +} + // type ObjectAPI interface { // New() (cid.Cid, Object) // Get(string) (Object, error) diff --git a/core/coreapi/name.go b/core/coreapi/name.go new file mode 100644 index 00000000000..9d68f46b775 --- /dev/null +++ b/core/coreapi/name.go @@ -0,0 +1,148 @@ +package coreapi + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + core "github.com/ipfs/go-ipfs/core" + coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" + keystore "github.com/ipfs/go-ipfs/keystore" + namesys "github.com/ipfs/go-ipfs/namesys" + ipath "github.com/ipfs/go-ipfs/path" + offline "github.com/ipfs/go-ipfs/routing/offline" + + peer "gx/ipfs/QmXYjuNuxVzXKJCfWasQk1RqkhVLDM9jtUKhqc2WPQmFSB/go-libp2p-peer" + crypto "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto" +) + +type NameAPI CoreAPI + +func (api *NameAPI) Publish(ctx context.Context, p coreiface.Path, validTime time.Duration, key string) (*coreiface.IpnsEntry, error) { + n := api.node + + if !n.OnlineMode() { + err := n.SetupOfflineRouting() + if err != nil { + return nil, err + } + } + + if n.Mounts.Ipns != nil && n.Mounts.Ipns.IsActive() { + return nil, errors.New("cannot manually publish while IPNS is mounted") + } + + if n.Identity == "" { + return nil, errors.New("identity not loaded") + } + + pth, err := ipath.ParsePath(p.String()) + if err != nil { + return nil, err + } + + k, err := keylookup(n, key) + if err != nil { + return nil, err + } + + eol := time.Now().Add(validTime) + err = n.Namesys.PublishWithEOL(ctx, k, pth, eol) + if err != nil { + return nil, err + } + + pid, err := peer.IDFromPrivateKey(k) + if err != nil { + return nil, err + } + + return &coreiface.IpnsEntry{ + Name: pid.Pretty(), + Value: p, + }, nil +} + +func (api *NameAPI) Resolve(ctx context.Context, name string, recursive bool, local bool, nocache bool) (coreiface.Path, error) { + n := api.node + + if !n.OnlineMode() { + err := n.SetupOfflineRouting() + if err != nil { + return nil, err + } + } + + var resolver namesys.Resolver = n.Namesys + + if local && nocache { + return nil, errors.New("cannot specify both local and nocache") + } + + if local { + offroute := offline.NewOfflineRouter(n.Repo.Datastore(), n.PrivateKey) + resolver = namesys.NewRoutingResolver(offroute, 0) + } + + if nocache { + resolver = namesys.NewNameSystem(n.Routing, n.Repo.Datastore(), 0) + } + + depth := 1 + if recursive { + depth = namesys.DefaultDepthLimit + } + + if !strings.HasPrefix(name, "/ipns/") { + name = "/ipns/" + name + } + + output, err := resolver.ResolveN(ctx, name, depth) + if err != nil { + return nil, err + } + + return &path{path: output}, nil +} + +func (api *NameAPI) core() coreiface.CoreAPI { + return (*CoreAPI)(api) +} + +func keylookup(n *core.IpfsNode, k string) (crypto.PrivKey, error) { + res, err := n.GetKey(k) + if res != nil { + return res, nil + } + + if err != nil && err != keystore.ErrNoSuchKey { + return nil, err + } + + keys, err := n.Repo.Keystore().List() + if err != nil { + return nil, err + } + + for _, key := range keys { + privKey, err := n.Repo.Keystore().Get(key) + if err != nil { + return nil, err + } + + pubKey := privKey.GetPublic() + + pid, err := peer.IDFromPublicKey(pubKey) + if err != nil { + return nil, err + } + + if pid.Pretty() == k { + return privKey, nil + } + } + + return nil, fmt.Errorf("no key by the given name or PeerID was found") +} From 1b5fbb09b508c979120fe43ac21b274b73542508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Sun, 10 Dec 2017 23:48:16 +0100 Subject: [PATCH 2/7] coreapi: Keystore API proposal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit License: MIT Signed-off-by: Łukasz Magiera --- core/coreapi/coreapi.go | 4 + core/coreapi/interface/interface.go | 9 +- core/coreapi/key.go | 164 ++++++++++++++++++++++++++++ 3 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 core/coreapi/key.go diff --git a/core/coreapi/coreapi.go b/core/coreapi/coreapi.go index fa0a1d6f9c9..b8e7f28965a 100644 --- a/core/coreapi/coreapi.go +++ b/core/coreapi/coreapi.go @@ -33,6 +33,10 @@ func (api *CoreAPI) Name() coreiface.NameAPI { return (*NameAPI)(api) } +func (api *CoreAPI) Key() coreiface.KeyAPI { + return (*KeyAPI)(api) +} + func (api *CoreAPI) ResolveNode(ctx context.Context, p coreiface.Path) (coreiface.Node, error) { p, err := api.ResolvePath(ctx, p) if err != nil { diff --git a/core/coreapi/interface/interface.go b/core/coreapi/interface/interface.go index 3fcf7374e1b..f6412d68fad 100644 --- a/core/coreapi/interface/interface.go +++ b/core/coreapi/interface/interface.go @@ -44,6 +44,7 @@ type CoreAPI interface { Unixfs() UnixfsAPI Dag() DagAPI Name() NameAPI + Key() KeyAPI // ResolvePath resolves the path using Unixfs resolver ResolvePath(context.Context, Path) (Path, error) @@ -102,11 +103,11 @@ type NameAPI interface { Resolve(ctx context.Context, name string, recursive bool, local bool, nocache bool) (Path, error) } -type KeyApi interface { - Generate(ctx context.Context, name string, algorithm string, size int) error +type KeyAPI interface { + Generate(ctx context.Context, name string, algorithm string, size int) (string, error) List(ctx context.Context) (map[string]string, error) //TODO: better key type? - Rename(ctx context.Context, oldName string, newName string) error - Remove(ctx context.Context, name string) error + Rename(ctx context.Context, oldName string, newName string, force bool) (string, bool, error) + Remove(ctx context.Context, name string) (string, error) } // type ObjectAPI interface { diff --git a/core/coreapi/key.go b/core/coreapi/key.go new file mode 100644 index 00000000000..238ebe902f4 --- /dev/null +++ b/core/coreapi/key.go @@ -0,0 +1,164 @@ +package coreapi + +import ( + "context" + "crypto/rand" + "fmt" + "sort" + + coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" + + peer "gx/ipfs/QmXYjuNuxVzXKJCfWasQk1RqkhVLDM9jtUKhqc2WPQmFSB/go-libp2p-peer" + crypto "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto" +) + +type KeyAPI CoreAPI + +func (api *KeyAPI) Generate(ctx context.Context, name string, algorithm string, size int) (string, error) { + var sk crypto.PrivKey + var pk crypto.PubKey + + switch algorithm { + case "rsa": + if size == 0 { + return "", fmt.Errorf("please specify a key size with --size") + } + + priv, pub, err := crypto.GenerateKeyPairWithReader(crypto.RSA, size, rand.Reader) + if err != nil { + return "", err + } + + sk = priv + pk = pub + case "ed25519": + priv, pub, err := crypto.GenerateEd25519Key(rand.Reader) + if err != nil { + return "", err + } + + sk = priv + pk = pub + default: + return "", fmt.Errorf("unrecognized key type: %s", algorithm) + } + + err := api.node.Repo.Keystore().Put(name, sk) + if err != nil { + return "", err + } + + pid, err := peer.IDFromPublicKey(pk) + if err != nil { + return "", err + } + + return pid.String(), nil +} + +func (api *KeyAPI) List(ctx context.Context) (map[string]string, error) { + keys, err := api.node.Repo.Keystore().List() + if err != nil { + return nil, err + } + + sort.Strings(keys) + + out := make(map[string]string, len(keys)+1) + out["self"] = api.node.Identity.Pretty() + + for _, key := range keys { + privKey, err := api.node.Repo.Keystore().Get(key) + if err != nil { + return nil, err + } + + pubKey := privKey.GetPublic() + + pid, err := peer.IDFromPublicKey(pubKey) + if err != nil { + return nil, err + } + + out[key] = pid.Pretty() + } + return out, nil +} + +func (api *KeyAPI) Rename(ctx context.Context, oldName string, newName string, force bool) (string, bool, error) { + ks := api.node.Repo.Keystore() + + if oldName == "self" { + return "", false, fmt.Errorf("cannot rename key with name 'self'") + } + + if newName == "self" { + return "", false, fmt.Errorf("cannot overwrite key with name 'self'") + } + + oldKey, err := ks.Get(oldName) + if err != nil { + return "", false, fmt.Errorf("no key named %s was found", oldName) + } + + pubKey := oldKey.GetPublic() + + pid, err := peer.IDFromPublicKey(pubKey) + if err != nil { + return "", false, err + } + + overwrite := false + if force { + exist, err := ks.Has(newName) + if err != nil { + return "", false, err + } + + if exist { + overwrite = true + err := ks.Delete(newName) + if err != nil { + return "", false, err + } + } + } + + err = ks.Put(newName, oldKey) + if err != nil { + return "", false, err + } + + return pid.Pretty(), overwrite, ks.Delete(oldName) +} + +func (api *KeyAPI) Remove(ctx context.Context, name string) (string, error) { + ks := api.node.Repo.Keystore() + + if name == "self" { + return "", fmt.Errorf("cannot remove key with name 'self'") + } + + removed, err := ks.Get(name) + if err != nil { + return "", fmt.Errorf("no key named %s was found", name) + } + + pubKey := removed.GetPublic() + + pid, err := peer.IDFromPublicKey(pubKey) + if err != nil { + return "", err + } + + err = ks.Delete(name) + if err != nil { + return "", err + } + + return pid.Pretty(), nil +} + +func (api *KeyAPI) core() coreiface.CoreAPI { + return (*CoreAPI)(api) +} From 1c73d48e5b7a480dd1b7cf025e92f8e30948f705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 13 Dec 2017 19:05:23 +0100 Subject: [PATCH 3/7] coreapi: name/key functional options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit License: MIT Signed-off-by: Łukasz Magiera --- core/coreapi/coreapi.go | 10 ++- core/coreapi/interface/interface.go | 19 ++++-- core/coreapi/interface/options/key.go | 65 +++++++++++++++++++ core/coreapi/interface/options/name.go | 89 ++++++++++++++++++++++++++ core/coreapi/key.go | 34 +++++++--- core/coreapi/name.go | 33 +++++++--- 6 files changed, 224 insertions(+), 26 deletions(-) create mode 100644 core/coreapi/interface/options/key.go create mode 100644 core/coreapi/interface/options/name.go diff --git a/core/coreapi/coreapi.go b/core/coreapi/coreapi.go index b8e7f28965a..1870d7dc049 100644 --- a/core/coreapi/coreapi.go +++ b/core/coreapi/coreapi.go @@ -30,11 +30,17 @@ func (api *CoreAPI) Dag() coreiface.DagAPI { } func (api *CoreAPI) Name() coreiface.NameAPI { - return (*NameAPI)(api) + return &NameAPI{ + api, + nil, + } } func (api *CoreAPI) Key() coreiface.KeyAPI { - return (*KeyAPI)(api) + return &KeyAPI{ + api, + nil, + } } func (api *CoreAPI) ResolveNode(ctx context.Context, p coreiface.Path) (coreiface.Node, error) { diff --git a/core/coreapi/interface/interface.go b/core/coreapi/interface/interface.go index f6412d68fad..36bd801a140 100644 --- a/core/coreapi/interface/interface.go +++ b/core/coreapi/interface/interface.go @@ -99,14 +99,25 @@ type DagAPI interface { } type NameAPI interface { - Publish(ctx context.Context, path Path, validTime time.Duration, key string) (*IpnsEntry, error) - Resolve(ctx context.Context, name string, recursive bool, local bool, nocache bool) (Path, error) + Publish(ctx context.Context, path Path, opts ...options.NamePublishOption) (*IpnsEntry, error) + WithValidTime(validTime time.Duration) options.NamePublishOption + WithKey(key string) options.NamePublishOption + + Resolve(ctx context.Context, name string, opts ...options.NameResolveOption) (Path, error) + WithRecursive(recursive bool) options.NameResolveOption + WithLocal(local bool) options.NameResolveOption + WithNoCache(nocache bool) options.NameResolveOption } type KeyAPI interface { - Generate(ctx context.Context, name string, algorithm string, size int) (string, error) + Generate(ctx context.Context, name string, opts ...options.KeyGenerateOption) (string, error) + WithAlgorithm(algorithm string) options.KeyGenerateOption + WithSize(size int) options.KeyGenerateOption + + Rename(ctx context.Context, oldName string, newName string, opts ...options.KeyRenameOption) (string, bool, error) + WithForce(force bool) options.KeyRenameOption + List(ctx context.Context) (map[string]string, error) //TODO: better key type? - Rename(ctx context.Context, oldName string, newName string, force bool) (string, bool, error) Remove(ctx context.Context, name string) (string, error) } diff --git a/core/coreapi/interface/options/key.go b/core/coreapi/interface/options/key.go new file mode 100644 index 00000000000..5ed7b408f56 --- /dev/null +++ b/core/coreapi/interface/options/key.go @@ -0,0 +1,65 @@ +package options + +type KeyGenerateSettings struct { + Algorithm string + Size int +} + +type KeyRenameSettings struct { + Force bool +} + +type KeyGenerateOption func(*KeyGenerateSettings) error +type KeyRenameOption func(*KeyRenameSettings) error + +func KeyGenerateOptions(opts ...KeyGenerateOption) (*KeyGenerateSettings, error) { + options := &KeyGenerateSettings{ + Algorithm: "rsa", + Size: 0, + } + + for _, opt := range opts { + err := opt(options) + if err != nil { + return nil, err + } + } + return options, nil +} + +func KeyRenameOptions(opts ...KeyRenameOption) (*KeyRenameSettings, error) { + options := &KeyRenameSettings{ + Force: false, + } + + for _, opt := range opts { + err := opt(options) + if err != nil { + return nil, err + } + } + return options, nil +} + +type KeyOptions struct{} + +func (api *KeyOptions) WithAlgorithm(algorithm string) KeyGenerateOption { + return func(settings *KeyGenerateSettings) error { + settings.Algorithm = algorithm + return nil + } +} + +func (api *KeyOptions) WithSize(size int) KeyGenerateOption { + return func(settings *KeyGenerateSettings) error { + settings.Size = size + return nil + } +} + +func (api *KeyOptions) WithForce(force bool) KeyRenameOption { + return func(settings *KeyRenameSettings) error { + settings.Force = force + return nil + } +} diff --git a/core/coreapi/interface/options/name.go b/core/coreapi/interface/options/name.go new file mode 100644 index 00000000000..aa21291620d --- /dev/null +++ b/core/coreapi/interface/options/name.go @@ -0,0 +1,89 @@ +package options + +import ( + "time" +) + +type NamePublishSettings struct { + ValidTime time.Duration + Key string +} + +type NameResolveSettings struct { + Recursive bool + Local bool + Nocache bool +} + +type NamePublishOption func(*NamePublishSettings) error +type NameResolveOption func(*NameResolveSettings) error + +func NamePublishOptions(opts ...NamePublishOption) (*NamePublishSettings, error) { + options := &NamePublishSettings{ + ValidTime: 24 * time.Hour, + Key: "self", + } + + for _, opt := range opts { + err := opt(options) + if err != nil { + return nil, err + } + } + + return options, nil +} + +func NameResolveOptions(opts ...NameResolveOption) (*NameResolveSettings, error) { + options := &NameResolveSettings{ + Recursive: false, + Local: false, + Nocache: false, + } + + for _, opt := range opts { + err := opt(options) + if err != nil { + return nil, err + } + } + + return options, nil +} + +type NameOptions struct{} + +func (api *NameOptions) WithValidTime(validTime time.Duration) NamePublishOption { + return func(settings *NamePublishSettings) error { + settings.ValidTime = validTime + return nil + } +} + +func (api *NameOptions) WithKey(key string) NamePublishOption { + return func(settings *NamePublishSettings) error { + settings.Key = key + return nil + } +} + +func (api *NameOptions) WithRecursive(recursive bool) NameResolveOption { + return func(settings *NameResolveSettings) error { + settings.Recursive = recursive + return nil + } +} + +func (api *NameOptions) WithLocal(local bool) NameResolveOption { + return func(settings *NameResolveSettings) error { + settings.Local = local + return nil + } +} + +func (api *NameOptions) WithNoCache(nocache bool) NameResolveOption { + return func(settings *NameResolveSettings) error { + settings.Nocache = nocache + return nil + } +} diff --git a/core/coreapi/key.go b/core/coreapi/key.go index 238ebe902f4..525adcc38cb 100644 --- a/core/coreapi/key.go +++ b/core/coreapi/key.go @@ -7,24 +7,33 @@ import ( "sort" coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" + caopts "github.com/ipfs/go-ipfs/core/coreapi/interface/options" peer "gx/ipfs/QmXYjuNuxVzXKJCfWasQk1RqkhVLDM9jtUKhqc2WPQmFSB/go-libp2p-peer" crypto "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto" ) -type KeyAPI CoreAPI +type KeyAPI struct { + *CoreAPI + *caopts.KeyOptions +} + +func (api *KeyAPI) Generate(ctx context.Context, name string, opts ...caopts.KeyGenerateOption) (string, error) { + options, err := caopts.KeyGenerateOptions(opts...) + if err != nil { + return "", err + } -func (api *KeyAPI) Generate(ctx context.Context, name string, algorithm string, size int) (string, error) { var sk crypto.PrivKey var pk crypto.PubKey - switch algorithm { + switch options.Algorithm { case "rsa": - if size == 0 { + if options.Size == 0 { return "", fmt.Errorf("please specify a key size with --size") } - priv, pub, err := crypto.GenerateKeyPairWithReader(crypto.RSA, size, rand.Reader) + priv, pub, err := crypto.GenerateKeyPairWithReader(crypto.RSA, options.Size, rand.Reader) if err != nil { return "", err } @@ -40,10 +49,10 @@ func (api *KeyAPI) Generate(ctx context.Context, name string, algorithm string, sk = priv pk = pub default: - return "", fmt.Errorf("unrecognized key type: %s", algorithm) + return "", fmt.Errorf("unrecognized key type: %s", options.Algorithm) } - err := api.node.Repo.Keystore().Put(name, sk) + err = api.node.Repo.Keystore().Put(name, sk) if err != nil { return "", err } @@ -85,7 +94,12 @@ func (api *KeyAPI) List(ctx context.Context) (map[string]string, error) { return out, nil } -func (api *KeyAPI) Rename(ctx context.Context, oldName string, newName string, force bool) (string, bool, error) { +func (api *KeyAPI) Rename(ctx context.Context, oldName string, newName string, opts ...caopts.KeyRenameOption) (string, bool, error) { + options, err := caopts.KeyRenameOptions(opts...) + if newName == "self" { + return "", false, err + } + ks := api.node.Repo.Keystore() if oldName == "self" { @@ -109,7 +123,7 @@ func (api *KeyAPI) Rename(ctx context.Context, oldName string, newName string, f } overwrite := false - if force { + if options.Force { exist, err := ks.Has(newName) if err != nil { return "", false, err @@ -160,5 +174,5 @@ func (api *KeyAPI) Remove(ctx context.Context, name string) (string, error) { } func (api *KeyAPI) core() coreiface.CoreAPI { - return (*CoreAPI)(api) + return api.CoreAPI } diff --git a/core/coreapi/name.go b/core/coreapi/name.go index 9d68f46b775..8899347d22a 100644 --- a/core/coreapi/name.go +++ b/core/coreapi/name.go @@ -9,6 +9,7 @@ import ( core "github.com/ipfs/go-ipfs/core" coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" + caopts "github.com/ipfs/go-ipfs/core/coreapi/interface/options" keystore "github.com/ipfs/go-ipfs/keystore" namesys "github.com/ipfs/go-ipfs/namesys" ipath "github.com/ipfs/go-ipfs/path" @@ -18,9 +19,16 @@ import ( crypto "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto" ) -type NameAPI CoreAPI +type NameAPI struct { + *CoreAPI + *caopts.NameOptions +} -func (api *NameAPI) Publish(ctx context.Context, p coreiface.Path, validTime time.Duration, key string) (*coreiface.IpnsEntry, error) { +func (api *NameAPI) Publish(ctx context.Context, p coreiface.Path, opts ...caopts.NamePublishOption) (*coreiface.IpnsEntry, error) { + options, err := caopts.NamePublishOptions(opts...) + if err != nil { + return nil, err + } n := api.node if !n.OnlineMode() { @@ -43,12 +51,12 @@ func (api *NameAPI) Publish(ctx context.Context, p coreiface.Path, validTime tim return nil, err } - k, err := keylookup(n, key) + k, err := keylookup(n, options.Key) if err != nil { return nil, err } - eol := time.Now().Add(validTime) + eol := time.Now().Add(options.ValidTime) err = n.Namesys.PublishWithEOL(ctx, k, pth, eol) if err != nil { return nil, err @@ -65,7 +73,12 @@ func (api *NameAPI) Publish(ctx context.Context, p coreiface.Path, validTime tim }, nil } -func (api *NameAPI) Resolve(ctx context.Context, name string, recursive bool, local bool, nocache bool) (coreiface.Path, error) { +func (api *NameAPI) Resolve(ctx context.Context, name string, opts ...caopts.NameResolveOption) (coreiface.Path, error) { + options, err := caopts.NameResolveOptions(opts...) + if err != nil { + return nil, err + } + n := api.node if !n.OnlineMode() { @@ -77,21 +90,21 @@ func (api *NameAPI) Resolve(ctx context.Context, name string, recursive bool, lo var resolver namesys.Resolver = n.Namesys - if local && nocache { + if options.Local && options.Nocache { return nil, errors.New("cannot specify both local and nocache") } - if local { + if options.Local { offroute := offline.NewOfflineRouter(n.Repo.Datastore(), n.PrivateKey) resolver = namesys.NewRoutingResolver(offroute, 0) } - if nocache { + if options.Nocache { resolver = namesys.NewNameSystem(n.Routing, n.Repo.Datastore(), 0) } depth := 1 - if recursive { + if options.Recursive { depth = namesys.DefaultDepthLimit } @@ -108,7 +121,7 @@ func (api *NameAPI) Resolve(ctx context.Context, name string, recursive bool, lo } func (api *NameAPI) core() coreiface.CoreAPI { - return (*CoreAPI)(api) + return api.CoreAPI } func keylookup(n *core.IpfsNode, k string) (crypto.PrivKey, error) { From 587dc187c69945d44e5781a4e5862cab52b2325e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 15 Dec 2017 01:03:53 +0100 Subject: [PATCH 4/7] coreapi: Documentation for Name/Key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit License: MIT Signed-off-by: Łukasz Magiera --- core/coreapi/coreapi.go | 10 ++---- core/coreapi/interface/interface.go | 47 +++++++++++++++++++++++++++++ core/coreapi/key.go | 2 +- 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/core/coreapi/coreapi.go b/core/coreapi/coreapi.go index 1870d7dc049..14e43483b87 100644 --- a/core/coreapi/coreapi.go +++ b/core/coreapi/coreapi.go @@ -30,17 +30,11 @@ func (api *CoreAPI) Dag() coreiface.DagAPI { } func (api *CoreAPI) Name() coreiface.NameAPI { - return &NameAPI{ - api, - nil, - } + return &NameAPI{api, nil} } func (api *CoreAPI) Key() coreiface.KeyAPI { - return &KeyAPI{ - api, - nil, - } + return &KeyAPI{api, nil} } func (api *CoreAPI) ResolveNode(ctx context.Context, p coreiface.Path) (coreiface.Node, error) { diff --git a/core/coreapi/interface/interface.go b/core/coreapi/interface/interface.go index 36bd801a140..a861e470013 100644 --- a/core/coreapi/interface/interface.go +++ b/core/coreapi/interface/interface.go @@ -98,26 +98,73 @@ type DagAPI interface { WithDepth(depth int) options.DagTreeOption } +// NameAPI specifies the interface to IPNS. +// +// IPNS is a PKI namespace, where names are the hashes of public keys, and the +// private key enables publishing new (signed) values. In both publish and +// resolve, the default name used is the node's own PeerID, which is the hash of +// its public key. +// +// You can use .Key API to list and generate more names and their respective keys. type NameAPI interface { + // Publish announces new IPNS name Publish(ctx context.Context, path Path, opts ...options.NamePublishOption) (*IpnsEntry, error) + + // WithValidTime is an option for Publish which specifies for how long the + // entry will remain valid. Default value is 24h WithValidTime(validTime time.Duration) options.NamePublishOption + + // WithKey is an option for Publish which specifies the key to use for + // publishing. Default value is "self" which is the node's own PeerID. + // + // You can use .Key API to list and generate more names and their respective keys. WithKey(key string) options.NamePublishOption + // Resolve attempts to resolve the newest version of the specified name Resolve(ctx context.Context, name string, opts ...options.NameResolveOption) (Path, error) + + // WithRecursive is an option for Resolve which specifies whether to perform a + // recursive lookup. Default value is false WithRecursive(recursive bool) options.NameResolveOption + + // WithLocal is an option for Resolve which specifies if the lookup should be + // offline. Default value is false WithLocal(local bool) options.NameResolveOption + + // WithNoCache is an option for Resolve which specifies when set to true + // disables the use of local name cache. Default value is false WithNoCache(nocache bool) options.NameResolveOption } +// KeyAPI specifies the interface to Keystore type KeyAPI interface { + // Generate generates new key, stores it in the keystore under the specified + // name and returns a base58 encoded multihash of it's public key Generate(ctx context.Context, name string, opts ...options.KeyGenerateOption) (string, error) + + // WithAlgorithm is an option for Generate which specifies which algorithm + // should be used for the key. Default is "rsa" + // + // Supported algorithms: + // * rsa + // * ed25519 WithAlgorithm(algorithm string) options.KeyGenerateOption + + // WithSize is an option for Generate which specifies the size of the key to + // generated. Default is 0 WithSize(size int) options.KeyGenerateOption + // Rename renames oldName key to newName. Rename(ctx context.Context, oldName string, newName string, opts ...options.KeyRenameOption) (string, bool, error) + + // WithForce is an option for Rename which specifies whether to allow to + // replace existing keys. WithForce(force bool) options.KeyRenameOption + // List lists keys stored in keystore List(ctx context.Context) (map[string]string, error) //TODO: better key type? + + // Remove removes keys from keystore Remove(ctx context.Context, name string) (string, error) } diff --git a/core/coreapi/key.go b/core/coreapi/key.go index 525adcc38cb..87eea083387 100644 --- a/core/coreapi/key.go +++ b/core/coreapi/key.go @@ -30,7 +30,7 @@ func (api *KeyAPI) Generate(ctx context.Context, name string, opts ...caopts.Key switch options.Algorithm { case "rsa": if options.Size == 0 { - return "", fmt.Errorf("please specify a key size with --size") + return "", fmt.Errorf("please specify a key size with WithSize option") } priv, pub, err := crypto.GenerateKeyPairWithReader(crypto.RSA, options.Size, rand.Reader) From 8df2d1a92eee8a1369e73aaa379c160d028bd3d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Sun, 17 Dec 2017 03:44:13 +0100 Subject: [PATCH 5/7] coreapi: name/key review suggestions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit License: MIT Signed-off-by: Łukasz Magiera --- core/coreapi/interface/interface.go | 45 +++++++++------ core/coreapi/interface/options/key.go | 7 ++- core/coreapi/interface/options/name.go | 14 +++-- core/coreapi/key.go | 80 +++++++++++++++----------- core/coreapi/name.go | 31 ++++++---- 5 files changed, 108 insertions(+), 69 deletions(-) diff --git a/core/coreapi/interface/interface.go b/core/coreapi/interface/interface.go index a861e470013..0086bd0d629 100644 --- a/core/coreapi/interface/interface.go +++ b/core/coreapi/interface/interface.go @@ -28,16 +28,21 @@ type Path interface { type Node ipld.Node type Link ipld.Link -type IpnsEntry struct { - Name string - Value Path -} - type Reader interface { io.ReadSeeker io.Closer } +type IpnsEntry interface { + Name() string + Value() Path +} + +type Key interface { + Name() string + Path() Path +} + // CoreAPI defines an unified interface to IPFS for Go programs. type CoreAPI interface { // Unixfs returns an implementation of Unixfs API @@ -108,7 +113,7 @@ type DagAPI interface { // You can use .Key API to list and generate more names and their respective keys. type NameAPI interface { // Publish announces new IPNS name - Publish(ctx context.Context, path Path, opts ...options.NamePublishOption) (*IpnsEntry, error) + Publish(ctx context.Context, path Path, opts ...options.NamePublishOption) (IpnsEntry, error) // WithValidTime is an option for Publish which specifies for how long the // entry will remain valid. Default value is 24h @@ -116,8 +121,9 @@ type NameAPI interface { // WithKey is an option for Publish which specifies the key to use for // publishing. Default value is "self" which is the node's own PeerID. + // The key parameter must be either PeerID or keystore key alias. // - // You can use .Key API to list and generate more names and their respective keys. + // You can use KeyAPI to list and generate more names and their respective keys. WithKey(key string) options.NamePublishOption // Resolve attempts to resolve the newest version of the specified name @@ -131,41 +137,42 @@ type NameAPI interface { // offline. Default value is false WithLocal(local bool) options.NameResolveOption - // WithNoCache is an option for Resolve which specifies when set to true - // disables the use of local name cache. Default value is false - WithNoCache(nocache bool) options.NameResolveOption + // WithCache is an option for Resolve which specifies if cache should be used. + // Default value is true + WithCache(cache bool) options.NameResolveOption } // KeyAPI specifies the interface to Keystore type KeyAPI interface { // Generate generates new key, stores it in the keystore under the specified // name and returns a base58 encoded multihash of it's public key - Generate(ctx context.Context, name string, opts ...options.KeyGenerateOption) (string, error) + Generate(ctx context.Context, name string, opts ...options.KeyGenerateOption) (Key, error) // WithAlgorithm is an option for Generate which specifies which algorithm - // should be used for the key. Default is "rsa" + // should be used for the key. Default is options.RSAKey // // Supported algorithms: - // * rsa - // * ed25519 + // * options.RSAKey + // * options.Ed25519Key WithAlgorithm(algorithm string) options.KeyGenerateOption // WithSize is an option for Generate which specifies the size of the key to // generated. Default is 0 WithSize(size int) options.KeyGenerateOption - // Rename renames oldName key to newName. - Rename(ctx context.Context, oldName string, newName string, opts ...options.KeyRenameOption) (string, bool, error) + // Rename renames oldName key to newName. Returns the key and whether another + // key was overwritten, or an error + Rename(ctx context.Context, oldName string, newName string, opts ...options.KeyRenameOption) (Key, bool, error) // WithForce is an option for Rename which specifies whether to allow to // replace existing keys. WithForce(force bool) options.KeyRenameOption // List lists keys stored in keystore - List(ctx context.Context) (map[string]string, error) //TODO: better key type? + List(ctx context.Context) ([]Key, error) - // Remove removes keys from keystore - Remove(ctx context.Context, name string) (string, error) + // Remove removes keys from keystore. Returns ipns path of the removed key + Remove(ctx context.Context, name string) (Path, error) } // type ObjectAPI interface { diff --git a/core/coreapi/interface/options/key.go b/core/coreapi/interface/options/key.go index 5ed7b408f56..c84f0f8f8b8 100644 --- a/core/coreapi/interface/options/key.go +++ b/core/coreapi/interface/options/key.go @@ -1,5 +1,10 @@ package options +const ( + RSAKey = "rsa" + Ed25519Key = "ed25519" +) + type KeyGenerateSettings struct { Algorithm string Size int @@ -14,7 +19,7 @@ type KeyRenameOption func(*KeyRenameSettings) error func KeyGenerateOptions(opts ...KeyGenerateOption) (*KeyGenerateSettings, error) { options := &KeyGenerateSettings{ - Algorithm: "rsa", + Algorithm: RSAKey, Size: 0, } diff --git a/core/coreapi/interface/options/name.go b/core/coreapi/interface/options/name.go index aa21291620d..9f8aaafc83e 100644 --- a/core/coreapi/interface/options/name.go +++ b/core/coreapi/interface/options/name.go @@ -4,6 +4,10 @@ import ( "time" ) +const ( + DefaultNameValidTime = 24 * time.Hour +) + type NamePublishSettings struct { ValidTime time.Duration Key string @@ -12,7 +16,7 @@ type NamePublishSettings struct { type NameResolveSettings struct { Recursive bool Local bool - Nocache bool + Cache bool } type NamePublishOption func(*NamePublishSettings) error @@ -20,7 +24,7 @@ type NameResolveOption func(*NameResolveSettings) error func NamePublishOptions(opts ...NamePublishOption) (*NamePublishSettings, error) { options := &NamePublishSettings{ - ValidTime: 24 * time.Hour, + ValidTime: DefaultNameValidTime, Key: "self", } @@ -38,7 +42,7 @@ func NameResolveOptions(opts ...NameResolveOption) (*NameResolveSettings, error) options := &NameResolveSettings{ Recursive: false, Local: false, - Nocache: false, + Cache: true, } for _, opt := range opts { @@ -81,9 +85,9 @@ func (api *NameOptions) WithLocal(local bool) NameResolveOption { } } -func (api *NameOptions) WithNoCache(nocache bool) NameResolveOption { +func (api *NameOptions) WithCache(cache bool) NameResolveOption { return func(settings *NameResolveSettings) error { - settings.Nocache = nocache + settings.Cache = cache return nil } } diff --git a/core/coreapi/key.go b/core/coreapi/key.go index 87eea083387..99ae490f54e 100644 --- a/core/coreapi/key.go +++ b/core/coreapi/key.go @@ -8,8 +8,9 @@ import ( coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" caopts "github.com/ipfs/go-ipfs/core/coreapi/interface/options" + ipfspath "github.com/ipfs/go-ipfs/path" - peer "gx/ipfs/QmXYjuNuxVzXKJCfWasQk1RqkhVLDM9jtUKhqc2WPQmFSB/go-libp2p-peer" + peer "gx/ipfs/QmWNY7dV54ZDYmTA1ykVdwNCqC11mpU4zSUp6XDpLTH9eG/go-libp2p-peer" crypto "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto" ) @@ -18,10 +19,23 @@ type KeyAPI struct { *caopts.KeyOptions } -func (api *KeyAPI) Generate(ctx context.Context, name string, opts ...caopts.KeyGenerateOption) (string, error) { +type key struct { + name string + peerId string +} + +func (k *key) Name() string { + return k.name +} + +func (k *key) Path() coreiface.Path { + return &path{path: ipfspath.FromString(ipfspath.Join([]string{"/ipns/", k.peerId}))} +} + +func (api *KeyAPI) Generate(ctx context.Context, name string, opts ...caopts.KeyGenerateOption) (coreiface.Key, error) { options, err := caopts.KeyGenerateOptions(opts...) if err != nil { - return "", err + return nil, err } var sk crypto.PrivKey @@ -30,12 +44,12 @@ func (api *KeyAPI) Generate(ctx context.Context, name string, opts ...caopts.Key switch options.Algorithm { case "rsa": if options.Size == 0 { - return "", fmt.Errorf("please specify a key size with WithSize option") + return nil, fmt.Errorf("please specify a key size with WithSize option") } priv, pub, err := crypto.GenerateKeyPairWithReader(crypto.RSA, options.Size, rand.Reader) if err != nil { - return "", err + return nil, err } sk = priv @@ -43,29 +57,29 @@ func (api *KeyAPI) Generate(ctx context.Context, name string, opts ...caopts.Key case "ed25519": priv, pub, err := crypto.GenerateEd25519Key(rand.Reader) if err != nil { - return "", err + return nil, err } sk = priv pk = pub default: - return "", fmt.Errorf("unrecognized key type: %s", options.Algorithm) + return nil, fmt.Errorf("unrecognized key type: %s", options.Algorithm) } err = api.node.Repo.Keystore().Put(name, sk) if err != nil { - return "", err + return nil, err } pid, err := peer.IDFromPublicKey(pk) if err != nil { - return "", err + return nil, err } - return pid.String(), nil + return &key{name, pid.String()}, nil } -func (api *KeyAPI) List(ctx context.Context) (map[string]string, error) { +func (api *KeyAPI) List(ctx context.Context) ([]coreiface.Key, error) { keys, err := api.node.Repo.Keystore().List() if err != nil { return nil, err @@ -73,11 +87,11 @@ func (api *KeyAPI) List(ctx context.Context) (map[string]string, error) { sort.Strings(keys) - out := make(map[string]string, len(keys)+1) - out["self"] = api.node.Identity.Pretty() + out := make([]coreiface.Key, len(keys)+1) + out[0] = &key{"self", api.node.Identity.Pretty()} - for _, key := range keys { - privKey, err := api.node.Repo.Keystore().Get(key) + for n, k := range keys { + privKey, err := api.node.Repo.Keystore().Get(k) if err != nil { return nil, err } @@ -89,88 +103,88 @@ func (api *KeyAPI) List(ctx context.Context) (map[string]string, error) { return nil, err } - out[key] = pid.Pretty() + out[n+1] = &key{k, pid.Pretty()} } return out, nil } -func (api *KeyAPI) Rename(ctx context.Context, oldName string, newName string, opts ...caopts.KeyRenameOption) (string, bool, error) { +func (api *KeyAPI) Rename(ctx context.Context, oldName string, newName string, opts ...caopts.KeyRenameOption) (coreiface.Key, bool, error) { options, err := caopts.KeyRenameOptions(opts...) - if newName == "self" { - return "", false, err + if err != nil { + return nil, false, err } ks := api.node.Repo.Keystore() if oldName == "self" { - return "", false, fmt.Errorf("cannot rename key with name 'self'") + return nil, false, fmt.Errorf("cannot rename key with name 'self'") } if newName == "self" { - return "", false, fmt.Errorf("cannot overwrite key with name 'self'") + return nil, false, fmt.Errorf("cannot overwrite key with name 'self'") } oldKey, err := ks.Get(oldName) if err != nil { - return "", false, fmt.Errorf("no key named %s was found", oldName) + return nil, false, fmt.Errorf("no key named %s was found", oldName) } pubKey := oldKey.GetPublic() pid, err := peer.IDFromPublicKey(pubKey) if err != nil { - return "", false, err + return nil, false, err } overwrite := false if options.Force { exist, err := ks.Has(newName) if err != nil { - return "", false, err + return nil, false, err } if exist { overwrite = true err := ks.Delete(newName) if err != nil { - return "", false, err + return nil, false, err } } } err = ks.Put(newName, oldKey) if err != nil { - return "", false, err + return nil, false, err } - return pid.Pretty(), overwrite, ks.Delete(oldName) + return &key{newName, pid.Pretty()}, overwrite, ks.Delete(oldName) } -func (api *KeyAPI) Remove(ctx context.Context, name string) (string, error) { +func (api *KeyAPI) Remove(ctx context.Context, name string) (coreiface.Path, error) { ks := api.node.Repo.Keystore() if name == "self" { - return "", fmt.Errorf("cannot remove key with name 'self'") + return nil, fmt.Errorf("cannot remove key with name 'self'") } removed, err := ks.Get(name) if err != nil { - return "", fmt.Errorf("no key named %s was found", name) + return nil, fmt.Errorf("no key named %s was found", name) } pubKey := removed.GetPublic() pid, err := peer.IDFromPublicKey(pubKey) if err != nil { - return "", err + return nil, err } err = ks.Delete(name) if err != nil { - return "", err + return nil, err } - return pid.Pretty(), nil + return (&key{"", pid.Pretty()}).Path(), nil } func (api *KeyAPI) core() coreiface.CoreAPI { diff --git a/core/coreapi/name.go b/core/coreapi/name.go index 8899347d22a..fd61d250df4 100644 --- a/core/coreapi/name.go +++ b/core/coreapi/name.go @@ -15,7 +15,7 @@ import ( ipath "github.com/ipfs/go-ipfs/path" offline "github.com/ipfs/go-ipfs/routing/offline" - peer "gx/ipfs/QmXYjuNuxVzXKJCfWasQk1RqkhVLDM9jtUKhqc2WPQmFSB/go-libp2p-peer" + peer "gx/ipfs/QmWNY7dV54ZDYmTA1ykVdwNCqC11mpU4zSUp6XDpLTH9eG/go-libp2p-peer" crypto "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto" ) @@ -24,7 +24,20 @@ type NameAPI struct { *caopts.NameOptions } -func (api *NameAPI) Publish(ctx context.Context, p coreiface.Path, opts ...caopts.NamePublishOption) (*coreiface.IpnsEntry, error) { +type ipnsEntry struct { + name string + value coreiface.Path +} + +func (e *ipnsEntry) Name() string { + return e.name +} + +func (e *ipnsEntry) Value() coreiface.Path { + return e.value +} + +func (api *NameAPI) Publish(ctx context.Context, p coreiface.Path, opts ...caopts.NamePublishOption) (coreiface.IpnsEntry, error) { options, err := caopts.NamePublishOptions(opts...) if err != nil { return nil, err @@ -42,10 +55,6 @@ func (api *NameAPI) Publish(ctx context.Context, p coreiface.Path, opts ...caopt return nil, errors.New("cannot manually publish while IPNS is mounted") } - if n.Identity == "" { - return nil, errors.New("identity not loaded") - } - pth, err := ipath.ParsePath(p.String()) if err != nil { return nil, err @@ -67,9 +76,9 @@ func (api *NameAPI) Publish(ctx context.Context, p coreiface.Path, opts ...caopt return nil, err } - return &coreiface.IpnsEntry{ - Name: pid.Pretty(), - Value: p, + return &ipnsEntry{ + name: pid.Pretty(), + value: p, }, nil } @@ -90,7 +99,7 @@ func (api *NameAPI) Resolve(ctx context.Context, name string, opts ...caopts.Nam var resolver namesys.Resolver = n.Namesys - if options.Local && options.Nocache { + if options.Local && !options.Cache { return nil, errors.New("cannot specify both local and nocache") } @@ -99,7 +108,7 @@ func (api *NameAPI) Resolve(ctx context.Context, name string, opts ...caopts.Nam resolver = namesys.NewRoutingResolver(offroute, 0) } - if options.Nocache { + if !options.Cache { resolver = namesys.NewNameSystem(n.Routing, n.Repo.Datastore(), 0) } From 396c34b4e11ba773687f746d6bf1086a270721ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Mon, 1 Jan 2018 18:59:07 +0100 Subject: [PATCH 6/7] coreapi: key tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit License: MIT Signed-off-by: Łukasz Magiera --- core/coreapi/interface/interface.go | 6 +- core/coreapi/interface/options/key.go | 6 +- core/coreapi/key.go | 17 +- core/coreapi/key_test.go | 416 ++++++++++++++++++++++++++ core/coreapi/unixfs_test.go | 3 + repo/mock.go | 2 +- 6 files changed, 440 insertions(+), 10 deletions(-) create mode 100644 core/coreapi/key_test.go diff --git a/core/coreapi/interface/interface.go b/core/coreapi/interface/interface.go index 0086bd0d629..dc836566991 100644 --- a/core/coreapi/interface/interface.go +++ b/core/coreapi/interface/interface.go @@ -148,13 +148,13 @@ type KeyAPI interface { // name and returns a base58 encoded multihash of it's public key Generate(ctx context.Context, name string, opts ...options.KeyGenerateOption) (Key, error) - // WithAlgorithm is an option for Generate which specifies which algorithm + // WithType is an option for Generate which specifies which algorithm // should be used for the key. Default is options.RSAKey // - // Supported algorithms: + // Supported key types: // * options.RSAKey // * options.Ed25519Key - WithAlgorithm(algorithm string) options.KeyGenerateOption + WithType(algorithm string) options.KeyGenerateOption // WithSize is an option for Generate which specifies the size of the key to // generated. Default is 0 diff --git a/core/coreapi/interface/options/key.go b/core/coreapi/interface/options/key.go index c84f0f8f8b8..114361875a5 100644 --- a/core/coreapi/interface/options/key.go +++ b/core/coreapi/interface/options/key.go @@ -3,6 +3,8 @@ package options const ( RSAKey = "rsa" Ed25519Key = "ed25519" + + DefaultRSALen = 2048 ) type KeyGenerateSettings struct { @@ -20,7 +22,7 @@ type KeyRenameOption func(*KeyRenameSettings) error func KeyGenerateOptions(opts ...KeyGenerateOption) (*KeyGenerateSettings, error) { options := &KeyGenerateSettings{ Algorithm: RSAKey, - Size: 0, + Size: -1, } for _, opt := range opts { @@ -48,7 +50,7 @@ func KeyRenameOptions(opts ...KeyRenameOption) (*KeyRenameSettings, error) { type KeyOptions struct{} -func (api *KeyOptions) WithAlgorithm(algorithm string) KeyGenerateOption { +func (api *KeyOptions) WithType(algorithm string) KeyGenerateOption { return func(settings *KeyGenerateSettings) error { settings.Algorithm = algorithm return nil diff --git a/core/coreapi/key.go b/core/coreapi/key.go index 99ae490f54e..b5a0e3308ba 100644 --- a/core/coreapi/key.go +++ b/core/coreapi/key.go @@ -29,7 +29,7 @@ func (k *key) Name() string { } func (k *key) Path() coreiface.Path { - return &path{path: ipfspath.FromString(ipfspath.Join([]string{"/ipns/", k.peerId}))} + return &path{path: ipfspath.FromString(ipfspath.Join([]string{"/ipns", k.peerId}))} } func (api *KeyAPI) Generate(ctx context.Context, name string, opts ...caopts.KeyGenerateOption) (coreiface.Key, error) { @@ -38,13 +38,22 @@ func (api *KeyAPI) Generate(ctx context.Context, name string, opts ...caopts.Key return nil, err } + if name == "self" { + return nil, fmt.Errorf("cannot overwrite key with name 'self'") + } + + _, err = api.node.Repo.Keystore().Get(name) + if err == nil { + return nil, fmt.Errorf("key with name '%s' already exists", name) + } + var sk crypto.PrivKey var pk crypto.PubKey switch options.Algorithm { case "rsa": - if options.Size == 0 { - return nil, fmt.Errorf("please specify a key size with WithSize option") + if options.Size == -1 { + options.Size = caopts.DefaultRSALen } priv, pub, err := crypto.GenerateKeyPairWithReader(crypto.RSA, options.Size, rand.Reader) @@ -76,7 +85,7 @@ func (api *KeyAPI) Generate(ctx context.Context, name string, opts ...caopts.Key return nil, err } - return &key{name, pid.String()}, nil + return &key{name, pid.Pretty()}, nil } func (api *KeyAPI) List(ctx context.Context) ([]coreiface.Key, error) { diff --git a/core/coreapi/key_test.go b/core/coreapi/key_test.go new file mode 100644 index 00000000000..7e1b23ac9d0 --- /dev/null +++ b/core/coreapi/key_test.go @@ -0,0 +1,416 @@ +package coreapi_test + +import ( + "context" + "strings" + "testing" + + opts "github.com/ipfs/go-ipfs/core/coreapi/interface/options" +) + +func TestListSelf(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPI(ctx) + if err != nil { + t.Fatal(err) + return + } + + keys, err := api.Key().List(ctx) + if err != nil { + t.Fatalf("failed to list keys: %s", err) + return + } + + if len(keys) != 1 { + t.Fatalf("there should be 1 key (self), got %d", len(keys)) + return + } + + if keys[0].Name() != "self" { + t.Errorf("expected the key to be called 'self', got '%s'", keys[0].Name()) + } + + if keys[0].Path().String() != "/ipns/Qmfoo" { + t.Errorf("expected the key to have path '/ipns/Qmfoo', got '%s'", keys[0].Path().String()) + } +} + +func TestRenameSelf(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPI(ctx) + if err != nil { + t.Fatal(err) + return + } + + _, _, err = api.Key().Rename(ctx, "self", "foo") + if err == nil { + t.Error("expected error to not be nil") + } else { + if err.Error() != "cannot rename key with name 'self'" { + t.Fatalf("expected error 'cannot rename key with name 'self'', got '%s'", err.Error()) + } + } + + _, _, err = api.Key().Rename(ctx, "self", "foo", api.Key().WithForce(true)) + if err == nil { + t.Error("expected error to not be nil") + } else { + if err.Error() != "cannot rename key with name 'self'" { + t.Fatalf("expected error 'cannot rename key with name 'self'', got '%s'", err.Error()) + } + } +} + +func TestRemoveSelf(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPI(ctx) + if err != nil { + t.Fatal(err) + return + } + + _, err = api.Key().Remove(ctx, "self") + if err == nil { + t.Error("expected error to not be nil") + } else { + if err.Error() != "cannot remove key with name 'self'" { + t.Fatalf("expected error 'cannot remove key with name 'self'', got '%s'", err.Error()) + } + } +} + +func TestGenerate(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPI(ctx) + if err != nil { + t.Error(err) + } + + k, err := api.Key().Generate(ctx, "foo") + if err != nil { + t.Fatal(err) + return + } + + if k.Name() != "foo" { + t.Errorf("expected the key to be called 'foo', got '%s'", k.Name()) + } + + if !strings.HasPrefix(k.Path().String(), "/ipns/Qm") { + t.Errorf("expected the key to be prefixed with '/ipns/Qm', got '%s'", k.Path().String()) + } +} + +func TestGenerateSize(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPI(ctx) + if err != nil { + t.Error(err) + } + + k, err := api.Key().Generate(ctx, "foo", api.Key().WithSize(1024)) + if err != nil { + t.Fatal(err) + return + } + + if k.Name() != "foo" { + t.Errorf("expected the key to be called 'foo', got '%s'", k.Name()) + } + + if !strings.HasPrefix(k.Path().String(), "/ipns/Qm") { + t.Errorf("expected the key to be prefixed with '/ipns/Qm', got '%s'", k.Path().String()) + } +} + +func TestGenerateType(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPI(ctx) + if err != nil { + t.Error(err) + } + + k, err := api.Key().Generate(ctx, "bar", api.Key().WithType(opts.Ed25519Key)) + if err != nil { + t.Fatal(err) + return + } + + if k.Name() != "bar" { + t.Errorf("expected the key to be called 'foo', got '%s'", k.Name()) + } + + if !strings.HasPrefix(k.Path().String(), "/ipns/Qm") { + t.Errorf("expected the key to be prefixed with '/ipns/Qm', got '%s'", k.Path().String()) + } +} + +func TestGenerateExisting(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPI(ctx) + if err != nil { + t.Error(err) + } + + _, err = api.Key().Generate(ctx, "foo") + if err != nil { + t.Fatal(err) + return + } + + _, err = api.Key().Generate(ctx, "foo") + if err == nil { + t.Error("expected error to not be nil") + } else { + if err.Error() != "key with name 'foo' already exists" { + t.Fatalf("expected error 'key with name 'foo' already exists', got '%s'", err.Error()) + } + } + + _, err = api.Key().Generate(ctx, "self") + if err == nil { + t.Error("expected error to not be nil") + } else { + if err.Error() != "cannot overwrite key with name 'self'" { + t.Fatalf("expected error 'cannot overwrite key with name 'self'', got '%s'", err.Error()) + } + } +} + +func TestList(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPI(ctx) + if err != nil { + t.Error(err) + } + + _, err = api.Key().Generate(ctx, "foo") + if err != nil { + t.Fatal(err) + return + } + + l, err := api.Key().List(ctx) + if err != nil { + t.Fatal(err) + return + } + + if len(l) != 2 { + t.Fatalf("expected to get 2 keys, got %d", len(l)) + return + } + + if l[0].Name() != "self" { + t.Fatalf("expected key 0 to be called 'self', got '%s'", l[0].Name()) + return + } + + if l[1].Name() != "foo" { + t.Fatalf("expected key 1 to be called 'foo', got '%s'", l[1].Name()) + return + } + + if !strings.HasPrefix(l[0].Path().String(), "/ipns/Qm") { + t.Fatalf("expected key 0 to be prefixed with '/ipns/Qm', got '%s'", l[0].Name()) + return + } + + if !strings.HasPrefix(l[1].Path().String(), "/ipns/Qm") { + t.Fatalf("expected key 1 to be prefixed with '/ipns/Qm', got '%s'", l[1].Name()) + return + } +} + +func TestRename(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPI(ctx) + if err != nil { + t.Error(err) + } + + _, err = api.Key().Generate(ctx, "foo") + if err != nil { + t.Fatal(err) + return + } + + k, overwrote, err := api.Key().Rename(ctx, "foo", "bar") + if err != nil { + t.Fatal(err) + return + } + + if overwrote { + t.Error("overwrote should be false") + } + + if k.Name() != "bar" { + t.Errorf("returned key should be called 'bar', got '%s'", k.Name()) + } +} + +func TestRenameToSelf(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPI(ctx) + if err != nil { + t.Error(err) + } + + _, err = api.Key().Generate(ctx, "foo") + if err != nil { + t.Fatal(err) + return + } + + _, _, err = api.Key().Rename(ctx, "foo", "self") + if err == nil { + t.Error("expected error to not be nil") + } else { + if err.Error() != "cannot overwrite key with name 'self'" { + t.Fatalf("expected error 'cannot overwrite key with name 'self'', got '%s'", err.Error()) + } + } +} + +func TestRenameToSelfForce(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPI(ctx) + if err != nil { + t.Error(err) + } + + _, err = api.Key().Generate(ctx, "foo") + if err != nil { + t.Fatal(err) + return + } + + _, _, err = api.Key().Rename(ctx, "foo", "self", api.Key().WithForce(true)) + if err == nil { + t.Error("expected error to not be nil") + } else { + if err.Error() != "cannot overwrite key with name 'self'" { + t.Fatalf("expected error 'cannot overwrite key with name 'self'', got '%s'", err.Error()) + } + } +} + +func TestRenameOverwriteNoForce(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPI(ctx) + if err != nil { + t.Error(err) + } + + _, err = api.Key().Generate(ctx, "foo") + if err != nil { + t.Fatal(err) + return + } + + _, err = api.Key().Generate(ctx, "bar") + if err != nil { + t.Fatal(err) + return + } + + _, _, err = api.Key().Rename(ctx, "foo", "bar") + if err == nil { + t.Error("expected error to not be nil") + } else { + if err.Error() != "key by that name already exists, refusing to overwrite" { + t.Fatalf("expected error 'key by that name already exists, refusing to overwrite', got '%s'", err.Error()) + } + } +} + +func TestRenameOverwrite(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPI(ctx) + if err != nil { + t.Error(err) + } + + kfoo, err := api.Key().Generate(ctx, "foo") + if err != nil { + t.Fatal(err) + return + } + + _, err = api.Key().Generate(ctx, "bar") + if err != nil { + t.Fatal(err) + return + } + + k, overwrote, err := api.Key().Rename(ctx, "foo", "bar", api.Key().WithForce(true)) + if err != nil { + t.Fatal(err) + return + } + + if !overwrote { + t.Error("overwrote should be true") + } + + if k.Name() != "bar" { + t.Errorf("returned key should be called 'bar', got '%s'", k.Name()) + } + + if k.Path().String() != kfoo.Path().String() { + t.Errorf("k and kfoo should have equal paths, '%s'!='%s'", k.Path().String(), kfoo.Path().String()) + } +} + +func TestRemove(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPI(ctx) + if err != nil { + t.Error(err) + } + + k, err := api.Key().Generate(ctx, "foo") + if err != nil { + t.Fatal(err) + return + } + + l, err := api.Key().List(ctx) + if err != nil { + t.Fatal(err) + return + } + + if len(l) != 2 { + t.Fatalf("expected to get 2 keys, got %d", len(l)) + return + } + + p, err := api.Key().Remove(ctx, "foo") + if err != nil { + t.Fatal(err) + return + } + + if k.Path().String() != p.String() { + t.Errorf("k and p should have equal paths, '%s'!='%s'", k.Path().String(), p.String()) + } + + l, err = api.Key().List(ctx) + if err != nil { + t.Fatal(err) + return + } + + if len(l) != 1 { + t.Fatalf("expected to get 1 key, got %d", len(l)) + return + } + + if l[0].Name() != "self" { + t.Errorf("expected the key to be called 'self', got '%s'", l[0].Name()) + } +} diff --git a/core/coreapi/unixfs_test.go b/core/coreapi/unixfs_test.go index 01beccc20b4..fbcc2ebbb45 100644 --- a/core/coreapi/unixfs_test.go +++ b/core/coreapi/unixfs_test.go @@ -19,6 +19,8 @@ import ( unixfs "github.com/ipfs/go-ipfs/unixfs" cbor "gx/ipfs/QmeZv9VXw2SfVbX55LV6kGTWASKBc9ZxAVqGBeJcDGdoXy/go-ipld-cbor" + + "github.com/ipfs/go-ipfs/keystore" ) // `echo -n 'hello, world!' | ipfs add` @@ -39,6 +41,7 @@ func makeAPI(ctx context.Context) (*core.IpfsNode, coreiface.CoreAPI, error) { }, }, D: ds2.ThreadSafeCloserMapDatastore(), + K: keystore.NewMemKeystore(), } node, err := core.NewNode(ctx, &core.BuildCfg{Repo: r}) if err != nil { diff --git a/repo/mock.go b/repo/mock.go index 030c2ff28d1..8b72d92ddfc 100644 --- a/repo/mock.go +++ b/repo/mock.go @@ -44,7 +44,7 @@ func (m *Mock) Close() error { return errTODO } func (m *Mock) SetAPIAddr(addr ma.Multiaddr) error { return errTODO } -func (m *Mock) Keystore() keystore.Keystore { return nil } +func (m *Mock) Keystore() keystore.Keystore { return m.K } func (m *Mock) SwarmKey() ([]byte, error) { return nil, nil From 2109cbc1728b25c3f57d67cc920bc37dbb9cf194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 2 Jan 2018 00:53:48 +0100 Subject: [PATCH 7/7] coreapi: Name tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit License: MIT Signed-off-by: Łukasz Magiera --- core/coreapi/interface/interface.go | 5 +- core/coreapi/name_test.go | 144 ++++++++++++++++++++++++++++ core/coreapi/unixfs_test.go | 43 +++++++-- 3 files changed, 185 insertions(+), 7 deletions(-) create mode 100644 core/coreapi/name_test.go diff --git a/core/coreapi/interface/interface.go b/core/coreapi/interface/interface.go index dc836566991..cdbb2508bd0 100644 --- a/core/coreapi/interface/interface.go +++ b/core/coreapi/interface/interface.go @@ -157,7 +157,10 @@ type KeyAPI interface { WithType(algorithm string) options.KeyGenerateOption // WithSize is an option for Generate which specifies the size of the key to - // generated. Default is 0 + // generated. Default is -1 + // + // value of -1 means 'use default size for key type': + // * 2048 for RSA WithSize(size int) options.KeyGenerateOption // Rename renames oldName key to newName. Returns the key and whether another diff --git a/core/coreapi/name_test.go b/core/coreapi/name_test.go new file mode 100644 index 00000000000..74fdacda41b --- /dev/null +++ b/core/coreapi/name_test.go @@ -0,0 +1,144 @@ +package coreapi_test + +import ( + "context" + "io" + "math/rand" + "testing" + "time" + + ipath "github.com/ipfs/go-ipfs/path" + + coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" +) + +var rnd = rand.New(rand.NewSource(0x62796532303137)) + +func addTestObject(ctx context.Context, api coreiface.CoreAPI) (coreiface.Path, error) { + return api.Unixfs().Add(ctx, &io.LimitedReader{R: rnd, N: 4092}) +} + +func TestBasicPublishResolve(t *testing.T) { + ctx := context.Background() + n, api, err := makeAPIIdent(ctx, true) + if err != nil { + t.Fatal(err) + return + } + + p, err := addTestObject(ctx, api) + if err != nil { + t.Fatal(err) + return + } + + e, err := api.Name().Publish(ctx, p) + if err != nil { + t.Fatal(err) + return + } + + if e.Name() != n.Identity.Pretty() { + t.Errorf("expected e.Name to equal '%s', got '%s'", n.Identity.Pretty(), e.Name()) + } + + if e.Value().String() != p.String() { + t.Errorf("expected paths to match, '%s'!='%s'", e.Value().String(), p.String()) + } + + resPath, err := api.Name().Resolve(ctx, e.Name()) + if err != nil { + t.Fatal(err) + return + } + + if resPath.String() != p.String() { + t.Errorf("expected paths to match, '%s'!='%s'", resPath.String(), p.String()) + } +} + +func TestBasicPublishResolveKey(t *testing.T) { + ctx := context.Background() + _, api, err := makeAPIIdent(ctx, true) + if err != nil { + t.Fatal(err) + return + } + + k, err := api.Key().Generate(ctx, "foo") + if err != nil { + t.Fatal(err) + return + } + + p, err := addTestObject(ctx, api) + if err != nil { + t.Fatal(err) + return + } + + e, err := api.Name().Publish(ctx, p, api.Name().WithKey(k.Name())) + if err != nil { + t.Fatal(err) + return + } + + if ipath.Join([]string{"/ipns", e.Name()}) != k.Path().String() { + t.Errorf("expected e.Name to equal '%s', got '%s'", e.Name(), k.Path().String()) + } + + if e.Value().String() != p.String() { + t.Errorf("expected paths to match, '%s'!='%s'", e.Value().String(), p.String()) + } + + resPath, err := api.Name().Resolve(ctx, e.Name()) + if err != nil { + t.Fatal(err) + return + } + + if resPath.String() != p.String() { + t.Errorf("expected paths to match, '%s'!='%s'", resPath.String(), p.String()) + } +} + +func TestBasicPublishResolveTimeout(t *testing.T) { + t.Skip("ValidTime doesn't appear to work at this time resolution") + + ctx := context.Background() + n, api, err := makeAPIIdent(ctx, true) + if err != nil { + t.Fatal(err) + return + } + + p, err := addTestObject(ctx, api) + if err != nil { + t.Fatal(err) + return + } + + e, err := api.Name().Publish(ctx, p, api.Name().WithValidTime(time.Millisecond*100)) + if err != nil { + t.Fatal(err) + return + } + + if e.Name() != n.Identity.Pretty() { + t.Errorf("expected e.Name to equal '%s', got '%s'", n.Identity.Pretty(), e.Name()) + } + + if e.Value().String() != p.String() { + t.Errorf("expected paths to match, '%s'!='%s'", e.Value().String(), p.String()) + } + + time.Sleep(time.Second) + + _, err = api.Name().Resolve(ctx, e.Name()) + if err == nil { + t.Fatal("Expected an error") + return + } +} + +//TODO: When swarm api is created, add multinode tests diff --git a/core/coreapi/unixfs_test.go b/core/coreapi/unixfs_test.go index fbcc2ebbb45..c4b00e4b650 100644 --- a/core/coreapi/unixfs_test.go +++ b/core/coreapi/unixfs_test.go @@ -3,6 +3,7 @@ package coreapi_test import ( "bytes" "context" + "encoding/base64" "io" "math" "strings" @@ -12,15 +13,16 @@ import ( coreapi "github.com/ipfs/go-ipfs/core/coreapi" coreiface "github.com/ipfs/go-ipfs/core/coreapi/interface" coreunix "github.com/ipfs/go-ipfs/core/coreunix" + keystore "github.com/ipfs/go-ipfs/keystore" mdag "github.com/ipfs/go-ipfs/merkledag" repo "github.com/ipfs/go-ipfs/repo" config "github.com/ipfs/go-ipfs/repo/config" ds2 "github.com/ipfs/go-ipfs/thirdparty/datastore2" unixfs "github.com/ipfs/go-ipfs/unixfs" + peer "gx/ipfs/QmWNY7dV54ZDYmTA1ykVdwNCqC11mpU4zSUp6XDpLTH9eG/go-libp2p-peer" + ci "gx/ipfs/QmaPbCnUMBohSGo3KnxEa2bHqyJVVeEEcwtqJAYxerieBo/go-libp2p-crypto" cbor "gx/ipfs/QmeZv9VXw2SfVbX55LV6kGTWASKBc9ZxAVqGBeJcDGdoXy/go-ipld-cbor" - - "github.com/ipfs/go-ipfs/keystore" ) // `echo -n 'hello, world!' | ipfs add` @@ -33,12 +35,37 @@ var emptyDir = coreapi.ResolvedPath("/ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbs // `echo -n | ipfs add` var emptyFile = coreapi.ResolvedPath("/ipfs/QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH", nil, nil) -func makeAPI(ctx context.Context) (*core.IpfsNode, coreiface.CoreAPI, error) { +func makeAPIIdent(ctx context.Context, fullIdentity bool) (*core.IpfsNode, coreiface.CoreAPI, error) { + var ident config.Identity + if fullIdentity { + sk, pk, err := ci.GenerateKeyPair(ci.RSA, 512) + if err != nil { + return nil, nil, err + } + + id, err := peer.IDFromPublicKey(pk) + if err != nil { + return nil, nil, err + } + + kbytes, err := sk.Bytes() + if err != nil { + return nil, nil, err + } + + ident = config.Identity{ + PeerID: id.Pretty(), + PrivKey: base64.StdEncoding.EncodeToString(kbytes), + } + } else { + ident = config.Identity{ + PeerID: "Qmfoo", + } + } + r := &repo.Mock{ C: config.Config{ - Identity: config.Identity{ - PeerID: "Qmfoo", // required by offline node - }, + Identity: ident, }, D: ds2.ThreadSafeCloserMapDatastore(), K: keystore.NewMemKeystore(), @@ -51,6 +78,10 @@ func makeAPI(ctx context.Context) (*core.IpfsNode, coreiface.CoreAPI, error) { return node, api, nil } +func makeAPI(ctx context.Context) (*core.IpfsNode, coreiface.CoreAPI, error) { + return makeAPIIdent(ctx, false) +} + func TestAdd(t *testing.T) { ctx := context.Background() _, api, err := makeAPI(ctx)