-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
conn.go
289 lines (252 loc) · 9.07 KB
/
conn.go
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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
package brontide
import (
"bytes"
"io"
"math"
"net"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tor"
)
// Conn is an implementation of net.Conn which enforces an authenticated key
// exchange and message encryption protocol dubbed "Brontide" after initial TCP
// connection establishment. In the case of a successful handshake, all
// messages sent via the .Write() method are encrypted with an AEAD cipher
// along with an encrypted length-prefix. See the Machine struct for
// additional details w.r.t to the handshake and encryption scheme.
type Conn struct {
conn net.Conn
noise *Machine
readBuf bytes.Buffer
}
// A compile-time assertion to ensure that Conn meets the net.Conn interface.
var _ net.Conn = (*Conn)(nil)
// Dial attempts to establish an encrypted authenticated connection with the
// remote peer located at address which has remotePub as its long-term static
// public key. In the case of a handshake failure, the connection is closed and
// a non-nil error is returned.
func Dial(local keychain.SingleKeyECDH, netAddr *lnwire.NetAddress,
timeout time.Duration, dialer tor.DialFunc) (*Conn, error) {
ipAddr := netAddr.Address.String()
var conn net.Conn
var err error
conn, err = dialer("tcp", ipAddr, timeout)
if err != nil {
return nil, err
}
b := &Conn{
conn: conn,
noise: NewBrontideMachine(true, local, netAddr.IdentityKey),
}
// Initiate the handshake by sending the first act to the receiver.
actOne, err := b.noise.GenActOne()
if err != nil {
b.conn.Close()
return nil, err
}
if _, err := conn.Write(actOne[:]); err != nil {
b.conn.Close()
return nil, err
}
// We'll ensure that we get ActTwo from the remote peer in a timely
// manner. If they don't respond within handshakeReadTimeout, then
// we'll kill the connection.
err = conn.SetReadDeadline(time.Now().Add(handshakeReadTimeout))
if err != nil {
b.conn.Close()
return nil, err
}
// If the first act was successful (we know that address is actually
// remotePub), then read the second act after which we'll be able to
// send our static public key to the remote peer with strong forward
// secrecy.
var actTwo [ActTwoSize]byte
if _, err := io.ReadFull(conn, actTwo[:]); err != nil {
b.conn.Close()
return nil, err
}
if err := b.noise.RecvActTwo(actTwo); err != nil {
b.conn.Close()
return nil, err
}
// Finally, complete the handshake by sending over our encrypted static
// key and execute the final ECDH operation.
actThree, err := b.noise.GenActThree()
if err != nil {
b.conn.Close()
return nil, err
}
if _, err := conn.Write(actThree[:]); err != nil {
b.conn.Close()
return nil, err
}
// We'll reset the deadline as it's no longer critical beyond the
// initial handshake.
err = conn.SetReadDeadline(time.Time{})
if err != nil {
b.conn.Close()
return nil, err
}
return b, nil
}
// ReadNextMessage uses the connection in a message-oriented manner, instructing
// it to read the next _full_ message with the brontide stream. This function
// will block until the read of the header and body succeeds.
//
// NOTE: This method SHOULD NOT be used in the case that the connection may be
// adversarial and induce long delays. If the caller needs to set read deadlines
// appropriately, it is preferred that they use the split ReadNextHeader and
// ReadNextBody methods so that the deadlines can be set appropriately on each.
func (c *Conn) ReadNextMessage() ([]byte, error) {
return c.noise.ReadMessage(c.conn)
}
// ReadNextHeader uses the connection to read the next header from the brontide
// stream. This function will block until the read of the header succeeds and
// return the packet length (including MAC overhead) that is expected from the
// subsequent call to ReadNextBody.
func (c *Conn) ReadNextHeader() (uint32, error) {
return c.noise.ReadHeader(c.conn)
}
// ReadNextBody uses the connection to read the next message body from the
// brontide stream. This function will block until the read of the body succeeds
// and return the decrypted payload. The provided buffer MUST be the packet
// length returned by the preceding call to ReadNextHeader.
func (c *Conn) ReadNextBody(buf []byte) ([]byte, error) {
return c.noise.ReadBody(c.conn, buf)
}
// Read reads data from the connection. Read can be made to time out and
// return an Error with Timeout() == true after a fixed time limit; see
// SetDeadline and SetReadDeadline.
//
// Part of the net.Conn interface.
func (c *Conn) Read(b []byte) (n int, err error) {
// In order to reconcile the differences between the record abstraction
// of our AEAD connection, and the stream abstraction of TCP, we
// maintain an intermediate read buffer. If this buffer becomes
// depleted, then we read the next record, and feed it into the
// buffer. Otherwise, we read directly from the buffer.
if c.readBuf.Len() == 0 {
plaintext, err := c.noise.ReadMessage(c.conn)
if err != nil {
return 0, err
}
if _, err := c.readBuf.Write(plaintext); err != nil {
return 0, err
}
}
return c.readBuf.Read(b)
}
// Write writes data to the connection. Write can be made to time out and
// return an Error with Timeout() == true after a fixed time limit; see
// SetDeadline and SetWriteDeadline.
//
// Part of the net.Conn interface.
func (c *Conn) Write(b []byte) (n int, err error) {
// If the message doesn't require any chunking, then we can go ahead
// with a single write.
if len(b) <= math.MaxUint16 {
err = c.noise.WriteMessage(b)
if err != nil {
return 0, err
}
return c.noise.Flush(c.conn)
}
// If we need to split the message into fragments, then we'll write
// chunks which maximize usage of the available payload.
chunkSize := math.MaxUint16
bytesToWrite := len(b)
bytesWritten := 0
for bytesWritten < bytesToWrite {
// If we're on the last chunk, then truncate the chunk size as
// necessary to avoid an out-of-bounds array memory access.
if bytesWritten chunkSize > len(b) {
chunkSize = len(b) - bytesWritten
}
// Slice off the next chunk to be written based on our running
// counter and next chunk size.
chunk := b[bytesWritten : bytesWritten chunkSize]
if err := c.noise.WriteMessage(chunk); err != nil {
return bytesWritten, err
}
n, err := c.noise.Flush(c.conn)
bytesWritten = n
if err != nil {
return bytesWritten, err
}
}
return bytesWritten, nil
}
// WriteMessage encrypts and buffers the next message p for the connection. The
// ciphertext of the message is prepended with an encrypt auth'd length which
// must be used as the AD to the AEAD construction when being decrypted by the
// other side.
//
// NOTE: This DOES NOT write the message to the wire, it should be followed by a
// call to Flush to ensure the message is written.
func (c *Conn) WriteMessage(b []byte) error {
return c.noise.WriteMessage(b)
}
// Flush attempts to write a message buffered using WriteMessage to the
// underlying connection. If no buffered message exists, this will result in a
// NOP. Otherwise, it will continue to write the remaining bytes, picking up
// where the byte stream left off in the event of a partial write. The number of
// bytes returned reflects the number of plaintext bytes in the payload, and
// does not account for the overhead of the header or MACs.
//
// NOTE: It is safe to call this method again iff a timeout error is returned.
func (c *Conn) Flush() (int, error) {
return c.noise.Flush(c.conn)
}
// Close closes the connection. Any blocked Read or Write operations will be
// unblocked and return errors.
//
// Part of the net.Conn interface.
func (c *Conn) Close() error {
// TODO(roasbeef): reset brontide state?
return c.conn.Close()
}
// LocalAddr returns the local network address.
//
// Part of the net.Conn interface.
func (c *Conn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
// RemoteAddr returns the remote network address.
//
// Part of the net.Conn interface.
func (c *Conn) RemoteAddr() net.Addr {
return c.conn.RemoteAddr()
}
// SetDeadline sets the read and write deadlines associated with the
// connection. It is equivalent to calling both SetReadDeadline and
// SetWriteDeadline.
//
// Part of the net.Conn interface.
func (c *Conn) SetDeadline(t time.Time) error {
return c.conn.SetDeadline(t)
}
// SetReadDeadline sets the deadline for future Read calls. A zero value for t
// means Read will not time out.
//
// Part of the net.Conn interface.
func (c *Conn) SetReadDeadline(t time.Time) error {
return c.conn.SetReadDeadline(t)
}
// SetWriteDeadline sets the deadline for future Write calls. Even if write
// times out, it may return n > 0, indicating that some of the data was
// successfully written. A zero value for t means Write will not time out.
//
// Part of the net.Conn interface.
func (c *Conn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
}
// RemotePub returns the remote peer's static public key.
func (c *Conn) RemotePub() *btcec.PublicKey {
return c.noise.remoteStatic
}
// LocalPub returns the local peer's static public key.
func (c *Conn) LocalPub() *btcec.PublicKey {
return c.noise.localStatic.PubKey()
}