Browse Source

p2p/conn: add a test for MakeSecretConnection (#4829)

Refs #4154
pull/4952/head
Anton Kaliaev 4 years ago
committed by GitHub
parent
commit
994912211c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 353 additions and 88 deletions
  1. +5
    -1
      libs/async/async.go
  2. +1
    -1
      libs/async/async_test.go
  3. +258
    -0
      p2p/conn/evil_secret_connection_test.go
  4. +89
    -86
      p2p/conn/secret_connection_test.go

+ 5
- 1
libs/async/async.go View File

@ -2,6 +2,7 @@ package async
import (
"fmt"
"runtime"
"sync/atomic"
)
@ -143,7 +144,10 @@ func Parallel(tasks ...Task) (trs *TaskResultSet, ok bool) {
if pnk := recover(); pnk != nil {
atomic.AddInt32(numPanics, 1)
// Send panic to taskResultCh.
taskResultCh <- TaskResult{nil, fmt.Errorf("panic in task %v", pnk)}
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
taskResultCh <- TaskResult{nil, fmt.Errorf("panic in task %v : %s", pnk, buf)}
// Closing taskResultCh lets trs.Wait() work.
close(taskResultCh)
// Decrement waitgroup.


+ 1
- 1
libs/async/async_test.go View File

@ -139,7 +139,7 @@ func checkResult(t *testing.T, taskResultSet *TaskResultSet, index int,
case err != nil:
assert.Equal(t, err.Error(), taskResult.Error.Error(), taskName)
case pnk != nil:
assert.Equal(t, pnk, taskResult.Error.Error(), taskName)
assert.Contains(t, taskResult.Error.Error(), pnk, taskName)
default:
assert.Nil(t, taskResult.Error, taskName)
}


+ 258
- 0
p2p/conn/evil_secret_connection_test.go View 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)
}
})
}
}

+ 89
- 86
p2p/conn/secret_connection_test.go View File

@ -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()


Loading…
Cancel
Save