github.com/vipernet-xyz/tm@v0.34.24/p2p/conn/evil_secret_connection_test.go (about)

     1  package conn
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"io"
     7  	"testing"
     8  
     9  	gogotypes "github.com/gogo/protobuf/types"
    10  	"github.com/gtank/merlin"
    11  	"github.com/stretchr/testify/assert"
    12  	"golang.org/x/crypto/chacha20poly1305"
    13  
    14  	"github.com/vipernet-xyz/tm/crypto"
    15  	"github.com/vipernet-xyz/tm/crypto/ed25519"
    16  	cryptoenc "github.com/vipernet-xyz/tm/crypto/encoding"
    17  	"github.com/vipernet-xyz/tm/libs/protoio"
    18  	tmp2p "github.com/vipernet-xyz/tm/proto/tendermint/p2p"
    19  )
    20  
    21  type buffer struct {
    22  	next bytes.Buffer
    23  }
    24  
    25  func (b *buffer) Read(data []byte) (n int, err error) {
    26  	return b.next.Read(data)
    27  }
    28  
    29  func (b *buffer) Write(data []byte) (n int, err error) {
    30  	return b.next.Write(data)
    31  }
    32  
    33  func (b *buffer) Bytes() []byte {
    34  	return b.next.Bytes()
    35  }
    36  
    37  func (b *buffer) Close() error {
    38  	return nil
    39  }
    40  
    41  type evilConn struct {
    42  	secretConn *SecretConnection
    43  	buffer     *buffer
    44  
    45  	locEphPub  *[32]byte
    46  	locEphPriv *[32]byte
    47  	remEphPub  *[32]byte
    48  	privKey    crypto.PrivKey
    49  
    50  	readStep   int
    51  	writeStep  int
    52  	readOffset int
    53  
    54  	shareEphKey        bool
    55  	badEphKey          bool
    56  	shareAuthSignature bool
    57  	badAuthSignature   bool
    58  }
    59  
    60  func newEvilConn(shareEphKey, badEphKey, shareAuthSignature, badAuthSignature bool) *evilConn {
    61  	privKey := ed25519.GenPrivKey()
    62  	locEphPub, locEphPriv := genEphKeys()
    63  	var rep [32]byte
    64  	c := &evilConn{
    65  		locEphPub:  locEphPub,
    66  		locEphPriv: locEphPriv,
    67  		remEphPub:  &rep,
    68  		privKey:    privKey,
    69  
    70  		shareEphKey:        shareEphKey,
    71  		badEphKey:          badEphKey,
    72  		shareAuthSignature: shareAuthSignature,
    73  		badAuthSignature:   badAuthSignature,
    74  	}
    75  
    76  	return c
    77  }
    78  
    79  func (c *evilConn) Read(data []byte) (n int, err error) {
    80  	if !c.shareEphKey {
    81  		return 0, io.EOF
    82  	}
    83  
    84  	switch c.readStep {
    85  	case 0:
    86  		if !c.badEphKey {
    87  			lc := *c.locEphPub
    88  			bz, err := protoio.MarshalDelimited(&gogotypes.BytesValue{Value: lc[:]})
    89  			if err != nil {
    90  				panic(err)
    91  			}
    92  			copy(data, bz[c.readOffset:])
    93  			n = len(data)
    94  		} else {
    95  			bz, err := protoio.MarshalDelimited(&gogotypes.BytesValue{Value: []byte("drop users;")})
    96  			if err != nil {
    97  				panic(err)
    98  			}
    99  			copy(data, bz)
   100  			n = len(data)
   101  		}
   102  		c.readOffset += n
   103  
   104  		if n >= 32 {
   105  			c.readOffset = 0
   106  			c.readStep = 1
   107  			if !c.shareAuthSignature {
   108  				c.readStep = 2
   109  			}
   110  		}
   111  
   112  		return n, nil
   113  	case 1:
   114  		signature := c.signChallenge()
   115  		if !c.badAuthSignature {
   116  			pkpb, err := cryptoenc.PubKeyToProto(c.privKey.PubKey())
   117  			if err != nil {
   118  				panic(err)
   119  			}
   120  			bz, err := protoio.MarshalDelimited(&tmp2p.AuthSigMessage{PubKey: pkpb, Sig: signature})
   121  			if err != nil {
   122  				panic(err)
   123  			}
   124  			n, err = c.secretConn.Write(bz)
   125  			if err != nil {
   126  				panic(err)
   127  			}
   128  			if c.readOffset > len(c.buffer.Bytes()) {
   129  				return len(data), nil
   130  			}
   131  			copy(data, c.buffer.Bytes()[c.readOffset:])
   132  		} else {
   133  			bz, err := protoio.MarshalDelimited(&gogotypes.BytesValue{Value: []byte("select * from users;")})
   134  			if err != nil {
   135  				panic(err)
   136  			}
   137  			n, err = c.secretConn.Write(bz)
   138  			if err != nil {
   139  				panic(err)
   140  			}
   141  			if c.readOffset > len(c.buffer.Bytes()) {
   142  				return len(data), nil
   143  			}
   144  			copy(data, c.buffer.Bytes())
   145  		}
   146  		c.readOffset += len(data)
   147  		return n, nil
   148  	default:
   149  		return 0, io.EOF
   150  	}
   151  }
   152  
   153  func (c *evilConn) Write(data []byte) (n int, err error) {
   154  	switch c.writeStep {
   155  	case 0:
   156  		var (
   157  			bytes     gogotypes.BytesValue
   158  			remEphPub [32]byte
   159  		)
   160  		err := protoio.UnmarshalDelimited(data, &bytes)
   161  		if err != nil {
   162  			panic(err)
   163  		}
   164  		copy(remEphPub[:], bytes.Value)
   165  		c.remEphPub = &remEphPub
   166  		c.writeStep = 1
   167  		if !c.shareAuthSignature {
   168  			c.writeStep = 2
   169  		}
   170  		return len(data), nil
   171  	case 1:
   172  		// Signature is not needed, therefore skipped.
   173  		return len(data), nil
   174  	default:
   175  		return 0, io.EOF
   176  	}
   177  }
   178  
   179  func (c *evilConn) Close() error {
   180  	return nil
   181  }
   182  
   183  func (c *evilConn) signChallenge() []byte {
   184  	// Sort by lexical order.
   185  	loEphPub, hiEphPub := sort32(c.locEphPub, c.remEphPub)
   186  
   187  	transcript := merlin.NewTranscript("TENDERMINT_SECRET_CONNECTION_TRANSCRIPT_HASH")
   188  
   189  	transcript.AppendMessage(labelEphemeralLowerPublicKey, loEphPub[:])
   190  	transcript.AppendMessage(labelEphemeralUpperPublicKey, hiEphPub[:])
   191  
   192  	// Check if the local ephemeral public key was the least, lexicographically
   193  	// sorted.
   194  	locIsLeast := bytes.Equal(c.locEphPub[:], loEphPub[:])
   195  
   196  	// Compute common diffie hellman secret using X25519.
   197  	dhSecret, err := computeDHSecret(c.remEphPub, c.locEphPriv)
   198  	if err != nil {
   199  		panic(err)
   200  	}
   201  
   202  	transcript.AppendMessage(labelDHSecret, dhSecret[:])
   203  
   204  	// Generate the secret used for receiving, sending, challenge via HKDF-SHA2
   205  	// on the transcript state (which itself also uses HKDF-SHA2 to derive a key
   206  	// from the dhSecret).
   207  	recvSecret, sendSecret := deriveSecrets(dhSecret, locIsLeast)
   208  
   209  	const challengeSize = 32
   210  	var challenge [challengeSize]byte
   211  	challengeSlice := transcript.ExtractBytes(labelSecretConnectionMac, challengeSize)
   212  
   213  	copy(challenge[:], challengeSlice[0:challengeSize])
   214  
   215  	sendAead, err := chacha20poly1305.New(sendSecret[:])
   216  	if err != nil {
   217  		panic(errors.New("invalid send SecretConnection Key"))
   218  	}
   219  	recvAead, err := chacha20poly1305.New(recvSecret[:])
   220  	if err != nil {
   221  		panic(errors.New("invalid receive SecretConnection Key"))
   222  	}
   223  
   224  	b := &buffer{}
   225  	c.secretConn = &SecretConnection{
   226  		conn:       b,
   227  		recvBuffer: nil,
   228  		recvNonce:  new([aeadNonceSize]byte),
   229  		sendNonce:  new([aeadNonceSize]byte),
   230  		recvAead:   recvAead,
   231  		sendAead:   sendAead,
   232  	}
   233  	c.buffer = b
   234  
   235  	// Sign the challenge bytes for authentication.
   236  	locSignature, err := signChallenge(&challenge, c.privKey)
   237  	if err != nil {
   238  		panic(err)
   239  	}
   240  
   241  	return locSignature
   242  }
   243  
   244  // TestMakeSecretConnection creates an evil connection and tests that
   245  // MakeSecretConnection errors at different stages.
   246  func TestMakeSecretConnection(t *testing.T) {
   247  	testCases := []struct {
   248  		name   string
   249  		conn   *evilConn
   250  		errMsg string
   251  	}{
   252  		{"refuse to share ethimeral key", newEvilConn(false, false, false, false), "EOF"},
   253  		{"share bad ethimeral key", newEvilConn(true, true, false, false), "wrong wireType"},
   254  		{"refuse to share auth signature", newEvilConn(true, false, false, false), "EOF"},
   255  		{"share bad auth signature", newEvilConn(true, false, true, true), "failed to decrypt SecretConnection"},
   256  		{"all good", newEvilConn(true, false, true, false), ""},
   257  	}
   258  
   259  	for _, tc := range testCases {
   260  		tc := tc
   261  		t.Run(tc.name, func(t *testing.T) {
   262  			privKey := ed25519.GenPrivKey()
   263  			_, err := MakeSecretConnection(tc.conn, privKey)
   264  			if tc.errMsg != "" {
   265  				if assert.Error(t, err) {
   266  					assert.Contains(t, err.Error(), tc.errMsg)
   267  				}
   268  			} else {
   269  				assert.NoError(t, err)
   270  			}
   271  		})
   272  	}
   273  }