From 2c4f45c2856338346d5de356718cd3a45404b0a8 Mon Sep 17 00:00:00 2001 From: Chad Retz Date: Sat, 12 May 2018 23:58:15 -0500 Subject: [PATCH] Docs and beginning of simple iface and examples --- control/cmd_circuit.go | 3 ++ control/cmd_conf.go | 5 +++ control/cmd_event.go | 2 + control/cmd_hiddenservice.go | 2 + control/cmd_misc.go | 10 ++++- control/cmd_onion.go | 69 ++++++++++++++++++++++++++++------- examples/simpleserver/main.go | 25 +++++++++++++ tor/tor.go | 47 ++++++++++++++++++++++++ 8 files changed, 149 insertions(+), 14 deletions(-) create mode 100644 examples/simpleserver/main.go create mode 100644 tor/tor.go diff --git a/control/cmd_circuit.go b/control/cmd_circuit.go index 6c162b7..f796145 100644 --- a/control/cmd_circuit.go +++ b/control/cmd_circuit.go @@ -4,6 +4,7 @@ import ( "strings" ) +// ExtendCircuit invokes EXTENDCIRCUIT and returns the circuit ID on success. func (c *Conn) ExtendCircuit(circuitID string, path []string, purpose string) (string, error) { if circuitID == "" { circuitID = "0" @@ -22,10 +23,12 @@ func (c *Conn) ExtendCircuit(circuitID string, path []string, purpose string) (s return resp.Reply[strings.LastIndexByte(resp.Reply, ' ')+1:], nil } +// SetCircuitPurpose invokes SETCIRCUITPURPOSE. func (c *Conn) SetCircuitPurpose(circuitID string, purpose string) error { return c.sendRequestIgnoreResponse("SETCIRCUITPURPOSE %v purpose=%v", circuitID, purpose) } +// CloseCircuit invokes CLOSECIRCUIT. func (c *Conn) CloseCircuit(circuitID string, flags []string) error { cmd := "CLOSECIRCUIT " + circuitID for _, flag := range flags { diff --git a/control/cmd_conf.go b/control/cmd_conf.go index bb5080a..39701c5 100644 --- a/control/cmd_conf.go +++ b/control/cmd_conf.go @@ -6,10 +6,12 @@ import ( "github.com/cretz/bine/util" ) +// SetConf invokes SETCONF. func (c *Conn) SetConf(entries ...*KeyVal) error { return c.sendSetConf("SETCONF", entries) } +// ResetConf invokes RESETCONF. func (c *Conn) ResetConf(entries ...*KeyVal) error { return c.sendSetConf("RESETCONF", entries) } @@ -24,6 +26,7 @@ func (c *Conn) sendSetConf(cmd string, entries []*KeyVal) error { return c.sendRequestIgnoreResponse(cmd) } +// GetConf invokes GETCONF and returns the values for the requested keys. func (c *Conn) GetConf(keys ...string) ([]*KeyVal, error) { resp, err := c.SendRequest("GETCONF %v", strings.Join(keys, " ")) if err != nil { @@ -47,6 +50,7 @@ func (c *Conn) GetConf(keys ...string) ([]*KeyVal, error) { return ret, nil } +// SaveConf invokes SAVECONF. func (c *Conn) SaveConf(force bool) error { cmd := "SAVECONF" if force { @@ -55,6 +59,7 @@ func (c *Conn) SaveConf(force bool) error { return c.sendRequestIgnoreResponse(cmd) } +// LoadConf invokes LOADCONF. func (c *Conn) LoadConf(conf string) error { return c.sendRequestIgnoreResponse("+LOADCONF\r\n%v\r\n.", conf) } diff --git a/control/cmd_event.go b/control/cmd_event.go index 3f33714..718d3b8 100644 --- a/control/cmd_event.go +++ b/control/cmd_event.go @@ -9,9 +9,11 @@ import ( "github.com/cretz/bine/util" ) +// EventCode represents an asynchronous event code (ref control spec 4.1) type EventCode string const ( + // EventCodeAddrMap is ADDRMAP EventCodeAddrMap EventCode = "ADDRMAP" EventCodeBandwidth EventCode = "BW" EventCodeBuildTimeoutSet EventCode = "BUILDTIMEOUT_SET" diff --git a/control/cmd_hiddenservice.go b/control/cmd_hiddenservice.go index 7f9150f..ab52142 100644 --- a/control/cmd_hiddenservice.go +++ b/control/cmd_hiddenservice.go @@ -1,5 +1,6 @@ package control +// GetHiddenServiceDescriptorAsync invokes HSFETCH. func (c *Conn) GetHiddenServiceDescriptorAsync(address string, server string) error { cmd := "HSFETCH " + address if server != "" { @@ -8,6 +9,7 @@ func (c *Conn) GetHiddenServiceDescriptorAsync(address string, server string) er return c.sendRequestIgnoreResponse(cmd) } +// PostHiddenServiceDescriptorAsync invokes HSPOST. func (c *Conn) PostHiddenServiceDescriptorAsync(desc string, servers []string, address string) error { cmd := "+HSPOST" for _, server := range servers { diff --git a/control/cmd_misc.go b/control/cmd_misc.go index 73cf3ba..ff20e44 100644 --- a/control/cmd_misc.go +++ b/control/cmd_misc.go @@ -6,14 +6,17 @@ import ( "github.com/cretz/bine/util" ) +// Signal invokes SIGNAL. func (c *Conn) Signal(signal string) error { return c.sendRequestIgnoreResponse("SIGNAL %v", signal) } +// Quit invokes QUIT. func (c *Conn) Quit() error { return c.sendRequestIgnoreResponse("QUIT") } +// MapAddresses invokes MAPADDRESS and returns mapped addresses. func (c *Conn) MapAddresses(addresses ...*KeyVal) ([]*KeyVal, error) { cmd := "MAPADDRESS" for _, address := range addresses { @@ -33,6 +36,7 @@ func (c *Conn) MapAddresses(addresses ...*KeyVal) ([]*KeyVal, error) { return ret, nil } +// GetInfo invokes GETINTO and returns values for requested keys. func (c *Conn) GetInfo(keys ...string) ([]*KeyVal, error) { resp, err := c.SendRequest("GETINFO %v", strings.Join(keys, " ")) if err != nil { @@ -47,6 +51,7 @@ func (c *Conn) GetInfo(keys ...string) ([]*KeyVal, error) { return ret, nil } +// PostDescriptor invokes POSTDESCRIPTOR. func (c *Conn) PostDescriptor(descriptor string, purpose string, cache string) error { cmd := "+POSTDESCRIPTOR" if purpose != "" { @@ -59,11 +64,12 @@ func (c *Conn) PostDescriptor(descriptor string, purpose string, cache string) e return c.sendRequestIgnoreResponse(cmd) } +// UseFeatures invokes USEFEATURE. func (c *Conn) UseFeatures(features ...string) error { return c.sendRequestIgnoreResponse("USEFEATURE " + strings.Join(features, " ")) } -// TODO: can this take multiple +// ResolveAsync invokes RESOLVE. func (c *Conn) ResolveAsync(address string, reverse bool) error { cmd := "RESOLVE " if reverse { @@ -72,10 +78,12 @@ func (c *Conn) ResolveAsync(address string, reverse bool) error { return c.sendRequestIgnoreResponse(cmd + address) } +// TakeOwnership invokes TAKEOWNERSHIP. func (c *Conn) TakeOwnership() error { return c.sendRequestIgnoreResponse("TAKEOWNERSHIP") } +// DropGuards invokes DROPGUARDS. func (c *Conn) DropGuards() error { return c.sendRequestIgnoreResponse("DROPGUARDS") } diff --git a/control/cmd_onion.go b/control/cmd_onion.go index 4e5cdb3..aa94a7f 100644 --- a/control/cmd_onion.go +++ b/control/cmd_onion.go @@ -12,27 +12,39 @@ import ( "golang.org/x/crypto/ed25519" ) +// KeyType is a key type for Key in AddOnion. type KeyType string const ( - KeyTypeNew KeyType = "NEW" - KeyTypeRSA1024 KeyType = "RSA1024" + // KeyTypeNew is NEW. + KeyTypeNew KeyType = "NEW" + // KeyTypeRSA1024 is RSA1024. + KeyTypeRSA1024 KeyType = "RSA1024" + // KeyTypeED25519V3 is ED25519-V3. KeyTypeED25519V3 KeyType = "ED25519-V3" ) +// KeyAlgo is a key algorithm for GenKey on AddOnion. type KeyAlgo string const ( - KeyAlgoBest KeyAlgo = "BEST" - KeyAlgoRSA1024 KeyAlgo = "RSA1024" + // KeyAlgoBest is BEST. + KeyAlgoBest KeyAlgo = "BEST" + // KeyAlgoRSA1024 is RSA1024. + KeyAlgoRSA1024 KeyAlgo = "RSA1024" + // KeyAlgoED25519V3 is ED25519-V3. KeyAlgoED25519V3 KeyAlgo = "ED25519-V3" ) +// Key is a type of key to use for AddOnion. Implementations include GenKey, RSAKey, and ED25519Key. type Key interface { + // Type is the KeyType for AddOnion. Type() KeyType + // Blob is the serialized key for AddOnion. Blob() string } +// KeyFromString creates a Key for AddOnion based on a response string. func KeyFromString(str string) (Key, error) { typ, blob, _ := util.PartitionString(str, ':') switch KeyType(typ) { @@ -47,14 +59,22 @@ func KeyFromString(str string) (Key, error) { } } +// GenKey is a Key for AddOnion that asks Tor to generate a key for the given algorithm. type GenKey KeyAlgo +// GenKeyFromBlob creates a GenKey for the given response blob which is a KeyAlgo. func GenKeyFromBlob(blob string) GenKey { return GenKey(KeyAlgo(blob)) } -func (GenKey) Type() KeyType { return KeyTypeNew } -func (g GenKey) Blob() string { return string(g) } +// Type implements Key.Type. +func (GenKey) Type() KeyType { return KeyTypeNew } + +// Blob implements Key.Blob. +func (g GenKey) Blob() string { return string(g) } + +// RSAKey is a Key for AddOnion that is a RSA-1024 key (i.e. v2). type RSAKey struct{ *rsa.PrivateKey } +// RSA1024KeyFromBlob creates a RSAKey for the given response blob. func RSA1024KeyFromBlob(blob string) (*RSAKey, error) { byts, err := base64.StdEncoding.DecodeString(blob) if err != nil { @@ -66,13 +86,19 @@ func RSA1024KeyFromBlob(blob string) (*RSAKey, error) { } return &RSAKey{rsaKey}, nil } + +// Type implements Key.Type. func (*RSAKey) Type() KeyType { return KeyTypeRSA1024 } + +// Blob implements Key.Blob. func (r *RSAKey) Blob() string { return base64.StdEncoding.EncodeToString(x509.MarshalPKCS1PrivateKey(r.PrivateKey)) } +// ED25519Key is a Key for AddOnion that is a ed25519 key (i.e. v3). type ED25519Key ed25519.PrivateKey +// ED25519KeyFromBlob creates a ED25519Key for the given response blob. func ED25519KeyFromBlob(blob string) (ED25519Key, error) { byts, err := base64.StdEncoding.DecodeString(blob) if err != nil { @@ -80,24 +106,40 @@ func ED25519KeyFromBlob(blob string) (ED25519Key, error) { } return ED25519Key(ed25519.PrivateKey(byts)), nil } -func (ED25519Key) Type() KeyType { return KeyTypeED25519V3 } + +// Type implements Key.Type. +func (ED25519Key) Type() KeyType { return KeyTypeED25519V3 } + +// Blob implements Key.Blob. func (e ED25519Key) Blob() string { return base64.StdEncoding.EncodeToString(e) } +// AddOnionRequest is a set of request params for AddOnion. type AddOnionRequest struct { - Key Key - Flags []string - MaxStreams int - Ports map[string]string + // Key is the key to use or GenKey if Tor should generate it. + Key Key + // Flags are ADD_ONION flags. + Flags []string + // MaxStreams is ADD_ONION MaxStreams. + MaxStreams int + // Ports are ADD_ONION Port values. Key is virtual port, value is target port (or can be empty to use virtual port). + Ports map[string]string + // ClientAuths are ADD_ONION ClientAuth values. If value is empty string, Tor will generate the password. ClientAuths map[string]string } +// AddOnionResponse is the response for AddOnion. type AddOnionResponse struct { - ServiceID string - Key Key + // ServiceID is the ADD_ONION response ServiceID value. + ServiceID string + // Key is the ADD_ONION response PrivateKey value. + Key Key + // ClientAuths are the ADD_ONION response ClientAuth values. ClientAuths map[string]string + // RawResponse is the raw ADD_ONION response. RawResponse *Response } +// AddOnion invokes ADD_ONION and returns its response. func (c *Conn) AddOnion(req *AddOnionRequest) (*AddOnionResponse, error) { // Build command if req.Key == nil { @@ -148,6 +190,7 @@ func (c *Conn) AddOnion(req *AddOnionRequest) (*AddOnionResponse, error) { return ret, nil } +// DelOnion invokes DELONION. func (c *Conn) DelOnion(serviceID string) error { return c.sendRequestIgnoreResponse("DELONION %v", serviceID) } diff --git a/examples/simpleserver/main.go b/examples/simpleserver/main.go new file mode 100644 index 0000000..31bdbdf --- /dev/null +++ b/examples/simpleserver/main.go @@ -0,0 +1,25 @@ +package main + +import ( + "log" + "net/http" +) + +func main() { + // Start tor with default config + t, err := tor.Start(nil) + if err != nil { + log.Fatal(err) + } + // Add a handler + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello, World!")) + }) + // Create an onion service to listen on 8080 but show as 80 + l, err := t.Listen(&tor.OnionConf{Port: 80, TargetPort: 8080}) + if err != nil { + log.Fatal(err) + } + // Serve on HTTP + log.Fatal(http.Serve(l, nil)) +} diff --git a/tor/tor.go b/tor/tor.go new file mode 100644 index 0000000..d7ef093 --- /dev/null +++ b/tor/tor.go @@ -0,0 +1,47 @@ +package tor + +import ( + "context" + "io" + "net" +) + +type Tor struct { +} + +type StartConf struct { + // TODO: docs...Nil means contet.Background + Context context.Context + // TODO: docs...Empty string means just "tor" either locally or on PATH + ExePath string + // TODO: docs...If true, doesn't use exe path, uses statically compiled Tor + Embedded bool + // TODO: docs...If 0, Tor is asked to store the control port in a temporary file in the data directory that is + // deleted after read + ControlPort int + // TODO: docs...If not empty, this is the data directory used and *TempDataDir* fields are unused + DataDir string + // TODO: docs...If not empty, this is the parent directory that a child dir is created for data. If empty, the + // current dir is assumed. This has no effect if DataDir is set. + TempDataDirBase string + // TODO: docs...If true the temporary data dir is not deleted on close. This has no effect if DataDir is set. + RetainTempDataDir bool + // TODO: docs...Any extra CLI arguments to pass to Tor. This are applied after other CLI args. + ExtraArgs []string + // TODO: docs... + DebugWriter io.Writer +} + +func (t *Tor) Start(conf *StartConf) error { + // actualConf := *conf + panic("TODO") +} + +type OnionConf struct { + Port int + TargetPort int +} + +func (t *Tor) Listen(conf *OnionConf) (net.Listener, error) { + panic("TODO") +}