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  }