commit 2f8bd41a607d578b727c1c8ee20f10b2cebb1bdc
parent 625b81777d0403bc36ebada9ecafd04071aef6f3
Author: parazyd <parazyd@dyne.org>
Date: Sun, 7 Mar 2021 20:07:26 +0100
Remove old code.
Diffstat:
M | README.md | | | 49 | ++++++++----------------------------------------- |
D | announce.go | | | 179 | ------------------------------------------------------------------------------- |
D | api.go | | | 191 | ------------------------------------------------------------------------------- |
D | config.go | | | 36 | ------------------------------------ |
A | contrib/tordam.png | | | 0 | |
D | crypto.go | | | 63 | --------------------------------------------------------------- |
D | helpers.go | | | 88 | ------------------------------------------------------------------------------- |
D | net.go | | | 83 | ------------------------------------------------------------------------------- |
D | redis.go | | | 141 | ------------------------------------------------------------------------------- |
D | tor-dam.go | | | 191 | ------------------------------------------------------------------------------- |
D | tor.go | | | 68 | -------------------------------------------------------------------- |
D | types.go | | | 37 | ------------------------------------- |
D | validate.go | | | 158 | ------------------------------------------------------------------------------- |
13 files changed, 8 insertions(+), 1276 deletions(-)
diff --git a/README.md b/README.md
@@ -1,55 +1,22 @@
tor-dam (Tor Distributed Announce Mechanism)
============================================
-Protocol and tooling for mapping machines in the Tor network running
-this software.
+![tordam](contrib/tordam.png)
-![Network visualization](https://raw.githubusercontent.com/parazyd/tor-dam/master/contrib/network.gif)
+A library for peer discovery inside the Tor network.
Installation
------------
```
-go get github.com/parazyd/tor-dam
+go get github.com/parazyd/tordam
```
-Usage
------
+Documentation
+-------------
-```
-Usage of ./tor-dam:
- -d string
- Working directory (default "/home/parazyd/.dam")
- -e int
- Node expiry time in minutes (0=unlimited)
- -g (Re)generate keys and exit
- -i int
- Announce interval (in minutes) (default 5)
- -n Don't fetch remote entrypoints
- -p string
- Map of ports forwarded to/from Tor (default "13010:13010,13011:13011,5000:5000")
- -r string
- Remote list of entrypoints (comma-separated) (default "https://parazyd.org/pub/tmp/tor-dam-dirs.txt")
- -t Trust all new nodes automatically
-```
+https://pkg.go.dev/github.com/parazyd/tordam
-Protocol
---------
-
-* Every node has an HTTP API allowing to list other nodes and announce
- new ones.
-* They keep propagating to all trusted nodes they know.
-* Announcing implies the need of knowledge of at least one node.
- * It is possible to make this random enough once there are at least
- 6 nodes in the network.
-* A node announces itself to others by sending a JSON-formatted HTTP
- POST request to one or more active nodes.
- * Once the initial POST request is received, the receiving node will
- ACK and return a random string (nonce) back to the requester for
- them to sign with their cryptographic key.
- * The requester will try to sign this nonce and return it back to
- the node it's announcing to, so the node can confirm the requester
- is in actual posession of the private key.
-* tor-dam **does not validate** if a node should be trusted or not.
- This is a layer that has to be implemented with external software.
+tor-dam is a small library that can be used to facilitate peer to peer
+services in the Tor network with simple mechanisms.
diff --git a/announce.go b/announce.go
@@ -1,179 +0,0 @@
-package main
-
-/*
- * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
- *
- * This file is part of tor-dam
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-import (
- "bytes"
- "compress/gzip"
- "crypto/ed25519"
- "crypto/rand"
- "encoding/base64"
- "encoding/json"
- "io/ioutil"
- "log"
- "math/big"
- "os"
- "strings"
-)
-
-func fetchNodeList(epLists []string, remote bool) ([]string, error) {
- var ns, nl []string
-
- log.Println("Building a list of nodes")
-
- // Remote network entrypoints
- if !remote {
- for _, i := range epLists {
- log.Println("Fetching", i)
- n, err := httpGet(i)
- if err != nil {
- return nil, err
- }
- ns = parseDirs(ns, n)
- }
- }
-
- // Local workdir/dirs.txt
- ld := strings.Join([]string{*workdir, "dirs.txt"}, "/")
- if _, err := os.Stat(ld); err == nil {
- ln, err := ioutil.ReadFile(ld)
- if err != nil {
- return nil, err
- }
- ns = parseDirs(ns, ln)
- }
-
- // Local nodes from redis
- nodes, _ := rcli.Keys(rctx, "*.onion").Result()
- for _, i := range nodes {
- valid, err := rcli.HGet(rctx, i, "valid").Result()
- if err != nil {
- // Possible RedisCli bug, possible Redis bug. To be investigated.
- // Sometimes it returns err, but it's empty and does not say what's
- // happening exactly.
- continue
- }
- if valid == "1" {
- ns = append(ns, i)
- }
- }
-
- // Remove possible dupes. Duplicates can cause race conditions and are
- // redundant to the entire logic.
- // TODO: Work this in above automatically (by changing the var type)
- encounter := map[string]bool{}
- for i := range ns {
- encounter[ns[i]] = true
- }
- ns = []string{}
- for key := range encounter {
- ns = append(ns, key)
- }
-
- if len(ns) < 1 {
- log.Fatal("Couldn't find any nodes to announce to. Exiting...")
- } else if len(ns) <= 6 {
- log.Printf("Found %d nodes\n", len(ns))
- nl = ns
- } else {
- log.Printf("Found %d nodes. Picking out 6 at random\n", len(ns))
- for i := 0; i <= 5; i++ {
- n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(ns))))
- nl = append(nl, ns[n.Int64()])
- ns[n.Int64()] = ns[len(ns)-1]
- ns = ns[:len(ns)-1]
- }
- }
-
- return nl, nil
-}
-
-func announce(addr string, vals map[string]string) (bool, error) {
- msg, _ := json.Marshal(vals)
-
- log.Println("Announcing keypair to", addr)
- resp, err := httpPost("http://"+addr+":49371"+"/announce", msg)
- if err != nil {
- return false, err
- }
-
- // Parse server's reply
- var m Message
- dec := json.NewDecoder(resp.Body)
- if err := dec.Decode(&m); err != nil {
- return false, err
- }
-
- if resp.StatusCode != 200 {
- log.Printf("%s returned error: %s\n", addr, m.Secret)
- return false, nil
- }
-
- log.Println("Got nonce from", addr)
-
- sig := ed25519.Sign(signingKey, []byte(m.Secret))
-
- vals["secret"] = m.Secret
- vals["message"] = m.Secret
- vals["signature"] = base64.StdEncoding.EncodeToString(sig)
- msg, _ = json.Marshal(vals)
-
- log.Println("Sending back signed secret to", addr)
- resp, err = httpPost("http://"+addr+":49371"+"/announce", msg)
- if err != nil {
- return false, err
- }
-
- dec = json.NewDecoder(resp.Body)
- if err := dec.Decode(&m); err != nil {
- return false, err
- }
-
- if resp.StatusCode != 200 {
- log.Printf("%s returned error: %s\n", addr, m.Secret)
- return false, nil
- }
-
- log.Printf("%s handshake valid\n", addr)
- data, err := base64.StdEncoding.DecodeString(m.Secret)
- if err != nil {
- // Not a list of nodes
- log.Printf("%s replied: %s\n", addr, m.Secret)
- return true, nil
- }
-
- log.Println("Got node data, processing...")
- b := bytes.NewReader(data)
- r, _ := gzip.NewReader(b)
- nodes := make(map[string]map[string]interface{})
- dec = json.NewDecoder(r)
- if err = dec.Decode(&nodes); err != nil {
- return false, err
- }
-
- for k, v := range nodes {
- log.Printf("Adding %s to redis\n", k)
- if _, err := rcli.HSet(rctx, k, v).Result(); err != nil {
- log.Fatal(err)
- }
- }
-
- return true, nil
-}
diff --git a/api.go b/api.go
@@ -1,191 +0,0 @@
-package main
-
-/*
- * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
- *
- * This file is part of tor-dam
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-import (
- "encoding/json"
- "log"
- "net/http"
- "strings"
-)
-
-func postback(rw http.ResponseWriter, data map[string]string, ret int) error {
- val, err := json.Marshal(data)
- if err != nil {
- return err
- }
-
- rw.Header().Set("Content-Type", "application/json")
- rw.WriteHeader(ret)
- if _, err := rw.Write(val); err != nil {
- return err
- }
- return nil
-}
-
-func handleAnnounce(rw http.ResponseWriter, req *http.Request) {
- var r map[string]string
- var n Node
-
- if req.Method != "POST" || req.Header["Content-Type"][0] != "application/json" {
- r = map[string]string{"secret": "Invalid request format"}
- if err := postback(rw, r, 400); err != nil {
- log.Fatal(err)
- }
- return
- }
-
- dec := json.NewDecoder(req.Body)
- if err := dec.Decode(&n); err != nil {
- log.Println("Failed decoding request:", err)
- return
- }
-
- // Bail out as soon as possible
- if len(n.Address) == 0 || len(n.Message) == 0 || len(n.Signature) == 0 {
- r = map[string]string{"secret": "Invalid request format"}
- if err := postback(rw, r, 400); err != nil {
- log.Fatal(err)
- }
- return
- }
-
- if !validateOnionAddress(n.Address) {
- log.Println("Invalid onion address:", n.Address)
- r = map[string]string{"secret": "Invalid onion address"}
- if err := postback(rw, r, 400); err != nil {
- log.Fatal(err)
- }
- return
- }
-
- rq := map[string]string{
- "address": n.Address,
- "message": n.Message,
- "pubkey": n.Pubkey,
- "signature": n.Signature,
- "secret": n.Secret,
- }
-
- // First handshake
- if len(n.Message) != 88 || len(n.Secret) != 88 {
- valid, msg := firstHandshake(rq)
- r = map[string]string{"secret": msg}
- if valid {
- log.Printf("%s: 1/2 handskake valid\n", n.Address)
- log.Println("Sending nonce to", n.Address)
- if err := postback(rw, r, 200); err != nil {
- log.Fatal(err)
- }
- return
- }
- log.Printf("%s: 1/2 handshake invalid: %s\n", n.Address, msg)
- // Delete it all from redis
- // TODO: Can this be abused?
- if _, err := rcli.Del(rctx, n.Address).Result(); err != nil {
- log.Fatal(err)
- }
- return
- }
-
- // Second handshake
- if len(rq["secret"]) == 88 && len(rq["message"]) == 88 {
- valid, msg := secondHandshake(rq)
- r = map[string]string{"secret": msg}
-
- if valid {
- log.Printf("%s: 2/2 handshake valid\n", n.Address)
- isTrusted, err := rcli.HGet(rctx, n.Address, "trusted").Result()
- if err != nil {
- log.Fatal(err)
- }
-
- // Assume our name is what was requested
- us := strings.TrimSuffix(req.Host, ":49371")
- nodemap := make(map[string]map[string]string)
-
- if isTrusted == "1" {
- // The node is marked as trusted so we'll teack it about other
- // trusted nodes we know about.
- log.Printf("%s is trusted. Propagating knowledge...\n", n.Address)
- nodes, err := rcli.Keys(rctx, "*.onion").Result()
- if err != nil {
- log.Fatal(err)
- }
- for _, i := range nodes {
- if i == n.Address {
- continue
- }
- nodedata, err := rcli.HGetAll(rctx, i).Result()
- if err != nil {
- log.Fatal(err)
- }
- if nodedata["trusted"] == "1" {
- nodemap[i] = nodedata
- delete(nodemap[i], "secret")
- }
- }
- } else {
- log.Printf("%s is not trusted. Propagating self...", n.Address)
- // The node doesn't have trust in the network. We will only
- // teach it about ourself.
- nodedata, err := rcli.HGetAll(rctx, us).Result()
- if err != nil {
- log.Fatal(err)
- }
- nodemap[us] = nodedata
- delete(nodemap[us], "secret")
- }
-
- nodestr, err := json.Marshal(nodemap)
- if err != nil {
- log.Fatal(err)
- }
- comp, err := gzipEncode(nodestr)
- if err != nil {
- log.Fatal(err)
- }
- r = map[string]string{"secret": comp}
- if err := postback(rw, r, 200); err != nil {
- log.Fatal(err)
- }
-
- publishToRedis('M', n.Address)
- return
- }
-
- // If we haven't returned so far, the handshake is invalid
- log.Printf("%s: 2/2 handshake invalid\n", n.Address)
- // Delete it all from redis
- // TODO: Can this be abused?
- publishToRedis('D', n.Address)
- if _, err := rcli.Del(rctx, n.Address).Result(); err != nil {
- log.Fatal(err)
- }
- if err := postback(rw, r, 400); err != nil {
- log.Fatal(err)
- }
- return
- }
-}
-
-func handleElse(rw http.ResponseWriter, req *http.Request) {
- log.Println("Got handleElse")
-}
diff --git a/config.go b/config.go
@@ -1,36 +0,0 @@
-package main
-
-/*
- * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
- *
- * This file is part of tor-dam
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-import (
- "crypto/ed25519"
- "net"
-)
-
-const (
- seedName = "ed25519.seed"
- pubsubChan = "tordam"
-)
-
-var (
- redisAddr *net.TCPAddr
- torAddr *net.TCPAddr
- signingKey ed25519.PrivateKey
-)
diff --git a/contrib/tordam.png b/contrib/tordam.png
Binary files differ.
diff --git a/crypto.go b/crypto.go
@@ -1,63 +0,0 @@
-package main
-
-/*
- * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
- *
- * This file is part of tor-dam
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-import (
- "crypto/ed25519"
- "crypto/rand"
- "encoding/base64"
- "io/ioutil"
- "log"
- "os"
- "strings"
-)
-
-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, seedName}, "/")
-
- 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
-}
diff --git a/helpers.go b/helpers.go
@@ -1,88 +0,0 @@
-package main
-
-/*
- * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
- *
- * This file is part of tor-dam
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-import (
- "bytes"
- "compress/gzip"
- "crypto/rand"
- "encoding/base64"
- "fmt"
- "math/big"
- "strings"
-)
-
-func genRandomASCII(length int) (string, error) {
- var res string
- for {
- if len(res) == length {
- return res, nil
- }
- num, err := rand.Int(rand.Reader, big.NewInt(int64(127)))
- if err != nil {
- return "", err
- }
- n := num.Int64()
- if n > 32 && n < 127 {
- res += fmt.Sprint(n)
- }
- }
-}
-
-func gzipEncode(data []byte) (string, error) {
- var b bytes.Buffer
- gz := gzip.NewWriter(&b)
- if _, err := gz.Write(data); err != nil {
- return "", err
- }
- if err := gz.Flush(); err != nil {
- return "", err
- }
- if err := gz.Close(); err != nil {
- return "", err
- }
-
- return base64.StdEncoding.EncodeToString(b.Bytes()), nil
-}
-
-func stringInSlice(str string, slice []string) bool {
- for _, i := range slice {
- if str == i {
- return true
- }
- }
- return false
-}
-
-func parseDirs(sl []string, data []byte) []string {
- dirstr := string(data)
- _dirs := strings.Split(dirstr, "\n")
- for _, i := range _dirs {
- if strings.HasPrefix(i, "DIR:") {
- t := strings.Split(i, "DIR:")
- if !stringInSlice(t[1], sl) {
- if validateOnionAddress(t[1]) {
- sl = append(sl, t[1])
- }
- }
- }
- }
- return sl
-}
diff --git a/net.go b/net.go
@@ -1,83 +0,0 @@
-package main
-
-/*
- * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
- *
- * This file is part of tor-dam
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-import (
- "bytes"
- "io/ioutil"
- "net"
- "net/http"
-
- "golang.org/x/net/proxy"
-)
-
-func getListener() (*net.TCPAddr, error) {
- addr, err := net.ResolveTCPAddr("tcp4", "localhost:0")
- if err != nil {
- return nil, err
- }
-
- l, err := net.ListenTCP("tcp4", addr)
- if err != nil {
- return nil, err
- }
- defer l.Close()
- return l.Addr().(*net.TCPAddr), nil
-}
-
-func httpPost(host string, data []byte) (*http.Response, error) {
- httpTransp := &http.Transport{}
- httpClient := &http.Client{Transport: httpTransp}
- dialer, err := proxy.SOCKS5("tcp", torAddr.String(), nil, proxy.Direct)
- if err != nil {
- return nil, err
- }
- httpTransp.Dial = dialer.Dial
-
- request, err := http.NewRequest("POST", host, bytes.NewBuffer(data))
- if err != nil {
- return nil, err
- }
-
- request.Header.Set("Content-Type", "application/json")
- return httpClient.Do(request)
-}
-
-func httpGet(uri string) ([]byte, error) {
- httpTransp := &http.Transport{}
- httpClient := &http.Client{Transport: httpTransp}
- dialer, err := proxy.SOCKS5("tcp", torAddr.String(), nil, proxy.Direct)
- if err != nil {
- return nil, err
- }
- httpTransp.Dial = dialer.Dial
-
- request, err := http.NewRequest("GET", uri, nil)
- if err != nil {
- return nil, err
- }
-
- res, err := httpClient.Do(request)
- if err != nil {
- return nil, err
- }
- defer res.Body.Close()
- return ioutil.ReadAll(res.Body)
-}
diff --git a/redis.go b/redis.go
@@ -1,141 +0,0 @@
-package main
-
-/*
- * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
- *
- * This file is part of tor-dam
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-import (
- "context"
- "fmt"
- "log"
- "os/exec"
- "strconv"
- "strings"
- "time"
-
- "github.com/go-redis/redis"
-)
-
-// rctx is the Redis context (necessary in newer go-redis)
-var rctx = context.Background()
-var rcli *redis.Client
-
-func pollPrune(interval int64) {
- for {
- log.Println("Polling redis for expired nodes")
- nodes, err := rcli.Keys(rctx, "*.onion").Result()
- if err != nil {
- log.Println("WARNING: Nonfatal error in pollPrune:", err.Error())
- }
- now := time.Now().Unix()
-
- for _, i := range nodes {
- res, err := rcli.HGet(rctx, i, "lastseen").Result()
- if err != nil {
- log.Println("WARNING: Nonfatal error in pollPrune:", err.Error())
- continue
- }
- ls, err := strconv.Atoi(res)
- if err != nil {
- log.Println("WARNING: Nonfatal error in pollPrune:", err.Error())
- continue
- }
-
- diff := (now - int64(ls)) / 60
- if diff > interval {
- log.Printf("Deleting %s (expired)\n", i)
- publishToRedis('D', i)
- rcli.Del(rctx, i)
- }
- }
- time.Sleep(time.Duration(interval) * time.Minute)
- }
-}
-
-func publishToRedis(mt rune, addr string) {
- data, err := rcli.HGetAll(rctx, addr).Result()
- if err != nil {
- log.Println("WARNING: Nonfatal err in publishToRedis:", err.Error())
- return
- }
-
- if data["lastseen"] == data["firstseen"] {
- mt = 'A'
- } else if mt != 'D' {
- mt = 'M'
- }
-
- // TODO: First of the "addr" references could be alias/nickname
-
- rcli.Publish(rctx, pubsubChan, fmt.Sprintf("%s|%s|%v|%s",
- data["lastseen"], addr, mt, addr))
-}
-
-func newredisrc(dir string) string {
- return fmt.Sprintf(`daemonize no
-bind %s
-port %d
-databases 1
-dir %s
-dbfilename tor-dam.rdb
-save 900 1
-save 300 10
-save 60 10000
-rdbcompression yes
-rdbchecksum yes
-stop-writes-on-bgsave-error no`,
- redisAddr.IP.String(), redisAddr.Port, dir)
-}
-
-func spawnRedis() (*exec.Cmd, error) {
- var err error
- redisAddr, err = getListener()
- if err != nil {
- return nil, err
- }
-
- rcli = redis.NewClient(&redis.Options{
- Addr: redisAddr.String(),
- Password: "",
- DB: 0,
- })
-
- log.Println("Forking Redis daemon on", redisAddr.String())
-
- cmd := exec.Command("redis-server", "-")
- cmd.Stdin = strings.NewReader(newredisrc(*workdir))
-
- err = cmd.Start()
- if err != nil {
- return nil, err
- }
-
- time.Sleep(500 * time.Millisecond)
- if _, err := rcli.Ping(rctx).Result(); err != nil {
- return cmd, err
- }
-
- pubsub := rcli.Subscribe(rctx, pubsubChan)
- if _, err := pubsub.Receive(rctx); err != nil {
- return cmd, err
- }
-
- log.Printf("Created \"%s\" channel in Redis\n", pubsubChan)
-
- return cmd, nil
-}
diff --git a/tor-dam.go b/tor-dam.go
@@ -1,191 +0,0 @@
-package main
-
-/*
- * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
- *
- * This file is part of tor-dam
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-import (
- "crypto/ed25519"
- "encoding/base64"
- "flag"
- "fmt"
- "io/ioutil"
- "log"
- "net"
- "net/http"
- "net/url"
- "os"
- "strconv"
- "strings"
- "sync"
- "time"
-)
-
-var (
- noremote = flag.Bool("n", false, "Don't fetch remote entrypoints")
- generate = flag.Bool("g", false, "(Re)generate keys and exit")
- annint = flag.Int("i", 5, "Announce interval (in minutes)")
- remote = flag.String("r", "https://parazyd.org/pub/tmp/tor-dam-dirs.txt",
- "Remote list of entrypoints (comma-separated)")
- portmap = flag.String("p", "13010:13010,13011:13011,5000:5000",
- "Map of ports forwarded to/from Tor")
- expiry = flag.Int64("e", 0, "Node expiry time in minutes (0=unlimited)")
- trustall = flag.Bool("t", false, "Trust all new nodes automatically")
- listen = "127.0.0.1:49371"
- //listen = flag.String("l", "127.0.0.1:49371",
- //"Listen address for daemon (Will also map in Tor HS)")
- workdir = flag.String("d", os.Getenv("HOME")+"/.dam", "Working directory")
-)
-
-func flagSanity() error {
- for _, i := range strings.Split(*remote, ",") {
- if _, err := url.ParseRequestURI(i); err != nil {
- return fmt.Errorf("invalid URL \"%s\" in remote entrypoints", i)
- }
- }
-
- for _, i := range strings.Split(*portmap, ",") {
- t := strings.Split(i, ":")
- if len(t) != 2 {
- return fmt.Errorf("invalid portmap: %s (len != 2)", i)
- }
- if _, err := strconv.Atoi(t[0]); err != nil {
- return fmt.Errorf("invalid portmap: %s (%s)", i, err)
- }
- if _, err := strconv.Atoi(t[1]); err != nil {
- return fmt.Errorf("invalid portmap: %s (%s)", i, err)
- }
- }
-
- if _, err := net.ResolveTCPAddr("tcp", listen); err != nil {
- return fmt.Errorf("invalid listen address: %s (%s)", listen, err)
- }
-
- return nil
-}
-
-func main() {
- flag.Parse()
- var wg sync.WaitGroup
- var err error
-
- if err := flagSanity(); err != nil {
- log.Fatal(err)
- }
-
- if *generate {
- if err := generateED25519Keypair(*workdir); err != nil {
- log.Fatal(err)
- }
- os.Exit(0)
- }
-
- signingKey, err = loadED25519Seed(strings.Join(
- []string{*workdir, seedName}, "/"))
- if err != nil {
- log.Fatal(err)
- }
-
- tor, err := spawnTor()
- defer tor.Process.Kill()
- if err != nil {
- log.Fatal(err)
- }
-
- red, err := spawnRedis()
- defer red.Process.Kill()
- if err != nil {
- log.Fatal(err)
- }
-
- mux := http.NewServeMux()
- mux.HandleFunc("/announce", handleAnnounce)
- mux.HandleFunc("/", handleElse)
- srv := &http.Server{
- Addr: listen,
- Handler: mux,
- ReadTimeout: 30 * time.Second,
- WriteTimeout: 30 * time.Second,
- }
-
- go srv.ListenAndServe()
- log.Println("tor-dam directory listening on", listen)
-
- if *trustall {
- log.Println("Trustall enabled, will mark all nodes trusted by default")
- }
-
- if *expiry > 0 {
- log.Printf("Enabling db prune polling (%d minute interval)\n", *expiry)
- go pollPrune(*expiry)
- }
-
- onionaddr, err := ioutil.ReadFile(strings.Join([]string{
- *workdir, "hs", "hostname"}, "/"))
- if err != nil {
- log.Fatal(err)
- }
- onionaddr = []byte(strings.TrimSuffix(string(onionaddr), "\n"))
- log.Printf("Our hostname is: %s\n", string(onionaddr))
-
- // Network entrypoints. These files hold the lists of nodes we can announce
- // to initially. Format is "DIR:unlikelynameforan.onion", other lines are
- // ignored and can be used as comments or siimilar.
- epLists := strings.Split(*remote, ",")
-
- for {
- log.Println("Announcing to nodes...")
- var ann = 0 // Track of successful authentications
- nodes, err := fetchNodeList(epLists, *noremote)
- if err != nil {
- // No route to host, or failed download. Try later.
- log.Printf("Failed to fetch nodes, retrying in 1m (%s)\n", err)
- time.Sleep(60 * time.Second)
- continue
- }
-
- sigmsg := []byte("Hi tor-dam!")
-
- nv := map[string]string{
- "address": string(onionaddr),
- "pubkey": base64.StdEncoding.EncodeToString(signingKey.Public().(ed25519.PublicKey)),
- "message": string(sigmsg),
- "signature": base64.StdEncoding.EncodeToString(ed25519.Sign(signingKey, sigmsg)),
- "secret": "",
- }
-
- for _, i := range nodes {
- wg.Add(1)
- go func(x string) {
- valid, err := announce(x, nv)
- if err != nil {
- log.Printf("%s: %s\n", x, err)
- }
- if valid {
- ann++
- }
- wg.Done()
- }(i)
- }
- wg.Wait()
-
- log.Printf("%d successful authentications\n", ann)
- log.Printf("Waiting %d min before next announce\n", *annint)
- time.Sleep(time.Duration(*annint) * time.Minute)
- }
-}
diff --git a/tor.go b/tor.go
@@ -1,68 +0,0 @@
-package main
-
-/*
- * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
- *
- * This file is part of tor-dam
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-import (
- "fmt"
- "log"
- "os/exec"
- "strings"
-)
-
-func newtorrc(dir string) string {
- var pm []string
-
- for _, i := range strings.Split(*portmap, ",") {
- p := strings.Split(i, ":")
- pm = append(pm, fmt.Sprintf("HiddenServicePort %s %s",
- p[0], strings.Join([]string{"127.0.0.1", p[1]}, ":")))
- }
-
- return fmt.Sprintf(`Log warn syslog
-RunAsDaemon 0
-DataDirectory %s/tor
-SocksPort %s
-HiddenServiceDir %s/hs
-HiddenServicePort %s %s
-%s
-`,
- dir, torAddr.String(), dir, strings.Split(listen, ":")[1],
- listen, strings.Join(pm, "\n"))
-}
-
-func spawnTor() (*exec.Cmd, error) {
- var err error
- torAddr, err = getListener()
- if err != nil {
- return nil, err
- }
-
- log.Println("Forking Tor daemon on", torAddr.String())
-
- cmd := exec.Command("tor", "-f", "-")
- cmd.Stdin = strings.NewReader(newtorrc(*workdir))
-
- err = cmd.Start()
- if err != nil {
- return nil, err
- }
-
- return cmd, nil
-}
diff --git a/types.go b/types.go
@@ -1,37 +0,0 @@
-package main
-
-/*
- * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
- *
- * This file is part of tor-dam
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-// Message represents the message struct type
-type Message struct {
- Secret string `json:"secret"`
-}
-
-// Node represents the node struct type
-type Node struct {
- Address string `json:"address"`
- Message string `json:"message"`
- Signature string `json:"signature"`
- Secret string `json:"secret"`
- Pubkey string `json:"pubkey"`
- Firstseen int64 `json:"firstseen"`
- Lastseen int64 `json:"lastseen"`
- Trusted int `json:"trusted"`
-}
diff --git a/validate.go b/validate.go
@@ -1,158 +0,0 @@
-package main
-
-/*
- * Copyright (c) 2017-2021 Ivan Jelincic <parazyd@dyne.org>
- *
- * This file is part of tor-dam
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-import (
- "crypto/ed25519"
- "encoding/base64"
- "log"
- "regexp"
- "time"
-)
-
-func validateOnionAddress(addr string) bool {
- re, _ := regexp.Compile(`^[a-z2-7](?:.{55})\.onion`)
- return len(re.FindString(addr)) == 62
-}
-
-// firstHandshake will take the incoming public key either from the request
-// or, if found, from redis. This key is stored, and a nonce is generated.
-// This nonce is returned back to the client to sign with the key. In the
-// second handshake, we verify this nonce signature against the retrieved
-// public key.
-func firstHandshake(req map[string]string) (bool, string) {
- var pubstr string
-
- // Check if we have seen this node already
- ex, err := rcli.Exists(rctx, req["address"]).Result()
- if err != nil {
- log.Fatal(err)
- }
-
- if ex == 1 {
- // We saw it so we should hae the public key stored in redis.
- // If we do not, that is an internal error.
- pubstr, err = rcli.HGet(rctx, req["address"], "pubkey").Result()
- if err != nil {
- log.Fatal(err)
- }
- } else {
- // We take it from the request
- pubstr = req["pubkey"]
- }
-
- randString, err := genRandomASCII(64)
- if err != nil {
- log.Fatal(err)
- }
-
- enc := base64.StdEncoding.EncodeToString([]byte(randString))
-
- var info = map[string]interface{}{
- "address": req["address"],
- "message": enc,
- "signature": req["signature"],
- "secret": enc,
- "lastseen": time.Now().Unix(),
- } // Can not cast, need this for HSet
-
- if ex != 1 {
- // We did not have this node in redis
- info["pubkey"] = pubstr
- info["firstseen"] = time.Now().Unix()
- if *trustall {
- info["trusted"] = 1
- } else {
- info["trusted"] = 0
- }
- }
-
- log.Printf("%s: Writing to redis\n", req["address"])
- if _, err := rcli.HSet(rctx, req["address"], info).Result(); err != nil {
- log.Fatal(err)
- }
-
- return true, enc
-}
-
-func secondHandshake(req map[string]string) (bool, string) {
- // Check if we have seen this node already
- ex, err := rcli.Exists(rctx, req["address"]).Result()
- if err != nil {
- log.Fatal(err)
- }
- if ex != 1 {
- log.Printf("%s tried to jump in 2/2 handshake before getting a nonce\n",
- req["address"])
- return false, "We have not seen you before. Authenticate properly."
- }
-
- // We saw it so we should have the public key in redis. If we do not,
- // then it's an internal error.
- pubstr, err := rcli.HGet(rctx, req["address"], "pubkey").Result()
- if err != nil {
- log.Fatal(err)
- }
-
- lSec, err := rcli.HGet(rctx, req["address"], "secret").Result()
- if err != nil {
- log.Fatal(err)
- }
-
- if lSec != req["secret"] || lSec != req["message"] {
- log.Printf("%s: Secrets didn't match\n", req["address"])
- return false, "Secrets didn't match."
- }
-
- // Validate signature.
- msg := []byte(lSec)
- sig, _ := base64.StdEncoding.DecodeString(req["signature"])
- deckey, err := base64.StdEncoding.DecodeString(pubstr)
- if err != nil {
- log.Fatal(err)
- }
- pubkey := ed25519.PublicKey(deckey)
-
- if !ed25519.Verify(pubkey, msg, sig) {
- log.Println("crypto/ed25519: Signature verification failure")
- return false, "Signature verification failure"
- }
-
- // The request is valid at this point
-
- // Make a new random secret to prevent reuse.
- randString, _ := genRandomASCII(64)
- encSecret := base64.StdEncoding.EncodeToString([]byte(randString))
-
- var info = map[string]interface{}{
- "address": req["address"],
- "message": encSecret,
- "signature": req["signature"],
- "secret": encSecret,
- "lastseen": time.Now().Unix(),
- } // TODO: Use struct
-
- log.Printf("Adding %s to redis\n", req["address"])
- if _, err := rcli.HSet(rctx, req["address"], info).Result(); err != nil {
- log.Fatal(err)
- }
-
- return true, "Welcome to tor-dam"
-}