diff --git a/go.mod b/go.mod index 36d2d0f..06afffa 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/libp2p/go-libp2p-record v0.2.0 github.com/libp2p/go-libp2p-routing-helpers v0.7.3 github.com/mitchellh/go-server-timing v1.0.1 + github.com/mr-tron/base58 v1.2.0 github.com/multiformats/go-multiaddr v0.11.0 github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 github.com/prometheus/client_golang v1.16.0 @@ -35,6 +36,7 @@ require ( go.opentelemetry.io/otel v1.16.0 go.opentelemetry.io/otel/sdk v1.16.0 go.opentelemetry.io/otel/trace v1.16.0 + golang.org/x/crypto v0.12.0 golang.org/x/sys v0.11.0 ) @@ -119,7 +121,6 @@ require ( github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/minio/sha256-simd v1.0.1 // indirect - github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect @@ -172,7 +173,6 @@ require ( go.uber.org/fx v1.20.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.25.0 // indirect - golang.org/x/crypto v0.12.0 // indirect golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.14.0 // indirect diff --git a/keys.go b/keys.go new file mode 100644 index 0000000..a22cf39 --- /dev/null +++ b/keys.go @@ -0,0 +1,45 @@ +package main + +import ( + "crypto/ed25519" + crand "crypto/rand" + "crypto/sha256" + "errors" + "io" + + libp2p "github.com/libp2p/go-libp2p/core/crypto" + "github.com/mr-tron/base58" + "golang.org/x/crypto/hkdf" +) + +const seedBytes = 32 + +// newSeed returns a b58 encoded random seed. +func newSeed() (string, error) { + bs := make([]byte, seedBytes) + _, err := io.ReadFull(crand.Reader, bs) + if err != nil { + return "", err + } + return base58.Encode(bs), nil +} + +// derive derives libp2p keys from a b58-encoded seed. +func deriveKey(b58secret string, info []byte) (libp2p.PrivKey, error) { + secret, err := base58.Decode(b58secret) + if err != nil { + return nil, err + } + if len(secret) < seedBytes { + return nil, errors.New("derivation seed is too short") + } + + hash := sha256.New + hkdf := hkdf.New(hash, secret, nil, info) + keySeed := make([]byte, ed25519.SeedSize) + if _, err := io.ReadFull(hkdf, keySeed); err != nil { + return nil, err + } + key := ed25519.NewKeyFromSeed(keySeed) + return libp2p.UnmarshalEd25519PrivateKey(key) +} diff --git a/main.go b/main.go index 8e50d1f..9fea8b1 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,8 @@ import ( "time" logging "github.com/ipfs/go-log/v2" + "github.com/libp2p/go-libp2p/core/crypto" + peer "github.com/libp2p/go-libp2p/core/peer" "github.com/urfave/cli/v2" "go.opentelemetry.io/contrib/propagators/autoprop" "go.opentelemetry.io/otel" @@ -35,6 +37,18 @@ func main() { Value: "", Usage: "specify the directory that cache data will be stored", }, + &cli.StringFlag{ + Name: "seed", + Value: "", + EnvVars: []string{"RAINBOW_SEED"}, + Usage: "Specify a seed to derive peerID from (needs --seed-index)", + }, + &cli.IntFlag{ + Name: "seed-index", + Value: -1, + EnvVars: []string{"RAINBOW_SEED_INDEX"}, + Usage: "Specify an index to derivate the peerID from the key (needs --seed)", + }, &cli.IntFlag{ Name: "gateway-port", Value: 8090, @@ -93,6 +107,27 @@ func main() { }, } + app.Commands = []*cli.Command{ + { + Name: "gen-seed", + Usage: "Generate a seed for key derivation", + Description: ` +Running this command will generate a random seed and print it. The value can +be used with the RAINBOW_SEED env-var to use key-derivation from a single seed +to create libp2p identities for the gateway. +`, + Flags: []cli.Flag{}, + Action: func(c *cli.Context) error { + seed, err := newSeed() + if err != nil { + return err + } + fmt.Println(seed) + return nil + }, + }, + } + app.Name = "rainbow" app.Usage = "a standalone ipfs gateway" app.Version = version @@ -101,6 +136,21 @@ func main() { cdns := newCachedDNS(dnsCacheRefreshInterval) defer cdns.Close() + var priv crypto.PrivKey + var err error + seed := cctx.String("seed") + + index := cctx.Int("seed-index") + if len(seed) > 0 && index >= 0 { + priv, err = deriveKey(seed, []byte(fmt.Sprintf("rainbow-%d", index))) + } else { + keyFile := filepath.Join(ddir, "libp2p.key") + priv, err = loadOrInitPeerKey(keyFile) + } + if err != nil { + return err + } + gnd, err := Setup(cctx.Context, Config{ DataDir: ddir, ConnMgrLow: cctx.Int("connmgr-low"), @@ -109,7 +159,7 @@ func main() { MaxMemory: cctx.Uint64("max-memory"), MaxFD: cctx.Int("max-fd"), InMemBlockCache: cctx.Int64("inmem-block-cache"), - Libp2pKeyFile: filepath.Join(ddir, "libp2p.key"), + Libp2pKey: priv, RoutingV1: cctx.String("routing"), KuboRPCURLs: getEnvs(EnvKuboRPC, DefaultKuboRPC), DHTSharedHost: cctx.Bool("dht-fallback-shared-host"), @@ -133,7 +183,12 @@ func main() { Handler: handler, } - fmt.Printf("Starting %s %s\n\n", name, version) + fmt.Printf("Starting %s %s\n", name, version) + pid, err := peer.IDFromPublicKey(priv.GetPublic()) + if err != nil { + return err + } + fmt.Printf("PeerID: %s\n\n", pid) registerVersionMetric(version) tp, shutdown, err := newTracerProvider(cctx.Context) diff --git a/setup.go b/setup.go index 1c0e24a..3e3d53e 100644 --- a/setup.go +++ b/setup.go @@ -3,7 +3,9 @@ package main import ( "context" crand "crypto/rand" + "errors" "fmt" + "io/fs" "net/http" "os" "path/filepath" @@ -83,7 +85,7 @@ type Config struct { ListenAddrs []string AnnounceAddrs []string - Libp2pKeyFile string + Libp2pKey crypto.PrivKey ConnMgrLow int ConnMgrHi int @@ -102,11 +104,6 @@ type Config struct { } func Setup(ctx context.Context, cfg Config) (*Node, error) { - peerkey, err := loadOrInitPeerKey(cfg.Libp2pKeyFile) - if err != nil { - return nil, err - } - ds, err := setupDatastore(cfg) if err != nil { return nil, err @@ -129,7 +126,7 @@ func Setup(ctx context.Context, cfg Config) (*Node, error) { libp2p.ListenAddrStrings(cfg.ListenAddrs...), libp2p.NATPortMap(), libp2p.ConnectionManager(cmgr), - libp2p.Identity(peerkey), + libp2p.Identity(cfg.Libp2pKey), libp2p.BandwidthReporter(bwc), libp2p.DefaultTransports, libp2p.DefaultMuxers, @@ -296,7 +293,7 @@ func Setup(ctx context.Context, cfg Config) (*Node, error) { bn.Start(bswap) err = os.Mkdir("denylists", 0755) - if err != nil { + if err != nil && !errors.Is(err, fs.ErrExist) { return nil, err }