github.com/number571/tendermint@v0.34.11-gost/internal/p2p/conn/secret_connection.go (about)

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