175 lines
4.5 KiB
Go
175 lines
4.5 KiB
Go
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)
|
|
}
|