tadd go implementation - tordam - A library for peer discovery inside the Tor network
 (HTM) git clone https://git.parazyd.org/tordam
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 1d6eeec83217c844e0dc3b723e640fff551786d4
 (DIR) parent 883e09cc342d50cc90671f6b6f09364565c252c8
 (HTM) Author: parazyd <parazyd@dyne.org>
       Date:   Thu,  7 Dec 2017 17:27:22 +0100
       
       add go implementation
       
       Diffstat:
         A go/dam/dam.go                       |      56 +++++++++++++++++++++++++++++++
         A go/ddir/ddir.go                     |      40 +++++++++++++++++++++++++++++++
         A go/ddir/dirauth.py                  |      15 +++++++++++++++
         A go/lib/crypto.go                    |     140 +++++++++++++++++++++++++++++++
         A go/lib/helpers.go                   |      78 +++++++++++++++++++++++++++++++
       
       5 files changed, 329 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/go/dam/dam.go b/go/dam/dam.go
       t@@ -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)
       +}
 (DIR) diff --git a/go/ddir/ddir.go b/go/ddir/ddir.go
       t@@ -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)
       +}
 (DIR) diff --git a/go/ddir/dirauth.py b/go/ddir/dirauth.py
       t@@ -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)
 (DIR) diff --git a/go/lib/crypto.go b/go/lib/crypto.go
       t@@ -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"
       +}
 (DIR) diff --git a/go/lib/helpers.go b/go/lib/helpers.go
       t@@ -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
       +}