forked from mirror/go-ethereum
Merge branch 'p2p-handshake-2' of https://github.com/fjl/go-ethereum into fjl-p2p-handshake-2
commit
ba0c41436c
@ -0,0 +1,174 @@ |
|||||||
|
package p2p |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"crypto/aes" |
||||||
|
"crypto/cipher" |
||||||
|
"crypto/hmac" |
||||||
|
"errors" |
||||||
|
"hash" |
||||||
|
"io" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/rlp" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
// this is used in place of actual frame header data.
|
||||||
|
// TODO: replace this when Msg contains the protocol type code.
|
||||||
|
zeroHeader = []byte{0xC2, 0x80, 0x80} |
||||||
|
|
||||||
|
// sixteen zero bytes
|
||||||
|
zero16 = make([]byte, 16) |
||||||
|
|
||||||
|
maxUint24 = ^uint32(0) >> 8 |
||||||
|
) |
||||||
|
|
||||||
|
// rlpxFrameRW implements a simplified version of RLPx framing.
|
||||||
|
// chunked messages are not supported and all headers are equal to
|
||||||
|
// zeroHeader.
|
||||||
|
//
|
||||||
|
// rlpxFrameRW is not safe for concurrent use from multiple goroutines.
|
||||||
|
type rlpxFrameRW struct { |
||||||
|
conn io.ReadWriter |
||||||
|
enc cipher.Stream |
||||||
|
dec cipher.Stream |
||||||
|
|
||||||
|
macCipher cipher.Block |
||||||
|
egressMAC hash.Hash |
||||||
|
ingressMAC hash.Hash |
||||||
|
} |
||||||
|
|
||||||
|
func newRlpxFrameRW(conn io.ReadWriter, s secrets) *rlpxFrameRW { |
||||||
|
macc, err := aes.NewCipher(s.MAC) |
||||||
|
if err != nil { |
||||||
|
panic("invalid MAC secret: " + err.Error()) |
||||||
|
} |
||||||
|
encc, err := aes.NewCipher(s.AES) |
||||||
|
if err != nil { |
||||||
|
panic("invalid AES secret: " + err.Error()) |
||||||
|
} |
||||||
|
// we use an all-zeroes IV for AES because the key used
|
||||||
|
// for encryption is ephemeral.
|
||||||
|
iv := make([]byte, encc.BlockSize()) |
||||||
|
return &rlpxFrameRW{ |
||||||
|
conn: conn, |
||||||
|
enc: cipher.NewCTR(encc, iv), |
||||||
|
dec: cipher.NewCTR(encc, iv), |
||||||
|
macCipher: macc, |
||||||
|
egressMAC: s.EgressMAC, |
||||||
|
ingressMAC: s.IngressMAC, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (rw *rlpxFrameRW) WriteMsg(msg Msg) error { |
||||||
|
ptype, _ := rlp.EncodeToBytes(msg.Code) |
||||||
|
|
||||||
|
// write header
|
||||||
|
headbuf := make([]byte, 32) |
||||||
|
fsize := uint32(len(ptype)) + msg.Size |
||||||
|
if fsize > maxUint24 { |
||||||
|
return errors.New("message size overflows uint24") |
||||||
|
} |
||||||
|
putInt24(fsize, headbuf) // TODO: check overflow
|
||||||
|
copy(headbuf[3:], zeroHeader) |
||||||
|
rw.enc.XORKeyStream(headbuf[:16], headbuf[:16]) // first half is now encrypted
|
||||||
|
|
||||||
|
// write header MAC
|
||||||
|
copy(headbuf[16:], updateMAC(rw.egressMAC, rw.macCipher, headbuf[:16])) |
||||||
|
if _, err := rw.conn.Write(headbuf); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// write encrypted frame, updating the egress MAC hash with
|
||||||
|
// the data written to conn.
|
||||||
|
tee := cipher.StreamWriter{S: rw.enc, W: io.MultiWriter(rw.conn, rw.egressMAC)} |
||||||
|
if _, err := tee.Write(ptype); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if _, err := io.Copy(tee, msg.Payload); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if padding := fsize % 16; padding > 0 { |
||||||
|
if _, err := tee.Write(zero16[:16-padding]); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// write frame MAC. egress MAC hash is up to date because
|
||||||
|
// frame content was written to it as well.
|
||||||
|
fmacseed := rw.egressMAC.Sum(nil) |
||||||
|
mac := updateMAC(rw.egressMAC, rw.macCipher, fmacseed) |
||||||
|
_, err := rw.conn.Write(mac) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
func (rw *rlpxFrameRW) ReadMsg() (msg Msg, err error) { |
||||||
|
// read the header
|
||||||
|
headbuf := make([]byte, 32) |
||||||
|
if _, err := io.ReadFull(rw.conn, headbuf); err != nil { |
||||||
|
return msg, err |
||||||
|
} |
||||||
|
// verify header mac
|
||||||
|
shouldMAC := updateMAC(rw.ingressMAC, rw.macCipher, headbuf[:16]) |
||||||
|
if !hmac.Equal(shouldMAC, headbuf[16:]) { |
||||||
|
return msg, errors.New("bad header MAC") |
||||||
|
} |
||||||
|
rw.dec.XORKeyStream(headbuf[:16], headbuf[:16]) // first half is now decrypted
|
||||||
|
fsize := readInt24(headbuf) |
||||||
|
// ignore protocol type for now
|
||||||
|
|
||||||
|
// read the frame content
|
||||||
|
var rsize = fsize // frame size rounded up to 16 byte boundary
|
||||||
|
if padding := fsize % 16; padding > 0 { |
||||||
|
rsize += 16 - padding |
||||||
|
} |
||||||
|
framebuf := make([]byte, rsize) |
||||||
|
if _, err := io.ReadFull(rw.conn, framebuf); err != nil { |
||||||
|
return msg, err |
||||||
|
} |
||||||
|
|
||||||
|
// read and validate frame MAC. we can re-use headbuf for that.
|
||||||
|
rw.ingressMAC.Write(framebuf) |
||||||
|
fmacseed := rw.ingressMAC.Sum(nil) |
||||||
|
if _, err := io.ReadFull(rw.conn, headbuf[:16]); err != nil { |
||||||
|
return msg, err |
||||||
|
} |
||||||
|
shouldMAC = updateMAC(rw.ingressMAC, rw.macCipher, fmacseed) |
||||||
|
if !hmac.Equal(shouldMAC, headbuf[:16]) { |
||||||
|
return msg, errors.New("bad frame MAC") |
||||||
|
} |
||||||
|
|
||||||
|
// decrypt frame content
|
||||||
|
rw.dec.XORKeyStream(framebuf, framebuf) |
||||||
|
|
||||||
|
// decode message code
|
||||||
|
content := bytes.NewReader(framebuf[:fsize]) |
||||||
|
if err := rlp.Decode(content, &msg.Code); err != nil { |
||||||
|
return msg, err |
||||||
|
} |
||||||
|
msg.Size = uint32(content.Len()) |
||||||
|
msg.Payload = content |
||||||
|
return msg, nil |
||||||
|
} |
||||||
|
|
||||||
|
// updateMAC reseeds the given hash with encrypted seed.
|
||||||
|
// it returns the first 16 bytes of the hash sum after seeding.
|
||||||
|
func updateMAC(mac hash.Hash, block cipher.Block, seed []byte) []byte { |
||||||
|
aesbuf := make([]byte, aes.BlockSize) |
||||||
|
block.Encrypt(aesbuf, mac.Sum(nil)) |
||||||
|
for i := range aesbuf { |
||||||
|
aesbuf[i] ^= seed[i] |
||||||
|
} |
||||||
|
mac.Write(aesbuf) |
||||||
|
return mac.Sum(nil)[:16] |
||||||
|
} |
||||||
|
|
||||||
|
func readInt24(b []byte) uint32 { |
||||||
|
return uint32(b[2]) | uint32(b[1])<<8 | uint32(b[0])<<16 |
||||||
|
} |
||||||
|
|
||||||
|
func putInt24(v uint32, b []byte) { |
||||||
|
b[0] = byte(v >> 16) |
||||||
|
b[1] = byte(v >> 8) |
||||||
|
b[2] = byte(v) |
||||||
|
} |
@ -0,0 +1,124 @@ |
|||||||
|
package p2p |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"crypto/rand" |
||||||
|
"io/ioutil" |
||||||
|
"strings" |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/crypto" |
||||||
|
"github.com/ethereum/go-ethereum/crypto/sha3" |
||||||
|
"github.com/ethereum/go-ethereum/rlp" |
||||||
|
) |
||||||
|
|
||||||
|
func TestRlpxFrameFake(t *testing.T) { |
||||||
|
buf := new(bytes.Buffer) |
||||||
|
hash := fakeHash([]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) |
||||||
|
rw := newRlpxFrameRW(buf, secrets{ |
||||||
|
AES: crypto.Sha3(), |
||||||
|
MAC: crypto.Sha3(), |
||||||
|
IngressMAC: hash, |
||||||
|
EgressMAC: hash, |
||||||
|
}) |
||||||
|
|
||||||
|
golden := unhex(` |
||||||
|
00828ddae471818bb0bfa6b551d1cb42 |
||||||
|
01010101010101010101010101010101 |
||||||
|
ba628a4ba590cb43f7848f41c4382885 |
||||||
|
01010101010101010101010101010101 |
||||||
|
`) |
||||||
|
|
||||||
|
// Check WriteMsg. This puts a message into the buffer.
|
||||||
|
if err := EncodeMsg(rw, 8, 1, 2, 3, 4); err != nil { |
||||||
|
t.Fatalf("WriteMsg error: %v", err) |
||||||
|
} |
||||||
|
written := buf.Bytes() |
||||||
|
if !bytes.Equal(written, golden) { |
||||||
|
t.Fatalf("output mismatch:\n got: %x\n want: %x", written, golden) |
||||||
|
} |
||||||
|
|
||||||
|
// Check ReadMsg. It reads the message encoded by WriteMsg, which
|
||||||
|
// is equivalent to the golden message above.
|
||||||
|
msg, err := rw.ReadMsg() |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("ReadMsg error: %v", err) |
||||||
|
} |
||||||
|
if msg.Size != 5 { |
||||||
|
t.Errorf("msg size mismatch: got %d, want %d", msg.Size, 5) |
||||||
|
} |
||||||
|
if msg.Code != 8 { |
||||||
|
t.Errorf("msg code mismatch: got %d, want %d", msg.Code, 8) |
||||||
|
} |
||||||
|
payload, _ := ioutil.ReadAll(msg.Payload) |
||||||
|
wantPayload := unhex("C401020304") |
||||||
|
if !bytes.Equal(payload, wantPayload) { |
||||||
|
t.Errorf("msg payload mismatch:\ngot %x\nwant %x", payload, wantPayload) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
type fakeHash []byte |
||||||
|
|
||||||
|
func (fakeHash) Write(p []byte) (int, error) { return len(p), nil } |
||||||
|
func (fakeHash) Reset() {} |
||||||
|
func (fakeHash) BlockSize() int { return 0 } |
||||||
|
|
||||||
|
func (h fakeHash) Size() int { return len(h) } |
||||||
|
func (h fakeHash) Sum(b []byte) []byte { return append(b, h...) } |
||||||
|
|
||||||
|
func TestRlpxFrameRW(t *testing.T) { |
||||||
|
var ( |
||||||
|
aesSecret = make([]byte, 16) |
||||||
|
macSecret = make([]byte, 16) |
||||||
|
egressMACinit = make([]byte, 32) |
||||||
|
ingressMACinit = make([]byte, 32) |
||||||
|
) |
||||||
|
for _, s := range [][]byte{aesSecret, macSecret, egressMACinit, ingressMACinit} { |
||||||
|
rand.Read(s) |
||||||
|
} |
||||||
|
conn := new(bytes.Buffer) |
||||||
|
|
||||||
|
s1 := secrets{ |
||||||
|
AES: aesSecret, |
||||||
|
MAC: macSecret, |
||||||
|
EgressMAC: sha3.NewKeccak256(), |
||||||
|
IngressMAC: sha3.NewKeccak256(), |
||||||
|
} |
||||||
|
s1.EgressMAC.Write(egressMACinit) |
||||||
|
s1.IngressMAC.Write(ingressMACinit) |
||||||
|
rw1 := newRlpxFrameRW(conn, s1) |
||||||
|
|
||||||
|
s2 := secrets{ |
||||||
|
AES: aesSecret, |
||||||
|
MAC: macSecret, |
||||||
|
EgressMAC: sha3.NewKeccak256(), |
||||||
|
IngressMAC: sha3.NewKeccak256(), |
||||||
|
} |
||||||
|
s2.EgressMAC.Write(ingressMACinit) |
||||||
|
s2.IngressMAC.Write(egressMACinit) |
||||||
|
rw2 := newRlpxFrameRW(conn, s2) |
||||||
|
|
||||||
|
// send some messages
|
||||||
|
for i := 0; i < 10; i++ { |
||||||
|
// write message into conn buffer
|
||||||
|
wmsg := []interface{}{"foo", "bar", strings.Repeat("test", i)} |
||||||
|
err := EncodeMsg(rw1, uint64(i), wmsg...) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("WriteMsg error (i=%d): %v", i, err) |
||||||
|
} |
||||||
|
|
||||||
|
// read message that rw1 just wrote
|
||||||
|
msg, err := rw2.ReadMsg() |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("ReadMsg error (i=%d): %v", i, err) |
||||||
|
} |
||||||
|
if msg.Code != uint64(i) { |
||||||
|
t.Fatalf("msg code mismatch: got %d, want %d", msg.Code, i) |
||||||
|
} |
||||||
|
payload, _ := ioutil.ReadAll(msg.Payload) |
||||||
|
wantPayload, _ := rlp.EncodeToBytes(wmsg) |
||||||
|
if !bytes.Equal(payload, wantPayload) { |
||||||
|
t.Fatalf("msg payload mismatch:\ngot %x\nwant %x", payload, wantPayload) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue