Skip to content

Commit 9949122

Browse files
authoredJun 3, 2020
p2p/conn: add a test for MakeSecretConnection (#4829)
Refs #4154

File tree

4 files changed

+353
-88
lines changed

4 files changed

+353
-88
lines changed
 

‎libs/async/async.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package async
22

33
import (
44
"fmt"
5+
"runtime"
56
"sync/atomic"
67
)
78

@@ -143,7 +144,10 @@ func Parallel(tasks ...Task) (trs *TaskResultSet, ok bool) {
143144
if pnk := recover(); pnk != nil {
144145
atomic.AddInt32(numPanics, 1)
145146
// Send panic to taskResultCh.
146-
taskResultCh <- TaskResult{nil, fmt.Errorf("panic in task %v", pnk)}
147+
const size = 64 << 10
148+
buf := make([]byte, size)
149+
buf = buf[:runtime.Stack(buf, false)]
150+
taskResultCh <- TaskResult{nil, fmt.Errorf("panic in task %v : %s", pnk, buf)}
147151
// Closing taskResultCh lets trs.Wait() work.
148152
close(taskResultCh)
149153
// Decrement waitgroup.

‎libs/async/async_test.go

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

‎p2p/conn/secret_connection_test.go

+89-86
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ import (
2525
tmrand "github.com/tendermint/tendermint/libs/rand"
2626
)
2727

28+
// Run go test -update from within this module
29+
// to update the golden test vector file
30+
var update = flag.Bool("update", false, "update .golden files")
31+
2832
type kvstoreConn struct {
2933
*io.PipeReader
3034
*io.PipeWriter
@@ -39,60 +43,14 @@ func (drw kvstoreConn) Close() (err error) {
3943
return err1
4044
}
4145

42-
// Each returned ReadWriteCloser is akin to a net.Connection
43-
func makeKVStoreConnPair() (fooConn, barConn kvstoreConn) {
44-
barReader, fooWriter := io.Pipe()
45-
fooReader, barWriter := io.Pipe()
46-
return kvstoreConn{fooReader, fooWriter}, kvstoreConn{barReader, barWriter}
46+
type privKeyWithNilPubKey struct {
47+
orig crypto.PrivKey
4748
}
4849

49-
func makeSecretConnPair(tb testing.TB) (fooSecConn, barSecConn *SecretConnection) {
50-
51-
var fooConn, barConn = makeKVStoreConnPair()
52-
var fooPrvKey = ed25519.GenPrivKey()
53-
var fooPubKey = fooPrvKey.PubKey()
54-
var barPrvKey = ed25519.GenPrivKey()
55-
var barPubKey = barPrvKey.PubKey()
56-
57-
// Make connections from both sides in parallel.
58-
var trs, ok = async.Parallel(
59-
func(_ int) (val interface{}, abort bool, err error) {
60-
fooSecConn, err = MakeSecretConnection(fooConn, fooPrvKey)
61-
if err != nil {
62-
tb.Errorf("failed to establish SecretConnection for foo: %v", err)
63-
return nil, true, err
64-
}
65-
remotePubBytes := fooSecConn.RemotePubKey()
66-
if !remotePubBytes.Equals(barPubKey) {
67-
err = fmt.Errorf("unexpected fooSecConn.RemotePubKey. Expected %v, got %v",
68-
barPubKey, fooSecConn.RemotePubKey())
69-
tb.Error(err)
70-
return nil, false, err
71-
}
72-
return nil, false, nil
73-
},
74-
func(_ int) (val interface{}, abort bool, err error) {
75-
barSecConn, err = MakeSecretConnection(barConn, barPrvKey)
76-
if barSecConn == nil {
77-
tb.Errorf("failed to establish SecretConnection for bar: %v", err)
78-
return nil, true, err
79-
}
80-
remotePubBytes := barSecConn.RemotePubKey()
81-
if !remotePubBytes.Equals(fooPubKey) {
82-
err = fmt.Errorf("unexpected barSecConn.RemotePubKey. Expected %v, got %v",
83-
fooPubKey, barSecConn.RemotePubKey())
84-
tb.Error(err)
85-
return nil, false, nil
86-
}
87-
return nil, false, nil
88-
},
89-
)
90-
91-
require.Nil(tb, trs.FirstError())
92-
require.True(tb, ok, "Unexpected task abortion")
93-
94-
return fooSecConn, barSecConn
95-
}
50+
func (pk privKeyWithNilPubKey) Bytes() []byte { return pk.orig.Bytes() }
51+
func (pk privKeyWithNilPubKey) Sign(msg []byte) ([]byte, error) { return pk.orig.Sign(msg) }
52+
func (pk privKeyWithNilPubKey) PubKey() crypto.PubKey { return nil }
53+
func (pk privKeyWithNilPubKey) Equals(pk2 crypto.PrivKey) bool { return pk.orig.Equals(pk2) }
9654

9755
func TestSecretConnectionHandshake(t *testing.T) {
9856
fooSecConn, barSecConn := makeSecretConnPair(t)
@@ -148,26 +106,6 @@ func TestConcurrentRead(t *testing.T) {
148106
}
149107
}
150108

151-
func writeLots(t *testing.T, wg *sync.WaitGroup, conn io.Writer, txt string, n int) {
152-
defer wg.Done()
153-
for i := 0; i < n; i++ {
154-
_, err := conn.Write([]byte(txt))
155-
if err != nil {
156-
t.Errorf("failed to write to fooSecConn: %v", err)
157-
return
158-
}
159-
}
160-
}
161-
162-
func readLots(t *testing.T, wg *sync.WaitGroup, conn io.Reader, n int) {
163-
readBuffer := make([]byte, dataMaxSize)
164-
for i := 0; i < n; i++ {
165-
_, err := conn.Read(readBuffer)
166-
assert.NoError(t, err)
167-
}
168-
wg.Done()
169-
}
170-
171109
func TestSecretConnectionReadWrite(t *testing.T) {
172110
fooConn, barConn := makeKVStoreConnPair()
173111
fooWrites, barWrites := []string{}, []string{}
@@ -282,13 +220,8 @@ func TestSecretConnectionReadWrite(t *testing.T) {
282220

283221
compareWritesReads(fooWrites, barReads)
284222
compareWritesReads(barWrites, fooReads)
285-
286223
}
287224

288-
// Run go test -update from within this module
289-
// to update the golden test vector file
290-
var update = flag.Bool("update", false, "update .golden files")
291-
292225
func TestDeriveSecretsAndChallengeGolden(t *testing.T) {
293226
goldenFilepath := filepath.Join("testdata", t.Name()+".golden")
294227
if *update {
@@ -322,15 +255,6 @@ func TestDeriveSecretsAndChallengeGolden(t *testing.T) {
322255
}
323256
}
324257

325-
type privKeyWithNilPubKey struct {
326-
orig crypto.PrivKey
327-
}
328-
329-
func (pk privKeyWithNilPubKey) Bytes() []byte { return pk.orig.Bytes() }
330-
func (pk privKeyWithNilPubKey) Sign(msg []byte) ([]byte, error) { return pk.orig.Sign(msg) }
331-
func (pk privKeyWithNilPubKey) PubKey() crypto.PubKey { return nil }
332-
func (pk privKeyWithNilPubKey) Equals(pk2 crypto.PrivKey) bool { return pk.orig.Equals(pk2) }
333-
334258
func TestNilPubkey(t *testing.T) {
335259
var fooConn, barConn = makeKVStoreConnPair()
336260
var fooPrvKey = ed25519.GenPrivKey()
@@ -367,6 +291,26 @@ func TestNonEd25519Pubkey(t *testing.T) {
367291
})
368292
}
369293

294+
func writeLots(t *testing.T, wg *sync.WaitGroup, conn io.Writer, txt string, n int) {
295+
defer wg.Done()
296+
for i := 0; i < n; i++ {
297+
_, err := conn.Write([]byte(txt))
298+
if err != nil {
299+
t.Errorf("failed to write to fooSecConn: %v", err)
300+
return
301+
}
302+
}
303+
}
304+
305+
func readLots(t *testing.T, wg *sync.WaitGroup, conn io.Reader, n int) {
306+
readBuffer := make([]byte, dataMaxSize)
307+
for i := 0; i < n; i++ {
308+
_, err := conn.Read(readBuffer)
309+
assert.NoError(t, err)
310+
}
311+
wg.Done()
312+
}
313+
370314
// Creates the data for a test vector file.
371315
// The file format is:
372316
// Hex(diffie_hellman_secret), loc_is_least, Hex(recvSecret), Hex(sendSecret), Hex(challenge)
@@ -386,6 +330,65 @@ func createGoldenTestVectors(t *testing.T) string {
386330
return data
387331
}
388332

333+
// Each returned ReadWriteCloser is akin to a net.Connection
334+
func makeKVStoreConnPair() (fooConn, barConn kvstoreConn) {
335+
barReader, fooWriter := io.Pipe()
336+
fooReader, barWriter := io.Pipe()
337+
return kvstoreConn{fooReader, fooWriter}, kvstoreConn{barReader, barWriter}
338+
}
339+
340+
func makeSecretConnPair(tb testing.TB) (fooSecConn, barSecConn *SecretConnection) {
341+
var (
342+
fooConn, barConn = makeKVStoreConnPair()
343+
fooPrvKey = ed25519.GenPrivKey()
344+
fooPubKey = fooPrvKey.PubKey()
345+
barPrvKey = ed25519.GenPrivKey()
346+
barPubKey = barPrvKey.PubKey()
347+
)
348+
349+
// Make connections from both sides in parallel.
350+
var trs, ok = async.Parallel(
351+
func(_ int) (val interface{}, abort bool, err error) {
352+
fooSecConn, err = MakeSecretConnection(fooConn, fooPrvKey)
353+
if err != nil {
354+
tb.Errorf("failed to establish SecretConnection for foo: %v", err)
355+
return nil, true, err
356+
}
357+
remotePubBytes := fooSecConn.RemotePubKey()
358+
if !remotePubBytes.Equals(barPubKey) {
359+
err = fmt.Errorf("unexpected fooSecConn.RemotePubKey. Expected %v, got %v",
360+
barPubKey, fooSecConn.RemotePubKey())
361+
tb.Error(err)
362+
return nil, true, err
363+
}
364+
return nil, false, nil
365+
},
366+
func(_ int) (val interface{}, abort bool, err error) {
367+
barSecConn, err = MakeSecretConnection(barConn, barPrvKey)
368+
if barSecConn == nil {
369+
tb.Errorf("failed to establish SecretConnection for bar: %v", err)
370+
return nil, true, err
371+
}
372+
remotePubBytes := barSecConn.RemotePubKey()
373+
if !remotePubBytes.Equals(fooPubKey) {
374+
err = fmt.Errorf("unexpected barSecConn.RemotePubKey. Expected %v, got %v",
375+
fooPubKey, barSecConn.RemotePubKey())
376+
tb.Error(err)
377+
return nil, true, err
378+
}
379+
return nil, false, nil
380+
},
381+
)
382+
383+
require.Nil(tb, trs.FirstError())
384+
require.True(tb, ok, "Unexpected task abortion")
385+
386+
return fooSecConn, barSecConn
387+
}
388+
389+
///////////////////////////////////////////////////////////////////////////////
390+
// Benchmarks
391+
389392
func BenchmarkWriteSecretConnection(b *testing.B) {
390393
b.StopTimer()
391394
b.ReportAllocs()

0 commit comments

Comments
 (0)
Please sign in to comment.