Implement onion listener

This commit is contained in:
Chad Retz 2018-05-15 11:45:59 -05:00
parent ca48a86ff3
commit 5bc2e90d25
2 changed files with 276 additions and 12 deletions

View File

@ -124,9 +124,9 @@ type AddOnionRequest struct {
Flags []string
// MaxStreams is ADD_ONION MaxStreams.
MaxStreams int
// Ports are ADD_ONION Port values. Key is virtual port, value is target
// Ports are ADD_ONION Port values. Key is virtual port, Val is target
// port (or can be empty to use virtual port).
Ports map[string]string
Ports []*KeyVal
// ClientAuths are ADD_ONION ClientAuth values. If value is empty string,
// Tor will generate the password.
ClientAuths map[string]string
@ -157,10 +157,10 @@ func (c *Conn) AddOnion(req *AddOnionRequest) (*AddOnionResponse, error) {
if req.MaxStreams > 0 {
cmd += " MaxStreams=" + strconv.Itoa(req.MaxStreams)
}
for virt, target := range req.Ports {
cmd += " Port=" + virt
if target != "" {
cmd += "," + target
for _, port := range req.Ports {
cmd += " Port=" + port.Key
if port.Val != "" {
cmd += "," + port.Val
}
}
for name, blob := range req.ClientAuths {

View File

@ -1,12 +1,276 @@
package tor
import "net"
import (
"crypto"
"crypto/rsa"
"fmt"
"net"
"strconv"
type OnionConf struct {
Port int
TargetPort int
"golang.org/x/crypto/ed25519"
"github.com/cretz/bine/control"
)
// OnionService implements net.Listener and net.Addr for an onion service.
type OnionService struct {
// ID is the service ID for this onion service.
ID string
// Key is the private key for this service. It is either the set key, the
// generated key, or nil if asked to discard the key. If present, it is
// *crypto/rsa.PrivateKey (1024 bit) when Version3 is false or
// golang.org/x/crypto/ed25519.PrivateKey when Version3 is true.
Key crypto.PrivateKey
// Version3 says whether or not this service is a V3 service.
Version3 bool
// ClientAuths is the credential set for clients. The keys are username and
// the values are credentials. The credentials will always be present even
// if Tor had to generate them.
ClientAuths map[string]string
// LocalListener is the local TCP listener. This is always present.
LocalListener net.Listener
// RemotePorts is the set of remote ports that are forwarded to the local
// listener. This will always have at least one value.
RemotePorts []int
// CloseLocalListenerOnClose is true if the local listener should be closed
// on Close. This is set to true if a listener was created by Listen and set
// to false of an existing LocalListener was provided to Listen.
CloseLocalListenerOnClose bool
// The Tor object that created this. Needed for Close.
Tor *Tor
}
func (t *Tor) Listen(conf *OnionConf) (net.Listener, error) {
panic("TODO")
// ListenConf is the configuration for Listen calls.
type ListenConf struct {
// LocalPort is the local port to create a TCP listener on. If the port is
// 0, it is automatically chosen. This is ignored if LocalListener is set.
LocalPort int
// LocalListener is the specific local listener to back the onion service.
// If this is nil (the default), then a listener is created with LocalPort.
LocalListener net.Listener
// RemotePorts are the remote ports to serve the onion service on. If empty,
// it is the same as the local port or local listener. This must have at
// least one value if the local listener is not a *net.TCPListener.
RemotePorts []int
// Key is the private key to use. If not present, a key is generated based
// on whether Version3 is true or false. If present, it must be a
// *crypto/rsa.PrivateKey (1024 bit), a
// golang.org/x/crypto/ed25519.PrivateKey, or a
// github.com/cretz/bine/control.Key.
Key crypto.PrivateKey
// Version3 determines whether, when Key is nil, a version 2 or version 3
// service/key will be generated. If true it is version 3 (an ed25519 key
// and v3 onion service) and if false it is version 2 (a RSA-2014 key and v2
// onion service). If Key is not nil, this value is ignored.
Version3 bool
// ClientAuths is the set of usernames and credentials for client
// authentication. The keys are usernames and the values are credentials. If
// a username is present but the credential is empty, a credential is
// generated by Tor for that user. If this is empty there is no
// authentication.
ClientAuths map[string]string
// MaxStreams is the maximum number of streams the service will accept. 0
// means unlimited.
MaxStreams int
// DiscardKey, if true and Key is nil (meaning a private key is generated),
// tells Tor not to return the generated private key. This value is ignored
// if Key is not nil.
DiscardKey bool
// Detach, if true, prevents the default behavior of the onion service being
// deleted when this controller connection is closed.
Detach bool
// NonAnonymous must be true if Tor options HiddenServiceSingleHopMode and
// HiddenServiceNonAnonymousMode are set. Otherwise, it must be false.
NonAnonymous bool
// MaxStreamsCloseCircuit determines whether to close the circuit when the
// maximum number of streams is exceeded. If true, the circuit is closed. If
// false, the stream is simply not connected but the circuit stays open.
MaxStreamsCloseCircuit bool
}
// Listen creates an onion service and local listener. If conf is nil, the
// default struct value is used. Note, if this errors, any listeners created
// here are closed but if a LocalListener is provided it may remain open.
func (t *Tor) Listen(conf *ListenConf) (*OnionService, error) {
// Create the service up here and make sure we close it no matter the error within
svc := &OnionService{Key: conf.Key, CloseLocalListenerOnClose: conf.LocalListener == nil}
var err error
// Create the local listener if necessary
svc.LocalListener = conf.LocalListener
if svc.LocalListener == nil {
if svc.LocalListener, err = net.Listen("tcp", "127.0.0.1:"+strconv.Itoa(conf.LocalPort)); err != nil {
return nil, err
}
}
// Henceforth, any error requires we close the svc
// Build the onion request
req := &control.AddOnionRequest{MaxStreams: conf.MaxStreams, ClientAuths: conf.ClientAuths}
// Set flags
if conf.DiscardKey {
req.Flags = append(req.Flags, "DiscardPK")
}
if conf.Detach {
req.Flags = append(req.Flags, "Detach")
}
if len(conf.ClientAuths) > 0 {
req.Flags = append(req.Flags, "BasicAuth")
}
if conf.NonAnonymous {
req.Flags = append(req.Flags, "NonAnonymous")
}
if conf.MaxStreamsCloseCircuit {
req.Flags = append(req.Flags, "MaxStreamsCloseCircuit")
}
// Set the key
switch key := conf.Key.(type) {
case nil:
svc.Version3 = conf.Version3
if conf.Version3 {
req.Key = control.GenKey(control.KeyAlgoED25519V3)
} else {
req.Key = control.GenKey(control.KeyAlgoRSA1024)
}
case *rsa.PrivateKey:
svc.Version3 = false
if key.N == nil || key.N.BitLen() != 1024 {
err = fmt.Errorf("RSA key must be 1024 bits")
} else {
req.Key = &control.RSAKey{PrivateKey: key}
}
case ed25519.PrivateKey:
svc.Version3 = true
req.Key = control.ED25519Key(key)
default:
err = fmt.Errorf("Unrecognized key type: %T", key)
}
// Apply the remote ports
if err == nil {
if len(conf.RemotePorts) == 0 {
tcpAddr, ok := svc.LocalListener.Addr().(*net.TCPAddr)
if !ok {
err = fmt.Errorf("Unable to derive local TCP port")
} else {
svc.RemotePorts = []int{tcpAddr.Port}
}
} else {
svc.RemotePorts = make([]int, len(conf.RemotePorts))
copy(svc.RemotePorts, conf.RemotePorts)
}
}
// Apply the local ports with the remote ports
if err == nil {
localAddr := svc.LocalListener.Addr().String()
if _, ok := svc.LocalListener.(*net.UnixListener); ok {
localAddr = "unix:" + localAddr
}
for _, remotePort := range conf.RemotePorts {
req.Ports = append(req.Ports, &control.KeyVal{Key: strconv.Itoa(remotePort), Val: localAddr})
}
}
// Create the onion service
var resp *control.AddOnionResponse
if err == nil {
resp, err = t.Control.AddOnion(req)
}
// Apply the response to the service
if err == nil {
svc.ID = resp.ServiceID
switch key := resp.Key.(type) {
case nil:
// Do nothing
case *control.RSAKey:
svc.Key = key.PrivateKey
case control.ED25519Key:
svc.Key = ed25519.PrivateKey(key)
default:
err = fmt.Errorf("Unrecognized result key type: %T", key)
}
// Client auths are the conf and then overridden by results
if len(conf.ClientAuths) > 0 {
svc.ClientAuths = make(map[string]string, len(conf.ClientAuths))
for k, v := range conf.ClientAuths {
svc.ClientAuths[k] = v
}
for k, v := range resp.ClientAuths {
svc.ClientAuths[k] = v
}
}
}
if err != nil {
if closeErr := svc.Close(); closeErr != nil {
err = fmt.Errorf("Error on listen: %v (also got error trying to close: %v)", err, closeErr)
}
return nil, err
}
return svc, nil
}
// Accept implements net.Listener.Accept.
func (o *OnionService) Accept() (net.Conn, error) {
return o.LocalListener.Accept()
}
// Addr implements net.Listener.Addr just returning this object.
func (o *OnionService) Addr() net.Addr {
return o
}
// Network implements net.Addr.Network always returning "tcp".
func (o *OnionService) Network() string {
return "tcp"
}
// String implements net.Addr.String and returns "<serviceID>.onion:<virtport>".
func (o *OnionService) String() string {
return fmt.Sprintf("%v.onion:%v", o.ID, o.RemotePorts[0])
}
// Close implements net.Listener.Close and deletes the onion service and closes
// the LocalListener if CloseLocalListenerOnClose is true.
func (o *OnionService) Close() (err error) {
// Delete the onion first
if o.ID != "" {
if o.Tor == nil {
err = fmt.Errorf("No Tor object")
} else {
err = o.Tor.Control.DelOnion(o.ID)
o.ID = ""
}
}
// Now if the local one needs to be closed, do it
if o.CloseLocalListenerOnClose && o.LocalListener != nil {
if closeErr := o.LocalListener.Close(); closeErr != nil {
if err != nil {
err = fmt.Errorf("Unable to close onion: %v (also unable to close local listener: %v)", err, closeErr)
} else {
err = closeErr
}
}
o.LocalListener = nil
}
return
}