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 }