tor/controller: add support for v3 onion services
In this commit, we extend our Tor controller to also support creating v3 onion services, as they are now supported by the Tor daemon. We also refactor our existing AddOnion method to take in a config struct that houses all of the required options to create/restore an onion service.
This commit is contained in:
parent
ea5a18f4e5
commit
e38174c7ce
@ -338,60 +338,79 @@ func (c *Controller) ProtocolInfo() ([]string, string, string, error) {
|
|||||||
return authMethods, cookieFilePath, torVersion, nil
|
return authMethods, cookieFilePath, torVersion, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// VirtToTargPorts is a mapping of virtual ports to target ports. When creating
|
// OnionType denotes the type of the onion service.
|
||||||
// an onion service, it will be listening externally on each virtual port. Each
|
type OnionType int
|
||||||
// virtual port can then be mapped to one or many target ports internally. When
|
|
||||||
// accessing the onion service at a specific virtual port, it will forward the
|
|
||||||
// traffic to a mapped randomly chosen target port.
|
|
||||||
type VirtToTargPorts = map[int]map[int]struct{}
|
|
||||||
|
|
||||||
// AddOnionV2 creates a new v2 onion service and returns its onion address(es).
|
const (
|
||||||
// Once created, the new onion service will remain active until the connection
|
// V2 denotes that the onion service is V2.
|
||||||
// between the controller and the Tor server is closed. The path to a private
|
V2 OnionType = iota
|
||||||
// key can be provided in order to restore a previously created onion service.
|
|
||||||
// If a file at this path does not exist, a new onion service will be created
|
|
||||||
// and its private key will be saved to a file at this path. A mapping of
|
|
||||||
// virtual ports to target ports should also be provided. Each virtual port will
|
|
||||||
// be the ports where the onion service can be reached at, while the mapped
|
|
||||||
// target ports will be the ports where the onion service is running locally.
|
|
||||||
func (c *Controller) AddOnionV2(privateKeyFilename string,
|
|
||||||
virtToTargPorts VirtToTargPorts) ([]*OnionAddr, error) {
|
|
||||||
|
|
||||||
|
// 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) {
|
||||||
// We'll start off by checking if the file containing the private key
|
// 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
|
// 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
|
// a new onion service and return its private key. Otherwise, we'll
|
||||||
// request the server to recreate the onion server from our private key.
|
// request the server to recreate the onion server from our private key.
|
||||||
var keyParam string
|
var keyParam string
|
||||||
if _, err := os.Stat(privateKeyFilename); os.IsNotExist(err) {
|
if _, err := os.Stat(cfg.PrivateKeyPath); os.IsNotExist(err) {
|
||||||
|
switch cfg.Type {
|
||||||
|
case V2:
|
||||||
keyParam = "NEW:RSA1024"
|
keyParam = "NEW:RSA1024"
|
||||||
|
case V3:
|
||||||
|
keyParam = "NEW:ED25519-V3"
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
privateKey, err := ioutil.ReadFile(privateKeyFilename)
|
privateKey, err := ioutil.ReadFile(cfg.PrivateKeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
keyParam = string(privateKey)
|
keyParam = string(privateKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, we'll determine the different virtual ports on which this onion
|
// Now, we'll create a mapping from the virtual port to each target
|
||||||
// service will be accessed by.
|
// port. If no target ports were specified, we'll use the virtual port
|
||||||
|
// to provide a one-to-one mapping.
|
||||||
var portParam string
|
var portParam string
|
||||||
for virtPort, targPorts := range virtToTargPorts {
|
if len(cfg.TargetPorts) == 0 {
|
||||||
// If the virtual port doesn't map to any target ports, we'll
|
portParam += fmt.Sprintf("Port=%d,%d ", cfg.VirtualPort,
|
||||||
// use the virtual port as the target port.
|
cfg.VirtualPort)
|
||||||
if len(targPorts) == 0 {
|
} else {
|
||||||
portParam += fmt.Sprintf("Port=%d,%d ", virtPort,
|
for _, targetPort := range cfg.TargetPorts {
|
||||||
virtPort)
|
portParam += fmt.Sprintf("Port=%d,%d ", cfg.VirtualPort,
|
||||||
continue
|
targetPort)
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, we'll create a mapping from the virtual port to
|
|
||||||
// each target port.
|
|
||||||
for targPort := range targPorts {
|
|
||||||
portParam += fmt.Sprintf("Port=%d,%d ", virtPort,
|
|
||||||
targPort)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
cmd := fmt.Sprintf("ADD_ONION %s %s", keyParam, portParam)
|
||||||
_, reply, err := c.sendCommand(cmd)
|
_, reply, err := c.sendCommand(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -423,7 +442,7 @@ func (c *Controller) AddOnionV2(privateKeyFilename string,
|
|||||||
// recreated later on.
|
// recreated later on.
|
||||||
if privateKey, ok := replyParams["PrivateKey"]; ok {
|
if privateKey, ok := replyParams["PrivateKey"]; ok {
|
||||||
err := ioutil.WriteFile(
|
err := ioutil.WriteFile(
|
||||||
privateKeyFilename, []byte(privateKey), 0600,
|
cfg.PrivateKeyPath, []byte(privateKey), 0600,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to write private key "+
|
return nil, fmt.Errorf("unable to write private key "+
|
||||||
@ -431,18 +450,11 @@ func (c *Controller) AddOnionV2(privateKeyFilename string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, return the different onion addresses composed of the service
|
// Finally, we'll return the onion address composed of the service ID,
|
||||||
// ID, along with the onion suffix, and the different virtual ports this
|
// along with the onion suffix, and the port this onion service can be
|
||||||
// onion service can be reached at.
|
// reached at externally.
|
||||||
onionService := serviceID + ".onion"
|
return &OnionAddr{
|
||||||
addrs := make([]*OnionAddr, 0, len(virtToTargPorts))
|
OnionService: serviceID + ".onion",
|
||||||
for virtPort := range virtToTargPorts {
|
Port: cfg.VirtualPort,
|
||||||
addr := &OnionAddr{
|
}, nil
|
||||||
OnionService: onionService,
|
|
||||||
Port: virtPort,
|
|
||||||
}
|
|
||||||
addrs = append(addrs, addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return addrs, nil
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user