mirror of
https://github.com/muun/recovery.git
synced 2025-11-11 06:20:16 -05:00
Release v0.3.0
This commit is contained in:
4
vendor/github.com/lightningnetwork/lnd/tor/README.md
generated
vendored
4
vendor/github.com/lightningnetwork/lnd/tor/README.md
generated
vendored
@@ -8,8 +8,8 @@ Tor daemon. So far, supported functions include:
|
||||
* Routing DNS queries over Tor (A, AAAA, SRV).
|
||||
* Limited Tor Control functionality (synchronous messages only). So far, this
|
||||
includes:
|
||||
* Support for SAFECOOKIE authentication only as a sane default.
|
||||
* Creating v2 onion services.
|
||||
* Support for SAFECOOKIE, HASHEDPASSWORD, and NULL authentication methods.
|
||||
* Creating v2 and v3 onion services.
|
||||
|
||||
In the future, the Tor Control functionality will be extended to support v3
|
||||
onion services, asynchronous messages, etc.
|
||||
|
||||
217
vendor/github.com/lightningnetwork/lnd/tor/add_onion.go
generated
vendored
Normal file
217
vendor/github.com/lightningnetwork/lnd/tor/add_onion.go
generated
vendored
Normal file
@@ -0,0 +1,217 @@
|
||||
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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// 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.
|
||||
if cfg.Type == V3 {
|
||||
if err := supportsV3(c.version); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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 nil, 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)
|
||||
_, 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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
324
vendor/github.com/lightningnetwork/lnd/tor/controller.go
generated
vendored
324
vendor/github.com/lightningnetwork/lnd/tor/controller.go
generated
vendored
@@ -10,7 +10,6 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
@@ -36,6 +35,16 @@ const (
|
||||
// must be running on. This is needed in order to create v3 onion
|
||||
// services through Tor's control port.
|
||||
MinTorVersion = "0.3.3.6"
|
||||
|
||||
// authSafeCookie is the name of the SAFECOOKIE authentication method.
|
||||
authSafeCookie = "SAFECOOKIE"
|
||||
|
||||
// authHashedPassword is the name of the HASHEDPASSWORD authentication
|
||||
// method.
|
||||
authHashedPassword = "HASHEDPASSWORD"
|
||||
|
||||
// authNull is the name of the NULL authentication method.
|
||||
authNull = "NULL"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -79,14 +88,30 @@ type Controller struct {
|
||||
// controller connections on.
|
||||
controlAddr string
|
||||
|
||||
// password, if non-empty, signals that the controller should attempt to
|
||||
// authenticate itself with the backing Tor daemon through the
|
||||
// HASHEDPASSWORD authentication method with this value.
|
||||
password string
|
||||
|
||||
// version is the current version of the Tor server.
|
||||
version string
|
||||
|
||||
// targetIPAddress is the IP address which we tell the Tor server to use
|
||||
// to connect to the LND node. This is required when the Tor server
|
||||
// runs on another host, otherwise the service will not be reachable.
|
||||
targetIPAddress string
|
||||
}
|
||||
|
||||
// NewController returns a new Tor controller that will be able to interact with
|
||||
// a Tor server.
|
||||
func NewController(controlAddr string) *Controller {
|
||||
return &Controller{controlAddr: controlAddr}
|
||||
func NewController(controlAddr string, targetIPAddress string,
|
||||
password string) *Controller {
|
||||
|
||||
return &Controller{
|
||||
controlAddr: controlAddr,
|
||||
targetIPAddress: targetIPAddress,
|
||||
password: password,
|
||||
}
|
||||
}
|
||||
|
||||
// Start establishes and authenticates the connection between the controller and
|
||||
@@ -163,26 +188,74 @@ func parseTorReply(reply string) map[string]string {
|
||||
}
|
||||
|
||||
// authenticate authenticates the connection between the controller and the
|
||||
// Tor server using the SAFECOOKIE or NULL authentication method.
|
||||
// Tor server using either of the following supported authentication methods
|
||||
// depending on its configuration: SAFECOOKIE, HASHEDPASSWORD, and NULL.
|
||||
func (c *Controller) authenticate() error {
|
||||
protocolInfo, err := c.protocolInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// With the version retrieved, we'll cache it now in case it needs to be
|
||||
// used later on.
|
||||
c.version = protocolInfo.version()
|
||||
|
||||
switch {
|
||||
// If a password was provided, then we should attempt to use the
|
||||
// HASHEDPASSWORD authentication method.
|
||||
case c.password != "":
|
||||
if !protocolInfo.supportsAuthMethod(authHashedPassword) {
|
||||
return fmt.Errorf("%v authentication method not "+
|
||||
"supported", authHashedPassword)
|
||||
}
|
||||
|
||||
return c.authenticateViaHashedPassword()
|
||||
|
||||
// Otherwise, attempt to authentication via the SAFECOOKIE method as it
|
||||
// provides the most security.
|
||||
case protocolInfo.supportsAuthMethod(authSafeCookie):
|
||||
return c.authenticateViaSafeCookie(protocolInfo)
|
||||
|
||||
// Fallback to the NULL method if any others aren't supported.
|
||||
case protocolInfo.supportsAuthMethod(authNull):
|
||||
return c.authenticateViaNull()
|
||||
|
||||
// No supported authentication methods, fail.
|
||||
default:
|
||||
return errors.New("the Tor server must be configured with " +
|
||||
"NULL, SAFECOOKIE, or HASHEDPASSWORD authentication")
|
||||
}
|
||||
}
|
||||
|
||||
// authenticateViaNull authenticates the controller with the Tor server using
|
||||
// the NULL authentication method.
|
||||
func (c *Controller) authenticateViaNull() error {
|
||||
_, _, err := c.sendCommand("AUTHENTICATE")
|
||||
return err
|
||||
}
|
||||
|
||||
// authenticateViaHashedPassword authenticates the controller with the Tor
|
||||
// server using the HASHEDPASSWORD authentication method.
|
||||
func (c *Controller) authenticateViaHashedPassword() error {
|
||||
cmd := fmt.Sprintf("AUTHENTICATE \"%s\"", c.password)
|
||||
_, _, err := c.sendCommand(cmd)
|
||||
return err
|
||||
}
|
||||
|
||||
// authenticateViaSafeCookie authenticates the controller with the Tor server
|
||||
// using the SAFECOOKIE authentication method.
|
||||
func (c *Controller) authenticateViaSafeCookie(info protocolInfo) error {
|
||||
// Before proceeding to authenticate the connection, we'll retrieve
|
||||
// the authentication cookie of the Tor server. This will be used
|
||||
// throughout the authentication routine. We do this before as once the
|
||||
// authentication routine has begun, it is not possible to retrieve it
|
||||
// mid-way.
|
||||
cookie, err := c.getAuthCookie()
|
||||
cookie, err := c.getAuthCookie(info)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to retrieve authentication cookie: "+
|
||||
"%v", err)
|
||||
}
|
||||
|
||||
// If cookie is empty and there's no error, we have a NULL
|
||||
// authentication method that we should use instead.
|
||||
if len(cookie) == 0 {
|
||||
_, _, err := c.sendCommand("AUTHENTICATE")
|
||||
return err
|
||||
}
|
||||
|
||||
// Authenticating using the SAFECOOKIE authentication method is a two
|
||||
// step process. We'll kick off the authentication routine by sending
|
||||
// the AUTHCHALLENGE command followed by a hex-encoded 32-byte nonce.
|
||||
@@ -267,36 +340,15 @@ func (c *Controller) authenticate() error {
|
||||
}
|
||||
|
||||
// getAuthCookie retrieves the authentication cookie in bytes from the Tor
|
||||
// server. Cookie authentication must be enabled for this to work. The boolean
|
||||
func (c *Controller) getAuthCookie() ([]byte, error) {
|
||||
// Retrieve the authentication methods currently supported by the Tor
|
||||
// server.
|
||||
authMethods, cookieFilePath, version, err := c.ProtocolInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// With the version retrieved, we'll cache it now in case it needs to be
|
||||
// used later on.
|
||||
c.version = version
|
||||
|
||||
// Ensure that the Tor server supports the SAFECOOKIE authentication
|
||||
// method or the NULL method. If NULL, we don't need the cookie info
|
||||
// below this loop, so we just return.
|
||||
safeCookieSupport := false
|
||||
for _, authMethod := range authMethods {
|
||||
if authMethod == "SAFECOOKIE" {
|
||||
safeCookieSupport = true
|
||||
}
|
||||
if authMethod == "NULL" {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
if !safeCookieSupport {
|
||||
return nil, errors.New("the Tor server is currently not " +
|
||||
"configured for cookie or null authentication")
|
||||
// server. Cookie authentication must be enabled for this to work.
|
||||
func (c *Controller) getAuthCookie(info protocolInfo) ([]byte, error) {
|
||||
// Retrieve the cookie file path from the PROTOCOLINFO reply.
|
||||
cookieFilePath, ok := info["COOKIEFILE"]
|
||||
if !ok {
|
||||
return nil, errors.New("COOKIEFILE not found in PROTOCOLINFO " +
|
||||
"reply")
|
||||
}
|
||||
cookieFilePath = strings.Trim(cookieFilePath, "\"")
|
||||
|
||||
// Read the cookie from the file and ensure it has the correct length.
|
||||
cookie, err := ioutil.ReadFile(cookieFilePath)
|
||||
@@ -355,176 +407,34 @@ func supportsV3(version string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProtocolInfo returns the different authentication methods supported by the
|
||||
// Tor server and the version of the Tor server.
|
||||
func (c *Controller) ProtocolInfo() ([]string, string, string, error) {
|
||||
// We'll start off by sending the "PROTOCOLINFO" command to the Tor
|
||||
// server. We should receive a reply of the following format:
|
||||
//
|
||||
// METHODS=COOKIE,SAFECOOKIE
|
||||
// COOKIEFILE="/home/user/.tor/control_auth_cookie"
|
||||
// VERSION Tor="0.3.2.10"
|
||||
//
|
||||
// We're interested in retrieving all of these fields, so we'll parse
|
||||
// our reply to do so.
|
||||
// protocolInfo is encompasses the details of a response to a PROTOCOLINFO
|
||||
// command.
|
||||
type protocolInfo map[string]string
|
||||
|
||||
// version returns the Tor version as reported by the server.
|
||||
func (i protocolInfo) version() string {
|
||||
version := i["Tor"]
|
||||
return strings.Trim(version, "\"")
|
||||
}
|
||||
|
||||
// supportsAuthMethod determines whether the Tor server supports the given
|
||||
// authentication method.
|
||||
func (i protocolInfo) supportsAuthMethod(method string) bool {
|
||||
methods, ok := i["METHODS"]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(methods, method)
|
||||
}
|
||||
|
||||
// protocolInfo sends a "PROTOCOLINFO" command to the Tor server and returns its
|
||||
// response.
|
||||
func (c *Controller) protocolInfo() (protocolInfo, error) {
|
||||
cmd := fmt.Sprintf("PROTOCOLINFO %d", ProtocolInfoVersion)
|
||||
_, reply, err := c.sendCommand(cmd)
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
}
|
||||
|
||||
info := parseTorReply(reply)
|
||||
methods, ok := info["METHODS"]
|
||||
if !ok {
|
||||
return nil, "", "", errors.New("auth methods not found in " +
|
||||
"reply")
|
||||
}
|
||||
|
||||
cookieFile, ok := info["COOKIEFILE"]
|
||||
if !ok && !strings.Contains(methods, "NULL") {
|
||||
return nil, "", "", errors.New("cookie file path not found " +
|
||||
"in reply")
|
||||
}
|
||||
|
||||
version, ok := info["Tor"]
|
||||
if !ok {
|
||||
return nil, "", "", errors.New("Tor version not found in reply")
|
||||
}
|
||||
|
||||
// Finally, we'll clean up the results before returning them.
|
||||
authMethods := strings.Split(methods, ",")
|
||||
cookieFilePath := strings.Trim(cookieFile, "\"")
|
||||
torVersion := strings.Trim(version, "\"")
|
||||
|
||||
return authMethods, cookieFilePath, torVersion, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
// 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) {
|
||||
// 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.
|
||||
if cfg.Type == V3 {
|
||||
if err := supportsV3(c.version); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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
|
||||
if _, err := os.Stat(cfg.PrivateKeyPath); os.IsNotExist(err) {
|
||||
switch cfg.Type {
|
||||
case V2:
|
||||
keyParam = "NEW:RSA1024"
|
||||
case V3:
|
||||
keyParam = "NEW:ED25519-V3"
|
||||
}
|
||||
} else {
|
||||
privateKey, err := ioutil.ReadFile(cfg.PrivateKeyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyParam = string(privateKey)
|
||||
}
|
||||
|
||||
// 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
|
||||
if len(cfg.TargetPorts) == 0 {
|
||||
portParam += fmt.Sprintf("Port=%d,%d ", cfg.VirtualPort,
|
||||
cfg.VirtualPort)
|
||||
} else {
|
||||
for _, targetPort := range cfg.TargetPorts {
|
||||
portParam += fmt.Sprintf("Port=%d,%d ", cfg.VirtualPort,
|
||||
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)
|
||||
_, 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, we'll write its private key to
|
||||
// disk under strict permissions in the event that it needs to be
|
||||
// recreated later on.
|
||||
if privateKey, ok := replyParams["PrivateKey"]; ok {
|
||||
err := ioutil.WriteFile(
|
||||
cfg.PrivateKeyPath, []byte(privateKey), 0600,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to write private key "+
|
||||
"to file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
return protocolInfo(parseTorReply(reply)), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user