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