tordam

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

commit 7a90ac48a57eafee638e1c2b720487bc188ccdb5
parent 9bb95c0fdbb1c0bc6d70ad5d0b9e131141fc1e66
Author: parazyd <parazyd@dyne.org>
Date:   Wed, 20 Dec 2017 16:03:13 +0100

Add propagation functionality.

On success of the 2/2 handshake, the directory will evaulate if the node
is valid in the redis database, and if so, propagate back all the valid
nodes it knows. The client handles this accordingly and updates its own
redis database with the received information.

Diffstat:
Mcmd/dam-client/main.go | 29++++++++++++++++++++++++++---
Mcmd/dam-dir/main.go | 50+++++++++++++++++++++++++++++++++++++++++++++++++-
Mpkg/damlib/helpers.go | 20++++++++++++++++++++
Mprotocol.md | 13++++++++-----
4 files changed, 103 insertions(+), 9 deletions(-)

diff --git a/cmd/dam-client/main.go b/cmd/dam-client/main.go @@ -4,6 +4,8 @@ package main import ( "bufio" + "bytes" + "compress/gzip" "crypto/rsa" "encoding/base64" "encoding/json" @@ -92,14 +94,35 @@ func announce(dir string, vals map[string]string, privkey *rsa.PrivateKey) (bool return false, err } decoder = json.NewDecoder(resp.Body) - err = decoder.Decode(&m) - if err != nil { + if err = decoder.Decode(&m); err != nil { return false, err } if resp.StatusCode == 200 { log.Printf("%s: Success. 2/2 handshake valid.\n", dir) - log.Printf("%s: Reply: %s\n", dir, m.Secret) + // TODO: To TOFU or not to TOFU? + data, err := base64.StdEncoding.DecodeString(m.Secret) + if err != nil { + // Not a list of nodes. + log.Printf("%s: Reply: %s\n", dir, 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{}) + decoder = json.NewDecoder(r) + if err = decoder.Decode(&nodes); err != nil { + return false, err + } + for k, v := range nodes { + log.Printf("Adding %s to redis\n", k) + redRet, err := lib.RedisCli.HMSet(k, v).Result() + lib.CheckError(err) + if redRet != "OK" { + log.Println("Redis returned:", redRet) + } + } return true, nil } log.Printf("%s: Fail. Reply: %s\n", dir, m.Secret) diff --git a/cmd/dam-dir/main.go b/cmd/dam-dir/main.go @@ -8,6 +8,7 @@ import ( "net/http" "os" "os/exec" + "strings" "sync" "time" @@ -122,14 +123,61 @@ func handlePost(rw http.ResponseWriter, request *http.Request) { if len(req["secret"]) == 88 && len(req["message"]) == 88 { valid, msg := lib.ValidateSecondHandshake(req) ret = map[string]string{"secret": msg} + if valid { log.Printf("%s: 2/2 handshake valid.\n", n.Address) - log.Println("Sending back welcome message.") + hasConsensus, err := lib.RedisCli.HGet(n.Address, "valid").Result() + lib.CheckError(err) + + us := request.Host // Assume our name is what was requested as the URL. + if strings.HasPrefix(us, "localhost") { + // No need to propagate to ourself. + ret = map[string]string{"secret": "Welcome to the DAM network!"} + if err := postback(rw, ret, 200); err != nil { + lib.CheckError(err) + } + return + } + + nodemap := make(map[string]map[string]string) + + if hasConsensus == "1" { + // The node does have consensus, we'll teach it about the valid + // nodes we know. + log.Printf("%s has consensus. Propagating our nodes to it...\n", n.Address) + nodes, err := lib.RedisCli.Keys("*.onion").Result() + lib.CheckError(err) + for _, i := range nodes { + if i == n.Address { + continue + } + nodedata, err := lib.RedisCli.HGetAll(i).Result() + lib.CheckError(err) + if nodedata["valid"] == "1" { + nodemap[i] = nodedata + } + } + } else { + log.Printf("%s does not have consensus. Propagating ourself to it...\n", n.Address) + // The node doesn't have consensus in the network. We will only + // teach it about ourself. + nodedata, err := lib.RedisCli.HGetAll(us).Result() + lib.CheckError(err) + nodemap[us] = nodedata + } + + nodestr, err := json.Marshal(nodemap) + lib.CheckError(err) + comp, err := lib.GzipEncode(nodestr) + lib.CheckError(err) + ret = map[string]string{"secret": comp} if err := postback(rw, ret, 200); err != nil { lib.CheckError(err) } return } + + // If we have't returned so far, the handshake is invalid. log.Printf("%s: 2/2 handshake invalid.\n", n.Address) // Delete it all from redis. _, err := lib.RedisCli.Del(n.Address).Result() diff --git a/pkg/damlib/helpers.go b/pkg/damlib/helpers.go @@ -3,6 +3,9 @@ package damlib // See LICENSE file for copyright and license details. import ( + "bytes" + "compress/gzip" + "encoding/base64" "log" ) @@ -24,3 +27,20 @@ func StringInSlice(str string, slice []string) bool { } return false } + +// GzipEncode compresses a given string using gzip, and returns it as a base64 +// encoded string. Returns error upon failure. +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 +} diff --git a/protocol.md b/protocol.md @@ -93,9 +93,10 @@ this time will contain the following data: ``` The directory will verify the received plain secret against what it has -encrypted to validate. If the comparison yields no errors, we assume that -the requester is actually in possession of the private key. We will now -complete the handshake by welcoming the client into the network: +encrypted to validate. If the comparison yields no errors, we assume +that the requester is actually in possession of the private key. If the +node is not valid in our database, we will complete the handshake by +welcoming the client into the network: ``` @@ -110,5 +111,7 @@ first seen and last seen, and a field to indicate if the node is valid. The latter is not to be handled by Tor-DAM, but rather the upper layer, which actually has consensus handling. -Once a node is considered not malicious by a defined number of nodes, the -directories can then keep propagating addresses of other nodes to it. +If the node is valid in the directory's database, the directory will +then propagate back all the valid nodes it knows (including itself) back +to the client in a gzipped and base64 encoded JSON struct. The client +will then handle this and update its own database accordingly.