tordam

A library for peer discovery inside the Tor network
git clone https://git.parazyd.org/tordam
Log | Files | Refs | README | LICENSE

commit 9a3c22f8e25ae7dd29c5c5869191ad8d5ca8d233
parent 2f66ffd8201aa31aba279822a292125485bffe51
Author: parazyd <parazyd@dyne.org>
Date:   Sun,  7 Mar 2021 21:31:59 +0100

Add integration example in cmd/tor-dam.

Diffstat:
MREADME.md | 3+++
Mannounce_test.go | 4++--
Acmd/tor-dam/tor-dam.go | 187+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mconfig.go | 10++++------
Mgo.mod | 5+++++
Ago.sum | 18++++++++++++++++++
Mrpc_announce.go | 6+++---
7 files changed, 222 insertions(+), 11 deletions(-)

diff --git a/README.md b/README.md @@ -20,3 +20,6 @@ https://pkg.go.dev/github.com/parazyd/tordam tor-dam is a small library that can be used to facilitate peer to peer services in the Tor network with simple mechanisms. + +A basic integration example can be found and reviewed in the form of +a single go file: [cmd/tor-dam/tor-dam.go](cmd/tor-dam/tor-dam.go). diff --git a/announce_test.go b/announce_test.go @@ -37,7 +37,7 @@ func TestAnnounce(t *testing.T) { "12345:54321,666:3521", } - ret, err := ann.Init(ann{}, context.Background(), vals) + ret, err := Ann.Init(Ann{}, context.Background(), vals) if err != nil { t.Fatal(err) } @@ -52,7 +52,7 @@ func TestAnnounce(t *testing.T) { base64.StdEncoding.EncodeToString(ed25519.Sign(sk, []byte(ret[0]))), } - ret, err = ann.Validate(ann{}, context.Background(), vals) + ret, err = Ann.Validate(Ann{}, context.Background(), vals) if err != nil { t.Fatal(err) } diff --git a/cmd/tor-dam/tor-dam.go b/cmd/tor-dam/tor-dam.go @@ -0,0 +1,187 @@ +// Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org> +// +// This file is part of tordam +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +package main + +import ( + "crypto/ed25519" + "crypto/rand" + "encoding/base64" + "flag" + "io/ioutil" + "log" + "net" + "os" + "strings" + "sync" + "time" + + "github.com/creachadair/jrpc2" + "github.com/creachadair/jrpc2/handler" + "github.com/creachadair/jrpc2/server" + "github.com/parazyd/tordam" +) + +var ( + generate = flag.Bool("g", false, "(Re)generate keys and exit") + portmap = flag.String("m", "13010:13010,13011:13011", "Map of ports forwarded to/from Tor") + listen = flag.String("l", "127.0.0.1:49371", "Local listen address") + datadir = flag.String("datadir", os.Getenv("HOME")+"/.dam", "Data directory") + seeds = flag.String("s", + "p7qaewjgnvnaeihhyybmoofd5avh665kr3awoxlh5rt6ox743kjdr6qd.onion:49371", + "List of initial peers (comma-separated)") + noannounce = flag.Bool("n", false, "Do not announce to peers") +) + +func generateED25519Keypair(dir string) error { + _, sk, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + return err + } + if err := os.MkdirAll(dir, 0700); err != nil { + return err + } + + seedpath := strings.Join([]string{dir, "ed25519.seed"}, "/") + log.Println("Writing ed25519 key seed to", seedpath) + return ioutil.WriteFile(seedpath, + []byte(base64.StdEncoding.EncodeToString(sk.Seed())), 0600) +} + +func loadED25519Seed(file string) (ed25519.PrivateKey, error) { + log.Println("Reading ed25519 seed from", file) + + data, err := ioutil.ReadFile(file) + if err != nil { + return nil, err + } + dec, err := base64.StdEncoding.DecodeString(string(data)) + if err != nil { + return nil, err + } + return ed25519.NewKeyFromSeed(dec), nil +} + +func main() { + flag.Parse() + var wg sync.WaitGroup + var err error + + if *generate { + if err := generateED25519Keypair(*datadir); err != nil { + log.Fatal(err) + } + os.Exit(0) + } + + // Validate given seeds + for _, i := range strings.Split(*seeds, ",") { + if err := tordam.ValidateOnionInternal(i); err != nil { + log.Fatalf("invalid seed %s (%v)", i, err) + } + } + + // Assign portmap to tordam Cfg global and validate it + tordam.Cfg.Portmap = strings.Split(*portmap, ",") + if err := tordam.ValidatePortmap(tordam.Cfg.Portmap); err != nil { + log.Fatal(err) + } + + // Validate and assign the local listening address + tordam.Cfg.Listen, err = net.ResolveTCPAddr("tcp", *listen) + if err != nil { + log.Fatal("invalid listen address: %s (%v)", *listen, err) + } + + // Assign the global tordam data directory + tordam.Cfg.Datadir = *datadir + + // Load the ed25519 signing key into the tordam global + tordam.SignKey, err = loadED25519Seed(strings.Join( + []string{*datadir, "ed25519.seed"}, "/")) + if err != nil { + log.Fatal(err) + } + + // Spawn Tor daemon and let it settle + tor, err := tordam.SpawnTor(tordam.Cfg.Listen, tordam.Cfg.Portmap, + tordam.Cfg.Datadir) + defer tor.Process.Kill() + if err != nil { + log.Fatal(err) + } + time.Sleep(2 * time.Second) + log.Println("Started Tor daemon on", tordam.Cfg.TorAddr.String()) + + // Read the onion hostname from the datadir and map it into the + // global tordam.Onion variable + onionaddr, err := ioutil.ReadFile(strings.Join([]string{ + tordam.Cfg.Datadir, "hs", "hostname"}, "/")) + if err != nil { + log.Fatal(err) + } + onionaddr = []byte(strings.TrimSuffix(string(onionaddr), "\n")) + tordam.Onion = strings.Join([]string{ + string(onionaddr), string(tordam.Cfg.Listen.Port)}, ":") + log.Println("Our onion address is:", tordam.Onion) + + // Start the JSON-RPC server with announce endpoints. + // This is done in the library user rather than internally in the library + // because it is more useful and easier to add additional JSON-RPC + // endpoints to the same server. + l, err := net.Listen(jrpc2.Network(tordam.Cfg.Listen.String()), + tordam.Cfg.Listen.String()) + if err != nil { + log.Fatal(err) + } + defer l.Close() + // Endpoints are assigned here + assigner := handler.ServiceMap{ + // "ann" is the JSON-RPC endpoint for peer discovery/announcement + "ann": handler.NewService(tordam.Ann{}), + } + go server.Loop(l, server.NewStatic(assigner), nil) + log.Println("Started JSON-RPC server on", tordam.Cfg.Listen.String()) + + // If decided to not announce to anyone + if *noannounce { + // We shall sit here and wait + wg.Add(1) + wg.Wait() + } + + // Announce to initial seeds + var succ int = 0 // Track of successful announces + for _, i := range strings.Split(*seeds, ",") { + wg.Add(1) + go func(x string) { + if err := tordam.Announce(i); err != nil { + log.Println("error in announce:", err) + } else { + succ++ + } + wg.Done() + }(i) + } + wg.Wait() + + if succ < 1 { + log.Fatal("No successful announces.") + } else { + log.Printf("Successfully announced to %d peers.", succ) + } +} diff --git a/config.go b/config.go @@ -24,12 +24,10 @@ import ( // Config is the configuration structure, to be filled by library user. type Config struct { - Listen *net.TCPAddr // Local listen address for the JSON-RPC server - TorAddr *net.TCPAddr // Tor SOCKS5 proxy address, filled by SpawnTor() - Datadir string // Path to data directory - Portmap []string // The peer's portmap, to be mapped in the Tor HS - Seeds []string // Initial peer(s) - Announce bool // Announce or not + Listen *net.TCPAddr // Local listen address for the JSON-RPC server + TorAddr *net.TCPAddr // Tor SOCKS5 proxy address, filled by SpawnTor() + Datadir string // Path to data directory + Portmap []string // The peer's portmap, to be mapped in the Tor HS } // SignKey is an ed25519 private key, to be assigned by library user. diff --git a/go.mod b/go.mod @@ -1,3 +1,8 @@ module github.com/parazyd/tordam go 1.16 + +require ( + github.com/creachadair/jrpc2 v0.12.0 // indirect + golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect +) diff --git a/go.sum b/go.sum @@ -0,0 +1,18 @@ +bitbucket.org/creachadair/shell v0.0.6/go.mod h1:8Qqi/cYk7vPnsOePHroKXDJYmb5x7ENhtiFtfZq8K+M= +bitbucket.org/creachadair/stringset v0.0.9 h1:L4vld9nzPt90UZNrXjNelTshD74ps4P5NGs3Iq6yN3o= +bitbucket.org/creachadair/stringset v0.0.9/go.mod h1:t+4WcQ4+PXTa8aQdNKe40ZP6iwesoMFWAxPGd3UGjyY= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/creachadair/jrpc2 v0.12.0 h1:cr7QMg8JYLubTneMy7UCQLWPD6LObJ7ZK8T5GeiwbLQ= +github.com/creachadair/jrpc2 v0.12.0/go.mod h1:aACneXzxBPPoiu+nAo5duWP8L4y0//yuHkpkW9uDpo8= +github.com/creachadair/staticfile v0.1.3/go.mod h1:a3qySzCIXEprDGxk6tSxSI+dBBdLzqeBOMhZ+o2d3pM= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/rpc_announce.go b/rpc_announce.go @@ -26,7 +26,7 @@ import ( "time" ) -type ann struct{} +type Ann struct{} // Init takes three parameters: // - onion: onionaddress:port where the peer and tordam can be reached @@ -46,7 +46,7 @@ type ann struct{} // "result": ["somenonce", "somerevokekey"] // } // On any kind of failure returns an error and the reason. -func (ann) Init(ctx context.Context, vals []string) ([]string, error) { +func (Ann) Init(ctx context.Context, vals []string) ([]string, error) { if len(vals) != 3 && len(vals) != 4 { return nil, errors.New("invalid parameters") } @@ -137,7 +137,7 @@ func (ann) Init(ctx context.Context, vals []string) ([]string, error) { // "result": ["unlikelynameforan.onion:69", "yetanother.onion:420"] // } // On any kind of failure returns an error and the reason. -func (ann) Validate(ctx context.Context, vals []string) ([]string, error) { +func (Ann) Validate(ctx context.Context, vals []string) ([]string, error) { if len(vals) != 2 { return nil, errors.New("invalid parameters") }