github.com/adoriasoft/tendermint@v0.34.0-dev1.0.20200722151356-96d84601a75a/p2p/conn/secret_connection.go (about) 1 package conn 2 3 import ( 4 "bytes" 5 "crypto/cipher" 6 crand "crypto/rand" 7 "crypto/sha256" 8 "encoding/binary" 9 "errors" 10 "fmt" 11 "io" 12 "math" 13 "net" 14 "time" 15 16 gogotypes "github.com/gogo/protobuf/types" 17 "github.com/gtank/merlin" 18 pool "github.com/libp2p/go-buffer-pool" 19 "golang.org/x/crypto/chacha20poly1305" 20 "golang.org/x/crypto/curve25519" 21 "golang.org/x/crypto/hkdf" 22 "golang.org/x/crypto/nacl/box" 23 24 "github.com/tendermint/tendermint/crypto" 25 "github.com/tendermint/tendermint/crypto/ed25519" 26 cryptoenc "github.com/tendermint/tendermint/crypto/encoding" 27 "github.com/tendermint/tendermint/libs/async" 28 "github.com/tendermint/tendermint/libs/protoio" 29 tmsync "github.com/tendermint/tendermint/libs/sync" 30 tmp2p "github.com/tendermint/tendermint/proto/tendermint/p2p" 31 ) 32 33 // 4 + 1024 == 1028 total frame size 34 const ( 35 dataLenSize = 4 36 dataMaxSize = 1024 37 totalFrameSize = dataMaxSize + dataLenSize 38 aeadSizeOverhead = 16 // overhead of poly 1305 authentication tag 39 aeadKeySize = chacha20poly1305.KeySize 40 aeadNonceSize = chacha20poly1305.NonceSize 41 ) 42 43 var ( 44 ErrSmallOrderRemotePubKey = errors.New("detected low order point from remote peer") 45 46 labelEphemeralLowerPublicKey = []byte("EPHEMERAL_LOWER_PUBLIC_KEY") 47 labelEphemeralUpperPublicKey = []byte("EPHEMERAL_UPPER_PUBLIC_KEY") 48 labelDHSecret = []byte("DH_SECRET") 49 labelSecretConnectionMac = []byte("SECRET_CONNECTION_MAC") 50 51 secretConnKeyAndChallengeGen = []byte("TENDERMINT_SECRET_CONNECTION_KEY_AND_CHALLENGE_GEN") 52 ) 53 54 // SecretConnection implements net.Conn. 55 // It is an implementation of the STS protocol. 56 // See https://github.com/tendermint/tendermint/blob/0.1/docs/sts-final.pdf for 57 // details on the protocol. 58 // 59 // Consumers of the SecretConnection are responsible for authenticating 60 // the remote peer's pubkey against known information, like a nodeID. 61 // Otherwise they are vulnerable to MITM. 62 // (TODO(ismail): see also https://github.com/tendermint/tendermint/issues/3010) 63 type SecretConnection struct { 64 65 // immutable 66 recvAead cipher.AEAD 67 sendAead cipher.AEAD 68 69 remPubKey crypto.PubKey 70 conn io.ReadWriteCloser 71 72 // net.Conn must be thread safe: 73 // https://golang.org/pkg/net/#Conn. 74 // Since we have internal mutable state, 75 // we need mtxs. But recv and send states 76 // are independent, so we can use two mtxs. 77 // All .Read are covered by recvMtx, 78 // all .Write are covered by sendMtx. 79 recvMtx tmsync.Mutex 80 recvBuffer []byte 81 recvNonce *[aeadNonceSize]byte 82 83 sendMtx tmsync.Mutex 84 sendNonce *[aeadNonceSize]byte 85 } 86 87 // MakeSecretConnection performs handshake and returns a new authenticated 88 // SecretConnection. 89 // Returns nil if there is an error in handshake. 90 // Caller should call conn.Close() 91 // See docs/sts-final.pdf for more information. 92 func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKey) (*SecretConnection, error) { 93 var ( 94 locPubKey = locPrivKey.PubKey() 95 ) 96 97 // Generate ephemeral keys for perfect forward secrecy. 98 locEphPub, locEphPriv := genEphKeys() 99 100 // Write local ephemeral pubkey and receive one too. 101 // NOTE: every 32-byte string is accepted as a Curve25519 public key (see 102 // DJB's Curve25519 paper: http://cr.yp.to/ecdh/curve25519-20060209.pdf) 103 remEphPub, err := shareEphPubKey(conn, locEphPub) 104 if err != nil { 105 return nil, err 106 } 107 108 // Sort by lexical order. 109 loEphPub, hiEphPub := sort32(locEphPub, remEphPub) 110 111 transcript := merlin.NewTranscript("TENDERMINT_SECRET_CONNECTION_TRANSCRIPT_HASH") 112 113 transcript.AppendMessage(labelEphemeralLowerPublicKey, loEphPub[:]) 114 transcript.AppendMessage(labelEphemeralUpperPublicKey, hiEphPub[:]) 115 116 // Check if the local ephemeral public key was the least, lexicographically 117 // sorted. 118 locIsLeast := bytes.Equal(locEphPub[:], loEphPub[:]) 119 120 // Compute common diffie hellman secret using X25519. 121 dhSecret, err := computeDHSecret(remEphPub, locEphPriv) 122 if err != nil { 123 return nil, err 124 } 125 126 transcript.AppendMessage(labelDHSecret, dhSecret[:]) 127 128 // Generate the secret used for receiving, sending, challenge via HKDF-SHA2 129 // on the transcript state (which itself also uses HKDF-SHA2 to derive a key 130 // from the dhSecret). 131 recvSecret, sendSecret := deriveSecrets(dhSecret, locIsLeast) 132 133 const challengeSize = 32 134 var challenge [challengeSize]byte 135 challengeSlice := transcript.ExtractBytes(labelSecretConnectionMac, challengeSize) 136 137 copy(challenge[:], challengeSlice[0:challengeSize]) 138 139 sendAead, err := chacha20poly1305.New(sendSecret[:]) 140 if err != nil { 141 return nil, errors.New("invalid send SecretConnection Key") 142 } 143 recvAead, err := chacha20poly1305.New(recvSecret[:]) 144 if err != nil { 145 return nil, errors.New("invalid receive SecretConnection Key") 146 } 147 148 sc := &SecretConnection{ 149 conn: conn, 150 recvBuffer: nil, 151 recvNonce: new([aeadNonceSize]byte), 152 sendNonce: new([aeadNonceSize]byte), 153 recvAead: recvAead, 154 sendAead: sendAead, 155 } 156 157 // Sign the challenge bytes for authentication. 158 locSignature, err := signChallenge(&challenge, locPrivKey) 159 if err != nil { 160 return nil, err 161 } 162 163 // Share (in secret) each other's pubkey & challenge signature 164 authSigMsg, err := shareAuthSignature(sc, locPubKey, locSignature) 165 if err != nil { 166 return nil, err 167 } 168 169 remPubKey, remSignature := authSigMsg.Key, authSigMsg.Sig 170 if _, ok := remPubKey.(ed25519.PubKey); !ok { 171 return nil, fmt.Errorf("expected ed25519 pubkey, got %T", remPubKey) 172 } 173 if !remPubKey.VerifyBytes(challenge[:], remSignature) { 174 return nil, errors.New("challenge verification failed") 175 } 176 177 // We've authorized. 178 sc.remPubKey = remPubKey 179 return sc, nil 180 } 181 182 // RemotePubKey returns authenticated remote pubkey 183 func (sc *SecretConnection) RemotePubKey() crypto.PubKey { 184 return sc.remPubKey 185 } 186 187 // Writes encrypted frames of `totalFrameSize + aeadSizeOverhead`. 188 // CONTRACT: data smaller than dataMaxSize is written atomically. 189 func (sc *SecretConnection) Write(data []byte) (n int, err error) { 190 sc.sendMtx.Lock() 191 defer sc.sendMtx.Unlock() 192 193 for 0 < len(data) { 194 if err := func() error { 195 var sealedFrame = pool.Get(aeadSizeOverhead + totalFrameSize) 196 var frame = pool.Get(totalFrameSize) 197 defer func() { 198 pool.Put(sealedFrame) 199 pool.Put(frame) 200 }() 201 var chunk []byte 202 if dataMaxSize < len(data) { 203 chunk = data[:dataMaxSize] 204 data = data[dataMaxSize:] 205 } else { 206 chunk = data 207 data = nil 208 } 209 chunkLength := len(chunk) 210 binary.LittleEndian.PutUint32(frame, uint32(chunkLength)) 211 copy(frame[dataLenSize:], chunk) 212 213 // encrypt the frame 214 sc.sendAead.Seal(sealedFrame[:0], sc.sendNonce[:], frame, nil) 215 incrNonce(sc.sendNonce) 216 // end encryption 217 218 _, err = sc.conn.Write(sealedFrame) 219 if err != nil { 220 return err 221 } 222 n += len(chunk) 223 return nil 224 }(); err != nil { 225 return n, err 226 } 227 } 228 return n, err 229 } 230 231 // CONTRACT: data smaller than dataMaxSize is read atomically. 232 func (sc *SecretConnection) Read(data []byte) (n int, err error) { 233 sc.recvMtx.Lock() 234 defer sc.recvMtx.Unlock() 235 236 // read off and update the recvBuffer, if non-empty 237 if 0 < len(sc.recvBuffer) { 238 n = copy(data, sc.recvBuffer) 239 sc.recvBuffer = sc.recvBuffer[n:] 240 return 241 } 242 243 // read off the conn 244 var sealedFrame = pool.Get(aeadSizeOverhead + totalFrameSize) 245 defer pool.Put(sealedFrame) 246 _, err = io.ReadFull(sc.conn, sealedFrame) 247 if err != nil { 248 return 249 } 250 251 // decrypt the frame. 252 // reads and updates the sc.recvNonce 253 var frame = pool.Get(totalFrameSize) 254 defer pool.Put(frame) 255 _, err = sc.recvAead.Open(frame[:0], sc.recvNonce[:], sealedFrame, nil) 256 if err != nil { 257 return n, fmt.Errorf("failed to decrypt SecretConnection: %w", err) 258 } 259 incrNonce(sc.recvNonce) 260 // end decryption 261 262 // copy checkLength worth into data, 263 // set recvBuffer to the rest. 264 var chunkLength = binary.LittleEndian.Uint32(frame) // read the first four bytes 265 if chunkLength > dataMaxSize { 266 return 0, errors.New("chunkLength is greater than dataMaxSize") 267 } 268 var chunk = frame[dataLenSize : dataLenSize+chunkLength] 269 n = copy(data, chunk) 270 if n < len(chunk) { 271 sc.recvBuffer = make([]byte, len(chunk)-n) 272 copy(sc.recvBuffer, chunk[n:]) 273 } 274 return n, err 275 } 276 277 // Implements net.Conn 278 // nolint 279 func (sc *SecretConnection) Close() error { return sc.conn.Close() } 280 func (sc *SecretConnection) LocalAddr() net.Addr { return sc.conn.(net.Conn).LocalAddr() } 281 func (sc *SecretConnection) RemoteAddr() net.Addr { return sc.conn.(net.Conn).RemoteAddr() } 282 func (sc *SecretConnection) SetDeadline(t time.Time) error { return sc.conn.(net.Conn).SetDeadline(t) } 283 func (sc *SecretConnection) SetReadDeadline(t time.Time) error { 284 return sc.conn.(net.Conn).SetReadDeadline(t) 285 } 286 func (sc *SecretConnection) SetWriteDeadline(t time.Time) error { 287 return sc.conn.(net.Conn).SetWriteDeadline(t) 288 } 289 290 func genEphKeys() (ephPub, ephPriv *[32]byte) { 291 var err error 292 // TODO: Probably not a problem but ask Tony: different from the rust implementation (uses x25519-dalek), 293 // we do not "clamp" the private key scalar: 294 // see: https://github.com/dalek-cryptography/x25519-dalek/blob/34676d336049df2bba763cc076a75e47ae1f170f/src/x25519.rs#L56-L74 295 ephPub, ephPriv, err = box.GenerateKey(crand.Reader) 296 if err != nil { 297 panic("Could not generate ephemeral key-pair") 298 } 299 return 300 } 301 302 func shareEphPubKey(conn io.ReadWriter, locEphPub *[32]byte) (remEphPub *[32]byte, err error) { 303 304 // Send our pubkey and receive theirs in tandem. 305 var trs, _ = async.Parallel( 306 func(_ int) (val interface{}, abort bool, err error) { 307 lc := *locEphPub 308 _, err = protoio.NewDelimitedWriter(conn).WriteMsg(&gogotypes.BytesValue{Value: lc[:]}) 309 if err != nil { 310 return nil, true, err // abort 311 } 312 return nil, false, nil 313 }, 314 func(_ int) (val interface{}, abort bool, err error) { 315 var bytes gogotypes.BytesValue 316 err = protoio.NewDelimitedReader(conn, 1024*1024).ReadMsg(&bytes) 317 if err != nil { 318 return nil, true, err // abort 319 } 320 321 var _remEphPub [32]byte 322 copy(_remEphPub[:], bytes.Value) 323 return _remEphPub, false, nil 324 }, 325 ) 326 327 // If error: 328 if trs.FirstError() != nil { 329 err = trs.FirstError() 330 return 331 } 332 333 // Otherwise: 334 var _remEphPub = trs.FirstValue().([32]byte) 335 return &_remEphPub, nil 336 } 337 338 func deriveSecrets( 339 dhSecret *[32]byte, 340 locIsLeast bool, 341 ) (recvSecret, sendSecret *[aeadKeySize]byte) { 342 hash := sha256.New 343 hkdf := hkdf.New(hash, dhSecret[:], nil, secretConnKeyAndChallengeGen) 344 // get enough data for 2 aead keys, and a 32 byte challenge 345 res := new([2*aeadKeySize + 32]byte) 346 _, err := io.ReadFull(hkdf, res[:]) 347 if err != nil { 348 panic(err) 349 } 350 351 recvSecret = new([aeadKeySize]byte) 352 sendSecret = new([aeadKeySize]byte) 353 354 // bytes 0 through aeadKeySize - 1 are one aead key. 355 // bytes aeadKeySize through 2*aeadKeySize -1 are another aead key. 356 // which key corresponds to sending and receiving key depends on whether 357 // the local key is less than the remote key. 358 if locIsLeast { 359 copy(recvSecret[:], res[0:aeadKeySize]) 360 copy(sendSecret[:], res[aeadKeySize:aeadKeySize*2]) 361 } else { 362 copy(sendSecret[:], res[0:aeadKeySize]) 363 copy(recvSecret[:], res[aeadKeySize:aeadKeySize*2]) 364 } 365 366 return 367 } 368 369 // computeDHSecret computes a Diffie-Hellman shared secret key 370 // from our own local private key and the other's public key. 371 func computeDHSecret(remPubKey, locPrivKey *[32]byte) (*[32]byte, error) { 372 shrKey, err := curve25519.X25519(locPrivKey[:], remPubKey[:]) 373 if err != nil { 374 return nil, err 375 } 376 var shrKeyArray [32]byte 377 copy(shrKeyArray[:], shrKey) 378 return &shrKeyArray, nil 379 } 380 381 func sort32(foo, bar *[32]byte) (lo, hi *[32]byte) { 382 if bytes.Compare(foo[:], bar[:]) < 0 { 383 lo = foo 384 hi = bar 385 } else { 386 lo = bar 387 hi = foo 388 } 389 return 390 } 391 392 func signChallenge(challenge *[32]byte, locPrivKey crypto.PrivKey) ([]byte, error) { 393 signature, err := locPrivKey.Sign(challenge[:]) 394 if err != nil { 395 return nil, err 396 } 397 return signature, nil 398 } 399 400 type authSigMessage struct { 401 Key crypto.PubKey 402 Sig []byte 403 } 404 405 func shareAuthSignature(sc io.ReadWriter, pubKey crypto.PubKey, signature []byte) (recvMsg authSigMessage, err error) { 406 407 // Send our info and receive theirs in tandem. 408 var trs, _ = async.Parallel( 409 func(_ int) (val interface{}, abort bool, err error) { 410 pbpk, err := cryptoenc.PubKeyToProto(pubKey) 411 if err != nil { 412 return nil, true, err 413 } 414 _, err = protoio.NewDelimitedWriter(sc).WriteMsg(&tmp2p.AuthSigMessage{PubKey: pbpk, Sig: signature}) 415 if err != nil { 416 return nil, true, err // abort 417 } 418 return nil, false, nil 419 }, 420 func(_ int) (val interface{}, abort bool, err error) { 421 var pba tmp2p.AuthSigMessage 422 err = protoio.NewDelimitedReader(sc, 1024*1024).ReadMsg(&pba) 423 if err != nil { 424 return nil, true, err // abort 425 } 426 427 pk, err := cryptoenc.PubKeyFromProto(pba.PubKey) 428 if err != nil { 429 return nil, true, err // abort 430 } 431 432 _recvMsg := authSigMessage{ 433 Key: pk, 434 Sig: pba.Sig, 435 } 436 return _recvMsg, false, nil 437 }, 438 ) 439 440 // If error: 441 if trs.FirstError() != nil { 442 err = trs.FirstError() 443 return 444 } 445 446 var _recvMsg = trs.FirstValue().(authSigMessage) 447 return _recvMsg, nil 448 } 449 450 //-------------------------------------------------------------------------------- 451 452 // Increment nonce little-endian by 1 with wraparound. 453 // Due to chacha20poly1305 expecting a 12 byte nonce we do not use the first four 454 // bytes. We only increment a 64 bit unsigned int in the remaining 8 bytes 455 // (little-endian in nonce[4:]). 456 func incrNonce(nonce *[aeadNonceSize]byte) { 457 counter := binary.LittleEndian.Uint64(nonce[4:]) 458 if counter == math.MaxUint64 { 459 // Terminates the session and makes sure the nonce would not re-used. 460 // See https://github.com/tendermint/tendermint/issues/3531 461 panic("can't increase nonce without overflow") 462 } 463 counter++ 464 binary.LittleEndian.PutUint64(nonce[4:], counter) 465 }