-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
cmd_onion.go
301 lines (257 loc) · 9.2 KB
/
cmd_onion.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
package tor
import (
"errors"
"fmt"
"io/ioutil"
"os"
)
var (
// ErrNoPrivateKey is an error returned by the OnionStore.PrivateKey
// method when a private key hasn't yet been stored.
ErrNoPrivateKey = errors.New("private key not found")
)
// 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
)
// OnionStore is a store containing information about a particular onion
// service.
type OnionStore interface {
// StorePrivateKey stores the private key according to the
// implementation of the OnionStore interface.
StorePrivateKey(OnionType, []byte) error
// PrivateKey retrieves a stored private key. If it is not found, then
// ErrNoPrivateKey should be returned.
PrivateKey(OnionType) ([]byte, error)
// DeletePrivateKey securely removes the private key from the store.
DeletePrivateKey(OnionType) error
}
// OnionFile is a file-based implementation of the OnionStore interface that
// stores an onion service's private key.
type OnionFile struct {
privateKeyPath string
privateKeyPerm os.FileMode
}
// A compile-time constraint to ensure OnionFile satisfies the OnionStore
// interface.
var _ OnionStore = (*OnionFile)(nil)
// NewOnionFile creates a file-based implementation of the OnionStore interface
// to store an onion service's private key.
func NewOnionFile(privateKeyPath string,
privateKeyPerm os.FileMode) *OnionFile {
return &OnionFile{
privateKeyPath: privateKeyPath,
privateKeyPerm: privateKeyPerm,
}
}
// StorePrivateKey stores the private key at its expected path.
func (f *OnionFile) StorePrivateKey(_ OnionType, privateKey []byte) error {
return ioutil.WriteFile(f.privateKeyPath, privateKey, f.privateKeyPerm)
}
// PrivateKey retrieves the private key from its expected path. If the file
// does not exist, then ErrNoPrivateKey is returned.
func (f *OnionFile) PrivateKey(_ OnionType) ([]byte, error) {
if _, err := os.Stat(f.privateKeyPath); os.IsNotExist(err) {
return nil, ErrNoPrivateKey
}
return ioutil.ReadFile(f.privateKeyPath)
}
// DeletePrivateKey removes the file containing the private key.
func (f *OnionFile) DeletePrivateKey(_ OnionType) error {
return os.Remove(f.privateKeyPath)
}
// AddOnionConfig houses all of the required parameters in order to
// successfully 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
// Store is responsible for storing all onion service related
// information.
//
// NOTE: If not specified, then nothing will be stored, making onion
// services unrecoverable after shutdown.
Store OnionStore
}
// prepareKeyparam takes a config and prepares the key param to be used inside
// ADD_ONION.
func (c *Controller) prepareKeyparam(cfg AddOnionConfig) (string, error) {
// We'll start off by checking if the store contains an existing
// private key. 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
switch cfg.Type {
// TODO(yy): drop support for v2.
case V2:
keyParam = "NEW:RSA1024"
case V3:
keyParam = "NEW:ED25519-V3"
}
if cfg.Store != nil {
privateKey, err := cfg.Store.PrivateKey(cfg.Type)
switch err {
// Proceed to request a new onion service.
case ErrNoPrivateKey:
// Recover the onion service with the private key found.
case nil:
keyParam = string(privateKey)
default:
return "", err
}
}
return keyParam, nil
}
// prepareAddOnion constructs a cmd command string based on the specified
// config.
func (c *Controller) prepareAddOnion(cfg AddOnionConfig) (string, error) {
// Create the keyParam.
keyParam, err := c.prepareKeyparam(cfg)
if err != nil {
return "", err
}
// 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
// Helper function which appends the correct Port param depending on
// whether the user chose to use a custom target IP address or not.
pushPortParam := func(targetPort int) {
if c.targetIPAddress == "" {
portParam += fmt.Sprintf("Port=%d,%d ", cfg.VirtualPort,
targetPort)
} else {
portParam += fmt.Sprintf("Port=%d,%s:%d ",
cfg.VirtualPort, c.targetIPAddress, targetPort)
}
}
if len(cfg.TargetPorts) == 0 {
pushPortParam(cfg.VirtualPort)
} else {
for _, targetPort := range cfg.TargetPorts {
pushPortParam(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)
return cmd, nil
}
// AddOnion creates an ephemeral onion service and returns its onion address.
// Once created, the new onion service will remain active until either,
// - the onion service is removed via `DEL_ONION`.
// - the Tor daemon terminates.
// - the controller connection that originated the `ADD_ONION` is closed.
// Each connection can only see its own ephemeral services. If a service needs
// to survive beyond current controller connection, use the "Detach" flag when
// creating new service via `ADD_ONION`.
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.
// TODO(yy): drop support for v2.
if cfg.Type == V3 {
if err := supportsV3(c.version); err != nil {
return nil, err
}
}
// Construct the cmd command.
cmd, err := c.prepareAddOnion(cfg)
if err != nil {
return nil, err
}
// Send the command to create the onion service to the Tor server and
// await its response.
_, 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 and an onion store was provided,
// we'll store its private key to disk in the event that it needs to be
// recreated later on.
if privateKey, ok := replyParams["PrivateKey"]; cfg.Store != nil && ok {
err := cfg.Store.StorePrivateKey(cfg.Type, []byte(privateKey))
if err != nil {
return nil, fmt.Errorf("unable to write private key "+
"to file: %v", err)
}
}
c.activeServiceID = serviceID
log.Debugf("serviceID:%s added to tor controller", serviceID)
// 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
}
// DelOnion tells the Tor daemon to remove an onion service, which satisfies
// either,
// - the onion service was created on the same control connection as the
// "DEL_ONION" command.
// - the onion service was created using the "Detach" flag.
func (c *Controller) DelOnion(serviceID string) error {
log.Debugf("removing serviceID:%s from tor controller", serviceID)
cmd := fmt.Sprintf("DEL_ONION %s", serviceID)
// Send the command to create the onion service to the Tor server and
// await its response.
code, _, err := c.sendCommand(cmd)
// Tor replies with "250 OK" on success, or a 512 if there are an
// invalid number of arguments, or a 552 if it doesn't recognize the
// ServiceID.
switch code {
// Replied 250 OK.
case success:
return nil
// Replied 512 for invalid arguments. This is most likely that the
// serviceID is not set(empty string).
case invalidNumOfArguments:
return fmt.Errorf("invalid arguments: %w", err)
// Replied 552, which means either,
// - the serviceID is invalid.
// - the onion service is not owned by the current control connection
// and,
// - the onion service is not a detached service.
// In either case, we will ignore the error and log a warning as there
// not much we can do from the controller side.
case serviceIDNotRecognized:
log.Warnf("removing serviceID:%v not found", serviceID)
return nil
default:
return fmt.Errorf("undefined response code: %v, err: %w",
code, err)
}
}