github.com/decred/dcrlnd@v0.7.6/tor/cmd_onion.go (about) 1 package tor 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "os" 8 ) 9 10 var ( 11 // ErrNoPrivateKey is an error returned by the OnionStore.PrivateKey 12 // method when a private key hasn't yet been stored. 13 ErrNoPrivateKey = errors.New("private key not found") 14 ) 15 16 // OnionType denotes the type of the onion service. 17 type OnionType int 18 19 const ( 20 // V2 denotes that the onion service is V2. 21 V2 OnionType = iota 22 23 // V3 denotes that the onion service is V3. 24 V3 25 ) 26 27 // OnionStore is a store containing information about a particular onion 28 // service. 29 type OnionStore interface { 30 // StorePrivateKey stores the private key according to the 31 // implementation of the OnionStore interface. 32 StorePrivateKey(OnionType, []byte) error 33 34 // PrivateKey retrieves a stored private key. If it is not found, then 35 // ErrNoPrivateKey should be returned. 36 PrivateKey(OnionType) ([]byte, error) 37 38 // DeletePrivateKey securely removes the private key from the store. 39 DeletePrivateKey(OnionType) error 40 } 41 42 // OnionFile is a file-based implementation of the OnionStore interface that 43 // stores an onion service's private key. 44 type OnionFile struct { 45 privateKeyPath string 46 privateKeyPerm os.FileMode 47 } 48 49 // A compile-time constraint to ensure OnionFile satisfies the OnionStore 50 // interface. 51 var _ OnionStore = (*OnionFile)(nil) 52 53 // NewOnionFile creates a file-based implementation of the OnionStore interface 54 // to store an onion service's private key. 55 func NewOnionFile(privateKeyPath string, 56 privateKeyPerm os.FileMode) *OnionFile { 57 58 return &OnionFile{ 59 privateKeyPath: privateKeyPath, 60 privateKeyPerm: privateKeyPerm, 61 } 62 } 63 64 // StorePrivateKey stores the private key at its expected path. 65 func (f *OnionFile) StorePrivateKey(_ OnionType, privateKey []byte) error { 66 return ioutil.WriteFile(f.privateKeyPath, privateKey, f.privateKeyPerm) 67 } 68 69 // PrivateKey retrieves the private key from its expected path. If the file 70 // does not exist, then ErrNoPrivateKey is returned. 71 func (f *OnionFile) PrivateKey(_ OnionType) ([]byte, error) { 72 if _, err := os.Stat(f.privateKeyPath); os.IsNotExist(err) { 73 return nil, ErrNoPrivateKey 74 } 75 return ioutil.ReadFile(f.privateKeyPath) 76 } 77 78 // DeletePrivateKey removes the file containing the private key. 79 func (f *OnionFile) DeletePrivateKey(_ OnionType) error { 80 return os.Remove(f.privateKeyPath) 81 } 82 83 // AddOnionConfig houses all of the required parameters in order to 84 // successfully create a new onion service or restore an existing one. 85 type AddOnionConfig struct { 86 // Type denotes the type of the onion service that should be created. 87 Type OnionType 88 89 // VirtualPort is the externally reachable port of the onion address. 90 VirtualPort int 91 92 // TargetPorts is the set of ports that the service will be listening 93 // on locally. The Tor server will use choose a random port from this 94 // set to forward the traffic from the virtual port. 95 // 96 // NOTE: If nil/empty, the virtual port will be used as the only target 97 // port. 98 TargetPorts []int 99 100 // Store is responsible for storing all onion service related 101 // information. 102 // 103 // NOTE: If not specified, then nothing will be stored, making onion 104 // services unrecoverable after shutdown. 105 Store OnionStore 106 } 107 108 // prepareKeyparam takes a config and prepares the key param to be used inside 109 // ADD_ONION. 110 func (c *Controller) prepareKeyparam(cfg AddOnionConfig) (string, error) { 111 // We'll start off by checking if the store contains an existing 112 // private key. If it does not, then we should request the server to 113 // create a new onion service and return its private key. Otherwise, 114 // we'll request the server to recreate the onion server from our 115 // private key. 116 var keyParam string 117 switch cfg.Type { 118 // TODO(yy): drop support for v2. 119 case V2: 120 keyParam = "NEW:RSA1024" 121 case V3: 122 keyParam = "NEW:ED25519-V3" 123 } 124 125 if cfg.Store != nil { 126 privateKey, err := cfg.Store.PrivateKey(cfg.Type) 127 switch err { 128 // Proceed to request a new onion service. 129 case ErrNoPrivateKey: 130 131 // Recover the onion service with the private key found. 132 case nil: 133 keyParam = string(privateKey) 134 135 default: 136 return "", err 137 } 138 } 139 140 return keyParam, nil 141 } 142 143 // prepareAddOnion constructs a cmd command string based on the specified 144 // config. 145 func (c *Controller) prepareAddOnion(cfg AddOnionConfig) (string, error) { 146 // Create the keyParam. 147 keyParam, err := c.prepareKeyparam(cfg) 148 if err != nil { 149 return "", err 150 } 151 152 // Now, we'll create a mapping from the virtual port to each target 153 // port. If no target ports were specified, we'll use the virtual port 154 // to provide a one-to-one mapping. 155 var portParam string 156 157 // Helper function which appends the correct Port param depending on 158 // whether the user chose to use a custom target IP address or not. 159 pushPortParam := func(targetPort int) { 160 if c.targetIPAddress == "" { 161 portParam += fmt.Sprintf("Port=%d,%d ", cfg.VirtualPort, 162 targetPort) 163 } else { 164 portParam += fmt.Sprintf("Port=%d,%s:%d ", 165 cfg.VirtualPort, c.targetIPAddress, targetPort) 166 } 167 } 168 169 if len(cfg.TargetPorts) == 0 { 170 pushPortParam(cfg.VirtualPort) 171 } else { 172 for _, targetPort := range cfg.TargetPorts { 173 pushPortParam(targetPort) 174 } 175 } 176 177 // Send the command to create the onion service to the Tor server and 178 // await its response. 179 cmd := fmt.Sprintf("ADD_ONION %s %s", keyParam, portParam) 180 181 return cmd, nil 182 } 183 184 // AddOnion creates an ephemeral onion service and returns its onion address. 185 // Once created, the new onion service will remain active until either, 186 // - the onion service is removed via `DEL_ONION`. 187 // - the Tor daemon terminates. 188 // - the controller connection that originated the `ADD_ONION` is closed. 189 // 190 // Each connection can only see its own ephemeral services. If a service needs 191 // to survive beyond current controller connection, use the "Detach" flag when 192 // creating new service via `ADD_ONION`. 193 func (c *Controller) AddOnion(cfg AddOnionConfig) (*OnionAddr, error) { 194 // Before sending the request to create an onion service to the Tor 195 // server, we'll make sure that it supports V3 onion services if that 196 // was the type requested. 197 // TODO(yy): drop support for v2. 198 if cfg.Type == V3 { 199 if err := supportsV3(c.version); err != nil { 200 return nil, err 201 } 202 } 203 204 // Construct the cmd command. 205 cmd, err := c.prepareAddOnion(cfg) 206 if err != nil { 207 return nil, err 208 } 209 210 // Send the command to create the onion service to the Tor server and 211 // await its response. 212 _, reply, err := c.sendCommand(cmd) 213 if err != nil { 214 return nil, err 215 } 216 217 // If successful, the reply from the server should be of the following 218 // format, depending on whether a private key has been requested: 219 // 220 // C: ADD_ONION RSA1024:[Blob Redacted] Port=80,8080 221 // S: 250-ServiceID=testonion1234567 222 // S: 250 OK 223 // 224 // C: ADD_ONION NEW:RSA1024 Port=80,8080 225 // S: 250-ServiceID=testonion1234567 226 // S: 250-PrivateKey=RSA1024:[Blob Redacted] 227 // S: 250 OK 228 // 229 // We're interested in retrieving the service ID, which is the public 230 // name of the service, and the private key if requested. 231 replyParams := parseTorReply(reply) 232 serviceID, ok := replyParams["ServiceID"] 233 if !ok { 234 return nil, errors.New("service id not found in reply") 235 } 236 237 // If a new onion service was created and an onion store was provided, 238 // we'll store its private key to disk in the event that it needs to be 239 // recreated later on. 240 if privateKey, ok := replyParams["PrivateKey"]; cfg.Store != nil && ok { 241 err := cfg.Store.StorePrivateKey(cfg.Type, []byte(privateKey)) 242 if err != nil { 243 return nil, fmt.Errorf("unable to write private key "+ 244 "to file: %v", err) 245 } 246 } 247 248 c.activeServiceID = serviceID 249 log.Debugf("serviceID:%s added to tor controller", serviceID) 250 251 // Finally, we'll return the onion address composed of the service ID, 252 // along with the onion suffix, and the port this onion service can be 253 // reached at externally. 254 return &OnionAddr{ 255 OnionService: serviceID + ".onion", 256 Port: cfg.VirtualPort, 257 }, nil 258 } 259 260 // DelOnion tells the Tor daemon to remove an onion service, which satisfies 261 // either, 262 // - the onion service was created on the same control connection as the 263 // "DEL_ONION" command. 264 // - the onion service was created using the "Detach" flag. 265 func (c *Controller) DelOnion(serviceID string) error { 266 log.Debugf("removing serviceID:%s from tor controller", serviceID) 267 268 cmd := fmt.Sprintf("DEL_ONION %s", serviceID) 269 270 // Send the command to create the onion service to the Tor server and 271 // await its response. 272 code, _, err := c.sendCommand(cmd) 273 274 // Tor replies with "250 OK" on success, or a 512 if there are an 275 // invalid number of arguments, or a 552 if it doesn't recognize the 276 // ServiceID. 277 switch code { 278 // Replied 250 OK. 279 case success: 280 return nil 281 282 // Replied 512 for invalid arguments. This is most likely that the 283 // serviceID is not set(empty string). 284 case invalidNumOfArguments: 285 return fmt.Errorf("invalid arguments: %w", err) 286 287 // Replied 552, which means either, 288 // - the serviceID is invalid. 289 // - the onion service is not owned by the current control connection 290 // and, 291 // - the onion service is not a detached service. 292 // In either case, we will ignore the error and log a warning as there 293 // not much we can do from the controller side. 294 case serviceIDNotRecognized: 295 log.Warnf("removing serviceID:%v not found", serviceID) 296 return nil 297 298 default: 299 return fmt.Errorf("undefined response code: %v, err: %w", 300 code, err) 301 } 302 }