mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-07 13:55:17 +00:00
258
p2p/conn/evil_secret_connection_test.go
Normal file
258
p2p/conn/evil_secret_connection_test.go
Normal file
@@ -0,0 +1,258 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,10 @@ import (
|
||||
tmrand "github.com/tendermint/tendermint/libs/rand"
|
||||
)
|
||||
|
||||
// Run go test -update from within this module
|
||||
// to update the golden test vector file
|
||||
var update = flag.Bool("update", false, "update .golden files")
|
||||
|
||||
type kvstoreConn struct {
|
||||
*io.PipeReader
|
||||
*io.PipeWriter
|
||||
@@ -39,60 +43,14 @@ func (drw kvstoreConn) Close() (err error) {
|
||||
return err1
|
||||
}
|
||||
|
||||
// Each returned ReadWriteCloser is akin to a net.Connection
|
||||
func makeKVStoreConnPair() (fooConn, barConn kvstoreConn) {
|
||||
barReader, fooWriter := io.Pipe()
|
||||
fooReader, barWriter := io.Pipe()
|
||||
return kvstoreConn{fooReader, fooWriter}, kvstoreConn{barReader, barWriter}
|
||||
type privKeyWithNilPubKey struct {
|
||||
orig crypto.PrivKey
|
||||
}
|
||||
|
||||
func makeSecretConnPair(tb testing.TB) (fooSecConn, barSecConn *SecretConnection) {
|
||||
|
||||
var fooConn, barConn = makeKVStoreConnPair()
|
||||
var fooPrvKey = ed25519.GenPrivKey()
|
||||
var fooPubKey = fooPrvKey.PubKey()
|
||||
var barPrvKey = ed25519.GenPrivKey()
|
||||
var barPubKey = barPrvKey.PubKey()
|
||||
|
||||
// Make connections from both sides in parallel.
|
||||
var trs, ok = async.Parallel(
|
||||
func(_ int) (val interface{}, abort bool, err error) {
|
||||
fooSecConn, err = MakeSecretConnection(fooConn, fooPrvKey)
|
||||
if err != nil {
|
||||
tb.Errorf("failed to establish SecretConnection for foo: %v", err)
|
||||
return nil, true, err
|
||||
}
|
||||
remotePubBytes := fooSecConn.RemotePubKey()
|
||||
if !remotePubBytes.Equals(barPubKey) {
|
||||
err = fmt.Errorf("unexpected fooSecConn.RemotePubKey. Expected %v, got %v",
|
||||
barPubKey, fooSecConn.RemotePubKey())
|
||||
tb.Error(err)
|
||||
return nil, false, err
|
||||
}
|
||||
return nil, false, nil
|
||||
},
|
||||
func(_ int) (val interface{}, abort bool, err error) {
|
||||
barSecConn, err = MakeSecretConnection(barConn, barPrvKey)
|
||||
if barSecConn == nil {
|
||||
tb.Errorf("failed to establish SecretConnection for bar: %v", err)
|
||||
return nil, true, err
|
||||
}
|
||||
remotePubBytes := barSecConn.RemotePubKey()
|
||||
if !remotePubBytes.Equals(fooPubKey) {
|
||||
err = fmt.Errorf("unexpected barSecConn.RemotePubKey. Expected %v, got %v",
|
||||
fooPubKey, barSecConn.RemotePubKey())
|
||||
tb.Error(err)
|
||||
return nil, false, nil
|
||||
}
|
||||
return nil, false, nil
|
||||
},
|
||||
)
|
||||
|
||||
require.Nil(tb, trs.FirstError())
|
||||
require.True(tb, ok, "Unexpected task abortion")
|
||||
|
||||
return fooSecConn, barSecConn
|
||||
}
|
||||
func (pk privKeyWithNilPubKey) Bytes() []byte { return pk.orig.Bytes() }
|
||||
func (pk privKeyWithNilPubKey) Sign(msg []byte) ([]byte, error) { return pk.orig.Sign(msg) }
|
||||
func (pk privKeyWithNilPubKey) PubKey() crypto.PubKey { return nil }
|
||||
func (pk privKeyWithNilPubKey) Equals(pk2 crypto.PrivKey) bool { return pk.orig.Equals(pk2) }
|
||||
|
||||
func TestSecretConnectionHandshake(t *testing.T) {
|
||||
fooSecConn, barSecConn := makeSecretConnPair(t)
|
||||
@@ -148,26 +106,6 @@ func TestConcurrentRead(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func writeLots(t *testing.T, wg *sync.WaitGroup, conn io.Writer, txt string, n int) {
|
||||
defer wg.Done()
|
||||
for i := 0; i < n; i++ {
|
||||
_, err := conn.Write([]byte(txt))
|
||||
if err != nil {
|
||||
t.Errorf("failed to write to fooSecConn: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readLots(t *testing.T, wg *sync.WaitGroup, conn io.Reader, n int) {
|
||||
readBuffer := make([]byte, dataMaxSize)
|
||||
for i := 0; i < n; i++ {
|
||||
_, err := conn.Read(readBuffer)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
func TestSecretConnectionReadWrite(t *testing.T) {
|
||||
fooConn, barConn := makeKVStoreConnPair()
|
||||
fooWrites, barWrites := []string{}, []string{}
|
||||
@@ -282,13 +220,8 @@ func TestSecretConnectionReadWrite(t *testing.T) {
|
||||
|
||||
compareWritesReads(fooWrites, barReads)
|
||||
compareWritesReads(barWrites, fooReads)
|
||||
|
||||
}
|
||||
|
||||
// Run go test -update from within this module
|
||||
// to update the golden test vector file
|
||||
var update = flag.Bool("update", false, "update .golden files")
|
||||
|
||||
func TestDeriveSecretsAndChallengeGolden(t *testing.T) {
|
||||
goldenFilepath := filepath.Join("testdata", t.Name()+".golden")
|
||||
if *update {
|
||||
@@ -322,15 +255,6 @@ func TestDeriveSecretsAndChallengeGolden(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type privKeyWithNilPubKey struct {
|
||||
orig crypto.PrivKey
|
||||
}
|
||||
|
||||
func (pk privKeyWithNilPubKey) Bytes() []byte { return pk.orig.Bytes() }
|
||||
func (pk privKeyWithNilPubKey) Sign(msg []byte) ([]byte, error) { return pk.orig.Sign(msg) }
|
||||
func (pk privKeyWithNilPubKey) PubKey() crypto.PubKey { return nil }
|
||||
func (pk privKeyWithNilPubKey) Equals(pk2 crypto.PrivKey) bool { return pk.orig.Equals(pk2) }
|
||||
|
||||
func TestNilPubkey(t *testing.T) {
|
||||
var fooConn, barConn = makeKVStoreConnPair()
|
||||
var fooPrvKey = ed25519.GenPrivKey()
|
||||
@@ -367,6 +291,26 @@ func TestNonEd25519Pubkey(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func writeLots(t *testing.T, wg *sync.WaitGroup, conn io.Writer, txt string, n int) {
|
||||
defer wg.Done()
|
||||
for i := 0; i < n; i++ {
|
||||
_, err := conn.Write([]byte(txt))
|
||||
if err != nil {
|
||||
t.Errorf("failed to write to fooSecConn: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readLots(t *testing.T, wg *sync.WaitGroup, conn io.Reader, n int) {
|
||||
readBuffer := make([]byte, dataMaxSize)
|
||||
for i := 0; i < n; i++ {
|
||||
_, err := conn.Read(readBuffer)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
// Creates the data for a test vector file.
|
||||
// The file format is:
|
||||
// Hex(diffie_hellman_secret), loc_is_least, Hex(recvSecret), Hex(sendSecret), Hex(challenge)
|
||||
@@ -386,6 +330,65 @@ func createGoldenTestVectors(t *testing.T) string {
|
||||
return data
|
||||
}
|
||||
|
||||
// Each returned ReadWriteCloser is akin to a net.Connection
|
||||
func makeKVStoreConnPair() (fooConn, barConn kvstoreConn) {
|
||||
barReader, fooWriter := io.Pipe()
|
||||
fooReader, barWriter := io.Pipe()
|
||||
return kvstoreConn{fooReader, fooWriter}, kvstoreConn{barReader, barWriter}
|
||||
}
|
||||
|
||||
func makeSecretConnPair(tb testing.TB) (fooSecConn, barSecConn *SecretConnection) {
|
||||
var (
|
||||
fooConn, barConn = makeKVStoreConnPair()
|
||||
fooPrvKey = ed25519.GenPrivKey()
|
||||
fooPubKey = fooPrvKey.PubKey()
|
||||
barPrvKey = ed25519.GenPrivKey()
|
||||
barPubKey = barPrvKey.PubKey()
|
||||
)
|
||||
|
||||
// Make connections from both sides in parallel.
|
||||
var trs, ok = async.Parallel(
|
||||
func(_ int) (val interface{}, abort bool, err error) {
|
||||
fooSecConn, err = MakeSecretConnection(fooConn, fooPrvKey)
|
||||
if err != nil {
|
||||
tb.Errorf("failed to establish SecretConnection for foo: %v", err)
|
||||
return nil, true, err
|
||||
}
|
||||
remotePubBytes := fooSecConn.RemotePubKey()
|
||||
if !remotePubBytes.Equals(barPubKey) {
|
||||
err = fmt.Errorf("unexpected fooSecConn.RemotePubKey. Expected %v, got %v",
|
||||
barPubKey, fooSecConn.RemotePubKey())
|
||||
tb.Error(err)
|
||||
return nil, true, err
|
||||
}
|
||||
return nil, false, nil
|
||||
},
|
||||
func(_ int) (val interface{}, abort bool, err error) {
|
||||
barSecConn, err = MakeSecretConnection(barConn, barPrvKey)
|
||||
if barSecConn == nil {
|
||||
tb.Errorf("failed to establish SecretConnection for bar: %v", err)
|
||||
return nil, true, err
|
||||
}
|
||||
remotePubBytes := barSecConn.RemotePubKey()
|
||||
if !remotePubBytes.Equals(fooPubKey) {
|
||||
err = fmt.Errorf("unexpected barSecConn.RemotePubKey. Expected %v, got %v",
|
||||
fooPubKey, barSecConn.RemotePubKey())
|
||||
tb.Error(err)
|
||||
return nil, true, err
|
||||
}
|
||||
return nil, false, nil
|
||||
},
|
||||
)
|
||||
|
||||
require.Nil(tb, trs.FirstError())
|
||||
require.True(tb, ok, "Unexpected task abortion")
|
||||
|
||||
return fooSecConn, barSecConn
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Benchmarks
|
||||
|
||||
func BenchmarkWriteSecretConnection(b *testing.B) {
|
||||
b.StopTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
Reference in New Issue
Block a user