|
| 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, privateKeyPerm os.FileMode) *OnionFile { |
| 56 | + return &OnionFile{ |
| 57 | + privateKeyPath: privateKeyPath, |
| 58 | + privateKeyPerm: privateKeyPerm, |
| 59 | + } |
| 60 | +} |
| 61 | + |
| 62 | +// StorePrivateKey stores the private key at its expected path. |
| 63 | +func (f *OnionFile) StorePrivateKey(_ OnionType, privateKey []byte) error { |
| 64 | + return ioutil.WriteFile(f.privateKeyPath, privateKey, f.privateKeyPerm) |
| 65 | +} |
| 66 | + |
| 67 | +// PrivateKey retrieves the private key from its expected path. If the file does |
| 68 | +// not exist, then ErrNoPrivateKey is returned. |
| 69 | +func (f *OnionFile) PrivateKey(_ OnionType) ([]byte, error) { |
| 70 | + if _, err := os.Stat(f.privateKeyPath); os.IsNotExist(err) { |
| 71 | + return nil, ErrNoPrivateKey |
| 72 | + } |
| 73 | + return ioutil.ReadFile(f.privateKeyPath) |
| 74 | +} |
| 75 | + |
| 76 | +// DeletePrivateKey removes the file containing the private key. |
| 77 | +func (f *OnionFile) DeletePrivateKey(_ OnionType) error { |
| 78 | + return os.Remove(f.privateKeyPath) |
| 79 | +} |
| 80 | + |
| 81 | +// AddOnionConfig houses all of the required parameters in order to successfully |
| 82 | +// create a new onion service or restore an existing one. |
| 83 | +type AddOnionConfig struct { |
| 84 | + // Type denotes the type of the onion service that should be created. |
| 85 | + Type OnionType |
| 86 | + |
| 87 | + // VirtualPort is the externally reachable port of the onion address. |
| 88 | + VirtualPort int |
| 89 | + |
| 90 | + // TargetPorts is the set of ports that the service will be listening on |
| 91 | + // locally. The Tor server will use choose a random port from this set |
| 92 | + // to forward the traffic from the virtual port. |
| 93 | + // |
| 94 | + // NOTE: If nil/empty, the virtual port will be used as the only target |
| 95 | + // port. |
| 96 | + TargetPorts []int |
| 97 | + |
| 98 | + // Store is responsible for storing all onion service related |
| 99 | + // information. |
| 100 | + // |
| 101 | + // NOTE: If not specified, then nothing will be stored, making onion |
| 102 | + // services unrecoverable after shutdown. |
| 103 | + Store OnionStore |
| 104 | +} |
| 105 | + |
| 106 | +// AddOnion creates an onion service and returns its onion address. Once |
| 107 | +// created, the new onion service will remain active until the connection |
| 108 | +// between the controller and the Tor server is closed. |
| 109 | +func (c *Controller) AddOnion(cfg AddOnionConfig) (*OnionAddr, error) { |
| 110 | + // Before sending the request to create an onion service to the Tor |
| 111 | + // server, we'll make sure that it supports V3 onion services if that |
| 112 | + // was the type requested. |
| 113 | + if cfg.Type == V3 { |
| 114 | + if err := supportsV3(c.version); err != nil { |
| 115 | + return nil, err |
| 116 | + } |
| 117 | + } |
| 118 | + |
| 119 | + // We'll start off by checking if the store contains an existing private |
| 120 | + // key. If it does not, then we should request the server to create a |
| 121 | + // new onion service and return its private key. Otherwise, we'll |
| 122 | + // request the server to recreate the onion server from our private key. |
| 123 | + var keyParam string |
| 124 | + switch cfg.Type { |
| 125 | + case V2: |
| 126 | + keyParam = "NEW:RSA1024" |
| 127 | + case V3: |
| 128 | + keyParam = "NEW:ED25519-V3" |
| 129 | + } |
| 130 | + |
| 131 | + if cfg.Store != nil { |
| 132 | + privateKey, err := cfg.Store.PrivateKey(cfg.Type) |
| 133 | + switch err { |
| 134 | + // Proceed to request a new onion service. |
| 135 | + case ErrNoPrivateKey: |
| 136 | + |
| 137 | + // Recover the onion service with the private key found. |
| 138 | + case nil: |
| 139 | + keyParam = string(privateKey) |
| 140 | + |
| 141 | + default: |
| 142 | + return nil, err |
| 143 | + } |
| 144 | + } |
| 145 | + |
| 146 | + // Now, we'll create a mapping from the virtual port to each target |
| 147 | + // port. If no target ports were specified, we'll use the virtual port |
| 148 | + // to provide a one-to-one mapping. |
| 149 | + var portParam string |
| 150 | + |
| 151 | + // Helper function which appends the correct Port param depending on |
| 152 | + // whether the user chose to use a custom target IP address or not. |
| 153 | + pushPortParam := func(targetPort int) { |
| 154 | + if c.targetIPAddress == "" { |
| 155 | + portParam += fmt.Sprintf("Port=%d,%d ", cfg.VirtualPort, |
| 156 | + targetPort) |
| 157 | + } else { |
| 158 | + portParam += fmt.Sprintf("Port=%d,%s:%d ", cfg.VirtualPort, |
| 159 | + c.targetIPAddress, targetPort) |
| 160 | + } |
| 161 | + } |
| 162 | + |
| 163 | + if len(cfg.TargetPorts) == 0 { |
| 164 | + pushPortParam(cfg.VirtualPort) |
| 165 | + } else { |
| 166 | + for _, targetPort := range cfg.TargetPorts { |
| 167 | + pushPortParam(targetPort) |
| 168 | + } |
| 169 | + } |
| 170 | + |
| 171 | + // Send the command to create the onion service to the Tor server and |
| 172 | + // await its response. |
| 173 | + cmd := fmt.Sprintf("ADD_ONION %s %s", keyParam, portParam) |
| 174 | + _, reply, err := c.sendCommand(cmd) |
| 175 | + if err != nil { |
| 176 | + return nil, err |
| 177 | + } |
| 178 | + |
| 179 | + // If successful, the reply from the server should be of the following |
| 180 | + // format, depending on whether a private key has been requested: |
| 181 | + // |
| 182 | + // C: ADD_ONION RSA1024:[Blob Redacted] Port=80,8080 |
| 183 | + // S: 250-ServiceID=testonion1234567 |
| 184 | + // S: 250 OK |
| 185 | + // |
| 186 | + // C: ADD_ONION NEW:RSA1024 Port=80,8080 |
| 187 | + // S: 250-ServiceID=testonion1234567 |
| 188 | + // S: 250-PrivateKey=RSA1024:[Blob Redacted] |
| 189 | + // S: 250 OK |
| 190 | + // |
| 191 | + // We're interested in retrieving the service ID, which is the public |
| 192 | + // name of the service, and the private key if requested. |
| 193 | + replyParams := parseTorReply(reply) |
| 194 | + serviceID, ok := replyParams["ServiceID"] |
| 195 | + if !ok { |
| 196 | + return nil, errors.New("service id not found in reply") |
| 197 | + } |
| 198 | + |
| 199 | + // If a new onion service was created and an onion store was provided, |
| 200 | + // we'll store its private key to disk in the event that it needs to be |
| 201 | + // recreated later on. |
| 202 | + if privateKey, ok := replyParams["PrivateKey"]; cfg.Store != nil && ok { |
| 203 | + err := cfg.Store.StorePrivateKey(cfg.Type, []byte(privateKey)) |
| 204 | + if err != nil { |
| 205 | + return nil, fmt.Errorf("unable to write private key "+ |
| 206 | + "to file: %v", err) |
| 207 | + } |
| 208 | + } |
| 209 | + |
| 210 | + // Finally, we'll return the onion address composed of the service ID, |
| 211 | + // along with the onion suffix, and the port this onion service can be |
| 212 | + // reached at externally. |
| 213 | + return &OnionAddr{ |
| 214 | + OnionService: serviceID + ".onion", |
| 215 | + Port: cfg.VirtualPort, |
| 216 | + }, nil |
| 217 | +} |
0 commit comments