aboutsummaryrefslogtreecommitdiffstats
path: root/p2p/rlpx.go
blob: 6b533e2751b6c0c1466c59ca8e57dc469524b104 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
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)
}