|
|
- package conn
-
- import (
- "bytes"
- "errors"
- "io"
- "testing"
-
- "github.com/gtank/merlin"
- "github.com/stretchr/testify/assert"
- "golang.org/x/crypto/chacha20poly1305"
-
- "github.com/tendermint/tendermint/crypto"
- "github.com/tendermint/tendermint/crypto/ed25519"
- )
-
- type buffer struct {
- next bytes.Buffer
- }
-
- func (b *buffer) Read(data []byte) (n int, err error) {
- return b.next.Read(data)
- }
-
- func (b *buffer) Write(data []byte) (n int, err error) {
- return b.next.Write(data)
- }
-
- func (b *buffer) Bytes() []byte {
- return b.next.Bytes()
- }
-
- func (b *buffer) Close() error {
- return nil
- }
-
- type evilConn struct {
- secretConn *SecretConnection
- buffer *buffer
-
- locEphPub *[32]byte
- locEphPriv *[32]byte
- remEphPub *[32]byte
- privKey crypto.PrivKey
-
- readStep int
- writeStep int
- readOffset int
-
- shareEphKey bool
- badEphKey bool
- shareAuthSignature bool
- badAuthSignature bool
- }
-
- func newEvilConn(shareEphKey, badEphKey, shareAuthSignature, badAuthSignature bool) *evilConn {
- privKey := ed25519.GenPrivKey()
- locEphPub, locEphPriv := genEphKeys()
- var rep [32]byte
- c := &evilConn{
- locEphPub: locEphPub,
- locEphPriv: locEphPriv,
- remEphPub: &rep,
- privKey: privKey,
-
- shareEphKey: shareEphKey,
- badEphKey: badEphKey,
- shareAuthSignature: shareAuthSignature,
- badAuthSignature: badAuthSignature,
- }
-
- return c
- }
-
- func (c *evilConn) Read(data []byte) (n int, err error) {
- if !c.shareEphKey {
- return 0, io.EOF
- }
-
- switch c.readStep {
- case 0:
- if !c.badEphKey {
- bz, err := cdc.MarshalBinaryLengthPrefixed(c.locEphPub)
- if err != nil {
- panic(err)
- }
- copy(data, bz[c.readOffset:])
- n = len(data)
- } else {
- bz, err := cdc.MarshalBinaryLengthPrefixed([]byte("drop users;"))
- if err != nil {
- panic(err)
- }
- copy(data, bz)
- n = len(data)
- }
- c.readOffset += n
-
- if n >= 32 {
- c.readOffset = 0
- c.readStep = 1
- if !c.shareAuthSignature {
- c.readStep = 2
- }
- }
-
- return n, nil
- case 1:
- signature := c.signChallenge()
- if !c.badAuthSignature {
- bz, err := cdc.MarshalBinaryLengthPrefixed(authSigMessage{c.privKey.PubKey(), signature})
- if err != nil {
- panic(err)
- }
- n, err = c.secretConn.Write(bz)
- if err != nil {
- panic(err)
- }
- if c.readOffset > len(c.buffer.Bytes()) {
- return len(data), nil
- }
- copy(data, c.buffer.Bytes()[c.readOffset:])
- } else {
- bz, err := cdc.MarshalBinaryLengthPrefixed([]byte("select * from users;"))
- if err != nil {
- panic(err)
- }
- n, err = c.secretConn.Write(bz)
- if err != nil {
- panic(err)
- }
- if c.readOffset > len(c.buffer.Bytes()) {
- return len(data), nil
- }
- copy(data, c.buffer.Bytes())
- }
- c.readOffset += len(data)
- return n, nil
- default:
- return 0, io.EOF
- }
- }
-
- func (c *evilConn) Write(data []byte) (n int, err error) {
- switch c.writeStep {
- case 0:
- err := cdc.UnmarshalBinaryLengthPrefixed(data, c.remEphPub)
- if err != nil {
- panic(err)
- }
- c.writeStep = 1
- if !c.shareAuthSignature {
- c.writeStep = 2
- }
- return len(data), nil
- case 1:
- // Signature is not needed, therefore skipped.
- return len(data), nil
- default:
- return 0, io.EOF
- }
- }
-
- func (c *evilConn) Close() error {
- return nil
- }
-
- func (c *evilConn) signChallenge() []byte {
- // Sort by lexical order.
- loEphPub, hiEphPub := sort32(c.locEphPub, c.remEphPub)
-
- transcript := merlin.NewTranscript("TENDERMINT_SECRET_CONNECTION_TRANSCRIPT_HASH")
-
- transcript.AppendMessage(labelEphemeralLowerPublicKey, loEphPub[:])
- transcript.AppendMessage(labelEphemeralUpperPublicKey, hiEphPub[:])
-
- // Check if the local ephemeral public key was the least, lexicographically
- // sorted.
- locIsLeast := bytes.Equal(c.locEphPub[:], loEphPub[:])
-
- // Compute common diffie hellman secret using X25519.
- dhSecret, err := computeDHSecret(c.remEphPub, c.locEphPriv)
- if err != nil {
- panic(err)
- }
-
- transcript.AppendMessage(labelDHSecret, dhSecret[:])
-
- // Generate the secret used for receiving, sending, challenge via HKDF-SHA2
- // on the transcript state (which itself also uses HKDF-SHA2 to derive a key
- // from the dhSecret).
- recvSecret, sendSecret := deriveSecrets(dhSecret, locIsLeast)
-
- const challengeSize = 32
- var challenge [challengeSize]byte
- challengeSlice := transcript.ExtractBytes(labelSecretConnectionMac, challengeSize)
-
- copy(challenge[:], challengeSlice[0:challengeSize])
-
- sendAead, err := chacha20poly1305.New(sendSecret[:])
- if err != nil {
- panic(errors.New("invalid send SecretConnection Key"))
- }
- recvAead, err := chacha20poly1305.New(recvSecret[:])
- if err != nil {
- panic(errors.New("invalid receive SecretConnection Key"))
- }
-
- b := &buffer{}
- c.secretConn = &SecretConnection{
- conn: b,
- recvBuffer: nil,
- recvNonce: new([aeadNonceSize]byte),
- sendNonce: new([aeadNonceSize]byte),
- recvAead: recvAead,
- sendAead: sendAead,
- }
- c.buffer = b
-
- // Sign the challenge bytes for authentication.
- locSignature, err := signChallenge(&challenge, c.privKey)
- if err != nil {
- panic(err)
- }
-
- return locSignature
- }
-
- // TestMakeSecretConnection creates an evil connection and tests that
- // MakeSecretConnection errors at different stages.
- func TestMakeSecretConnection(t *testing.T) {
- testCases := []struct {
- name string
- conn *evilConn
- errMsg string
- }{
- {"refuse to share ethimeral key", newEvilConn(false, false, false, false), "EOF"},
- {"share bad ethimeral key", newEvilConn(true, true, false, false), "Insufficient bytes to decode"},
- {"refuse to share auth signature", newEvilConn(true, false, false, false), "EOF"},
- {"share bad auth signature", newEvilConn(true, false, true, true), "failed to decrypt SecretConnection"},
- {"all good", newEvilConn(true, false, true, false), ""},
- }
-
- for _, tc := range testCases {
- tc := tc
- t.Run(tc.name, func(t *testing.T) {
- privKey := ed25519.GenPrivKey()
- _, err := MakeSecretConnection(tc.conn, privKey)
- if tc.errMsg != "" {
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), tc.errMsg)
- }
- } else {
- assert.NoError(t, err)
- }
- })
- }
- }
|