tor-dam

tor distributed announce mechanism (not a dht)
git clone https://git.parazyd.org/tor-dam
Log | Files | Refs | README | LICENSE

validate.go (7473B)


      1 package damlib
      2 
      3 /*
      4  * Copyright (c) 2017-2018 Dyne.org Foundation
      5  * tor-dam is written and maintained by Ivan Jelincic <parazyd@dyne.org>
      6  *
      7  * This file is part of tor-dam
      8  *
      9  * This program is free software: you can redistribute it and/or modify
     10  * it under the terms of the GNU Affero General Public License as published by
     11  * the Free Software Foundation, either version 3 of the License, or
     12  * (at your option) any later version.
     13  *
     14  * This program is distributed in the hope that it will be useful,
     15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     17  * GNU Affero General Public License for more details.
     18  *
     19  * You should have received a copy of the GNU Affero General Public License
     20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
     21  */
     22 
     23 import (
     24 	"encoding/base64"
     25 	"log"
     26 	"regexp"
     27 	"time"
     28 
     29 	"golang.org/x/crypto/ed25519"
     30 )
     31 
     32 // ValidateOnionAddress matches a string against a regular expression matching
     33 // a Tor hidden service address. Returns true on success and false on failure.
     34 func ValidateOnionAddress(addr string) bool {
     35 	re, _ := regexp.Compile(`^[a-z2-7](?:.{55})\.onion`)
     36 	return len(re.FindString(addr)) == 62
     37 }
     38 
     39 // sanityCheck performs basic sanity checks against the incoming request.
     40 // Returns a boolean value according to the validity, and a string with an
     41 // according message.
     42 func sanityCheck(req map[string]string, handshake int) (bool, string) {
     43 	if !(ValidateOnionAddress(req["address"])) {
     44 		return false, "Invalid onion address"
     45 	}
     46 	if _, err := base64.StdEncoding.DecodeString(req["signature"]); err != nil {
     47 		return false, err.Error()
     48 	}
     49 
     50 	if handshake == 2 {
     51 		if _, err := base64.StdEncoding.DecodeString(req["message"]); err != nil {
     52 			return false, err.Error()
     53 		}
     54 		if _, err := base64.StdEncoding.DecodeString(req["secret"]); err != nil {
     55 			return false, err.Error()
     56 		}
     57 	}
     58 	return true, ""
     59 }
     60 
     61 // ValidateFirstHandshake validates the first incoming handshake. It first calls
     62 // sanityCheck to validate it's actually working with proper data. Next, it will
     63 // look if the node is already found in redis. If so, it will fetch its public
     64 // hey from redis, otherwise it will take it from the initial request from the
     65 // incoming node.  Once the public key is retrieved, it will validate the
     66 // received message signature against that key. If all is well, we consider the
     67 // request valid.  Continuing, a random ASCII string will be generated and
     68 // encrypted with the retrieved public key. All this data will be written into
     69 // redis, and finally the encrypted (and base64 encoded) secret will be returned
     70 // along with a true boolean value.  On any failure, the function will return
     71 // false, and produce an according string which is to be considered as an error
     72 // message.
     73 func ValidateFirstHandshake(req map[string]string) (bool, string) {
     74 	if sane, what := sanityCheck(req, 1); !(sane) {
     75 		return false, what
     76 	}
     77 
     78 	// Get the public key.
     79 	var pubstr string
     80 	var pubkey ed25519.PublicKey
     81 	// Check if we have seen this node already.
     82 	ex, err := RedisCli.Exists(Rctx, req["address"]).Result()
     83 	CheckError(err)
     84 	if ex == 1 {
     85 		// We saw it so we should have the public key in redis.
     86 		// If we do not, that is an internal error.
     87 		pubstr, err = RedisCli.HGet(Rctx, req["address"], "pubkey").Result()
     88 		CheckError(err)
     89 	} else {
     90 		// We take it from the announce.
     91 		pubstr = req["pubkey"]
     92 	}
     93 
     94 	// Validate signature.
     95 	msg := []byte(req["message"])
     96 	sig, _ := base64.StdEncoding.DecodeString(req["signature"])
     97 	deckey, err := base64.StdEncoding.DecodeString(pubstr)
     98 	CheckError(err)
     99 	pubkey = ed25519.PublicKey(deckey)
    100 
    101 	if !(ed25519.Verify(pubkey, msg, sig)) {
    102 		log.Println("crypto/ed25519: Signature verification failure.")
    103 		return false, "Signature verification vailure."
    104 	}
    105 
    106 	// The request is valid at this point.
    107 
    108 	// Make a random secret for them, and save our node info to redis.
    109 	randString, err := GenRandomASCII(64)
    110 	CheckError(err)
    111 	encodedSecret := base64.StdEncoding.EncodeToString([]byte(randString))
    112 
    113 	var info = map[string]interface{}{
    114 		"address":   req["address"],
    115 		"message":   encodedSecret,
    116 		"signature": req["signature"],
    117 		"secret":    encodedSecret,
    118 		"lastseen":  time.Now().Unix(),
    119 	} // Can not cast, need this for HMSet
    120 	if ex != 1 { // We did not have this node in redis.
    121 		info["pubkey"] = pubstr
    122 		info["firstseen"] = time.Now().Unix()
    123 		if Testnet {
    124 			info["valid"] = 1
    125 		} else {
    126 			info["valid"] = 0
    127 		}
    128 	}
    129 
    130 	log.Printf("%s: writing to redis\n", req["address"])
    131 	_, err = RedisCli.HMSet(Rctx, req["address"], info).Result()
    132 	CheckError(err)
    133 
    134 	return true, encodedSecret
    135 }
    136 
    137 // ValidateSecondHandshake validates the second part of the handshake.
    138 // First basic sanity checks are performed to ensure we are working with valid
    139 // data.
    140 // Next, the according public key will be retrieved from redis. If no key is
    141 // found, we will consider the handshake invalid.
    142 // Now the decrypted secret that was sent to us will be compared with what we
    143 // have saved before. Upon proving they are the same, the signature will now
    144 // be validated. If all is well, we consider the request valid.
    145 // Further on, we will generate a new random ASCII string and save it in redis
    146 // to prevent further reuse of the already known string. Upon success, the
    147 // function will return true, and a welcome message. Upon failure, the function
    148 // will return false, and an according string which is to be considered an error
    149 // message.
    150 func ValidateSecondHandshake(req map[string]string) (bool, string) {
    151 	if sane, what := sanityCheck(req, 2); !(sane) {
    152 		return false, what
    153 	}
    154 
    155 	// Get the public key.
    156 	var pubstr string
    157 	var pubkey ed25519.PublicKey
    158 	// Check if we have seen this node already.
    159 	ex, err := RedisCli.Exists(Rctx, req["address"]).Result()
    160 	CheckError(err)
    161 	if ex == 1 {
    162 		// We saw it so we should have the public key in redis.
    163 		// If we do not, that is an internal error.
    164 		pubstr, err = RedisCli.HGet(Rctx, req["address"], "pubkey").Result()
    165 		CheckError(err)
    166 	} else {
    167 		log.Printf("%s tried to jump in 2/2 handshake before doing the first.\n", req["address"])
    168 		return false, "We have not seen you before. Please authenticate properly."
    169 	}
    170 
    171 	localSec, err := RedisCli.HGet(Rctx, req["address"], "secret").Result()
    172 	CheckError(err)
    173 
    174 	if !(localSec == req["secret"] && localSec == req["message"]) {
    175 		log.Printf("%s: Secrets don't match.\n", req["address"])
    176 		return false, "Secrets don't match."
    177 	}
    178 
    179 	// Validate signature.
    180 	msg := []byte(req["message"])
    181 	sig, _ := base64.StdEncoding.DecodeString(req["signature"])
    182 	deckey, err := base64.StdEncoding.DecodeString(pubstr)
    183 	CheckError(err)
    184 	pubkey = ed25519.PublicKey(deckey)
    185 
    186 	if !(ed25519.Verify(pubkey, msg, sig)) {
    187 		log.Println("crypto/ed25519: Signature verification failure")
    188 		return false, "Signature verification failure."
    189 	}
    190 
    191 	// The request is valid at this point.
    192 
    193 	// Make a new random secret to prevent reuse.
    194 	randString, err := GenRandomASCII(64)
    195 	CheckError(err)
    196 	encodedSecret := base64.StdEncoding.EncodeToString([]byte(randString))
    197 
    198 	var info = map[string]interface{}{
    199 		"address":   req["address"],
    200 		"message":   encodedSecret,
    201 		"signature": req["signature"],
    202 		"secret":    encodedSecret,
    203 		"lastseen":  time.Now().Unix(),
    204 	} // Can not cast, need this for HMSet
    205 
    206 	log.Printf("%s: writing to redis\n", req["address"])
    207 	_, err = RedisCli.HMSet(Rctx, req["address"], info).Result()
    208 	CheckError(err)
    209 
    210 	return true, WelcomeMsg
    211 }