github.com/jonasnick/go-ethereum@v0.7.12-0.20150216215225-22176f05d387/p2p/crypto.go (about) 1 package p2p 2 3 import ( 4 // "binary" 5 "crypto/ecdsa" 6 "crypto/rand" 7 "fmt" 8 "io" 9 10 "github.com/jonasnick/go-ethereum/crypto" 11 "github.com/jonasnick/go-ethereum/crypto/ecies" 12 "github.com/jonasnick/go-ethereum/crypto/secp256k1" 13 ethlogger "github.com/jonasnick/go-ethereum/logger" 14 "github.com/jonasnick/go-ethereum/p2p/discover" 15 ) 16 17 var clogger = ethlogger.NewLogger("CRYPTOID") 18 19 const ( 20 sskLen = 16 // ecies.MaxSharedKeyLength(pubKey) / 2 21 sigLen = 65 // elliptic S256 22 pubLen = 64 // 512 bit pubkey in uncompressed representation without format byte 23 shaLen = 32 // hash length (for nonce etc) 24 25 authMsgLen = sigLen + shaLen + pubLen + shaLen + 1 26 authRespLen = pubLen + shaLen + 1 27 28 eciesBytes = 65 + 16 + 32 29 iHSLen = authMsgLen + eciesBytes // size of the final ECIES payload sent as initiator's handshake 30 rHSLen = authRespLen + eciesBytes // size of the final ECIES payload sent as receiver's handshake 31 ) 32 33 type hexkey []byte 34 35 func (self hexkey) String() string { 36 return fmt.Sprintf("(%d) %x", len(self), []byte(self)) 37 } 38 39 func encHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, dial *discover.Node) ( 40 remoteID discover.NodeID, 41 sessionToken []byte, 42 err error, 43 ) { 44 if dial == nil { 45 var remotePubkey []byte 46 sessionToken, remotePubkey, err = inboundEncHandshake(conn, prv, nil) 47 copy(remoteID[:], remotePubkey) 48 } else { 49 remoteID = dial.ID 50 sessionToken, err = outboundEncHandshake(conn, prv, remoteID[:], nil) 51 } 52 return remoteID, sessionToken, err 53 } 54 55 // outboundEncHandshake negotiates a session token on conn. 56 // it should be called on the dialing side of the connection. 57 // 58 // privateKey is the local client's private key 59 // remotePublicKey is the remote peer's node ID 60 // sessionToken is the token from a previous session with this node. 61 func outboundEncHandshake(conn io.ReadWriter, prvKey *ecdsa.PrivateKey, remotePublicKey []byte, sessionToken []byte) ( 62 newSessionToken []byte, 63 err error, 64 ) { 65 auth, initNonce, randomPrivKey, err := authMsg(prvKey, remotePublicKey, sessionToken) 66 if err != nil { 67 return nil, err 68 } 69 if sessionToken != nil { 70 clogger.Debugf("session-token: %v", hexkey(sessionToken)) 71 } 72 73 clogger.Debugf("initiator-nonce: %v", hexkey(initNonce)) 74 clogger.Debugf("initiator-random-private-key: %v", hexkey(crypto.FromECDSA(randomPrivKey))) 75 randomPublicKeyS, _ := exportPublicKey(&randomPrivKey.PublicKey) 76 clogger.Debugf("initiator-random-public-key: %v", hexkey(randomPublicKeyS)) 77 if _, err = conn.Write(auth); err != nil { 78 return nil, err 79 } 80 clogger.Debugf("initiator handshake: %v", hexkey(auth)) 81 82 response := make([]byte, rHSLen) 83 if _, err = io.ReadFull(conn, response); err != nil { 84 return nil, err 85 } 86 recNonce, remoteRandomPubKey, _, err := completeHandshake(response, prvKey) 87 if err != nil { 88 return nil, err 89 } 90 91 clogger.Debugf("receiver-nonce: %v", hexkey(recNonce)) 92 remoteRandomPubKeyS, _ := exportPublicKey(remoteRandomPubKey) 93 clogger.Debugf("receiver-random-public-key: %v", hexkey(remoteRandomPubKeyS)) 94 return newSession(initNonce, recNonce, randomPrivKey, remoteRandomPubKey) 95 } 96 97 // authMsg creates the initiator handshake. 98 func authMsg(prvKey *ecdsa.PrivateKey, remotePubKeyS, sessionToken []byte) ( 99 auth, initNonce []byte, 100 randomPrvKey *ecdsa.PrivateKey, 101 err error, 102 ) { 103 // session init, common to both parties 104 remotePubKey, err := importPublicKey(remotePubKeyS) 105 if err != nil { 106 return 107 } 108 109 var tokenFlag byte // = 0x00 110 if sessionToken == nil { 111 // no session token found means we need to generate shared secret. 112 // ecies shared secret is used as initial session token for new peers 113 // generate shared key from prv and remote pubkey 114 if sessionToken, err = ecies.ImportECDSA(prvKey).GenerateShared(ecies.ImportECDSAPublic(remotePubKey), sskLen, sskLen); err != nil { 115 return 116 } 117 // tokenFlag = 0x00 // redundant 118 } else { 119 // for known peers, we use stored token from the previous session 120 tokenFlag = 0x01 121 } 122 123 //E(remote-pubk, S(ecdhe-random, ecdh-shared-secret^nonce) || H(ecdhe-random-pubk) || pubk || nonce || 0x0) 124 // E(remote-pubk, S(ecdhe-random, token^nonce) || H(ecdhe-random-pubk) || pubk || nonce || 0x1) 125 // allocate msgLen long message, 126 var msg []byte = make([]byte, authMsgLen) 127 initNonce = msg[authMsgLen-shaLen-1 : authMsgLen-1] 128 if _, err = rand.Read(initNonce); err != nil { 129 return 130 } 131 // create known message 132 // ecdh-shared-secret^nonce for new peers 133 // token^nonce for old peers 134 var sharedSecret = xor(sessionToken, initNonce) 135 136 // generate random keypair to use for signing 137 if randomPrvKey, err = crypto.GenerateKey(); err != nil { 138 return 139 } 140 // sign shared secret (message known to both parties): shared-secret 141 var signature []byte 142 // signature = sign(ecdhe-random, shared-secret) 143 // uses secp256k1.Sign 144 if signature, err = crypto.Sign(sharedSecret, randomPrvKey); err != nil { 145 return 146 } 147 148 // message 149 // signed-shared-secret || H(ecdhe-random-pubk) || pubk || nonce || 0x0 150 copy(msg, signature) // copy signed-shared-secret 151 // H(ecdhe-random-pubk) 152 var randomPubKey64 []byte 153 if randomPubKey64, err = exportPublicKey(&randomPrvKey.PublicKey); err != nil { 154 return 155 } 156 var pubKey64 []byte 157 if pubKey64, err = exportPublicKey(&prvKey.PublicKey); err != nil { 158 return 159 } 160 copy(msg[sigLen:sigLen+shaLen], crypto.Sha3(randomPubKey64)) 161 // pubkey copied to the correct segment. 162 copy(msg[sigLen+shaLen:sigLen+shaLen+pubLen], pubKey64) 163 // nonce is already in the slice 164 // stick tokenFlag byte to the end 165 msg[authMsgLen-1] = tokenFlag 166 167 // encrypt using remote-pubk 168 // auth = eciesEncrypt(remote-pubk, msg) 169 if auth, err = crypto.Encrypt(remotePubKey, msg); err != nil { 170 return 171 } 172 return 173 } 174 175 // completeHandshake is called when the initiator receives an 176 // authentication response (aka receiver handshake). It completes the 177 // handshake by reading off parameters the remote peer provides needed 178 // to set up the secure session. 179 func completeHandshake(auth []byte, prvKey *ecdsa.PrivateKey) ( 180 respNonce []byte, 181 remoteRandomPubKey *ecdsa.PublicKey, 182 tokenFlag bool, 183 err error, 184 ) { 185 var msg []byte 186 // they prove that msg is meant for me, 187 // I prove I possess private key if i can read it 188 if msg, err = crypto.Decrypt(prvKey, auth); err != nil { 189 return 190 } 191 192 respNonce = msg[pubLen : pubLen+shaLen] 193 var remoteRandomPubKeyS = msg[:pubLen] 194 if remoteRandomPubKey, err = importPublicKey(remoteRandomPubKeyS); err != nil { 195 return 196 } 197 if msg[authRespLen-1] == 0x01 { 198 tokenFlag = true 199 } 200 return 201 } 202 203 // inboundEncHandshake negotiates a session token on conn. 204 // it should be called on the listening side of the connection. 205 // 206 // privateKey is the local client's private key 207 // sessionToken is the token from a previous session with this node. 208 func inboundEncHandshake(conn io.ReadWriter, prvKey *ecdsa.PrivateKey, sessionToken []byte) ( 209 token, remotePubKey []byte, 210 err error, 211 ) { 212 // we are listening connection. we are responders in the 213 // handshake. Extract info from the authentication. The initiator 214 // starts by sending us a handshake that we need to respond to. so 215 // we read auth message first, then respond. 216 auth := make([]byte, iHSLen) 217 if _, err := io.ReadFull(conn, auth); err != nil { 218 return nil, nil, err 219 } 220 response, recNonce, initNonce, remotePubKey, randomPrivKey, remoteRandomPubKey, err := authResp(auth, sessionToken, prvKey) 221 if err != nil { 222 return nil, nil, err 223 } 224 clogger.Debugf("receiver-nonce: %v", hexkey(recNonce)) 225 clogger.Debugf("receiver-random-priv-key: %v", hexkey(crypto.FromECDSA(randomPrivKey))) 226 if _, err = conn.Write(response); err != nil { 227 return nil, nil, err 228 } 229 clogger.Debugf("receiver handshake:\n%v", hexkey(response)) 230 token, err = newSession(initNonce, recNonce, randomPrivKey, remoteRandomPubKey) 231 return token, remotePubKey, err 232 } 233 234 // authResp is called by peer if it accepted (but not 235 // initiated) the connection from the remote. It is passed the initiator 236 // handshake received and the session token belonging to the 237 // remote initiator. 238 // 239 // The first return value is the authentication response (aka receiver 240 // handshake) that is to be sent to the remote initiator. 241 func authResp(auth, sessionToken []byte, prvKey *ecdsa.PrivateKey) ( 242 authResp, respNonce, initNonce, remotePubKeyS []byte, 243 randomPrivKey *ecdsa.PrivateKey, 244 remoteRandomPubKey *ecdsa.PublicKey, 245 err error, 246 ) { 247 // they prove that msg is meant for me, 248 // I prove I possess private key if i can read it 249 msg, err := crypto.Decrypt(prvKey, auth) 250 if err != nil { 251 return 252 } 253 254 remotePubKeyS = msg[sigLen+shaLen : sigLen+shaLen+pubLen] 255 remotePubKey, _ := importPublicKey(remotePubKeyS) 256 257 var tokenFlag byte 258 if sessionToken == nil { 259 // no session token found means we need to generate shared secret. 260 // ecies shared secret is used as initial session token for new peers 261 // generate shared key from prv and remote pubkey 262 if sessionToken, err = ecies.ImportECDSA(prvKey).GenerateShared(ecies.ImportECDSAPublic(remotePubKey), sskLen, sskLen); err != nil { 263 return 264 } 265 // tokenFlag = 0x00 // redundant 266 } else { 267 // for known peers, we use stored token from the previous session 268 tokenFlag = 0x01 269 } 270 271 // the initiator nonce is read off the end of the message 272 initNonce = msg[authMsgLen-shaLen-1 : authMsgLen-1] 273 // I prove that i own prv key (to derive shared secret, and read 274 // nonce off encrypted msg) and that I own shared secret they 275 // prove they own the private key belonging to ecdhe-random-pubk 276 // we can now reconstruct the signed message and recover the peers 277 // pubkey 278 var signedMsg = xor(sessionToken, initNonce) 279 var remoteRandomPubKeyS []byte 280 if remoteRandomPubKeyS, err = secp256k1.RecoverPubkey(signedMsg, msg[:sigLen]); err != nil { 281 return 282 } 283 // convert to ECDSA standard 284 if remoteRandomPubKey, err = importPublicKey(remoteRandomPubKeyS); err != nil { 285 return 286 } 287 288 // now we find ourselves a long task too, fill it random 289 var resp = make([]byte, authRespLen) 290 // generate shaLen long nonce 291 respNonce = resp[pubLen : pubLen+shaLen] 292 if _, err = rand.Read(respNonce); err != nil { 293 return 294 } 295 // generate random keypair for session 296 if randomPrivKey, err = crypto.GenerateKey(); err != nil { 297 return 298 } 299 // responder auth message 300 // E(remote-pubk, ecdhe-random-pubk || nonce || 0x0) 301 var randomPubKeyS []byte 302 if randomPubKeyS, err = exportPublicKey(&randomPrivKey.PublicKey); err != nil { 303 return 304 } 305 copy(resp[:pubLen], randomPubKeyS) 306 // nonce is already in the slice 307 resp[authRespLen-1] = tokenFlag 308 309 // encrypt using remote-pubk 310 // auth = eciesEncrypt(remote-pubk, msg) 311 // why not encrypt with ecdhe-random-remote 312 if authResp, err = crypto.Encrypt(remotePubKey, resp); err != nil { 313 return 314 } 315 return 316 } 317 318 // newSession is called after the handshake is completed. The 319 // arguments are values negotiated in the handshake. The return value 320 // is a new session Token to be remembered for the next time we 321 // connect with this peer. 322 func newSession(initNonce, respNonce []byte, privKey *ecdsa.PrivateKey, remoteRandomPubKey *ecdsa.PublicKey) ([]byte, error) { 323 // 3) Now we can trust ecdhe-random-pubk to derive new keys 324 //ecdhe-shared-secret = ecdh.agree(ecdhe-random, remote-ecdhe-random-pubk) 325 pubKey := ecies.ImportECDSAPublic(remoteRandomPubKey) 326 dhSharedSecret, err := ecies.ImportECDSA(privKey).GenerateShared(pubKey, sskLen, sskLen) 327 if err != nil { 328 return nil, err 329 } 330 sharedSecret := crypto.Sha3(dhSharedSecret, crypto.Sha3(respNonce, initNonce)) 331 sessionToken := crypto.Sha3(sharedSecret) 332 return sessionToken, nil 333 } 334 335 // importPublicKey unmarshals 512 bit public keys. 336 func importPublicKey(pubKey []byte) (pubKeyEC *ecdsa.PublicKey, err error) { 337 var pubKey65 []byte 338 switch len(pubKey) { 339 case 64: 340 // add 'uncompressed key' flag 341 pubKey65 = append([]byte{0x04}, pubKey...) 342 case 65: 343 pubKey65 = pubKey 344 default: 345 return nil, fmt.Errorf("invalid public key length %v (expect 64/65)", len(pubKey)) 346 } 347 return crypto.ToECDSAPub(pubKey65), nil 348 } 349 350 func exportPublicKey(pubKeyEC *ecdsa.PublicKey) (pubKey []byte, err error) { 351 if pubKeyEC == nil { 352 return nil, fmt.Errorf("no ECDSA public key given") 353 } 354 return crypto.FromECDSAPub(pubKeyEC)[1:], nil 355 } 356 357 func xor(one, other []byte) (xor []byte) { 358 xor = make([]byte, len(one)) 359 for i := 0; i < len(one); i++ { 360 xor[i] = one[i] ^ other[i] 361 } 362 return xor 363 }