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