tordam

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

commit 1d6eeec83217c844e0dc3b723e640fff551786d4
parent 883e09cc342d50cc90671f6b6f09364565c252c8
Author: parazyd <parazyd@dyne.org>
Date:   Thu,  7 Dec 2017 17:27:22 +0100

add go implementation

Diffstat:
Ago/dam/dam.go | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ago/ddir/ddir.go | 40++++++++++++++++++++++++++++++++++++++++
Ago/ddir/dirauth.py | 15+++++++++++++++
Ago/lib/crypto.go | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ago/lib/helpers.go | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 329 insertions(+), 0 deletions(-)

diff --git a/go/dam/dam.go b/go/dam/dam.go @@ -0,0 +1,56 @@ +package main + +// See LICENSE file for copyright and license details. + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "log" + "net/http" + "os" + + "../lib" +) + +// Bits hold the size of our RSA private key. Tor standard is 1024. +var Bits = 1024 + +// Privpath holds the path of where our private key is. +var Privpath = "private.key" + +// Pubpath holds the path of where our public key is. +var Pubpath = "public.key" + +func main() { + if _, err := os.Stat("private.key"); os.IsNotExist(err) { + key := lib.GenRsa(Bits) + lib.SavePriv(Privpath, key) + lib.SavePub(Pubpath, key.PublicKey) + } + + key, err := lib.LoadKeyFromFile(Privpath) + lib.CheckError(err) + + sig := lib.SignMsg([]byte("I am a DECODE node!"), key) + encodedSig := base64.StdEncoding.EncodeToString(sig) + + vals := map[string]string{ + "nodetype": "node", + "address": lib.OnionFromPubkey(key.PublicKey), + "message": "I'm a DECODE node!", + "signature": encodedSig, + } + + log.Println("Announcing keypair for:", vals["address"]) + + jsonVal, err := json.Marshal(vals) + lib.CheckError(err) + + log.Println("Sending request") + resp, err := http.Post("http://localhost:8080/announce", "application/json", + bytes.NewBuffer(jsonVal)) + lib.CheckError(err) + + log.Println(resp) +} diff --git a/go/ddir/ddir.go b/go/ddir/ddir.go @@ -0,0 +1,40 @@ +package main + +import ( + "encoding/json" + "log" + "net/http" + + "../lib" +) + +type nodeStruct struct { + Nodetype string + Address string + Message string + Signature string +} + +func parsePost(rw http.ResponseWriter, request *http.Request) { + decoder := json.NewDecoder(request.Body) + + var n nodeStruct + err := decoder.Decode(&n) + lib.CheckError(err) + + req := map[string]string{ + "nodetype": n.Nodetype, + "address": n.Address, + "message": n.Message, + "signature": n.Signature, + } + + if lib.ValidateReq(req) != true { + log.Fatal("Request is not valid.") + } +} + +func main() { + http.HandleFunc("/announce", parsePost) + http.ListenAndServe(":8080", nil) +} diff --git a/go/ddir/dirauth.py b/go/ddir/dirauth.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# See LICENSE file for copyright and license details. +""" +Retrieves and prints a hidden service's public key to stdout. + +Usage: dirauth.py <foo.onion> +""" + +from sys import argv, stdout +from stem.control import Controller + + +with Controller.from_port() as ctl: + ctl.authenticate(password='topkek') + stdout.write(ctl.get_hidden_service_descriptor(argv[1]).permanent_key) diff --git a/go/lib/crypto.go b/go/lib/crypto.go @@ -0,0 +1,140 @@ +package lib + +// See LICENSE file for copyright and license details. + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/sha512" + "crypto/x509" + "encoding/asn1" + "encoding/base32" + "encoding/pem" + "errors" + "io/ioutil" + "log" + "os" + "strings" +) + +// GenRsa generates a private RSA keypair of a given bitSize int. +func GenRsa(bitSize int) *rsa.PrivateKey { + log.Printf("Generating %d-bit RSA keypair...\n", bitSize) + rng := rand.Reader + key, err := rsa.GenerateKey(rng, bitSize) + CheckError(err) + + return key +} + +// SavePub saves a given RSA public key to a given filename. +func SavePub(filename string, pubkey rsa.PublicKey) { + log.Printf("Writing pubkey to %s\n", filename) + outfile, err := os.Create(filename) + CheckError(err) + defer outfile.Close() + + asn1Bytes, err := asn1.Marshal(pubkey) + CheckError(err) + + var pemkey = &pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: asn1Bytes, + } + err = pem.Encode(outfile, pemkey) + CheckError(err) +} + +// SavePriv saves a given RSA private key to a given filename. +func SavePriv(filename string, privkey *rsa.PrivateKey) { + log.Printf("Writing private key to %s\n", filename) + outfile, err := os.Create(filename) + CheckError(err) + defer outfile.Close() + + var pemkey = &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(privkey), + } + + err = pem.Encode(outfile, pemkey) + CheckError(err) +} + +// LoadKeyFromFile loads a RSA private key from a given filename. +func LoadKeyFromFile(filename string) (*rsa.PrivateKey, error) { + log.Println("Loading RSA private key from", filename) + dat, err := ioutil.ReadFile(filename) + CheckError(err) + + block, _ := pem.Decode(dat) + if block == nil { + return nil, errors.New("failed to parse PEM block containing the key") + } + + priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) + CheckError(err) + + return priv, nil +} + +// SignMsg signs a given []byte message using a given RSA private key. +func SignMsg(message []byte, privkey *rsa.PrivateKey) []byte { + log.Println("Signing message...") + rng := rand.Reader + + hashed := sha512.Sum512(message) + sig, err := rsa.SignPKCS1v15(rng, privkey, crypto.SHA512, hashed[:]) + CheckError(err) + + return sig +} + +// VerifyMsg verifies a []byte message and []byte signature against a given +// []byte RSA pubkey. +func VerifyMsg(message []byte, signature []byte, pubkey []byte) (bool, error) { + log.Println("Verifying message signature") + + block, _ := pem.Decode(pubkey) + if block == nil { + return false, errors.New("failed to parse PEM block containing the key") + } + + // This is a bug in golang. Reported at: https://github.com/golang/go/issues/23032 + pkey, err := x509.ParsePKIXPublicKey(block.Bytes) + CheckError(err) + + switch pkey := pkey.(type) { + case *rsa.PublicKey: + log.Println("Valid RSA key parsed.") + default: + log.Fatal("Public key is not of type RSA! It is: ", pkey) + return false, err + } + + hashed := sha512.Sum512(message) + ver := rsa.VerifyPKCS1v15(pkey.(*rsa.PublicKey), crypto.SHA512, hashed[:], signature) + if ver != nil { + log.Println("Signature invalid") + return false, nil + } + + log.Println("Signature valid") + return true, nil +} + +// OnionFromPubkey generates a valid onion address from a given RSA pubkey. +func OnionFromPubkey(pubkey rsa.PublicKey) string { + asn1Bytes, err := asn1.Marshal(pubkey) + CheckError(err) + + hashed := sha1.New() + _, err = hashed.Write(asn1Bytes) + CheckError(err) + + encoded := strings.ToLower(base32.StdEncoding.EncodeToString(hashed.Sum(nil)))[:16] + + return encoded + ".onion" +} diff --git a/go/lib/helpers.go b/go/lib/helpers.go @@ -0,0 +1,78 @@ +package lib + +import ( + "bytes" + "log" + "os/exec" + "regexp" + "strings" + "time" +) + +// CheckError is a handler for errors. +func CheckError(err error) { + if err != nil { + panic(err) + } +} + +// FetchHSPubkey fetches a hidden service's RSA pubkey by running an external +// program, giving it an onion address. +func FetchHSPubkey(addr string) string { + var outb, errb bytes.Buffer + + log.Println("Fetching pubkey for:", addr) + + cmd := exec.Command("./dirauth.py", addr) + cmd.Stdout = &outb + cmd.Stderr = &errb + + err := cmd.Start() + CheckError(err) + + err = cmd.Wait() + CheckError(err) + + return outb.String() +} + +// ValidateReq validates our given request against some checks. +func ValidateReq(req map[string]string) bool { + // Validate nodetype. + if req["nodetype"] != "node" { + return false + } + + // Validate address. + re, err := regexp.Compile("^[a-z2-7]{16}\\.onion$") + CheckError(err) + if len(re.FindString(req["address"])) != 22 { + return false + } + + // Address is valid, we try to fetch its pubkey from a HSDir + var pubkey string + log.Println("Onion seems valid") + for { // We try until we have it. + if strings.HasPrefix(pubkey, "-----BEGIN RSA PUBLIC KEY-----") && + strings.HasSuffix(pubkey, "-----END RSA PUBLIC KEY-----") { + log.Println("Got descriptor!") + break + } + time.Sleep(2000 * time.Millisecond) + pubkey = FetchHSPubkey(req["address"]) + //log.Println(pubkey) + } + + // Validate signature. + msg := []byte(req["message"]) + sig := []byte(req["signature"]) + pub := []byte(pubkey) + val, err := VerifyMsg(msg, sig, pub) + CheckError(err) + if val != true { + return false + } + + return true +}