commit 1d6eeec83217c844e0dc3b723e640fff551786d4
parent 883e09cc342d50cc90671f6b6f09364565c252c8
Author: parazyd <parazyd@dyne.org>
Date: Thu, 7 Dec 2017 17:27:22 +0100
add go implementation
Diffstat:
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
+}