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