github.com/vipernet-xyz/tm@v0.34.24/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/vipernet-xyz/tm/crypto"
    25  	"github.com/vipernet-xyz/tm/crypto/ed25519"
    26  	cryptoenc "github.com/vipernet-xyz/tm/crypto/encoding"
    27  	"github.com/vipernet-xyz/tm/libs/async"
    28  	"github.com/vipernet-xyz/tm/libs/protoio"
    29  	tmsync "github.com/vipernet-xyz/tm/libs/sync"
    30  	tmp2p "github.com/vipernet-xyz/tm/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/vipernet-xyz/tm/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/vipernet-xyz/tm/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.VerifySignature(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  func (sc *SecretConnection) Close() error                  { return sc.conn.Close() }
   279  func (sc *SecretConnection) LocalAddr() net.Addr           { return sc.conn.(net.Conn).LocalAddr() }
   280  func (sc *SecretConnection) RemoteAddr() net.Addr          { return sc.conn.(net.Conn).RemoteAddr() }
   281  func (sc *SecretConnection) SetDeadline(t time.Time) error { return sc.conn.(net.Conn).SetDeadline(t) }
   282  func (sc *SecretConnection) SetReadDeadline(t time.Time) error {
   283  	return sc.conn.(net.Conn).SetReadDeadline(t)
   284  }
   285  func (sc *SecretConnection) SetWriteDeadline(t time.Time) error {
   286  	return sc.conn.(net.Conn).SetWriteDeadline(t)
   287  }
   288  
   289  func genEphKeys() (ephPub, ephPriv *[32]byte) {
   290  	var err error
   291  	// TODO: Probably not a problem but ask Tony: different from the rust implementation (uses x25519-dalek),
   292  	// we do not "clamp" the private key scalar:
   293  	// see: https://github.com/dalek-cryptography/x25519-dalek/blob/34676d336049df2bba763cc076a75e47ae1f170f/src/x25519.rs#L56-L74
   294  	ephPub, ephPriv, err = box.GenerateKey(crand.Reader)
   295  	if err != nil {
   296  		panic("Could not generate ephemeral key-pair")
   297  	}
   298  	return
   299  }
   300  
   301  func shareEphPubKey(conn io.ReadWriter, locEphPub *[32]byte) (remEphPub *[32]byte, err error) {
   302  
   303  	// Send our pubkey and receive theirs in tandem.
   304  	var trs, _ = async.Parallel(
   305  		func(_ int) (val interface{}, abort bool, err error) {
   306  			lc := *locEphPub
   307  			_, err = protoio.NewDelimitedWriter(conn).WriteMsg(&gogotypes.BytesValue{Value: lc[:]})
   308  			if err != nil {
   309  				return nil, true, err // abort
   310  			}
   311  			return nil, false, nil
   312  		},
   313  		func(_ int) (val interface{}, abort bool, err error) {
   314  			var bytes gogotypes.BytesValue
   315  			_, err = protoio.NewDelimitedReader(conn, 1024*1024).ReadMsg(&bytes)
   316  			if err != nil {
   317  				return nil, true, err // abort
   318  			}
   319  
   320  			var _remEphPub [32]byte
   321  			copy(_remEphPub[:], bytes.Value)
   322  			return _remEphPub, false, nil
   323  		},
   324  	)
   325  
   326  	// If error:
   327  	if trs.FirstError() != nil {
   328  		err = trs.FirstError()
   329  		return
   330  	}
   331  
   332  	// Otherwise:
   333  	var _remEphPub = trs.FirstValue().([32]byte)
   334  	return &_remEphPub, nil
   335  }
   336  
   337  func deriveSecrets(
   338  	dhSecret *[32]byte,
   339  	locIsLeast bool,
   340  ) (recvSecret, sendSecret *[aeadKeySize]byte) {
   341  	hash := sha256.New
   342  	hkdf := hkdf.New(hash, dhSecret[:], nil, secretConnKeyAndChallengeGen)
   343  	// get enough data for 2 aead keys, and a 32 byte challenge
   344  	res := new([2*aeadKeySize + 32]byte)
   345  	_, err := io.ReadFull(hkdf, res[:])
   346  	if err != nil {
   347  		panic(err)
   348  	}
   349  
   350  	recvSecret = new([aeadKeySize]byte)
   351  	sendSecret = new([aeadKeySize]byte)
   352  
   353  	// bytes 0 through aeadKeySize - 1 are one aead key.
   354  	// bytes aeadKeySize through 2*aeadKeySize -1 are another aead key.
   355  	// which key corresponds to sending and receiving key depends on whether
   356  	// the local key is less than the remote key.
   357  	if locIsLeast {
   358  		copy(recvSecret[:], res[0:aeadKeySize])
   359  		copy(sendSecret[:], res[aeadKeySize:aeadKeySize*2])
   360  	} else {
   361  		copy(sendSecret[:], res[0:aeadKeySize])
   362  		copy(recvSecret[:], res[aeadKeySize:aeadKeySize*2])
   363  	}
   364  
   365  	return
   366  }
   367  
   368  // computeDHSecret computes a Diffie-Hellman shared secret key
   369  // from our own local private key and the other's public key.
   370  func computeDHSecret(remPubKey, locPrivKey *[32]byte) (*[32]byte, error) {
   371  	shrKey, err := curve25519.X25519(locPrivKey[:], remPubKey[:])
   372  	if err != nil {
   373  		return nil, err
   374  	}
   375  	var shrKeyArray [32]byte
   376  	copy(shrKeyArray[:], shrKey)
   377  	return &shrKeyArray, nil
   378  }
   379  
   380  func sort32(foo, bar *[32]byte) (lo, hi *[32]byte) {
   381  	if bytes.Compare(foo[:], bar[:]) < 0 {
   382  		lo = foo
   383  		hi = bar
   384  	} else {
   385  		lo = bar
   386  		hi = foo
   387  	}
   388  	return
   389  }
   390  
   391  func signChallenge(challenge *[32]byte, locPrivKey crypto.PrivKey) ([]byte, error) {
   392  	signature, err := locPrivKey.Sign(challenge[:])
   393  	if err != nil {
   394  		return nil, err
   395  	}
   396  	return signature, nil
   397  }
   398  
   399  type authSigMessage struct {
   400  	Key crypto.PubKey
   401  	Sig []byte
   402  }
   403  
   404  func shareAuthSignature(sc io.ReadWriter, pubKey crypto.PubKey, signature []byte) (recvMsg authSigMessage, err error) {
   405  
   406  	// Send our info and receive theirs in tandem.
   407  	var trs, _ = async.Parallel(
   408  		func(_ int) (val interface{}, abort bool, err error) {
   409  			pbpk, err := cryptoenc.PubKeyToProto(pubKey)
   410  			if err != nil {
   411  				return nil, true, err
   412  			}
   413  			_, err = protoio.NewDelimitedWriter(sc).WriteMsg(&tmp2p.AuthSigMessage{PubKey: pbpk, Sig: signature})
   414  			if err != nil {
   415  				return nil, true, err // abort
   416  			}
   417  			return nil, false, nil
   418  		},
   419  		func(_ int) (val interface{}, abort bool, err error) {
   420  			var pba tmp2p.AuthSigMessage
   421  			_, err = protoio.NewDelimitedReader(sc, 1024*1024).ReadMsg(&pba)
   422  			if err != nil {
   423  				return nil, true, err // abort
   424  			}
   425  
   426  			pk, err := cryptoenc.PubKeyFromProto(pba.PubKey)
   427  			if err != nil {
   428  				return nil, true, err // abort
   429  			}
   430  
   431  			_recvMsg := authSigMessage{
   432  				Key: pk,
   433  				Sig: pba.Sig,
   434  			}
   435  			return _recvMsg, false, nil
   436  		},
   437  	)
   438  
   439  	// If error:
   440  	if trs.FirstError() != nil {
   441  		err = trs.FirstError()
   442  		return
   443  	}
   444  
   445  	var _recvMsg = trs.FirstValue().(authSigMessage)
   446  	return _recvMsg, nil
   447  }
   448  
   449  //--------------------------------------------------------------------------------
   450  
   451  // Increment nonce little-endian by 1 with wraparound.
   452  // Due to chacha20poly1305 expecting a 12 byte nonce we do not use the first four
   453  // bytes. We only increment a 64 bit unsigned int in the remaining 8 bytes
   454  // (little-endian in nonce[4:]).
   455  func incrNonce(nonce *[aeadNonceSize]byte) {
   456  	counter := binary.LittleEndian.Uint64(nonce[4:])
   457  	if counter == math.MaxUint64 {
   458  		// Terminates the session and makes sure the nonce would not re-used.
   459  		// See https://github.com/vipernet-xyz/tm/issues/3531
   460  		panic("can't increase nonce without overflow")
   461  	}
   462  	counter++
   463  	binary.LittleEndian.PutUint64(nonce[4:], counter)
   464  }