This repository has been archived by the owner on Apr 30, 2021. It is now read-only.
forked from lightningnetwork/lnd
/
controller.go
523 lines (453 loc) · 16 KB
/
controller.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
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
package tor
import (
"bytes"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"io/ioutil"
"net/textproto"
"os"
"strconv"
"strings"
"sync/atomic"
)
const (
// success is the Tor Control response code representing a successful
// request.
success = 250
// nonceLen is the length of a nonce generated by either the controller
// or the Tor server
nonceLen = 32
// cookieLen is the length of the authentication cookie.
cookieLen = 32
// ProtocolInfoVersion is the `protocolinfo` version currently supported
// by the Tor server.
ProtocolInfoVersion = 1
// MinTorVersion is the minimum supported version that the Tor server
// must be running on. This is needed in order to create v3 onion
// services through Tor's control port.
MinTorVersion = "0.3.3.6"
)
var (
// serverKey is the key used when computing the HMAC-SHA256 of a message
// from the server.
serverKey = []byte("Tor safe cookie authentication " +
"server-to-controller hash")
// controllerKey is the key used when computing the HMAC-SHA256 of a
// message from the controller.
controllerKey = []byte("Tor safe cookie authentication " +
"controller-to-server hash")
)
// Controller is an implementation of the Tor Control protocol. This is used in
// order to communicate with a Tor server. Its only supported method of
// authentication is the SAFECOOKIE method.
//
// NOTE: The connection to the Tor server must be authenticated before
// proceeding to send commands. Otherwise, the connection will be closed.
//
// TODO:
// * if adding support for more commands, extend this with a command queue?
// * place under sub-package?
// * support async replies from the server
type Controller struct {
// started is used atomically in order to prevent multiple calls to
// Start.
started int32
// stopped is used atomically in order to prevent multiple calls to
// Stop.
stopped int32
// conn is the underlying connection between the controller and the
// Tor server. It provides read and write methods to simplify the
// text-based messages within the connection.
conn *textproto.Conn
// controlAddr is the host:port the Tor server is listening locally for
// controller connections on.
controlAddr string
// version is the current version of the Tor server.
version string
}
// NewController returns a new Tor controller that will be able to interact with
// a Tor server.
func NewController(controlAddr string) *Controller {
return &Controller{controlAddr: controlAddr}
}
// Start establishes and authenticates the connection between the controller and
// a Tor server. Once done, the controller will be able to send commands and
// expect responses.
func (c *Controller) Start() error {
if !atomic.CompareAndSwapInt32(&c.started, 0, 1) {
return nil
}
conn, err := textproto.Dial("tcp", c.controlAddr)
if err != nil {
return fmt.Errorf("unable to connect to Tor server: %v", err)
}
c.conn = conn
return c.authenticate()
}
// Stop closes the connection between the controller and the Tor server.
func (c *Controller) Stop() error {
if !atomic.CompareAndSwapInt32(&c.stopped, 0, 1) {
return nil
}
return c.conn.Close()
}
// sendCommand sends a command to the Tor server and returns its response, as a
// single space-delimited string, and code.
func (c *Controller) sendCommand(command string) (int, string, error) {
if err := c.conn.Writer.PrintfLine(command); err != nil {
return 0, "", err
}
// We'll use ReadResponse as it has built-in support for multi-line
// text protocol responses.
code, reply, err := c.conn.Reader.ReadResponse(success)
if err != nil {
return code, reply, err
}
return code, reply, nil
}
// parseTorReply parses the reply from the Tor server after receiving a command
// from a controller. This will parse the relevant reply parameters into a map
// of keys and values.
func parseTorReply(reply string) map[string]string {
params := make(map[string]string)
// Replies can either span single or multiple lines, so we'll default
// to stripping whitespace and newlines in order to retrieve the
// individual contents of it. The -1 indicates that we want this to span
// across all instances of a newline.
contents := strings.Split(strings.Replace(reply, "\n", " ", -1), " ")
for _, content := range contents {
// Each parameter within the reply should be of the form
// "KEY=VALUE". If the parameter doesn't contain "=", then we
// can assume it does not provide any other relevant information
// already known.
keyValue := strings.SplitN(content, "=", 2)
if len(keyValue) != 2 {
continue
}
key := keyValue[0]
value := keyValue[1]
params[key] = value
}
return params
}
// authenticate authenticates the connection between the controller and the
// Tor server using the SAFECOOKIE authentication method.
func (c *Controller) authenticate() error {
// Before proceeding to authenticate the connection, we'll retrieve
// the authentication cookie of the Tor server. This will be used
// throughout the authentication routine. We do this before as once the
// authentication routine has begun, it is not possible to retrieve it
// mid-way.
cookie, err := c.getAuthCookie()
if err != nil {
return fmt.Errorf("unable to retrieve authentication cookie: "+
"%v", err)
}
// Authenticating using the SAFECOOKIE authentication method is a two
// step process. We'll kick off the authentication routine by sending
// the AUTHCHALLENGE command followed by a hex-encoded 32-byte nonce.
clientNonce := make([]byte, nonceLen)
if _, err := rand.Read(clientNonce); err != nil {
return fmt.Errorf("unable to generate client nonce: %v", err)
}
cmd := fmt.Sprintf("AUTHCHALLENGE SAFECOOKIE %x", clientNonce)
_, reply, err := c.sendCommand(cmd)
if err != nil {
return err
}
// If successful, the reply from the server should be of the following
// format:
//
// "250 AUTHCHALLENGE"
// SP "SERVERHASH=" ServerHash
// SP "SERVERNONCE=" ServerNonce
// CRLF
//
// We're interested in retrieving the SERVERHASH and SERVERNONCE
// parameters, so we'll parse our reply to do so.
replyParams := parseTorReply(reply)
// Once retrieved, we'll ensure these values are of proper length when
// decoded.
serverHash, ok := replyParams["SERVERHASH"]
if !ok {
return errors.New("server hash not found in reply")
}
decodedServerHash, err := hex.DecodeString(serverHash)
if err != nil {
return fmt.Errorf("unable to decode server hash: %v", err)
}
if len(decodedServerHash) != sha256.Size {
return errors.New("invalid server hash length")
}
serverNonce, ok := replyParams["SERVERNONCE"]
if !ok {
return errors.New("server nonce not found in reply")
}
decodedServerNonce, err := hex.DecodeString(serverNonce)
if err != nil {
return fmt.Errorf("unable to decode server nonce: %v", err)
}
if len(decodedServerNonce) != nonceLen {
return errors.New("invalid server nonce length")
}
// The server hash above was constructed by computing the HMAC-SHA256
// of the message composed of the cookie, client nonce, and server
// nonce. We'll redo this computation ourselves to ensure the integrity
// and authentication of the message.
hmacMessage := bytes.Join(
[][]byte{cookie, clientNonce, decodedServerNonce}, []byte{},
)
computedServerHash := computeHMAC256(serverKey, hmacMessage)
if !hmac.Equal(computedServerHash, decodedServerHash) {
return fmt.Errorf("expected server hash %x, got %x",
decodedServerHash, computedServerHash)
}
// If the MAC check was successful, we'll proceed with the last step of
// the authentication routine. We'll now send the AUTHENTICATE command
// followed by a hex-encoded client hash constructed by computing the
// HMAC-SHA256 of the same message, but this time using the controller's
// key.
clientHash := computeHMAC256(controllerKey, hmacMessage)
if len(clientHash) != sha256.Size {
return errors.New("invalid client hash length")
}
cmd = fmt.Sprintf("AUTHENTICATE %x", clientHash)
if _, _, err := c.sendCommand(cmd); err != nil {
return err
}
return nil
}
// getAuthCookie retrieves the authentication cookie in bytes from the Tor
// server. Cookie authentication must be enabled for this to work. The boolean
func (c *Controller) getAuthCookie() ([]byte, error) {
// Retrieve the authentication methods currently supported by the Tor
// server.
authMethods, cookieFilePath, version, err := c.ProtocolInfo()
if err != nil {
return nil, err
}
// With the version retrieved, we'll cache it now in case it needs to be
// used later on.
c.version = version
// Ensure that the Tor server supports the SAFECOOKIE authentication
// method.
safeCookieSupport := false
for _, authMethod := range authMethods {
if authMethod == "SAFECOOKIE" {
safeCookieSupport = true
}
}
if !safeCookieSupport {
return nil, errors.New("the Tor server is currently not " +
"configured for cookie authentication")
}
// Read the cookie from the file and ensure it has the correct length.
cookie, err := ioutil.ReadFile(cookieFilePath)
if err != nil {
return nil, err
}
if len(cookie) != cookieLen {
return nil, errors.New("invalid authentication cookie length")
}
return cookie, nil
}
// computeHMAC256 computes the HMAC-SHA256 of a key and message.
func computeHMAC256(key, message []byte) []byte {
mac := hmac.New(sha256.New, key)
mac.Write(message)
return mac.Sum(nil)
}
// supportsV3 is a helper function that parses the current version of the Tor
// server and determines whether it supports creationg v3 onion services through
// Tor's control port. The version string should be of the format:
// major.minor.revision.build
func supportsV3(version string) error {
// We'll split the minimum Tor version that's supported and the given
// version in order to individually compare each number.
requiredParts := strings.Split(MinTorVersion, ".")
parts := strings.Split(version, ".")
if len(parts) != 4 {
return errors.New("version string is not of the format " +
"major.minor.revision.build")
}
// It's possible that the build number (the last part of the version
// string) includes a pre-release string, e.g. rc, beta, etc., so we'll
// parse that as well.
build := strings.Split(parts[len(parts)-1], "-")
parts[len(parts)-1] = build[0]
// Convert them each number from its string representation to integers
// and check that they respect the minimum version.
for i := range parts {
n, err := strconv.Atoi(parts[i])
if err != nil {
return err
}
requiredN, err := strconv.Atoi(requiredParts[i])
if err != nil {
return err
}
if n < requiredN {
return fmt.Errorf("version %v below minimum version "+
"supported %v", version, MinTorVersion)
}
}
return nil
}
// ProtocolInfo returns the different authentication methods supported by the
// Tor server and the version of the Tor server.
func (c *Controller) ProtocolInfo() ([]string, string, string, error) {
// We'll start off by sending the "PROTOCOLINFO" command to the Tor
// server. We should receive a reply of the following format:
//
// METHODS=COOKIE,SAFECOOKIE
// COOKIEFILE="/home/user/.tor/control_auth_cookie"
// VERSION Tor="0.3.2.10"
//
// We're interested in retrieving all of these fields, so we'll parse
// our reply to do so.
cmd := fmt.Sprintf("PROTOCOLINFO %d", ProtocolInfoVersion)
_, reply, err := c.sendCommand(cmd)
if err != nil {
return nil, "", "", err
}
info := parseTorReply(reply)
methods, ok := info["METHODS"]
if !ok {
return nil, "", "", errors.New("auth methods not found in " +
"reply")
}
cookieFile, ok := info["COOKIEFILE"]
if !ok {
return nil, "", "", errors.New("cookie file path not found " +
"in reply")
}
version, ok := info["Tor"]
if !ok {
return nil, "", "", errors.New("Tor version not found in reply")
}
// Finally, we'll clean up the results before returning them.
authMethods := strings.Split(methods, ",")
cookieFilePath := strings.Trim(cookieFile, "\"")
torVersion := strings.Trim(version, "\"")
return authMethods, cookieFilePath, torVersion, nil
}
// OnionType denotes the type of the onion service.
type OnionType int
const (
// V2 denotes that the onion service is V2.
V2 OnionType = iota
// V3 denotes that the onion service is V3.
V3
)
// AddOnionConfig houses all of the required paramaters in order to succesfully
// create a new onion service or restore an existing one.
type AddOnionConfig struct {
// Type denotes the type of the onion service that should be created.
Type OnionType
// VirtualPort is the externally reachable port of the onion address.
VirtualPort int
// TargetPorts is the set of ports that the service will be listening on
// locally. The Tor server will use choose a random port from this set
// to forward the traffic from the virtual port.
//
// NOTE: If nil/empty, the virtual port will be used as the only target
// port.
TargetPorts []int
// PrivateKeyPath is the full path to where the onion service's private
// key is stored. This can be used to restore an existing onion service.
PrivateKeyPath string
}
// AddOnion creates an onion service and returns its onion address. Once
// created, the new onion service will remain active until the connection
// between the controller and the Tor server is closed.
func (c *Controller) AddOnion(cfg AddOnionConfig) (*OnionAddr, error) {
// Before sending the request to create an onion service to the Tor
// server, we'll make sure that it supports V3 onion services if that
// was the type requested.
if cfg.Type == V3 {
if err := supportsV3(c.version); err != nil {
return nil, err
}
}
// We'll start off by checking if the file containing the private key
// exists. If it does not, then we should request the server to create
// a new onion service and return its private key. Otherwise, we'll
// request the server to recreate the onion server from our private key.
var keyParam string
if _, err := os.Stat(cfg.PrivateKeyPath); os.IsNotExist(err) {
switch cfg.Type {
case V2:
keyParam = "NEW:RSA1024"
case V3:
keyParam = "NEW:ED25519-V3"
}
} else {
privateKey, err := ioutil.ReadFile(cfg.PrivateKeyPath)
if err != nil {
return nil, err
}
keyParam = string(privateKey)
}
// Now, we'll create a mapping from the virtual port to each target
// port. If no target ports were specified, we'll use the virtual port
// to provide a one-to-one mapping.
var portParam string
if len(cfg.TargetPorts) == 0 {
portParam += fmt.Sprintf("Port=%d,%d ", cfg.VirtualPort,
cfg.VirtualPort)
} else {
for _, targetPort := range cfg.TargetPorts {
portParam += fmt.Sprintf("Port=%d,%d ", cfg.VirtualPort,
targetPort)
}
}
// Send the command to create the onion service to the Tor server and
// await its response.
cmd := fmt.Sprintf("ADD_ONION %s %s", keyParam, portParam)
_, reply, err := c.sendCommand(cmd)
if err != nil {
return nil, err
}
// If successful, the reply from the server should be of the following
// format, depending on whether a private key has been requested:
//
// C: ADD_ONION RSA1024:[Blob Redacted] Port=80,8080
// S: 250-ServiceID=testonion1234567
// S: 250 OK
//
// C: ADD_ONION NEW:RSA1024 Port=80,8080
// S: 250-ServiceID=testonion1234567
// S: 250-PrivateKey=RSA1024:[Blob Redacted]
// S: 250 OK
//
// We're interested in retrieving the service ID, which is the public
// name of the service, and the private key if requested.
replyParams := parseTorReply(reply)
serviceID, ok := replyParams["ServiceID"]
if !ok {
return nil, errors.New("service id not found in reply")
}
// If a new onion service was created, we'll write its private key to
// disk under strict permissions in the event that it needs to be
// recreated later on.
if privateKey, ok := replyParams["PrivateKey"]; ok {
err := ioutil.WriteFile(
cfg.PrivateKeyPath, []byte(privateKey), 0600,
)
if err != nil {
return nil, fmt.Errorf("unable to write private key "+
"to file: %v", err)
}
}
// Finally, we'll return the onion address composed of the service ID,
// along with the onion suffix, and the port this onion service can be
// reached at externally.
return &OnionAddr{
OnionService: serviceID + ".onion",
Port: cfg.VirtualPort,
}, nil
}