├── .gitignore ├── logo ├── bine-logo.png └── bine-logo.xcf ├── tests ├── doc.go ├── control_cmd_event_test.go ├── control_cmd_misc_test.go ├── control_cmd_protocolinfo_test.go ├── tor_geoip_test.go ├── control_cmd_hiddenservice_test.go ├── tor_dialer_test.go ├── tor_forward_test.go ├── control_cmd_authenticate_test.go ├── tor_listen_test.go ├── control_cmd_conf_test.go ├── tor_isolate_socks_auth_test.go └── context_test.go ├── torutil ├── doc.go ├── ed25519 │ ├── testdata │ │ └── sign.input.gz │ ├── internal │ │ └── edwards25519 │ │ │ └── README.md │ ├── ed25519_test.go │ └── ed25519.go ├── geoipembed │ ├── README.md │ └── geoipembed.go ├── key.go ├── string.go ├── string_test.go └── key_test.go ├── go.mod ├── examples ├── go.mod ├── embeddedversion │ └── main.go ├── README.md ├── simpleserver │ └── main.go ├── grpc │ ├── pb │ │ ├── service.proto │ │ └── service.pb.go │ └── main.go ├── simpleclient │ └── main.go ├── httpaltsvc │ ├── README.md │ └── main.go ├── embeddedfileserver │ └── main.go └── go.sum ├── tor ├── doc.go ├── log.go ├── dialer.go ├── forward.go ├── listen.go └── tor.go ├── control ├── doc.go ├── cmd_hiddenservice.go ├── cmd_stream.go ├── cmd_circuit.go ├── keyval.go ├── cmd_conf.go ├── cmd_protocolinfo.go ├── status.go ├── cmd_misc.go ├── conn.go ├── response.go ├── cmd_authenticate.go └── cmd_onion.go ├── process ├── embedded │ ├── tor-0.3.3 │ │ ├── embeddedtest │ │ │ └── main.go │ │ └── process.go │ ├── process.go │ ├── tor-0.3.5 │ │ ├── embeddedtest │ │ │ └── main.go │ │ └── process.go │ └── tor-0.4.7 │ │ ├── embeddedtest │ │ └── main.go │ │ └── process.go └── process.go ├── doc.go ├── LICENSE ├── go.sum └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /examples/httpaltsvc/tor-data 2 | -------------------------------------------------------------------------------- /logo/bine-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cretz/bine/HEAD/logo/bine-logo.png -------------------------------------------------------------------------------- /logo/bine-logo.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cretz/bine/HEAD/logo/bine-logo.xcf -------------------------------------------------------------------------------- /tests/doc.go: -------------------------------------------------------------------------------- 1 | // Package tests contains integration tests. 2 | package tests 3 | -------------------------------------------------------------------------------- /tests/control_cmd_event_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | // TODO: test several event parsers 4 | -------------------------------------------------------------------------------- /torutil/doc.go: -------------------------------------------------------------------------------- 1 | // Package torutil has generic utilities shared across the library. 2 | package torutil 3 | -------------------------------------------------------------------------------- /torutil/ed25519/testdata/sign.input.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cretz/bine/HEAD/torutil/ed25519/testdata/sign.input.gz -------------------------------------------------------------------------------- /torutil/ed25519/internal/edwards25519/README.md: -------------------------------------------------------------------------------- 1 | This is taken from https://github.com/golang/crypto/tree/1a580b3eff7814fc9b40602fd35256c63b50f491/ed25519/internal/edwards25519 -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cretz/bine 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/stretchr/testify v1.7.0 7 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a 8 | golang.org/x/net v0.0.0-20210525063256-abc453219eb5 9 | ) 10 | -------------------------------------------------------------------------------- /tests/control_cmd_misc_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import "testing" 4 | 5 | func TestSignal(t *testing.T) { 6 | ctx := NewTestContext(t, nil) 7 | defer ctx.Close() 8 | ctx.Require.NoError(ctx.Control.Signal("HEARTBEAT")) 9 | } 10 | -------------------------------------------------------------------------------- /examples/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cretz/bine/examples 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/cretz/bine v0.2.0 7 | github.com/golang/protobuf v1.5.2 8 | golang.org/x/net v0.0.0-20210525063256-abc453219eb5 9 | google.golang.org/grpc v1.38.0 10 | ) 11 | 12 | replace github.com/cretz/bine => ../ 13 | -------------------------------------------------------------------------------- /tor/doc.go: -------------------------------------------------------------------------------- 1 | // Package tor is the high-level client for Tor. 2 | // 3 | // The Tor type is a combination of a Tor instance and a connection to it. 4 | // Use Start to create Tor. Then Dialer or Listener can be used. 5 | // 6 | // Some of this code is lifted from https://github.com/yawning/bulb with thanks. 7 | package tor 8 | -------------------------------------------------------------------------------- /tests/control_cmd_protocolinfo_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func TestProtocolInfo(t *testing.T) { 9 | ctx := NewTestContext(t, nil) 10 | defer ctx.Close() 11 | info, err := ctx.Control.ProtocolInfo() 12 | ctx.Require.NoError(err) 13 | ctx.Require.Contains(info.AuthMethods, "SAFECOOKIE") 14 | ctx.Require.True(strings.HasPrefix(info.TorVersion, "0.4")) 15 | } 16 | -------------------------------------------------------------------------------- /tor/log.go: -------------------------------------------------------------------------------- 1 | package tor 2 | 3 | import "fmt" 4 | 5 | // DebugEnabled returns true if there is a DebugWriter. 6 | func (t *Tor) DebugEnabled() bool { 7 | return t.DebugWriter != nil 8 | } 9 | 10 | // Debugf writes the formatted string with a newline appended to the DebugWriter 11 | // if present. 12 | func (t *Tor) Debugf(format string, args ...interface{}) { 13 | if w := t.DebugWriter; w != nil { 14 | fmt.Fprintf(w, format+"\n", args...) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /control/doc.go: -------------------------------------------------------------------------------- 1 | // Package control implements a low-level client for the Tor control spec 2 | // version 1. 3 | // 4 | // The primary entrypoint is the Conn struct, instantiated with NewConn. This is 5 | // the low-level layer to the control port of an already-running Tor instance. 6 | // Most developers will prefer the tor package adjacent to this one for a higher 7 | // level abstraction over the process and this connection. 8 | // 9 | // Some of this code is lifted from https://github.com/yawning/bulb with thanks. 10 | package control 11 | -------------------------------------------------------------------------------- /examples/embeddedversion/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/cretz/bine/process/embedded" 9 | ) 10 | 11 | func main() { 12 | if err := run(); err != nil { 13 | log.Fatal(err) 14 | } 15 | } 16 | 17 | func run() error { 18 | p, err := embedded.NewCreator().New(context.Background(), "--version") 19 | if err != nil { 20 | return err 21 | } 22 | fmt.Printf("Starting...\n") 23 | if err = p.Start(); err != nil { 24 | return err 25 | } 26 | fmt.Printf("Waiting...\n") 27 | return p.Wait() 28 | } 29 | -------------------------------------------------------------------------------- /process/embedded/tor-0.3.3/embeddedtest/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "os" 7 | 8 | tor033 "github.com/cretz/bine/process/embedded/tor-0.3.3" 9 | ) 10 | 11 | // Simply calls Tor will the same parameters 12 | func main() { 13 | if err := runTor(os.Args[1:]...); err != nil { 14 | log.Fatal(err) 15 | } 16 | } 17 | 18 | func runTor(args ...string) error { 19 | process, err := tor033.NewCreator().New(context.Background(), args...) 20 | if err == nil { 21 | process.Start() 22 | err = process.Wait() 23 | } 24 | return err 25 | } 26 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Bine is a toolkit to assist in creating Tor clients and servers. Features: 2 | // 3 | // * Full support for the Tor controller API 4 | // 5 | // * Support for `net.Conn` and `net.Listen` style APIs 6 | // 7 | // * Supports statically compiled Tor to embed Tor into the binary 8 | // 9 | // * Supports both v2 and v3 onion services 10 | // 11 | // * Support for embedded control socket in Tor >= 0.3.5 (non-Windows) 12 | // 13 | // Users of this library will usually use the high-level tor package. See 14 | // README at https://github.com/cretz/bine for more info. 15 | package bine 16 | -------------------------------------------------------------------------------- /torutil/geoipembed/README.md: -------------------------------------------------------------------------------- 1 | **How to regen** 2 | 3 | With [go-bindata](https://github.com/go-bindata/go-bindata) installed and assuming `tor-static` is present: 4 | 5 | go-bindata -pkg geoipembed -prefix ..\..\..\tor-static\tor\src\config ..\..\..\tor-static\tor\src\config\geoip ..\..\..\tor-static\tor\src\config\geoip6 6 | 7 | Then delete the public API, delete the unused imports, remove the generated comments at the package level, and put the 8 | mod time in for `LastUpdated` in `geoipembed`. One day this might all be automated, e.g. download maxmind db ourselves, 9 | gen code, update last updated, etc. -------------------------------------------------------------------------------- /control/cmd_hiddenservice.go: -------------------------------------------------------------------------------- 1 | package control 2 | 3 | // GetHiddenServiceDescriptorAsync invokes HSFETCH. 4 | func (c *Conn) GetHiddenServiceDescriptorAsync(address string, server string) error { 5 | cmd := "HSFETCH " + address 6 | if server != "" { 7 | cmd += " SERVER=" + server 8 | } 9 | return c.sendRequestIgnoreResponse(cmd) 10 | } 11 | 12 | // PostHiddenServiceDescriptorAsync invokes HSPOST. 13 | func (c *Conn) PostHiddenServiceDescriptorAsync(desc string, servers []string, address string) error { 14 | cmd := "+HSPOST" 15 | for _, server := range servers { 16 | cmd += " SERVER=" + server 17 | } 18 | if address != "" { 19 | cmd += "HSADDRESS=" + address 20 | } 21 | cmd += "\r\n" + desc + "\r\n." 22 | return c.sendRequestIgnoreResponse(cmd) 23 | } 24 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | ### Bine Examples 2 | 3 | The following examples are in this directory: 4 | 5 | * [simpleclient](simpleclient) - A simple Tor client for connecting to the web or other onion services 6 | * [simpleserver](simpleserver) - Hosting simple "hello world" Tor onion service 7 | * [embeddedversion](embeddedversion) - Example showing how to dump the version of Tor embedded in the binary 8 | * [embeddedfileserver](embeddedfileserver) - Example showing a file server using Tor embedded in the binary 9 | * [grpc](grpc) - Example showing how to use gRPC over Tor 10 | * [httpaltsvc](httpaltsvc) - Example showing how to use .onion address as `Alt-Svc` of regular website (in development) 11 | 12 | To run an example, while in this directory run the following with `` replaced with the desired example: 13 | 14 | go run ./ -------------------------------------------------------------------------------- /control/cmd_stream.go: -------------------------------------------------------------------------------- 1 | package control 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | // AttachStream invokes ATTACHSTREAM. 8 | func (c *Conn) AttachStream(streamID string, circuitID string, hopNum int) error { 9 | if circuitID == "" { 10 | circuitID = "0" 11 | } 12 | cmd := "ATTACHSTREAM " + streamID + " " + circuitID 13 | if hopNum > 0 { 14 | cmd += " HOP=" + strconv.Itoa(hopNum) 15 | } 16 | return c.sendRequestIgnoreResponse(cmd) 17 | } 18 | 19 | // RedirectStream invokes REDIRECTSTREAM. 20 | func (c *Conn) RedirectStream(streamID string, address string, port int) error { 21 | cmd := "REDIRECTSTREAM " + streamID + " " + address 22 | if port > 0 { 23 | cmd += " " + strconv.Itoa(port) 24 | } 25 | return c.sendRequestIgnoreResponse(cmd) 26 | } 27 | 28 | // CloseStream invokes CLOSESTREAM. 29 | func (c *Conn) CloseStream(streamID string, reason string) error { 30 | return c.sendRequestIgnoreResponse("CLOSESTREAM %v %v", streamID, reason) 31 | } 32 | -------------------------------------------------------------------------------- /torutil/geoipembed/geoipembed.go: -------------------------------------------------------------------------------- 1 | // Package geoipembed contains embedded db files for GeoIP. 2 | // 3 | // The GeoIPReader can be used as tor.StartConf.GeoIPFileReader. 4 | package geoipembed 5 | 6 | import ( 7 | "bytes" 8 | "io" 9 | "time" 10 | ) 11 | 12 | // LastUpdated is the mod time of the embedded geoip files. 13 | func LastUpdated() time.Time { return time.Unix(1537539535, 0) } 14 | 15 | // GeoIPBytes returns the full byte slice of the geo IP file. 16 | func GeoIPBytes(ipv6 bool) ([]byte, error) { 17 | if ipv6 { 18 | return geoip6Bytes() 19 | } 20 | return geoipBytes() 21 | } 22 | 23 | // GeoIPReader returns a ReadCloser for GeoIPBytes. Close does nothing. 24 | func GeoIPReader(ipv6 bool) (io.ReadCloser, error) { 25 | if byts, err := GeoIPBytes(ipv6); err != nil { 26 | return nil, err 27 | } else { 28 | return &readNoopClose{bytes.NewReader(byts)}, nil 29 | } 30 | } 31 | 32 | type readNoopClose struct { 33 | *bytes.Reader 34 | } 35 | 36 | func (*readNoopClose) Close() error { return nil } 37 | -------------------------------------------------------------------------------- /tests/tor_geoip_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cretz/bine/tor" 7 | "github.com/cretz/bine/torutil/geoipembed" 8 | ) 9 | 10 | func TestEmbeddedGeoIPFile(t *testing.T) { 11 | ctx := NewTestContext(t, &tor.StartConf{GeoIPFileReader: geoipembed.GeoIPReader}) 12 | defer ctx.Close() 13 | // Check available and grab a couple of known IPs and check the country 14 | // (taken from https://my.pingdom.com/probes/feed) 15 | usIpv4, usIpv6 := "209.58.139.193", "2605:fe80:2100:a00f:4::4045" 16 | kv, err := ctx.Control.GetInfo( 17 | "ip-to-country/ipv4-available", 18 | "ip-to-country/ipv6-available", 19 | "ip-to-country/"+usIpv4, 20 | "ip-to-country/"+usIpv6, 21 | ) 22 | ctx.Require.NoError(err) 23 | vals := map[string]string{} 24 | for _, kv := range kv { 25 | vals[kv.Key] = kv.Val 26 | } 27 | ctx.Require.Len(vals, 4) 28 | ctx.Require.Equal("1", vals["ip-to-country/ipv4-available"]) 29 | ctx.Require.Equal("1", vals["ip-to-country/ipv6-available"]) 30 | ctx.Require.Equal("us", vals["ip-to-country/"+usIpv4]) 31 | ctx.Require.Equal("us", vals["ip-to-country/"+usIpv6]) 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Chad Retz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /control/cmd_circuit.go: -------------------------------------------------------------------------------- 1 | package control 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // ExtendCircuit invokes EXTENDCIRCUIT and returns the circuit ID on success. 8 | func (c *Conn) ExtendCircuit(circuitID string, path []string, purpose string) (string, error) { 9 | if circuitID == "" { 10 | circuitID = "0" 11 | } 12 | cmd := "EXTENDCIRCUIT " + circuitID 13 | if len(path) > 0 { 14 | cmd += " " + strings.Join(path, ",") 15 | } 16 | if purpose != "" { 17 | cmd += " purpose=" + purpose 18 | } 19 | resp, err := c.SendRequest(cmd) 20 | if err != nil { 21 | return "", err 22 | } 23 | return resp.Reply[strings.LastIndexByte(resp.Reply, ' ')+1:], nil 24 | } 25 | 26 | // SetCircuitPurpose invokes SETCIRCUITPURPOSE. 27 | func (c *Conn) SetCircuitPurpose(circuitID string, purpose string) error { 28 | return c.sendRequestIgnoreResponse("SETCIRCUITPURPOSE %v purpose=%v", circuitID, purpose) 29 | } 30 | 31 | // CloseCircuit invokes CLOSECIRCUIT. 32 | func (c *Conn) CloseCircuit(circuitID string, flags []string) error { 33 | cmd := "CLOSECIRCUIT " + circuitID 34 | for _, flag := range flags { 35 | cmd += " " + flag 36 | } 37 | return c.sendRequestIgnoreResponse(cmd) 38 | } 39 | -------------------------------------------------------------------------------- /tests/control_cmd_hiddenservice_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | 8 | "github.com/cretz/bine/control" 9 | ) 10 | 11 | const hsFetchOnion = "2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid" 12 | 13 | func TestHSFetch(t *testing.T) { 14 | ctx := GlobalEnabledNetworkContext(t) 15 | // Add listener 16 | eventCh := make(chan control.Event) 17 | defer close(eventCh) 18 | err := ctx.Control.AddEventListener(eventCh, control.EventCodeHSDescContent) 19 | ctx.Require.NoError(err) 20 | defer ctx.Control.RemoveEventListener(eventCh, control.EventCodeHSDescContent) 21 | // Lookup HS 22 | err = ctx.Control.GetHiddenServiceDescriptorAsync(hsFetchOnion, "") 23 | ctx.Require.NoError(err) 24 | // Grab events 25 | eventCtx, eventCancel := context.WithTimeout(ctx, 45*time.Second) 26 | defer eventCancel() 27 | errCh := make(chan error, 1) 28 | go func() { errCh <- ctx.Control.HandleEvents(eventCtx) }() 29 | select { 30 | case <-eventCtx.Done(): 31 | ctx.Require.NoError(eventCtx.Err()) 32 | case err := <-errCh: 33 | ctx.Require.NoError(err) 34 | case event := <-eventCh: 35 | hsEvent := event.(*control.HSDescContentEvent) 36 | ctx.Require.Equal(hsFetchOnion, hsEvent.Address) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/simpleserver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/cretz/bine/tor" 11 | ) 12 | 13 | func main() { 14 | if err := run(); err != nil { 15 | log.Fatal(err) 16 | } 17 | } 18 | 19 | func run() error { 20 | // Start tor with default config (can set start conf's DebugWriter to os.Stdout for debug logs) 21 | fmt.Println("Starting and registering onion service, please wait a couple of minutes...") 22 | t, err := tor.Start(nil, nil) 23 | if err != nil { 24 | return err 25 | } 26 | defer t.Close() 27 | // Add a handler 28 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 29 | w.Write([]byte("Hello, Dark World!")) 30 | }) 31 | // Wait at most a few minutes to publish the service 32 | listenCtx, listenCancel := context.WithTimeout(context.Background(), 3*time.Minute) 33 | defer listenCancel() 34 | // Create an onion service to listen on 8080 but show as 80 35 | onion, err := t.Listen(listenCtx, &tor.ListenConf{LocalPort: 8080, RemotePorts: []int{80}}) 36 | if err != nil { 37 | return err 38 | } 39 | defer onion.Close() 40 | // Serve on HTTP 41 | fmt.Printf("Open Tor browser and navigate to http://%v.onion\n", onion.ID) 42 | return http.Serve(onion, nil) 43 | } 44 | -------------------------------------------------------------------------------- /examples/grpc/pb/service.proto: -------------------------------------------------------------------------------- 1 | /* 2 | To regen, with `protoc` on the `PATH` and `protoc-gen-go` on the `PATH` (usually via `$GOPATH/bin`), from this dir run: 3 | 4 | protoc --go_out=plugins=grpc:. service.proto 5 | */ 6 | syntax = "proto3"; 7 | package pb; 8 | 9 | service SimpleService { 10 | rpc JoinStrings(JoinStringsRequest) returns (JoinStringsResponse); 11 | rpc ProvideStrings(ProvideStringsRequest) returns (stream ProvideStringsResponse); 12 | rpc ReceiveStrings(stream ReceiveStringsRequest) returns (ReceiveStringsResponse); 13 | rpc ExchangeStrings(stream ExchangeStringsRequest) returns (stream ExchangeStringsResponse); 14 | } 15 | 16 | message JoinStringsRequest { 17 | repeated string strings = 1; 18 | string delimiter = 2; 19 | } 20 | 21 | message JoinStringsResponse { 22 | string joined = 1; 23 | } 24 | 25 | message ProvideStringsRequest { 26 | uint32 count = 1; 27 | } 28 | 29 | message ProvideStringsResponse { 30 | string string = 1; 31 | } 32 | 33 | message ReceiveStringsRequest { 34 | string string = 1; 35 | } 36 | 37 | message ReceiveStringsResponse { 38 | repeated string received = 1; 39 | } 40 | 41 | message ExchangeStringsRequest { 42 | string string = 1; 43 | bool want_return = 2; 44 | } 45 | 46 | message ExchangeStringsResponse { 47 | repeated string received = 1; 48 | } -------------------------------------------------------------------------------- /control/keyval.go: -------------------------------------------------------------------------------- 1 | package control 2 | 3 | // KeyVal is a simple key-value struct. In cases where Val can be nil, an empty 4 | // string represents that unless ValSetAndEmpty is true. 5 | type KeyVal struct { 6 | // Key is the always-present key 7 | Key string 8 | 9 | // Val is the value. If it's an empty string and nils are accepted/supported 10 | // where this is used, it means nil unless ValSetAndEmpty is true. 11 | Val string 12 | 13 | // ValSetAndEmpty is true when Val is an empty string, the associated 14 | // command supports nils, and Val should NOT be treated as nil. False 15 | // otherwise. 16 | ValSetAndEmpty bool 17 | } 18 | 19 | // NewKeyVal creates a new key-value pair. 20 | func NewKeyVal(key string, val string) *KeyVal { 21 | return &KeyVal{Key: key, Val: val} 22 | } 23 | 24 | // KeyVals creates multiple new key-value pairs from the given strings. The 25 | // provided set of strings must have a length that is a multiple of 2. 26 | func KeyVals(keysAndVals ...string) []*KeyVal { 27 | if len(keysAndVals)%2 != 0 { 28 | panic("Expected multiple of 2") 29 | } 30 | ret := make([]*KeyVal, len(keysAndVals)/2) 31 | for i := 0; i < len(ret); i++ { 32 | ret[i] = NewKeyVal(keysAndVals[i*2], keysAndVals[i*2+1]) 33 | } 34 | return ret 35 | } 36 | 37 | // ValSet returns true if Val is either non empty or ValSetAndEmpty is true. 38 | func (k *KeyVal) ValSet() bool { 39 | return len(k.Val) > 0 || k.ValSetAndEmpty 40 | } 41 | -------------------------------------------------------------------------------- /tests/tor_dialer_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "io/ioutil" 7 | "net/http" 8 | "testing" 9 | "time" 10 | 11 | "github.com/cretz/bine/tor" 12 | "golang.org/x/net/context/ctxhttp" 13 | ) 14 | 15 | func TestDialerSimpleHTTP(t *testing.T) { 16 | ctx := GlobalEnabledNetworkContext(t) 17 | httpClient := httpClient(ctx, nil) 18 | // IsTor check 19 | byts := httpGet(ctx, httpClient, "https://check.torproject.org/api/ip") 20 | jsn := map[string]interface{}{} 21 | ctx.Require.NoError(json.Unmarshal(byts, &jsn)) 22 | ctx.Require.True(jsn["IsTor"].(bool)) 23 | } 24 | 25 | func httpClient(ctx *TestContext, conf *tor.DialConf) *http.Client { 26 | // 15 seconds max to dial 27 | dialCtx, dialCancel := context.WithTimeout(ctx, 15*time.Second) 28 | defer dialCancel() 29 | // Make connection 30 | dialer, err := ctx.Dialer(dialCtx, conf) 31 | ctx.Require.NoError(err) 32 | return &http.Client{Transport: &http.Transport{DialContext: dialer.DialContext}} 33 | } 34 | 35 | func httpGet(ctx *TestContext, client *http.Client, url string) []byte { 36 | // We'll give it 30 seconds to respond 37 | callCtx, callCancel := context.WithTimeout(ctx, 30*time.Second) 38 | defer callCancel() 39 | resp, err := ctxhttp.Get(callCtx, client, url) 40 | ctx.Require.NoError(err) 41 | defer resp.Body.Close() 42 | respBytes, err := ioutil.ReadAll(resp.Body) 43 | ctx.Require.NoError(err) 44 | return respBytes 45 | } 46 | -------------------------------------------------------------------------------- /process/embedded/process.go: -------------------------------------------------------------------------------- 1 | // Package embedded implements process interfaces for statically linked, 2 | // embedded Tor. Note, processes created here are not killed when a context is 3 | // done like w/ os.Exec. 4 | // 5 | // Usage 6 | // 7 | // This package can be used with CGO to statically compile Tor. This package 8 | // expects https://github.com/cretz/tor-static to be cloned at 9 | // $GOPATH/src/github.com/cretz/tor-static as if it was fetched with go get. 10 | // If you use go modules the expected path would be $GOPATH/pkg/mod/github.com/cretz/tor-static 11 | // To build the needed static libs, follow the README in that project. Once the 12 | // static libs are built, this uses CGO to statically link them here. For 13 | // Windows this means something like http://www.msys2.org/ needs to be 14 | // installed with gcc.exe on the PATH (i.e. the same gcc that was used to build 15 | // the static Tor lib). 16 | // 17 | // The default in here is currently for Tor 0.3.5.x which uses the tor-0.3.5 18 | // subdirectory. A different subdirectory can be used for a different version. 19 | // Note that the current version does support 20 | // process.Process.EmbeddedControlConn() on non-Windows. 21 | package embedded 22 | 23 | import ( 24 | "github.com/cretz/bine/process" 25 | 26 | tor035 "github.com/cretz/bine/process/embedded/tor-0.3.5" 27 | ) 28 | 29 | // NewCreator creates a process.Creator for statically-linked Tor embedded in 30 | // the binary. 31 | func NewCreator() process.Creator { 32 | return tor035.NewCreator() 33 | } 34 | -------------------------------------------------------------------------------- /tests/tor_forward_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | "time" 10 | 11 | "github.com/cretz/bine/tor" 12 | "github.com/cretz/bine/torutil" 13 | ) 14 | 15 | func testHandler(w http.ResponseWriter, r *http.Request) { 16 | _, err := w.Write([]byte("forward response")) 17 | if err != nil { 18 | panic(err) 19 | } 20 | } 21 | 22 | func TestForwardSimpleHTTP(t *testing.T) { 23 | remotePorts := []int{80, 8080} 24 | 25 | // Create a test server listening on a random port 26 | server := httptest.NewServer(http.HandlerFunc(testHandler)) 27 | t.Cleanup(server.Close) 28 | 29 | ctx := GlobalEnabledNetworkContext(t) 30 | 31 | // Forward as an onion service on test ports 32 | conf := &tor.ForwardConf{ 33 | PortForwards: map[string][]int{ 34 | server.Listener.Addr().String(): remotePorts, 35 | }, 36 | } 37 | httpClient := httpClient(ctx, nil) 38 | 39 | forwardCtx, forwardCancel := context.WithTimeout(context.Background(), 4*time.Minute) 40 | defer forwardCancel() 41 | // Create an onion service to listen on random port but show as 80 42 | onion, err := ctx.Forward(forwardCtx, conf) 43 | ctx.Require.NoError(err) 44 | 45 | // Check the service ID 46 | ctx.Require.Equal(torutil.OnionServiceIDFromPrivateKey(onion.Key), onion.ID) 47 | for _, remotePort := range remotePorts { 48 | // Request onion endpoint 49 | contents := httpGet(ctx, httpClient, fmt.Sprintf("http://"+onion.ID+".onion:%d/test", remotePort)) 50 | ctx.Require.Equal("forward response", string(contents)) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/control_cmd_authenticate_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cretz/bine/tor" 7 | ) 8 | 9 | func TestAuthenticateNull(t *testing.T) { 10 | ctx := NewTestContext(t, &tor.StartConf{DisableCookieAuth: true, DisableEagerAuth: true}) 11 | defer ctx.Close() 12 | // Verify auth methods before auth 13 | info, err := ctx.Control.ProtocolInfo() 14 | ctx.Require.NoError(err) 15 | ctx.Require.ElementsMatch([]string{"NULL"}, info.AuthMethods) 16 | ctx.Require.NoError(ctx.Control.Authenticate("")) 17 | } 18 | 19 | func TestAuthenticateSafeCookie(t *testing.T) { 20 | ctx := NewTestContext(t, &tor.StartConf{DisableEagerAuth: true}) 21 | defer ctx.Close() 22 | // Verify auth methods before auth 23 | info, err := ctx.Control.ProtocolInfo() 24 | ctx.Require.NoError(err) 25 | ctx.Require.ElementsMatch([]string{"COOKIE", "SAFECOOKIE"}, info.AuthMethods) 26 | ctx.Require.NoError(ctx.Control.Authenticate("")) 27 | } 28 | 29 | func TestAuthenticateHashedPassword(t *testing.T) { 30 | // "testpass" - 16:5417AE717521511A609921392778FFA8518EC089BF2162A199241AEB4A 31 | ctx := NewTestContext(t, &tor.StartConf{ 32 | DisableCookieAuth: true, 33 | DisableEagerAuth: true, 34 | ExtraArgs: []string{"--HashedControlPassword", "16:5417AE717521511A609921392778FFA8518EC089BF2162A199241AEB4A"}, 35 | }) 36 | defer ctx.Close() 37 | // Verify auth methods before auth 38 | info, err := ctx.Control.ProtocolInfo() 39 | ctx.Require.NoError(err) 40 | ctx.Require.ElementsMatch([]string{"HASHEDPASSWORD"}, info.AuthMethods) 41 | ctx.Require.NoError(ctx.Control.Authenticate("testpass")) 42 | } 43 | -------------------------------------------------------------------------------- /examples/simpleclient/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "strings" 10 | "time" 11 | 12 | "github.com/cretz/bine/tor" 13 | "golang.org/x/net/html" 14 | ) 15 | 16 | func main() { 17 | if err := run(); err != nil { 18 | log.Fatal(err) 19 | } 20 | } 21 | 22 | func run() error { 23 | // Start tor with default config (can set start conf's DebugWriter to os.Stdout for debug logs) 24 | fmt.Println("Starting tor and fetching title of https://check.torproject.org, please wait a few seconds...") 25 | t, err := tor.Start(nil, nil) 26 | if err != nil { 27 | return err 28 | } 29 | defer t.Close() 30 | // Wait at most a minute to start network and get 31 | dialCtx, dialCancel := context.WithTimeout(context.Background(), time.Minute) 32 | defer dialCancel() 33 | // Make connection 34 | dialer, err := t.Dialer(dialCtx, nil) 35 | if err != nil { 36 | return err 37 | } 38 | httpClient := &http.Client{Transport: &http.Transport{DialContext: dialer.DialContext}} 39 | // Get / 40 | resp, err := httpClient.Get("https://check.torproject.org") 41 | if err != nil { 42 | return err 43 | } 44 | defer resp.Body.Close() 45 | // Grab the 46 | parsed, err := html.Parse(resp.Body) 47 | if err != nil { 48 | return err 49 | } 50 | fmt.Printf("Title: %v\n", getTitle(parsed)) 51 | return nil 52 | } 53 | 54 | func getTitle(n *html.Node) string { 55 | if n.Type == html.ElementNode && n.Data == "title" { 56 | var title bytes.Buffer 57 | if err := html.Render(&title, n.FirstChild); err != nil { 58 | panic(err) 59 | } 60 | return strings.TrimSpace(title.String()) 61 | } 62 | for c := n.FirstChild; c != nil; c = c.NextSibling { 63 | if title := getTitle(c); title != "" { 64 | return title 65 | } 66 | } 67 | return "" 68 | } 69 | -------------------------------------------------------------------------------- /control/cmd_conf.go: -------------------------------------------------------------------------------- 1 | package control 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/cretz/bine/torutil" 7 | ) 8 | 9 | // SetConf invokes SETCONF. 10 | func (c *Conn) SetConf(entries ...*KeyVal) error { 11 | return c.sendSetConf("SETCONF", entries) 12 | } 13 | 14 | // ResetConf invokes RESETCONF. 15 | func (c *Conn) ResetConf(entries ...*KeyVal) error { 16 | return c.sendSetConf("RESETCONF", entries) 17 | } 18 | 19 | func (c *Conn) sendSetConf(cmd string, entries []*KeyVal) error { 20 | for _, entry := range entries { 21 | cmd += " " + entry.Key 22 | if entry.ValSet() { 23 | cmd += "=" + torutil.EscapeSimpleQuotedStringIfNeeded(entry.Val) 24 | } 25 | } 26 | return c.sendRequestIgnoreResponse(cmd) 27 | } 28 | 29 | // GetConf invokes GETCONF and returns the values for the requested keys. 30 | func (c *Conn) GetConf(keys ...string) ([]*KeyVal, error) { 31 | resp, err := c.SendRequest("GETCONF %v", strings.Join(keys, " ")) 32 | if err != nil { 33 | return nil, err 34 | } 35 | data := resp.DataWithReply() 36 | ret := make([]*KeyVal, 0, len(data)) 37 | for _, data := range data { 38 | key, val, ok := torutil.PartitionString(data, '=') 39 | entry := &KeyVal{Key: key} 40 | if ok { 41 | if entry.Val, err = torutil.UnescapeSimpleQuotedStringIfNeeded(val); err != nil { 42 | return nil, err 43 | } 44 | if len(entry.Val) == 0 { 45 | entry.ValSetAndEmpty = true 46 | } 47 | } 48 | ret = append(ret, entry) 49 | } 50 | return ret, nil 51 | } 52 | 53 | // SaveConf invokes SAVECONF. 54 | func (c *Conn) SaveConf(force bool) error { 55 | cmd := "SAVECONF" 56 | if force { 57 | cmd += " FORCE" 58 | } 59 | return c.sendRequestIgnoreResponse(cmd) 60 | } 61 | 62 | // LoadConf invokes LOADCONF. 63 | func (c *Conn) LoadConf(conf string) error { 64 | return c.sendRequestIgnoreResponse("+LOADCONF\r\n%v\r\n.", conf) 65 | } 66 | -------------------------------------------------------------------------------- /tests/tor_listen_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "testing" 7 | "time" 8 | 9 | "github.com/cretz/bine/tor" 10 | "github.com/cretz/bine/torutil" 11 | ) 12 | 13 | func TestListenSimpleHTTPV3(t *testing.T) { 14 | ctx := GlobalEnabledNetworkContext(t) 15 | // Create an onion service to listen on random port but show as 80 16 | conf := &tor.ListenConf{RemotePorts: []int{80}} 17 | // _, conf.Key, _ = ed25519.GenerateKey(nil) 18 | client, server, onion := startHTTPServer(ctx, conf, "/test", func(w http.ResponseWriter, r *http.Request) { 19 | _, err := w.Write([]byte("Test Content")) 20 | ctx.Require.NoError(err) 21 | }) 22 | defer server.Shutdown(ctx) 23 | // Check the service ID 24 | ctx.Require.Equal(torutil.OnionServiceIDFromPrivateKey(onion.Key), onion.ID) 25 | // Call /test 26 | byts := httpGet(ctx, client, "http://"+onion.ID+".onion/test") 27 | ctx.Require.Equal("Test Content", string(byts)) 28 | } 29 | 30 | // Only have to shutdown the HTTP server 31 | func startHTTPServer( 32 | ctx *TestContext, 33 | listenConf *tor.ListenConf, 34 | handlePattern string, 35 | handler func(http.ResponseWriter, *http.Request), 36 | ) (*http.Client, *http.Server, *tor.OnionService) { 37 | httpClient := httpClient(ctx, nil) 38 | // Wait at most a few minutes for the entire test 39 | listenCtx, listenCancel := context.WithTimeout(context.Background(), 4*time.Minute) 40 | defer listenCancel() 41 | // Create an onion service to listen on random port but show as 80 42 | onion, err := ctx.Listen(listenCtx, listenConf) 43 | ctx.Require.NoError(err) 44 | // Make HTTP server 45 | mux := http.NewServeMux() 46 | mux.HandleFunc(handlePattern, handler) 47 | httpServer := &http.Server{Handler: mux} 48 | go func() { ctx.Require.Equal(http.ErrServerClosed, httpServer.Serve(onion)) }() 49 | return httpClient, httpServer, onion 50 | } 51 | -------------------------------------------------------------------------------- /examples/httpaltsvc/README.md: -------------------------------------------------------------------------------- 1 | ## HTTP Alt-Svc Example 2 | 3 | The Tor browser now supports `Alt-Svc` headers to be onion services as 4 | [HTTP alternate services](https://tools.ietf.org/html/rfc7838) to traditional sites. 5 | [This Cloudflare post](https://blog.cloudflare.com/cloudflare-onion-service/) explains how they use it. This example 6 | shows how to do it yourself. Since 7 | 8 | Specifically, this example listens on all IPs on 80 for insecure HTTP requests. It also listens on an onion service for 9 | insecure HTTP requests. Both services return `Alt-Svc` addresses to an onion service that is run securely. 10 | 11 | **NOTE: Only do the steps in this example if you are comfortable with and aware of the consequences.** 12 | 13 | ### Setup 14 | 15 | We're going to self-sign a certificate for use in the Tor browser. First, download `mkcert` and run: 16 | 17 | mkcert -install 18 | 19 | This will install a fake CA on your local machine that certs can be generated from. Remember the path it says the local 20 | CA is at or run `mkcert -CAROOT` to get it back. We will use this "CA path" later. 21 | 22 | Now the CA must be added to the Tor browser. By default the Tor browser doesn't use the cert database so it cannot store 23 | the overrides. To change this, go to `about:config` click through warning and change `security.nocertdb` to `false`. Now 24 | that CAs can be added, go to `Options` (i.e. `about:preferences`) > `Privacy & Security` > `Certificates` section at the 25 | bottom > `View Certificates...` > `Authorities` tab > `Import...` > choose `rootCA.pem` from the "CA path" from 26 | earlier > check "Trust this CA to identify websites" and click `OK`. Restart the Tor browser. 27 | 28 | ### Running 29 | 30 | Point yor DNS to this machine's IP (or use start `ngrok http 80` via [ngrok.com](https://ngrok.com/)) 31 | 32 | TODO: the rest... 33 | 34 | TODO: alt-svc onions appear broken in TBB -------------------------------------------------------------------------------- /process/embedded/tor-0.3.5/embeddedtest/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net/textproto" 8 | "os" 9 | 10 | "github.com/cretz/bine/control" 11 | tor035 "github.com/cretz/bine/process/embedded/tor-0.3.5" 12 | ) 13 | 14 | // Simply calls Tor will the same parameters, unless "embedconn" is the arg 15 | func main() { 16 | fmt.Printf("Provider version: %v\n", tor035.ProviderVersion()) 17 | var err error 18 | if len(os.Args) == 2 && os.Args[1] == "embedconn" { 19 | fmt.Println("Testing embedded conn") 20 | err = testEmbedConn() 21 | } else { 22 | fmt.Println("Running Tor with given args") 23 | err = runTor(os.Args[1:]...) 24 | } 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | } 29 | 30 | func runTor(args ...string) error { 31 | process, err := tor035.NewCreator().New(context.Background(), args...) 32 | if err == nil { 33 | process.Start() 34 | err = process.Wait() 35 | } 36 | return err 37 | } 38 | 39 | func testEmbedConn() error { 40 | process, err := tor035.NewCreator().New(context.Background(), "--DisableNetwork", "1") 41 | if err != nil { 42 | return fmt.Errorf("Failed creating process: %v", err) 43 | } 44 | // Try to create an embedded conn 45 | embedConn, err := process.EmbeddedControlConn() 46 | if err != nil { 47 | return fmt.Errorf("Failed creating embedded control conn: %v", err) 48 | } 49 | if err = process.Start(); err != nil { 50 | return fmt.Errorf("Failed starting process: %v", err) 51 | } 52 | controlConn := control.NewConn(textproto.NewConn(embedConn)) 53 | info, err := controlConn.GetInfo("version") 54 | controlConn.Close() 55 | if err != nil { 56 | return fmt.Errorf("Failed getting version: %v", err) 57 | } 58 | fmt.Printf("Got info, %v: %v\n", info[0].Key, info[0].Val) 59 | if err = process.Wait(); err != nil { 60 | return fmt.Errorf("Failed waiting for process: %v", err) 61 | } 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /process/embedded/tor-0.4.7/embeddedtest/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net/textproto" 8 | "os" 9 | 10 | "github.com/cretz/bine/control" 11 | tor047 "github.com/cretz/bine/process/embedded/tor-0.4.7" 12 | ) 13 | 14 | // Simply calls Tor will the same parameters, unless "embedconn" is the arg 15 | func main() { 16 | fmt.Printf("Provider version: %v\n", tor047.ProviderVersion()) 17 | var err error 18 | if len(os.Args) == 2 && os.Args[1] == "embedconn" { 19 | fmt.Println("Testing embedded conn") 20 | err = testEmbedConn() 21 | } else { 22 | fmt.Println("Running Tor with given args") 23 | err = runTor(os.Args[1:]...) 24 | } 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | } 29 | 30 | func runTor(args ...string) error { 31 | process, err := tor047.NewCreator().New(context.Background(), args...) 32 | if err == nil { 33 | process.Start() 34 | err = process.Wait() 35 | } 36 | return err 37 | } 38 | 39 | func testEmbedConn() error { 40 | process, err := tor047.NewCreator().New(context.Background(), "--DisableNetwork", "1") 41 | if err != nil { 42 | return fmt.Errorf("Failed creating process: %v", err) 43 | } 44 | // Try to create an embedded conn 45 | embedConn, err := process.EmbeddedControlConn() 46 | if err != nil { 47 | return fmt.Errorf("Failed creating embedded control conn: %v", err) 48 | } 49 | if err = process.Start(); err != nil { 50 | return fmt.Errorf("Failed starting process: %v", err) 51 | } 52 | controlConn := control.NewConn(textproto.NewConn(embedConn)) 53 | info, err := controlConn.GetInfo("version") 54 | controlConn.Close() 55 | if err != nil { 56 | return fmt.Errorf("Failed getting version: %v", err) 57 | } 58 | fmt.Printf("Got info, %v: %v\n", info[0].Key, info[0].Val) 59 | if err = process.Wait(); err != nil { 60 | return fmt.Errorf("Failed waiting for process: %v", err) 61 | } 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /control/cmd_protocolinfo.go: -------------------------------------------------------------------------------- 1 | package control 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/cretz/bine/torutil" 7 | ) 8 | 9 | // ProtocolInfo is the protocol info result of Conn.ProtocolInfo. 10 | type ProtocolInfo struct { 11 | AuthMethods []string 12 | CookieFile string 13 | TorVersion string 14 | RawResponse *Response 15 | } 16 | 17 | // HasAuthMethod checks if ProtocolInfo contains the requested auth method. 18 | func (p *ProtocolInfo) HasAuthMethod(authMethod string) bool { 19 | for _, m := range p.AuthMethods { 20 | if m == authMethod { 21 | return true 22 | } 23 | } 24 | return false 25 | } 26 | 27 | // ProtocolInfo invokes PROTOCOLINFO on first invocation and returns a cached 28 | // result on all others. 29 | func (c *Conn) ProtocolInfo() (*ProtocolInfo, error) { 30 | var err error 31 | if c.protocolInfo == nil { 32 | c.protocolInfo, err = c.sendProtocolInfo() 33 | } 34 | return c.protocolInfo, err 35 | } 36 | 37 | func (c *Conn) sendProtocolInfo() (*ProtocolInfo, error) { 38 | resp, err := c.SendRequest("PROTOCOLINFO") 39 | if err != nil { 40 | return nil, err 41 | } 42 | // Check data vals 43 | ret := &ProtocolInfo{RawResponse: resp} 44 | for _, piece := range resp.Data { 45 | key, val, ok := torutil.PartitionString(piece, ' ') 46 | if !ok { 47 | continue 48 | } 49 | switch key { 50 | case "PROTOCOLINFO": 51 | if val != "1" { 52 | return nil, c.protoErr("Invalid PIVERSION: %v", val) 53 | } 54 | case "AUTH": 55 | methods, cookieFile, _ := torutil.PartitionString(val, ' ') 56 | if !strings.HasPrefix(methods, "METHODS=") { 57 | continue 58 | } 59 | if cookieFile != "" { 60 | if !strings.HasPrefix(cookieFile, "COOKIEFILE=") { 61 | continue 62 | } 63 | if ret.CookieFile, err = torutil.UnescapeSimpleQuotedString(cookieFile[11:]); err != nil { 64 | continue 65 | } 66 | } 67 | ret.AuthMethods = strings.Split(methods[8:], ",") 68 | case "VERSION": 69 | torVersion, _, _ := torutil.PartitionString(val, ' ') 70 | if strings.HasPrefix(torVersion, "Tor=") { 71 | ret.TorVersion, err = torutil.UnescapeSimpleQuotedString(torVersion[4:]) 72 | } 73 | } 74 | } 75 | return ret, nil 76 | } 77 | -------------------------------------------------------------------------------- /examples/embeddedfileserver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "os" 10 | "path/filepath" 11 | "time" 12 | 13 | "github.com/cretz/bine/process/embedded" 14 | "github.com/cretz/bine/tor" 15 | ) 16 | 17 | func main() { 18 | if err := run(); err != nil { 19 | log.Fatal(err) 20 | } 21 | } 22 | 23 | func run() error { 24 | // Parse flags. By default, non-verbose served in the current working dir. 25 | var verbose bool 26 | flag.BoolVar(&verbose, "verbose", false, "Whether to have verbose logging") 27 | var directory string 28 | flag.StringVar(&directory, "dir", ".", "The directory to serve (current dir is default)") 29 | flag.Parse() 30 | var err error 31 | if directory, err = filepath.Abs(directory); err != nil { 32 | return err 33 | } 34 | // Start tor 35 | startConf := &tor.StartConf{ProcessCreator: embedded.NewCreator()} 36 | if verbose { 37 | startConf.DebugWriter = os.Stdout 38 | } else { 39 | startConf.ExtraArgs = []string{"--quiet"} 40 | } 41 | fmt.Printf("Starting and registering onion service to serve files from %v\n", directory) 42 | fmt.Println("Please wait a couple of minutes...") 43 | t, err := tor.Start(nil, startConf) 44 | if err != nil { 45 | return err 46 | } 47 | defer t.Close() 48 | // Wait at most a few minutes to publish the service 49 | listenCtx, listenCancel := context.WithTimeout(context.Background(), 3*time.Minute) 50 | defer listenCancel() 51 | // Create an onion service to listen on a random local port but show as 52 | // Do version 3, it's faster to set up 53 | onion, err := t.Listen(listenCtx, &tor.ListenConf{RemotePorts: []int{80}}) 54 | if err != nil { 55 | return err 56 | } 57 | defer onion.Close() 58 | // Start server asynchronously 59 | fmt.Printf("Open Tor browser and navigate to http://%v.onion\n", onion.ID) 60 | fmt.Println("Press enter to exit") 61 | server := &http.Server{Handler: http.FileServer(http.Dir(directory))} 62 | defer server.Shutdown(context.Background()) 63 | errCh := make(chan error, 1) 64 | go func() { errCh <- server.Serve(onion) }() 65 | // Wait for key asynchronously 66 | go func() { 67 | fmt.Scanln() 68 | errCh <- nil 69 | }() 70 | // Stop when one happens 71 | defer fmt.Println("Closing") 72 | return <-errCh 73 | } 74 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 6 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 7 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 8 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= 9 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= 10 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 11 | golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo= 12 | golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 13 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 14 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= 15 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 16 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 17 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 18 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 19 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 20 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 21 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 22 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 23 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 24 | -------------------------------------------------------------------------------- /control/status.go: -------------------------------------------------------------------------------- 1 | package control 2 | 3 | import ( 4 | "fmt" 5 | "net/textproto" 6 | "strings" 7 | ) 8 | 9 | // The various control port StatusCode constants. 10 | const ( 11 | StatusOk = 250 12 | StatusOkUnnecessary = 251 13 | 14 | StatusErrResourceExhausted = 451 15 | StatusErrSyntaxError = 500 16 | StatusErrUnrecognizedCmd = 510 17 | StatusErrUnimplementedCmd = 511 18 | StatusErrSyntaxErrorArg = 512 19 | StatusErrUnrecognizedCmdArg = 513 20 | StatusErrAuthenticationRequired = 514 21 | StatusErrBadAuthentication = 515 22 | StatusErrUnspecifiedTorError = 550 23 | StatusErrInternalError = 551 24 | StatusErrUnrecognizedEntity = 552 25 | StatusErrInvalidConfigValue = 553 26 | StatusErrInvalidDescriptor = 554 27 | StatusErrUnmanagedEntity = 555 28 | 29 | StatusAsyncEvent = 650 30 | ) 31 | 32 | var statusCodeStringMap = map[int]string{ 33 | StatusOk: "OK", 34 | StatusOkUnnecessary: "Operation was unnecessary", 35 | 36 | StatusErrResourceExhausted: "Resource exhausted", 37 | StatusErrSyntaxError: "Syntax error: protocol", 38 | StatusErrUnrecognizedCmd: "Unrecognized command", 39 | StatusErrUnimplementedCmd: "Unimplemented command", 40 | StatusErrSyntaxErrorArg: "Syntax error in command argument", 41 | StatusErrUnrecognizedCmdArg: "Unrecognized command argument", 42 | StatusErrAuthenticationRequired: "Authentication required", 43 | StatusErrBadAuthentication: "Bad authentication", 44 | StatusErrUnspecifiedTorError: "Unspecified Tor error", 45 | StatusErrInternalError: "Internal error", 46 | StatusErrUnrecognizedEntity: "Unrecognized entity", 47 | StatusErrInvalidConfigValue: "Invalid configuration value", 48 | StatusErrInvalidDescriptor: "Invalid descriptor", 49 | StatusErrUnmanagedEntity: "Unmanaged entity", 50 | 51 | StatusAsyncEvent: "Asynchronous event notification", 52 | } 53 | 54 | func statusCodeToError(code int, reply string) *textproto.Error { 55 | err := new(textproto.Error) 56 | err.Code = code 57 | if msg, ok := statusCodeStringMap[code]; ok { 58 | trimmedReply := strings.TrimSpace(strings.TrimPrefix(reply, msg)) 59 | err.Msg = fmt.Sprintf("%s: %s", msg, trimmedReply) 60 | } else { 61 | err.Msg = fmt.Sprintf("Unknown status code (%03d): %s", code, reply) 62 | } 63 | return err 64 | } 65 | -------------------------------------------------------------------------------- /tests/control_cmd_conf_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "io/ioutil" 5 | "testing" 6 | 7 | "github.com/cretz/bine/control" 8 | ) 9 | 10 | func TestGetSetAndResetConf(t *testing.T) { 11 | ctx := NewTestContext(t, nil) 12 | defer ctx.Close() 13 | // Simple get conf 14 | assertConfVals := func(val string) { 15 | entries, err := ctx.Control.GetConf("LogMessageDomains", "ProtocolWarnings") 16 | ctx.Require.NoError(err) 17 | ctx.Require.Len(entries, 2) 18 | ctx.Require.Contains(entries, control.NewKeyVal("LogMessageDomains", val)) 19 | ctx.Require.Contains(entries, control.NewKeyVal("ProtocolWarnings", val)) 20 | } 21 | assertConfVals("0") 22 | // Change em both to 1 23 | one := "1" 24 | err := ctx.Control.SetConf(control.KeyVals("LogMessageDomains", "1", "ProtocolWarnings", "1")...) 25 | ctx.Require.NoError(err) 26 | // Check again 27 | assertConfVals(one) 28 | // Reset em both 29 | err = ctx.Control.ResetConf(control.KeyVals("LogMessageDomains", "", "ProtocolWarnings", "")...) 30 | ctx.Require.NoError(err) 31 | // Make sure both back to zero 32 | assertConfVals("0") 33 | } 34 | 35 | func TestLoadConf(t *testing.T) { 36 | ctx := NewTestContext(t, nil) 37 | defer ctx.Close() 38 | // Get entire conf text 39 | vals, err := ctx.Control.GetInfo("config-text") 40 | ctx.Require.NoError(err) 41 | ctx.Require.Len(vals, 1) 42 | ctx.Require.Equal("config-text", vals[0].Key) 43 | confText := vals[0].Val 44 | // Append new conf val and load 45 | ctx.Require.NotContains(confText, "LogMessageDomains") 46 | confText += "\r\nLogMessageDomains 1" 47 | ctx.Require.NoError(ctx.Control.LoadConf(confText)) 48 | // Check the new val 49 | vals, err = ctx.Control.GetInfo("config-text") 50 | ctx.Require.NoError(err) 51 | ctx.Require.Len(vals, 1) 52 | ctx.Require.Equal("config-text", vals[0].Key) 53 | ctx.Require.Contains(vals[0].Val, "LogMessageDomains 1") 54 | } 55 | 56 | func TestSaveConf(t *testing.T) { 57 | ctx := NewTestContext(t, nil) 58 | defer ctx.Close() 59 | // Get conf filename 60 | vals, err := ctx.Control.GetInfo("config-file") 61 | ctx.Require.NoError(err) 62 | ctx.Require.Len(vals, 1) 63 | ctx.Require.Equal("config-file", vals[0].Key) 64 | confFile := vals[0].Val 65 | // Save it 66 | ctx.Require.NoError(ctx.Control.SaveConf(false)) 67 | // Read and make sure, say, the DataDirectory is accurate 68 | confText, err := ioutil.ReadFile(confFile) 69 | ctx.Require.NoError(err) 70 | ctx.Require.Contains(string(confText), "DataDirectory "+ctx.DataDir) 71 | } 72 | -------------------------------------------------------------------------------- /control/cmd_misc.go: -------------------------------------------------------------------------------- 1 | package control 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/cretz/bine/torutil" 7 | ) 8 | 9 | // Signal invokes SIGNAL. 10 | func (c *Conn) Signal(signal string) error { 11 | return c.sendRequestIgnoreResponse("SIGNAL %v", signal) 12 | } 13 | 14 | // Quit invokes QUIT. 15 | func (c *Conn) Quit() error { 16 | return c.sendRequestIgnoreResponse("QUIT") 17 | } 18 | 19 | // MapAddresses invokes MAPADDRESS and returns mapped addresses. 20 | func (c *Conn) MapAddresses(addresses ...*KeyVal) ([]*KeyVal, error) { 21 | cmd := "MAPADDRESS" 22 | for _, address := range addresses { 23 | cmd += " " + address.Key + "=" + address.Val 24 | } 25 | resp, err := c.SendRequest(cmd) 26 | if err != nil { 27 | return nil, err 28 | } 29 | data := resp.DataWithReply() 30 | ret := make([]*KeyVal, 0, len(data)) 31 | for _, address := range data { 32 | mappedAddress := &KeyVal{} 33 | mappedAddress.Key, mappedAddress.Val, _ = torutil.PartitionString(address, '=') 34 | ret = append(ret, mappedAddress) 35 | } 36 | return ret, nil 37 | } 38 | 39 | // GetInfo invokes GETINTO and returns values for requested keys. 40 | func (c *Conn) GetInfo(keys ...string) ([]*KeyVal, error) { 41 | resp, err := c.SendRequest("GETINFO %v", strings.Join(keys, " ")) 42 | if err != nil { 43 | return nil, err 44 | } 45 | ret := make([]*KeyVal, 0, len(resp.Data)) 46 | for _, val := range resp.Data { 47 | infoVal := &KeyVal{} 48 | infoVal.Key, infoVal.Val, _ = torutil.PartitionString(val, '=') 49 | if infoVal.Val, err = torutil.UnescapeSimpleQuotedStringIfNeeded(infoVal.Val); err != nil { 50 | return nil, err 51 | } 52 | ret = append(ret, infoVal) 53 | } 54 | return ret, nil 55 | } 56 | 57 | // PostDescriptor invokes POSTDESCRIPTOR. 58 | func (c *Conn) PostDescriptor(descriptor string, purpose string, cache string) error { 59 | cmd := "+POSTDESCRIPTOR" 60 | if purpose != "" { 61 | cmd += " purpose=" + purpose 62 | } 63 | if cache != "" { 64 | cmd += " cache=" + cache 65 | } 66 | cmd += "\r\n" + descriptor + "\r\n." 67 | return c.sendRequestIgnoreResponse(cmd) 68 | } 69 | 70 | // UseFeatures invokes USEFEATURE. 71 | func (c *Conn) UseFeatures(features ...string) error { 72 | return c.sendRequestIgnoreResponse("USEFEATURE " + strings.Join(features, " ")) 73 | } 74 | 75 | // ResolveAsync invokes RESOLVE. 76 | func (c *Conn) ResolveAsync(address string, reverse bool) error { 77 | cmd := "RESOLVE " 78 | if reverse { 79 | cmd += "mode=reverse " 80 | } 81 | return c.sendRequestIgnoreResponse(cmd + address) 82 | } 83 | 84 | // TakeOwnership invokes TAKEOWNERSHIP. 85 | func (c *Conn) TakeOwnership() error { 86 | return c.sendRequestIgnoreResponse("TAKEOWNERSHIP") 87 | } 88 | 89 | // DropGuards invokes DROPGUARDS. 90 | func (c *Conn) DropGuards() error { 91 | return c.sendRequestIgnoreResponse("DROPGUARDS") 92 | } 93 | -------------------------------------------------------------------------------- /tests/tor_isolate_socks_auth_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | "strings" 7 | "testing" 8 | "time" 9 | 10 | "github.com/cretz/bine/tor" 11 | "golang.org/x/net/proxy" 12 | ) 13 | 14 | // TestIsolateSocksAuth simply confirms the functionality of IsolateSOCKSAuth, 15 | // namely that it uses a different circuit for different SOCKS credentials. 16 | func TestIsolateSocksAuth(t *testing.T) { 17 | // Create context w/ no isolate 18 | ctx := NewTestContext(t, &tor.StartConf{ 19 | NoAutoSocksPort: true, 20 | ExtraArgs: []string{"--SocksPort", "auto NoIsolateSOCKSAuth"}, 21 | }) 22 | // Make sure it reused the circuit (i.e. only has one) for both separate-auth calls 23 | uniqueCircuitIDs := doSeparateAuthHttpCalls(ctx) 24 | ctx.Debugf("Unique IDs without isolate: %v", uniqueCircuitIDs) 25 | ctx.Require.Len(uniqueCircuitIDs, 1) 26 | // Create context w/ isolate 27 | ctx = NewTestContext(t, nil) 28 | // Make sure it made a new circuit (i.e. has two) for each separate-auth call 29 | uniqueCircuitIDs = doSeparateAuthHttpCalls(ctx) 30 | ctx.Debugf("Unique IDs with isolate: %v", uniqueCircuitIDs) 31 | ctx.Require.Len(uniqueCircuitIDs, 2) 32 | } 33 | 34 | // Returns the map keyed with unique circuit IDs after second separate-auth HTTP call 35 | func doSeparateAuthHttpCalls(ctx *TestContext) map[int]struct{} { 36 | defer ctx.Close() 37 | enableCtx, enableCancel := context.WithTimeout(ctx, 100*time.Second) 38 | defer enableCancel() 39 | ctx.Require.NoError(ctx.EnableNetwork(enableCtx, true)) 40 | // Make HTTP call w/ an auth 41 | client := httpClient(ctx, &tor.DialConf{ProxyAuth: &proxy.Auth{"foo", "bar"}}) 42 | byts := httpGet(ctx, client, "https://check.torproject.org/api/ip") 43 | ctx.Debugf("Read bytes: %v", string(byts)) 44 | // Confirm just size 1 45 | ids := uniqueStreamCircuitIDs(ctx) 46 | ctx.Require.Len(ids, 1) 47 | // Now make call with another auth and just return circuit IDs 48 | client = httpClient(ctx, &tor.DialConf{ProxyAuth: &proxy.Auth{"baz", "qux"}}) 49 | byts = httpGet(ctx, client, "https://check.torproject.org/api/ip") 50 | ctx.Debugf("Read bytes: %v", string(byts)) 51 | return uniqueStreamCircuitIDs(ctx) 52 | } 53 | 54 | // Return each stream circuit as a key of an empty-val map 55 | func uniqueStreamCircuitIDs(ctx *TestContext) map[int]struct{} { 56 | ret := map[int]struct{}{} 57 | vals, err := ctx.Control.GetInfo("stream-status") 58 | ctx.Require.NoError(err) 59 | for _, val := range vals { 60 | ctx.Require.Equal("stream-status", val.Key) 61 | for _, line := range strings.Split(val.Val, "\n") { 62 | pieces := strings.Split(strings.TrimSpace(line), " ") 63 | if len(pieces) < 3 { 64 | continue 65 | } 66 | i, err := strconv.Atoi(pieces[2]) 67 | ctx.Require.NoError(err) 68 | ret[i] = struct{}{} 69 | } 70 | } 71 | return ret 72 | } 73 | -------------------------------------------------------------------------------- /process/process.go: -------------------------------------------------------------------------------- 1 | // Package process is the low-level abstraction for a Tor instance. 2 | // 3 | // The standard use is to create a Creator with NewCreator and the path to the 4 | // Tor executable. The child package 'embedded' can be used if Tor is statically 5 | // linked in the binary. Most developers will prefer the tor package adjacent to 6 | // this one for a higher level abstraction over the process and control port 7 | // connection. 8 | package process 9 | 10 | import ( 11 | "context" 12 | "fmt" 13 | "net" 14 | "os" 15 | "os/exec" 16 | "strconv" 17 | "strings" 18 | 19 | "github.com/cretz/bine/torutil" 20 | ) 21 | 22 | // Process is the interface implemented by Tor processes. 23 | type Process interface { 24 | // Start starts the Tor process in the background and returns. It is 25 | // analagous to os/exec.Cmd.Start. 26 | Start() error 27 | // Wait waits for the Tor process to exit and returns error if it was not a 28 | // successful exit. It is analagous to os/exec.Cmd.Wait. 29 | Wait() error 30 | // ControlConn is used for statically linked, embedded processes to create 31 | // a controller connection. For non-embedded processes or Tor versions that 32 | // don't support embedded control connections, ErrControlConnUnsupported is 33 | // returned. Note, this should only be called once per process before 34 | // Start, and the connection does not need to be closed. 35 | EmbeddedControlConn() (net.Conn, error) 36 | } 37 | 38 | // Creator is the interface for process creation. 39 | type Creator interface { 40 | New(ctx context.Context, args ...string) (Process, error) 41 | } 42 | 43 | type CmdCreatorFunc func(ctx context.Context, args ...string) (*exec.Cmd, error) 44 | 45 | // NewCreator creates a Creator for external Tor process execution based on the 46 | // given exe path. 47 | func NewCreator(exePath string) Creator { 48 | return CmdCreatorFunc(func(ctx context.Context, args ...string) (*exec.Cmd, error) { 49 | cmd := exec.CommandContext(ctx, exePath, args...) 50 | cmd.Stdout = os.Stdout 51 | cmd.Stderr = os.Stderr 52 | return cmd, nil 53 | }) 54 | } 55 | 56 | type exeProcess struct { 57 | *exec.Cmd 58 | } 59 | 60 | func (c CmdCreatorFunc) New(ctx context.Context, args ...string) (Process, error) { 61 | cmd, err := c(ctx, args...) 62 | return &exeProcess{cmd}, err 63 | } 64 | 65 | // ErrControlConnUnsupported is returned by Process.EmbeddedControlConn when 66 | // it is unsupported. 67 | var ErrControlConnUnsupported = fmt.Errorf("Control conn not supported") 68 | 69 | func (e *exeProcess) EmbeddedControlConn() (net.Conn, error) { 70 | return nil, ErrControlConnUnsupported 71 | } 72 | 73 | // ControlPortFromFileContents reads a control port file that is written by Tor 74 | // when ControlPortWriteToFile is set. 75 | func ControlPortFromFileContents(contents string) (int, error) { 76 | contents = strings.TrimSpace(contents) 77 | _, port, ok := torutil.PartitionString(contents, ':') 78 | if !ok || !strings.HasPrefix(contents, "PORT=") { 79 | return 0, fmt.Errorf("Invalid port format: %v", contents) 80 | } 81 | return strconv.Atoi(port) 82 | } 83 | -------------------------------------------------------------------------------- /tests/context_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "os" 7 | "testing" 8 | "time" 9 | 10 | "github.com/cretz/bine/tor" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | var torEnabled bool 15 | var torExePath string 16 | var torVerbose bool 17 | var torIncludeNetworkTests bool 18 | var globalEnabledNetworkContext *TestContext 19 | 20 | func TestMain(m *testing.M) { 21 | flag.BoolVar(&torEnabled, "tor", false, "Whether any of the integration tests are enabled") 22 | flag.StringVar(&torExePath, "tor.path", "tor", "The Tor exe path") 23 | flag.BoolVar(&torVerbose, "tor.verbose", false, "Show verbose test info") 24 | flag.BoolVar(&torIncludeNetworkTests, "tor.network", false, "Include network tests") 25 | flag.Parse() 26 | exitCode := m.Run() 27 | if globalEnabledNetworkContext != nil { 28 | globalEnabledNetworkContext.CloseTorOnClose = true 29 | globalEnabledNetworkContext.Close() 30 | } 31 | os.Exit(exitCode) 32 | } 33 | 34 | func GlobalEnabledNetworkContext(t *testing.T) *TestContext { 35 | if !torEnabled || !torIncludeNetworkTests { 36 | t.Skip("Only runs if -tor and -tor.network are set") 37 | } 38 | if globalEnabledNetworkContext == nil { 39 | ctx := NewTestContext(t, nil) 40 | ctx.CloseTorOnClose = false 41 | // 45 second wait for enable network 42 | enableCtx, enableCancel := context.WithTimeout(ctx, 45*time.Second) 43 | defer enableCancel() 44 | ctx.Require.NoError(ctx.EnableNetwork(enableCtx, true)) 45 | globalEnabledNetworkContext = ctx 46 | } else { 47 | globalEnabledNetworkContext.T = t 48 | globalEnabledNetworkContext.Require = require.New(t) 49 | } 50 | return globalEnabledNetworkContext 51 | } 52 | 53 | type TestContext struct { 54 | context.Context 55 | *testing.T 56 | *tor.Tor 57 | Require *require.Assertions 58 | CloseTorOnClose bool 59 | } 60 | 61 | func NewTestContext(t *testing.T, conf *tor.StartConf) *TestContext { 62 | if !torEnabled { 63 | t.Skip("Only runs if -tor is set") 64 | } 65 | // Build start conf 66 | if conf == nil { 67 | conf = &tor.StartConf{} 68 | } 69 | conf.ExePath = torExePath 70 | if torVerbose { 71 | conf.DebugWriter = os.Stdout 72 | conf.NoHush = true 73 | } else { 74 | conf.ExtraArgs = append(conf.ExtraArgs, "--quiet") 75 | } 76 | ret := &TestContext{Context: context.Background(), T: t, Require: require.New(t), CloseTorOnClose: true} 77 | // Start tor 78 | var err error 79 | if ret.Tor, err = tor.Start(ret.Context, conf); err != nil { 80 | defer ret.Close() 81 | t.Fatal(err) 82 | } 83 | return ret 84 | } 85 | 86 | // Deadline to disambiguate context and test deadline and use test one. 87 | func (t *TestContext) Deadline() (time.Time, bool) { return t.T.Deadline() } 88 | 89 | func (t *TestContext) Close() { 90 | if t.CloseTorOnClose { 91 | if err := t.Tor.Close(); err != nil { 92 | if t.Failed() { 93 | t.Logf("Failure on close: %v", err) 94 | } else { 95 | t.Errorf("Failure on close: %v", err) 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /torutil/key.go: -------------------------------------------------------------------------------- 1 | package torutil 2 | 3 | import ( 4 | "crypto" 5 | "crypto/rsa" 6 | "crypto/sha1" 7 | "crypto/x509" 8 | "encoding/base32" 9 | "fmt" 10 | "strings" 11 | 12 | "github.com/cretz/bine/torutil/ed25519" 13 | "golang.org/x/crypto/sha3" 14 | ) 15 | 16 | var serviceIDEncoding = base32.StdEncoding.WithPadding(base32.NoPadding) 17 | 18 | // OnionServiceIDFromPrivateKey generates the onion service ID from the given 19 | // private key. This panics if the private key is not a 1024-bit 20 | // crypto/*rsa.PrivateKey or github.com/cretz/bine/torutil/ed25519.KeyPair. 21 | func OnionServiceIDFromPrivateKey(key crypto.PrivateKey) string { 22 | switch k := key.(type) { 23 | case *rsa.PrivateKey: 24 | return OnionServiceIDFromV2PublicKey(&k.PublicKey) 25 | case ed25519.KeyPair: 26 | return OnionServiceIDFromV3PublicKey(k.PublicKey()) 27 | } 28 | panic(fmt.Sprintf("Unrecognized private key type: %T", key)) 29 | } 30 | 31 | // OnionServiceIDFromPublicKey generates the onion service ID from the given 32 | // public key. This panics if the public key is not a 1024-bit 33 | // crypto/*rsa.PublicKey or github.com/cretz/bine/torutil/ed25519.PublicKey. 34 | func OnionServiceIDFromPublicKey(key crypto.PublicKey) string { 35 | switch k := key.(type) { 36 | case *rsa.PublicKey: 37 | return OnionServiceIDFromV2PublicKey(k) 38 | case ed25519.PublicKey: 39 | return OnionServiceIDFromV3PublicKey(k) 40 | } 41 | panic(fmt.Sprintf("Unrecognized public key type: %T", key)) 42 | } 43 | 44 | // OnionServiceIDFromV2PublicKey generates a V2 service ID for the given 45 | // RSA-1024 public key. Panics if not a 1024-bit key. 46 | func OnionServiceIDFromV2PublicKey(key *rsa.PublicKey) string { 47 | if key.N.BitLen() != 1024 { 48 | panic("RSA key not 1024 bit") 49 | } 50 | h := sha1.New() 51 | h.Write(x509.MarshalPKCS1PublicKey(key)) 52 | return strings.ToLower(serviceIDEncoding.EncodeToString(h.Sum(nil)[:10])) 53 | } 54 | 55 | // OnionServiceIDFromV3PublicKey generates a V3 service ID for the given 56 | // ED25519 public key. 57 | func OnionServiceIDFromV3PublicKey(key ed25519.PublicKey) string { 58 | checkSum := sha3.Sum256(append(append([]byte(".onion checksum"), key...), 0x03)) 59 | var keyBytes [35]byte 60 | copy(keyBytes[:], key) 61 | keyBytes[32] = checkSum[0] 62 | keyBytes[33] = checkSum[1] 63 | keyBytes[34] = 0x03 64 | return strings.ToLower(serviceIDEncoding.EncodeToString(keyBytes[:])) 65 | } 66 | 67 | // PublicKeyFromV3OnionServiceID returns a public key for the given service ID 68 | // or an error if the service ID is invalid. 69 | func PublicKeyFromV3OnionServiceID(id string) (ed25519.PublicKey, error) { 70 | byts, err := serviceIDEncoding.DecodeString(strings.ToUpper(id)) 71 | if err != nil { 72 | return nil, err 73 | } else if len(byts) != 35 { 74 | return nil, fmt.Errorf("Invalid id length") 75 | } else if byts[34] != 0x03 { 76 | return nil, fmt.Errorf("Invalid version") 77 | } 78 | // Do a checksum check 79 | key := ed25519.PublicKey(byts[:32]) 80 | checkSum := sha3.Sum256(append(append([]byte(".onion checksum"), key...), 0x03)) 81 | if byts[32] != checkSum[0] || byts[33] != checkSum[1] { 82 | return nil, fmt.Errorf("Invalid checksum") 83 | } 84 | return key, nil 85 | } 86 | -------------------------------------------------------------------------------- /control/conn.go: -------------------------------------------------------------------------------- 1 | package control 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/textproto" 7 | "sync" 8 | ) 9 | 10 | // Conn is the connection to the Tor control port. 11 | type Conn struct { 12 | // DebugWriter is the writer that debug logs for this library (not Tor 13 | // itself) will be written to. If nil, no debug logs are generated/written. 14 | DebugWriter io.Writer 15 | 16 | // This is the underlying connection. 17 | conn *textproto.Conn 18 | 19 | // This is set lazily by ProtocolInfo(). 20 | protocolInfo *ProtocolInfo 21 | 22 | // True if Authenticate has been called successfully. 23 | Authenticated bool 24 | 25 | // The lock fot eventListeners 26 | eventListenersLock sync.RWMutex 27 | // The value slices can be traversed outside of lock, they are completely 28 | // replaced on change, never mutated. But the map itself must be locked on 29 | // when reading or writing. 30 | eventListeners map[EventCode][]chan<- Event 31 | 32 | // This mutex is locked on when an entire response needs to be read. It 33 | // helps synchronize accesses to the response by the asynchronous response 34 | // listeners and the synchronous responses. 35 | readLock sync.Mutex 36 | } 37 | 38 | // NewConn creates a Conn from the given textproto connection. 39 | func NewConn(conn *textproto.Conn) *Conn { 40 | return &Conn{ 41 | conn: conn, 42 | eventListeners: map[EventCode][]chan<- Event{}, 43 | } 44 | } 45 | 46 | func (c *Conn) sendRequestIgnoreResponse(format string, args ...interface{}) error { 47 | _, err := c.SendRequest(format, args...) 48 | return err 49 | } 50 | 51 | // SendRequest sends a synchronous request to Tor and awaits the response. If 52 | // the response errors, the error result will be set, but the response will be 53 | // set also. This is usually not directly used by callers, but instead called by 54 | // higher-level methods. 55 | func (c *Conn) SendRequest(format string, args ...interface{}) (*Response, error) { 56 | if c.debugEnabled() { 57 | c.debugf("Write line: %v", fmt.Sprintf(format, args...)) 58 | } 59 | id, err := c.conn.Cmd(format, args...) 60 | if err != nil { 61 | return nil, err 62 | } 63 | c.readLock.Lock() 64 | defer c.readLock.Unlock() 65 | c.conn.StartResponse(id) 66 | defer c.conn.EndResponse(id) 67 | // Get the first non-async response 68 | var resp *Response 69 | for { 70 | if resp, err = c.ReadResponse(); err != nil || !resp.IsAsync() { 71 | break 72 | } 73 | c.relayAsyncEvents(resp) 74 | } 75 | if err == nil && !resp.IsOk() { 76 | err = resp.Err 77 | } 78 | return resp, err 79 | } 80 | 81 | // Close sends a QUIT and closes the underlying Tor connection. This does not 82 | // error if the QUIT is not accepted but does relay any error that occurs while 83 | // closing the underlying connection. 84 | func (c *Conn) Close() error { 85 | // Ignore the response and ignore the error 86 | c.Quit() 87 | return c.conn.Close() 88 | } 89 | 90 | func (c *Conn) debugEnabled() bool { 91 | return c.DebugWriter != nil 92 | } 93 | 94 | func (c *Conn) debugf(format string, args ...interface{}) { 95 | if w := c.DebugWriter; w != nil { 96 | fmt.Fprintf(w, format+"\n", args...) 97 | } 98 | } 99 | 100 | func (*Conn) protoErr(format string, args ...interface{}) textproto.ProtocolError { 101 | return textproto.ProtocolError(fmt.Sprintf(format, args...)) 102 | } 103 | -------------------------------------------------------------------------------- /tor/dialer.go: -------------------------------------------------------------------------------- 1 | package tor 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "strings" 8 | 9 | "golang.org/x/net/proxy" 10 | ) 11 | 12 | // Dialer is a wrapper around a proxy.Dialer for dialing connections. 13 | type Dialer struct { 14 | proxy.Dialer 15 | } 16 | 17 | // DialConf is the configuration used for Dialer. 18 | type DialConf struct { 19 | // ProxyAddress is the address for the SOCKS5 proxy. If empty, it is looked 20 | // up. 21 | ProxyAddress string 22 | 23 | // ProxyNetwork is the network for the SOCKS5 proxy. If ProxyAddress is 24 | // empty, this value is ignored and overridden by what is looked up. If this 25 | // is empty and ProxyAddress is not empty, it defaults to "tcp". 26 | ProxyNetwork string 27 | 28 | // ProxyAuth is the auth for the proxy. Since Tor's SOCKS5 proxy is 29 | // unauthenticated, this is rarely needed. It can be used when 30 | // IsolateSOCKSAuth is set to ensure separate circuits. 31 | // 32 | // This should not be confused with downstream SOCKS proxy authentication 33 | // which is set via Tor values for Socks5ProxyUsername and 34 | // Socks5ProxyPassword when Socks5Proxy is set. 35 | ProxyAuth *proxy.Auth 36 | 37 | // SkipEnableNetwork, if true, will skip the enable network step in Dialer. 38 | SkipEnableNetwork bool 39 | 40 | // Forward is the dialer to forward to. If nil, just uses normal net dialer. 41 | Forward proxy.Dialer 42 | } 43 | 44 | // Dialer creates a new Dialer for the given configuration. Context can be nil. 45 | // If conf is nil, a default is used. 46 | func (t *Tor) Dialer(ctx context.Context, conf *DialConf) (*Dialer, error) { 47 | if ctx == nil { 48 | ctx = context.Background() 49 | } 50 | if conf == nil { 51 | conf = &DialConf{} 52 | } 53 | // Enable the network if requested 54 | if !conf.SkipEnableNetwork { 55 | if err := t.EnableNetwork(ctx, true); err != nil { 56 | return nil, err 57 | } 58 | } 59 | // Lookup proxy address as needed 60 | proxyNetwork := conf.ProxyNetwork 61 | proxyAddress := conf.ProxyAddress 62 | if proxyAddress == "" { 63 | info, err := t.Control.GetInfo("net/listeners/socks") 64 | if err != nil { 65 | return nil, err 66 | } 67 | if len(info) != 1 || info[0].Key != "net/listeners/socks" { 68 | return nil, fmt.Errorf("Unable to get socks proxy address") 69 | } 70 | proxyAddress = info[0].Val 71 | if strings.HasPrefix(proxyAddress, "unix:") { 72 | proxyAddress = proxyAddress[5:] 73 | proxyNetwork = "unix" 74 | } else { 75 | proxyNetwork = "tcp" 76 | } 77 | } else if proxyNetwork == "" { 78 | proxyNetwork = "tcp" 79 | } 80 | 81 | dialer, err := proxy.SOCKS5(proxyNetwork, proxyAddress, conf.ProxyAuth, conf.Forward) 82 | if err != nil { 83 | return nil, err 84 | } 85 | return &Dialer{dialer}, nil 86 | } 87 | 88 | // DialContext is the equivalent of net.DialContext. 89 | // 90 | // TODO: Remove when https://github.com/golang/go/issues/17759 is released. 91 | func (d *Dialer) DialContext(ctx context.Context, network string, addr string) (net.Conn, error) { 92 | errCh := make(chan error, 1) 93 | connCh := make(chan net.Conn, 1) 94 | go func() { 95 | if conn, err := d.Dial(network, addr); err != nil { 96 | errCh <- err 97 | } else if ctx.Err() != nil { 98 | conn.Close() 99 | } else { 100 | connCh <- conn 101 | } 102 | }() 103 | select { 104 | case err := <-errCh: 105 | return nil, err 106 | case conn := <-connCh: 107 | return conn, nil 108 | case <-ctx.Done(): 109 | return nil, ctx.Err() 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /control/response.go: -------------------------------------------------------------------------------- 1 | package control 2 | 3 | import ( 4 | "net/textproto" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // Response is a response to a control port command or an asynchronous event. 10 | type Response struct { 11 | // Err is the status code and string representation associated with a 12 | // response. Responses that have completed successfully will also have Err 13 | // set to indicate such. 14 | Err *textproto.Error 15 | 16 | // Reply is the text on the EndReplyLine of the response. 17 | Reply string 18 | 19 | // Data is the MidReplyLines/DataReplyLines of the response. Dot encoded 20 | // data is "decoded" and presented as a single string (terminal ".CRLF" 21 | // removed, all intervening CRs stripped). 22 | Data []string 23 | 24 | // RawLines is all of the lines of a response, without CRLFs. 25 | RawLines []string 26 | } 27 | 28 | // IsOk returns true if the response status code indicates success or an 29 | // asynchronous event. 30 | func (r *Response) IsOk() bool { 31 | switch r.Err.Code { 32 | case StatusOk, StatusOkUnnecessary, StatusAsyncEvent: 33 | return true 34 | default: 35 | return false 36 | } 37 | } 38 | 39 | // DataWithReply returns a combination of Data and Reply to give a full set of 40 | // the lines of the response. 41 | func (r *Response) DataWithReply() []string { 42 | ret := make([]string, len(r.Data)+1) 43 | copy(ret, r.Data) 44 | ret[len(ret)-1] = r.Reply 45 | return ret 46 | } 47 | 48 | // IsAsync returns true if the response is an asynchronous event. 49 | func (r *Response) IsAsync() bool { 50 | return r.Err.Code == StatusAsyncEvent 51 | } 52 | 53 | // ReadResponse returns the next response object. 54 | func (c *Conn) ReadResponse() (*Response, error) { 55 | var resp *Response 56 | var statusCode int 57 | for { 58 | line, err := c.conn.ReadLine() 59 | if err != nil { 60 | return nil, err 61 | } 62 | c.debugf("Read line: %v", line) 63 | 64 | // Parse the line that was just read. 65 | if len(line) < 4 { 66 | return nil, c.protoErr("Truncated response: %v", line) 67 | } 68 | if code, err := strconv.Atoi(line[0:3]); err != nil || code < 100 { 69 | return nil, c.protoErr("Invalid status code: %v", line[0:3]) 70 | } else if resp == nil { 71 | resp = &Response{} 72 | statusCode = code 73 | } else if code != statusCode { 74 | // The status code should stay fixed for all lines of the response, since events can't be interleaved with 75 | // response lines. 76 | return nil, c.protoErr("Status code changed: %v != %v", code, statusCode) 77 | } 78 | resp.RawLines = append(resp.RawLines, line) 79 | switch line[3] { 80 | case ' ': 81 | // Final line in the response. 82 | resp.Reply = line[4:] 83 | resp.Err = statusCodeToError(statusCode, resp.Reply) 84 | return resp, nil 85 | case '-': 86 | // Continuation, keep reading. 87 | resp.Data = append(resp.Data, line[4:]) 88 | case '+': 89 | // A "dot-encoded" payload follows. 90 | dotBody, err := c.conn.ReadDotBytes() 91 | if err != nil { 92 | return nil, err 93 | } 94 | dotBodyStr := strings.TrimRight(string(dotBody), "\n\r") 95 | // c.debugf("Read dot body:\n---\n%v\n---", dotBodyStr) 96 | resp.Data = append(resp.Data, line[4:]+"\r\n"+dotBodyStr) 97 | dotLines := strings.Split(dotBodyStr, "\n") 98 | for _, dotLine := range dotLines[:len(dotLines)-1] { 99 | resp.RawLines = append(resp.RawLines, dotLine) 100 | } 101 | resp.RawLines = append(resp.RawLines, ".") 102 | default: 103 | return nil, c.protoErr("Invalid separator: '%v'", line[3]) 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /torutil/string.go: -------------------------------------------------------------------------------- 1 | package torutil 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // PartitionString returns the two parts of a string delimited by the first 9 | // occurrence of ch. If ch does not exist, the second string is empty and the 10 | // resulting bool is false. Otherwise it is true. 11 | func PartitionString(str string, ch byte) (string, string, bool) { 12 | index := strings.IndexByte(str, ch) 13 | if index == -1 { 14 | return str, "", false 15 | } 16 | return str[:index], str[index+1:], true 17 | } 18 | 19 | // PartitionStringFromEnd is same as PartitionString except it delimts by the 20 | // last occurrence of ch instead of the first. 21 | func PartitionStringFromEnd(str string, ch byte) (string, string, bool) { 22 | index := strings.LastIndexByte(str, ch) 23 | if index == -1 { 24 | return str, "", false 25 | } 26 | return str[:index], str[index+1:], true 27 | } 28 | 29 | // EscapeSimpleQuotedStringIfNeeded calls EscapeSimpleQuotedString only if the 30 | // string contains a space, backslash, double quote, newline, or carriage return 31 | // character. 32 | func EscapeSimpleQuotedStringIfNeeded(str string) string { 33 | if strings.ContainsAny(str, " \\\"\r\n") { 34 | return EscapeSimpleQuotedString(str) 35 | } 36 | return str 37 | } 38 | 39 | var simpleQuotedStringEscapeReplacer = strings.NewReplacer( 40 | "\\", "\\\\", 41 | "\"", "\\\"", 42 | "\r", "\\r", 43 | "\n", "\\n", 44 | ) 45 | 46 | // EscapeSimpleQuotedString calls EscapeSimpleQuotedStringContents and then 47 | // surrounds the entire string with double quotes. 48 | func EscapeSimpleQuotedString(str string) string { 49 | return "\"" + EscapeSimpleQuotedStringContents(str) + "\"" 50 | } 51 | 52 | // EscapeSimpleQuotedStringContents escapes backslashes, double quotes, 53 | // newlines, and carriage returns in str. 54 | func EscapeSimpleQuotedStringContents(str string) string { 55 | return simpleQuotedStringEscapeReplacer.Replace(str) 56 | } 57 | 58 | // UnescapeSimpleQuotedStringIfNeeded calls UnescapeSimpleQuotedString only if 59 | // str is surrounded with double quotes. 60 | func UnescapeSimpleQuotedStringIfNeeded(str string) (string, error) { 61 | if len(str) >= 2 && str[0] == '"' && str[len(str)-1] == '"' { 62 | return UnescapeSimpleQuotedString(str) 63 | } 64 | return str, nil 65 | } 66 | 67 | // UnescapeSimpleQuotedString removes surrounding double quotes and calls 68 | // UnescapeSimpleQuotedStringContents. 69 | func UnescapeSimpleQuotedString(str string) (string, error) { 70 | if len(str) < 2 || str[0] != '"' || str[len(str)-1] != '"' { 71 | return "", fmt.Errorf("Missing quotes") 72 | } 73 | return UnescapeSimpleQuotedStringContents(str[1 : len(str)-1]) 74 | } 75 | 76 | // UnescapeSimpleQuotedStringContents unescapes backslashes, double quotes, 77 | // newlines, and carriage returns. Also errors if those aren't escaped. 78 | func UnescapeSimpleQuotedStringContents(str string) (string, error) { 79 | ret := "" 80 | escaping := false 81 | for _, c := range str { 82 | switch c { 83 | case '\\': 84 | if escaping { 85 | ret += "\\" 86 | } 87 | escaping = !escaping 88 | case '"': 89 | if !escaping { 90 | return "", fmt.Errorf("Unescaped quote") 91 | } 92 | ret += "\"" 93 | escaping = false 94 | case '\r', '\n': 95 | return "", fmt.Errorf("Unescaped newline or carriage return") 96 | default: 97 | if escaping { 98 | if c == 'r' { 99 | ret += "\r" 100 | } else if c == 'n' { 101 | ret += "\n" 102 | } else { 103 | return "", fmt.Errorf("Unexpected escape") 104 | } 105 | } else { 106 | ret += string(c) 107 | } 108 | escaping = false 109 | } 110 | } 111 | return ret, nil 112 | } 113 | -------------------------------------------------------------------------------- /torutil/string_test.go: -------------------------------------------------------------------------------- 1 | package torutil 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestPartitionString(t *testing.T) { 10 | assert := func(str string, ch byte, expectedA string, expectedB string, expectedOk bool) { 11 | a, b, ok := PartitionString(str, ch) 12 | require.Equal(t, expectedA, a) 13 | require.Equal(t, expectedB, b) 14 | require.Equal(t, expectedOk, ok) 15 | } 16 | assert("foo:bar", ':', "foo", "bar", true) 17 | assert(":bar", ':', "", "bar", true) 18 | assert("foo:", ':', "foo", "", true) 19 | assert("foo", ':', "foo", "", false) 20 | assert("foo:bar:baz", ':', "foo", "bar:baz", true) 21 | } 22 | 23 | func TestPartitionStringFromEnd(t *testing.T) { 24 | assert := func(str string, ch byte, expectedA string, expectedB string, expectedOk bool) { 25 | a, b, ok := PartitionStringFromEnd(str, ch) 26 | require.Equal(t, expectedA, a) 27 | require.Equal(t, expectedB, b) 28 | require.Equal(t, expectedOk, ok) 29 | } 30 | assert("foo:bar", ':', "foo", "bar", true) 31 | assert(":bar", ':', "", "bar", true) 32 | assert("foo:", ':', "foo", "", true) 33 | assert("foo", ':', "foo", "", false) 34 | assert("foo:bar:baz", ':', "foo:bar", "baz", true) 35 | } 36 | 37 | func TestEscapeSimpleQuotedStringIfNeeded(t *testing.T) { 38 | assert := func(str string, shouldBeDiff bool) { 39 | maybeEscaped := EscapeSimpleQuotedStringIfNeeded(str) 40 | if shouldBeDiff { 41 | require.NotEqual(t, str, maybeEscaped) 42 | } else { 43 | require.Equal(t, str, maybeEscaped) 44 | } 45 | } 46 | assert("foo", false) 47 | assert(" foo", true) 48 | assert("f\\oo", true) 49 | assert("fo\"o", true) 50 | assert("f\roo", true) 51 | assert("fo\no", true) 52 | } 53 | 54 | func TestEscapeSimpleQuotedString(t *testing.T) { 55 | require.Equal(t, "\"foo\"", EscapeSimpleQuotedString("foo")) 56 | } 57 | 58 | func TestEscapeSimpleQuotedStringContents(t *testing.T) { 59 | assert := func(str string, expected string) { 60 | require.Equal(t, expected, EscapeSimpleQuotedStringContents(str)) 61 | } 62 | assert("foo", "foo") 63 | assert("f\\oo", "f\\\\oo") 64 | assert("f\\noo", "f\\\\noo") 65 | assert("f\n o\ro", "f\\n o\\ro") 66 | assert("fo\r\\\"o", "fo\\r\\\\\\\"o") 67 | } 68 | 69 | func TestUnescapeSimpleQuotedStringIfNeeded(t *testing.T) { 70 | assert := func(str string, expectedStr string, expectedErr bool) { 71 | actualStr, actualErr := UnescapeSimpleQuotedStringIfNeeded(str) 72 | require.Equal(t, expectedStr, actualStr) 73 | require.Equal(t, expectedErr, actualErr != nil) 74 | } 75 | assert("foo", "foo", false) 76 | assert("\"foo\"", "foo", false) 77 | assert("\"f\"oo\"", "", true) 78 | } 79 | 80 | func TestUnescapeSimpleQuotedString(t *testing.T) { 81 | assert := func(str string, expectedStr string, expectedErr bool) { 82 | actualStr, actualErr := UnescapeSimpleQuotedString(str) 83 | require.Equal(t, expectedStr, actualStr) 84 | require.Equal(t, expectedErr, actualErr != nil) 85 | } 86 | assert("foo", "", true) 87 | assert("\"foo\"", "foo", false) 88 | assert("\"f\"oo\"", "", true) 89 | } 90 | 91 | func TestUnescapeSimpleQuotedStringContents(t *testing.T) { 92 | assert := func(str string, expectedStr string, expectedErr bool) { 93 | actualStr, actualErr := UnescapeSimpleQuotedStringContents(str) 94 | require.Equal(t, expectedStr, actualStr) 95 | require.Equal(t, expectedErr, actualErr != nil) 96 | } 97 | assert("foo", "foo", false) 98 | assert("f\\\\oo", "f\\oo", false) 99 | assert("f\\\\noo", "f\\noo", false) 100 | assert("f\\n o\\ro", "f\n o\ro", false) 101 | assert("fo\\r\\\\\\\"o", "fo\r\\\"o", false) 102 | assert("f\"oo", "", true) 103 | assert("f\roo", "", true) 104 | assert("f\noo", "", true) 105 | assert("f\\oo", "", true) 106 | } 107 | -------------------------------------------------------------------------------- /process/embedded/tor-0.3.3/process.go: -------------------------------------------------------------------------------- 1 | // Package tor033 implements process interfaces for statically linked 2 | // Tor 0.3.3.x versions. See the process/embedded package for the generic 3 | // abstraction 4 | package tor033 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "net" 10 | 11 | "github.com/cretz/bine/process" 12 | ) 13 | 14 | /* 15 | #cgo CFLAGS: -I${SRCDIR}/../../../../tor-static/tor/src/or 16 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/tor/src/or -ltor 17 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/tor/src/common -lor -lor-crypto -lcurve25519_donna -lor-ctime -lor-event 18 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/tor/src/trunnel -lor-trunnel 19 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/tor/src/ext/keccak-tiny -lkeccak-tiny 20 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/tor/src/ext/ed25519/ref10 -led25519_ref10 21 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/tor/src/ext/ed25519/donna -led25519_donna 22 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/libevent/dist/lib -levent 23 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/xz/dist/lib -llzma 24 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/zlib/dist/lib -lz 25 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/openssl/dist/lib -lssl -lcrypto 26 | #cgo windows LDFLAGS: -lws2_32 -lcrypt32 -lgdi32 -Wl,-Bstatic -lpthread 27 | #cgo !windows LDFLAGS: -lm 28 | 29 | #include <stdlib.h> 30 | #include <tor_api.h> 31 | 32 | // Ref: https://stackoverflow.com/questions/45997786/passing-array-of-string-as-parameter-from-go-to-c-function 33 | 34 | static char** makeCharArray(int size) { 35 | return calloc(sizeof(char*), size); 36 | } 37 | 38 | static void setArrayString(char **a, char *s, int n) { 39 | a[n] = s; 40 | } 41 | 42 | static void freeCharArray(char **a, int size) { 43 | int i; 44 | for (i = 0; i < size; i++) 45 | free(a[i]); 46 | free(a); 47 | } 48 | */ 49 | import "C" 50 | 51 | type embeddedCreator struct{} 52 | 53 | // NewCreator creates a process.Creator for statically-linked Tor embedded in 54 | // the binary. 55 | func NewCreator() process.Creator { 56 | return embeddedCreator{} 57 | } 58 | 59 | type embeddedProcess struct { 60 | ctx context.Context 61 | args []string 62 | doneCh chan int 63 | } 64 | 65 | func (embeddedCreator) New(ctx context.Context, args ...string) (process.Process, error) { 66 | return &embeddedProcess{ctx: ctx, args: args}, nil 67 | } 68 | 69 | func (e *embeddedProcess) Start() error { 70 | if e.doneCh != nil { 71 | return fmt.Errorf("Already started") 72 | } 73 | // Create the char array for the args 74 | args := append([]string{"tor"}, e.args...) 75 | charArray := C.makeCharArray(C.int(len(args))) 76 | for i, a := range args { 77 | C.setArrayString(charArray, C.CString(a), C.int(i)) 78 | } 79 | // Build the conf 80 | conf := C.tor_main_configuration_new() 81 | if code := C.tor_main_configuration_set_command_line(conf, C.int(len(args)), charArray); code != 0 { 82 | C.tor_main_configuration_free(conf) 83 | C.freeCharArray(charArray, C.int(len(args))) 84 | return fmt.Errorf("Failed to set command line args, code: %v", int(code)) 85 | } 86 | // Run it async 87 | e.doneCh = make(chan int, 1) 88 | go func() { 89 | defer C.freeCharArray(charArray, C.int(len(args))) 90 | defer C.tor_main_configuration_free(conf) 91 | e.doneCh <- int(C.tor_run_main(conf)) 92 | }() 93 | return nil 94 | } 95 | 96 | func (e *embeddedProcess) Wait() error { 97 | if e.doneCh == nil { 98 | return fmt.Errorf("Not started") 99 | } 100 | ctx := e.ctx 101 | if ctx == nil { 102 | ctx = context.Background() 103 | } 104 | select { 105 | case <-ctx.Done(): 106 | return ctx.Err() 107 | case code := <-e.doneCh: 108 | if code == 0 { 109 | return nil 110 | } 111 | return fmt.Errorf("Command completed with error exit code: %v", code) 112 | } 113 | } 114 | 115 | func (e *embeddedProcess) EmbeddedControlConn() (net.Conn, error) { 116 | return nil, process.ErrControlConnUnsupported 117 | } 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # <img src="logo/bine-logo.png" width="180px"> 2 | 3 | [![GoDoc](https://godoc.org/github.com/cretz/bine?status.svg)](https://godoc.org/github.com/cretz/bine) 4 | 5 | Bine is a Go API for using and controlling Tor. It is similar to [Stem](https://stem.torproject.org/). 6 | 7 | Features: 8 | 9 | * Full support for the Tor controller API 10 | * Support for `net.Conn` and `net.Listen` style APIs 11 | * Supports statically compiled Tor to embed Tor into the binary 12 | * Supports v3 onion services 13 | * Support for embedded control socket in Tor >= 0.3.5 (non-Windows) 14 | 15 | See info below, the [API docs](http://godoc.org/github.com/cretz/bine), and the [examples](examples). The project is 16 | MIT licensed. The Tor docs/specs and https://github.com/yawning/bulb were great helps when building this. 17 | 18 | ## Example 19 | 20 | It is really easy to create an onion service. For example, assuming `tor` is on the `PATH`, this bit of code will show 21 | a directory server of the current directory: 22 | 23 | ```go 24 | package main 25 | 26 | import ( 27 | "context" 28 | "fmt" 29 | "log" 30 | "net/http" 31 | "time" 32 | 33 | "github.com/cretz/bine/tor" 34 | ) 35 | 36 | func main() { 37 | // Start tor with default config (can set start conf's DebugWriter to os.Stdout for debug logs) 38 | fmt.Println("Starting and registering onion service, please wait a couple of minutes...") 39 | t, err := tor.Start(nil, nil) 40 | if err != nil { 41 | log.Panicf("Unable to start Tor: %v", err) 42 | } 43 | defer t.Close() 44 | // Wait at most a few minutes to publish the service 45 | listenCtx, listenCancel := context.WithTimeout(context.Background(), 3*time.Minute) 46 | defer listenCancel() 47 | // Create a v3 onion service to listen on any port but show as 80 48 | onion, err := t.Listen(listenCtx, &tor.ListenConf{RemotePorts: []int{80}}) 49 | if err != nil { 50 | log.Panicf("Unable to create onion service: %v", err) 51 | } 52 | defer onion.Close() 53 | fmt.Printf("Open Tor browser and navigate to http://%v.onion\n", onion.ID) 54 | fmt.Println("Press enter to exit") 55 | // Serve the current folder from HTTP 56 | errCh := make(chan error, 1) 57 | go func() { errCh <- http.Serve(onion, http.FileServer(http.Dir("."))) }() 58 | // End when enter is pressed 59 | go func() { 60 | fmt.Scanln() 61 | errCh <- nil 62 | }() 63 | if err = <-errCh; err != nil { 64 | log.Panicf("Failed serving: %v", err) 65 | } 66 | } 67 | ``` 68 | 69 | If in `main.go` it can simply be run with `go run main.go`. Of course this uses a separate `tor` process. To embed Tor 70 | statically in the binary, follow the [embedded package docs](https://godoc.org/github.com/cretz/bine/process/embedded) 71 | which will require [building Tor statically](https://github.com/cretz/tor-static). Then with 72 | `github.com/cretz/bine/process/embedded` imported, change the start line above to: 73 | 74 | ```go 75 | t, err := tor.Start(nil, &tor.StartConf{ProcessCreator: embedded.NewCreator()}) 76 | ``` 77 | 78 | This defaults to Tor 0.3.5.x versions but others can be used from different packages. In non-Windows environments, the 79 | `UseEmbeddedControlConn` field in `StartConf` can be set to `true` to use an embedded socket that does not open a 80 | control port. 81 | 82 | Tested on Windows, the original exe file is ~7MB. With Tor statically linked it comes to ~24MB, but Tor does not have to 83 | be distributed separately. Of course take notice of all licenses in accompanying projects. 84 | 85 | ## Testing 86 | 87 | To test, a simple `go test ./...` from the base of the repository will work (add in a `-v` in there to see the tests). 88 | The integration tests in `tests` however will be skipped. To execute those tests, `-tor` must be passed to the test. 89 | Also, `tor` must be on the `PATH` or `-tor.path` must be set to the path of the `tor` executable. Even with those flags, 90 | only the integration tests that do not connect to the Tor network are run. To also include the tests that use the Tor 91 | network, add the `-tor.network` flag. For details Tor logs during any of the integration tests, use the `-tor.verbose` 92 | flag. 93 | -------------------------------------------------------------------------------- /control/cmd_authenticate.go: -------------------------------------------------------------------------------- 1 | package control 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/rand" 6 | "crypto/sha256" 7 | "encoding/hex" 8 | "io/ioutil" 9 | "strings" 10 | ) 11 | 12 | // Authenticate authenticates with the Tor instance using the "best" possible 13 | // authentication method if not already authenticated and sets the Authenticated 14 | // field. The password argument is optional, and will only be used if the 15 | // "SAFECOOKIE" and "NULL" authentication methods are not available and 16 | // "HASHEDPASSWORD" is. 17 | func (c *Conn) Authenticate(password string) error { 18 | if c.Authenticated { 19 | return nil 20 | } 21 | // Determine the supported authentication methods, and the cookie path. 22 | pi, err := c.ProtocolInfo() 23 | if err != nil { 24 | return err 25 | } 26 | // Get the bytes to pass to with authenticate 27 | var authBytes []byte 28 | if pi.HasAuthMethod("NULL") { 29 | // No auth bytes 30 | } else if pi.HasAuthMethod("SAFECOOKIE") { 31 | if pi.CookieFile == "" { 32 | return c.protoErr("Invalid (empty) COOKIEFILE") 33 | } 34 | cookie, err := ioutil.ReadFile(pi.CookieFile) 35 | if err != nil { 36 | return c.protoErr("Failed to read COOKIEFILE: %v", err) 37 | } else if len(cookie) != 32 { 38 | return c.protoErr("Invalid cookie file length: %v", len(cookie)) 39 | } 40 | 41 | // Send an AUTHCHALLENGE command, and parse the response. 42 | var clientNonce [32]byte 43 | if _, err := rand.Read(clientNonce[:]); err != nil { 44 | return c.protoErr("Failed to generate clientNonce: %v", err) 45 | } 46 | resp, err := c.SendRequest("AUTHCHALLENGE %s %s", "SAFECOOKIE", hex.EncodeToString(clientNonce[:])) 47 | if err != nil { 48 | return err 49 | } 50 | splitResp := strings.Split(resp.Reply, " ") 51 | if len(splitResp) != 3 || !strings.HasPrefix(splitResp[1], "SERVERHASH=") || 52 | !strings.HasPrefix(splitResp[2], "SERVERNONCE=") { 53 | return c.protoErr("Invalid AUTHCHALLENGE response") 54 | } 55 | serverHash, err := hex.DecodeString(splitResp[1][11:]) 56 | if err != nil { 57 | return c.protoErr("Failed to decode ServerHash: %v", err) 58 | } 59 | if len(serverHash) != 32 { 60 | return c.protoErr("Invalid ServerHash length: %d", len(serverHash)) 61 | } 62 | serverNonce, err := hex.DecodeString(splitResp[2][12:]) 63 | if err != nil { 64 | return c.protoErr("Failed to decode ServerNonce: %v", err) 65 | } 66 | if len(serverNonce) != 32 { 67 | return c.protoErr("Invalid ServerNonce length: %d", len(serverNonce)) 68 | } 69 | 70 | // Validate the ServerHash. 71 | m := hmac.New(sha256.New, []byte("Tor safe cookie authentication server-to-controller hash")) 72 | m.Write(cookie) 73 | m.Write(clientNonce[:]) 74 | m.Write(serverNonce) 75 | dervServerHash := m.Sum(nil) 76 | if !hmac.Equal(serverHash, dervServerHash) { 77 | return c.protoErr("invalid ServerHash: mismatch") 78 | } 79 | 80 | // Calculate the ClientHash, and issue the AUTHENTICATE. 81 | m = hmac.New(sha256.New, []byte("Tor safe cookie authentication controller-to-server hash")) 82 | m.Write(cookie) 83 | m.Write(clientNonce[:]) 84 | m.Write(serverNonce) 85 | authBytes = m.Sum(nil) 86 | } else if pi.HasAuthMethod("HASHEDPASSWORD") { 87 | // Despite the name HASHEDPASSWORD, the raw password is actually sent. According to the code, this can either be 88 | // a QuotedString, or base16 encoded, so go with the later since it's easier to handle. 89 | if password == "" { 90 | return c.protoErr("password auth needs a password") 91 | } 92 | authBytes = []byte(password) 93 | } else { 94 | return c.protoErr("No supported authentication methods") 95 | } 96 | // Send it 97 | if err = c.sendAuthenticate(authBytes); err == nil { 98 | c.Authenticated = true 99 | } 100 | return err 101 | } 102 | 103 | func (c *Conn) sendAuthenticate(byts []byte) error { 104 | if len(byts) == 0 { 105 | return c.sendRequestIgnoreResponse("AUTHENTICATE") 106 | } 107 | return c.sendRequestIgnoreResponse("AUTHENTICATE %v", hex.EncodeToString(byts)) 108 | } 109 | -------------------------------------------------------------------------------- /process/embedded/tor-0.4.7/process.go: -------------------------------------------------------------------------------- 1 | // Package tor047 implements process interfaces for statically linked 2 | // Tor 0.4.7.x versions. See the process/embedded package for the generic 3 | // abstraction 4 | package tor047 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "net" 10 | "os" 11 | 12 | "github.com/cretz/bine/process" 13 | ) 14 | 15 | /* 16 | #cgo CFLAGS: -I${SRCDIR}/../../../../tor-static/tor/src/feature/api 17 | // The libs below are generated via tor-static's show-libs 18 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/tor -ltor 19 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/libevent/dist/lib -levent 20 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/xz/dist/lib -llzma 21 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/zlib/dist/lib -lz 22 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/openssl/dist/lib -lssl -lcrypto 23 | #cgo windows LDFLAGS: -lws2_32 -lcrypt32 -lgdi32 -liphlpapi -lshlwapi -Wl,-Bstatic -lpthread 24 | #cgo !windows LDFLAGS: -lm 25 | #include <stdlib.h> 26 | #ifdef _WIN32 27 | #include <winsock2.h> 28 | #endif 29 | #include <tor_api.h> 30 | // Ref: https://stackoverflow.com/questions/45997786/passing-array-of-string-as-parameter-from-go-to-c-function 31 | static char** makeCharArray(int size) { 32 | return calloc(sizeof(char*), size); 33 | } 34 | static void setArrayString(char **a, char *s, int n) { 35 | a[n] = s; 36 | } 37 | static void freeCharArray(char **a, int size) { 38 | int i; 39 | for (i = 0; i < size; i++) 40 | free(a[i]); 41 | free(a); 42 | } 43 | */ 44 | import "C" 45 | 46 | type embeddedCreator struct{} 47 | 48 | // ProviderVersion returns the Tor provider name and version exposed from the 49 | // Tor embedded API. 50 | func ProviderVersion() string { 51 | return C.GoString(C.tor_api_get_provider_version()) 52 | } 53 | 54 | // NewCreator creates a process.Creator for statically-linked Tor embedded in 55 | // the binary. 56 | func NewCreator() process.Creator { 57 | return embeddedCreator{} 58 | } 59 | 60 | type embeddedProcess struct { 61 | ctx context.Context 62 | mainConf *C.struct_tor_main_configuration_t 63 | args []string 64 | doneCh chan int 65 | } 66 | 67 | // New implements process.Creator.New 68 | func (embeddedCreator) New(ctx context.Context, args ...string) (process.Process, error) { 69 | return &embeddedProcess{ 70 | ctx: ctx, 71 | // TODO: mem leak if they never call Start; consider adding a Close() 72 | mainConf: C.tor_main_configuration_new(), 73 | args: args, 74 | }, nil 75 | } 76 | 77 | func (e *embeddedProcess) Start() error { 78 | if e.doneCh != nil { 79 | return fmt.Errorf("Already started") 80 | } 81 | // Create the char array for the args 82 | args := append([]string{"tor"}, e.args...) 83 | charArray := C.makeCharArray(C.int(len(args))) 84 | for i, a := range args { 85 | C.setArrayString(charArray, C.CString(a), C.int(i)) 86 | } 87 | // Build the conf 88 | if code := C.tor_main_configuration_set_command_line(e.mainConf, C.int(len(args)), charArray); code != 0 { 89 | C.tor_main_configuration_free(e.mainConf) 90 | C.freeCharArray(charArray, C.int(len(args))) 91 | return fmt.Errorf("Failed to set command line args, code: %v", int(code)) 92 | } 93 | // Run it async 94 | e.doneCh = make(chan int, 1) 95 | go func() { 96 | defer C.freeCharArray(charArray, C.int(len(args))) 97 | defer C.tor_main_configuration_free(e.mainConf) 98 | e.doneCh <- int(C.tor_run_main(e.mainConf)) 99 | }() 100 | return nil 101 | } 102 | 103 | func (e *embeddedProcess) Wait() error { 104 | if e.doneCh == nil { 105 | return fmt.Errorf("Not started") 106 | } 107 | ctx := e.ctx 108 | if ctx == nil { 109 | ctx = context.Background() 110 | } 111 | select { 112 | case <-ctx.Done(): 113 | return ctx.Err() 114 | case code := <-e.doneCh: 115 | if code == 0 { 116 | return nil 117 | } 118 | return fmt.Errorf("Command completed with error exit code: %v", code) 119 | } 120 | } 121 | 122 | func (e *embeddedProcess) EmbeddedControlConn() (net.Conn, error) { 123 | file := os.NewFile(uintptr(C.tor_main_configuration_setup_control_socket(e.mainConf)), "") 124 | conn, err := net.FileConn(file) 125 | if err != nil { 126 | err = fmt.Errorf("Unable to create conn from control socket: %v", err) 127 | } 128 | return conn, err 129 | } 130 | -------------------------------------------------------------------------------- /process/embedded/tor-0.3.5/process.go: -------------------------------------------------------------------------------- 1 | // Package tor035 implements process interfaces for statically linked 2 | // Tor 0.3.5.x versions. See the process/embedded package for the generic 3 | // abstraction 4 | package tor035 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | "net" 10 | "os" 11 | 12 | "github.com/cretz/bine/process" 13 | ) 14 | 15 | /* 16 | #cgo CFLAGS: -I${SRCDIR}/../../../../tor-static/tor/src/feature/api 17 | // The libs below are generated via tor-static's show-libs 18 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/tor/src/core -ltor-app 19 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/tor/src/lib -ltor-compress -ltor-evloop -ltor-tls -ltor-crypt-ops -lcurve25519_donna -ltor-geoip -ltor-process -ltor-time -ltor-fs -ltor-encoding -ltor-sandbox -ltor-container -ltor-net -ltor-thread -ltor-memarea -ltor-math -ltor-meminfo -ltor-osinfo -ltor-log -ltor-lock -ltor-fdio -ltor-string -ltor-term -ltor-smartlist-core -ltor-malloc -ltor-wallclock -ltor-err -ltor-intmath -ltor-ctime -ltor-trace 20 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/tor/src/ext/keccak-tiny -lkeccak-tiny 21 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/tor/src/ext/ed25519/ref10 -led25519_ref10 22 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/tor/src/ext/ed25519/donna -led25519_donna 23 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/tor/src/trunnel -lor-trunnel 24 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/libevent/dist/lib -levent 25 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/xz/dist/lib -llzma 26 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/zlib/dist/lib -lz 27 | #cgo LDFLAGS: -L${SRCDIR}/../../../../tor-static/openssl/dist/lib -lssl -lcrypto 28 | #cgo windows LDFLAGS: -lws2_32 -lcrypt32 -lgdi32 -liphlpapi -Wl,-Bstatic -lpthread 29 | #cgo !windows LDFLAGS: -lm 30 | 31 | #include <stdlib.h> 32 | #ifdef _WIN32 33 | #include <winsock2.h> 34 | #endif 35 | #include <tor_api.h> 36 | 37 | // Ref: https://stackoverflow.com/questions/45997786/passing-array-of-string-as-parameter-from-go-to-c-function 38 | 39 | static char** makeCharArray(int size) { 40 | return calloc(sizeof(char*), size); 41 | } 42 | 43 | static void setArrayString(char **a, char *s, int n) { 44 | a[n] = s; 45 | } 46 | 47 | static void freeCharArray(char **a, int size) { 48 | int i; 49 | for (i = 0; i < size; i++) 50 | free(a[i]); 51 | free(a); 52 | } 53 | */ 54 | import "C" 55 | 56 | type embeddedCreator struct{} 57 | 58 | // ProviderVersion returns the Tor provider name and version exposed from the 59 | // Tor embedded API. 60 | func ProviderVersion() string { 61 | return C.GoString(C.tor_api_get_provider_version()) 62 | } 63 | 64 | // NewCreator creates a process.Creator for statically-linked Tor embedded in 65 | // the binary. 66 | func NewCreator() process.Creator { 67 | return embeddedCreator{} 68 | } 69 | 70 | type embeddedProcess struct { 71 | ctx context.Context 72 | mainConf *C.struct_tor_main_configuration_t 73 | args []string 74 | doneCh chan int 75 | } 76 | 77 | // New implements process.Creator.New 78 | func (embeddedCreator) New(ctx context.Context, args ...string) (process.Process, error) { 79 | return &embeddedProcess{ 80 | ctx: ctx, 81 | // TODO: mem leak if they never call Start; consider adding a Close() 82 | mainConf: C.tor_main_configuration_new(), 83 | args: args, 84 | }, nil 85 | } 86 | 87 | func (e *embeddedProcess) Start() error { 88 | if e.doneCh != nil { 89 | return fmt.Errorf("Already started") 90 | } 91 | // Create the char array for the args 92 | args := append([]string{"tor"}, e.args...) 93 | charArray := C.makeCharArray(C.int(len(args))) 94 | for i, a := range args { 95 | C.setArrayString(charArray, C.CString(a), C.int(i)) 96 | } 97 | // Build the conf 98 | if code := C.tor_main_configuration_set_command_line(e.mainConf, C.int(len(args)), charArray); code != 0 { 99 | C.tor_main_configuration_free(e.mainConf) 100 | C.freeCharArray(charArray, C.int(len(args))) 101 | return fmt.Errorf("Failed to set command line args, code: %v", int(code)) 102 | } 103 | // Run it async 104 | e.doneCh = make(chan int, 1) 105 | go func() { 106 | defer C.freeCharArray(charArray, C.int(len(args))) 107 | defer C.tor_main_configuration_free(e.mainConf) 108 | e.doneCh <- int(C.tor_run_main(e.mainConf)) 109 | }() 110 | return nil 111 | } 112 | 113 | func (e *embeddedProcess) Wait() error { 114 | if e.doneCh == nil { 115 | return fmt.Errorf("Not started") 116 | } 117 | ctx := e.ctx 118 | if ctx == nil { 119 | ctx = context.Background() 120 | } 121 | select { 122 | case <-ctx.Done(): 123 | return ctx.Err() 124 | case code := <-e.doneCh: 125 | if code == 0 { 126 | return nil 127 | } 128 | return fmt.Errorf("Command completed with error exit code: %v", code) 129 | } 130 | } 131 | 132 | func (e *embeddedProcess) EmbeddedControlConn() (net.Conn, error) { 133 | file := os.NewFile(uintptr(C.tor_main_configuration_setup_control_socket(e.mainConf)), "") 134 | conn, err := net.FileConn(file) 135 | if err != nil { 136 | err = fmt.Errorf("Unable to create conn from control socket: %v", err) 137 | } 138 | return conn, err 139 | } 140 | -------------------------------------------------------------------------------- /control/cmd_onion.go: -------------------------------------------------------------------------------- 1 | package control 2 | 3 | import ( 4 | "crypto/rsa" 5 | "crypto/x509" 6 | "encoding/base64" 7 | "fmt" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/cretz/bine/torutil" 12 | "github.com/cretz/bine/torutil/ed25519" 13 | ) 14 | 15 | // KeyType is a key type for Key in AddOnion. 16 | type KeyType string 17 | 18 | const ( 19 | // KeyTypeNew is NEW. 20 | KeyTypeNew KeyType = "NEW" 21 | // KeyTypeRSA1024 is RSA1024. 22 | KeyTypeRSA1024 KeyType = "RSA1024" 23 | // KeyTypeED25519V3 is ED25519-V3. 24 | KeyTypeED25519V3 KeyType = "ED25519-V3" 25 | ) 26 | 27 | // KeyAlgo is a key algorithm for GenKey on AddOnion. 28 | type KeyAlgo string 29 | 30 | const ( 31 | // KeyAlgoBest is BEST. 32 | KeyAlgoBest KeyAlgo = "BEST" 33 | // KeyAlgoRSA1024 is RSA1024. 34 | KeyAlgoRSA1024 KeyAlgo = "RSA1024" 35 | // KeyAlgoED25519V3 is ED25519-V3. 36 | KeyAlgoED25519V3 KeyAlgo = "ED25519-V3" 37 | ) 38 | 39 | // Key is a type of key to use for AddOnion. Implementations include GenKey, 40 | // RSAKey, and ED25519Key. 41 | type Key interface { 42 | // Type is the KeyType for AddOnion. 43 | Type() KeyType 44 | // Blob is the serialized key for AddOnion. 45 | Blob() string 46 | } 47 | 48 | // KeyFromString creates a Key for AddOnion based on a response string. 49 | func KeyFromString(str string) (Key, error) { 50 | typ, blob, _ := torutil.PartitionString(str, ':') 51 | switch KeyType(typ) { 52 | case KeyTypeNew: 53 | return GenKeyFromBlob(blob), nil 54 | case KeyTypeRSA1024: 55 | return RSA1024KeyFromBlob(blob) 56 | case KeyTypeED25519V3: 57 | return ED25519KeyFromBlob(blob) 58 | default: 59 | return nil, fmt.Errorf("Unrecognized key type: %v", typ) 60 | } 61 | } 62 | 63 | // GenKey is a Key for AddOnion that asks Tor to generate a key for the given 64 | // algorithm. 65 | type GenKey KeyAlgo 66 | 67 | // GenKeyFromBlob creates a GenKey for the given response blob which is a 68 | // KeyAlgo. 69 | func GenKeyFromBlob(blob string) GenKey { return GenKey(KeyAlgo(blob)) } 70 | 71 | // Type implements Key.Type. 72 | func (GenKey) Type() KeyType { return KeyTypeNew } 73 | 74 | // Blob implements Key.Blob. 75 | func (g GenKey) Blob() string { return string(g) } 76 | 77 | // RSAKey is a Key for AddOnion that is a RSA-1024 key (i.e. v2). 78 | type RSAKey struct{ *rsa.PrivateKey } 79 | 80 | // RSA1024KeyFromBlob creates a RSAKey for the given response blob. 81 | func RSA1024KeyFromBlob(blob string) (*RSAKey, error) { 82 | byts, err := base64.StdEncoding.DecodeString(blob) 83 | if err != nil { 84 | return nil, err 85 | } 86 | rsaKey, err := x509.ParsePKCS1PrivateKey(byts) 87 | if err != nil { 88 | return nil, err 89 | } 90 | return &RSAKey{rsaKey}, nil 91 | } 92 | 93 | // Type implements Key.Type. 94 | func (*RSAKey) Type() KeyType { return KeyTypeRSA1024 } 95 | 96 | // Blob implements Key.Blob. 97 | func (r *RSAKey) Blob() string { 98 | return base64.StdEncoding.EncodeToString(x509.MarshalPKCS1PrivateKey(r.PrivateKey)) 99 | } 100 | 101 | // ED25519Key is a Key for AddOnion that is a ed25519 key (i.e. v3). 102 | type ED25519Key struct{ ed25519.KeyPair } 103 | 104 | // ED25519KeyFromBlob creates a ED25519Key for the given response blob. 105 | func ED25519KeyFromBlob(blob string) (*ED25519Key, error) { 106 | byts, err := base64.StdEncoding.DecodeString(blob) 107 | if err != nil { 108 | return nil, err 109 | } 110 | return &ED25519Key{ed25519.PrivateKey(byts).KeyPair()}, nil 111 | } 112 | 113 | // Type implements Key.Type. 114 | func (*ED25519Key) Type() KeyType { return KeyTypeED25519V3 } 115 | 116 | // Blob implements Key.Blob. 117 | func (e *ED25519Key) Blob() string { return base64.StdEncoding.EncodeToString(e.PrivateKey()) } 118 | 119 | // AddOnionRequest is a set of request params for AddOnion. 120 | type AddOnionRequest struct { 121 | // Key is the key to use or GenKey if Tor should generate it. 122 | Key Key 123 | // Flags are ADD_ONION flags. 124 | Flags []string 125 | // MaxStreams is ADD_ONION MaxStreams. 126 | MaxStreams int 127 | // Ports are ADD_ONION Port values. Key is virtual port, Val is target 128 | // port (or can be empty to use virtual port). 129 | Ports []*KeyVal 130 | // ClientAuths are ADD_ONION V3Key values. 131 | ClientAuths []string 132 | } 133 | 134 | // AddOnionResponse is the response for AddOnion. 135 | type AddOnionResponse struct { 136 | // ServiceID is the ADD_ONION response ServiceID value. 137 | ServiceID string 138 | // Key is the ADD_ONION response PrivateKey value. 139 | Key Key 140 | // RawResponse is the raw ADD_ONION response. 141 | RawResponse *Response 142 | } 143 | 144 | // AddOnion invokes ADD_ONION and returns its response. 145 | func (c *Conn) AddOnion(req *AddOnionRequest) (*AddOnionResponse, error) { 146 | // Build command 147 | if req.Key == nil { 148 | return nil, c.protoErr("Key required") 149 | } 150 | cmd := "ADD_ONION " + string(req.Key.Type()) + ":" + req.Key.Blob() 151 | if len(req.Flags) > 0 { 152 | cmd += " Flags=" + strings.Join(req.Flags, ",") 153 | } 154 | if req.MaxStreams > 0 { 155 | cmd += " MaxStreams=" + strconv.Itoa(req.MaxStreams) 156 | } 157 | for _, port := range req.Ports { 158 | cmd += " Port=" + port.Key 159 | if port.Val != "" { 160 | cmd += "," + port.Val 161 | } 162 | } 163 | for _, blob := range req.ClientAuths { 164 | cmd += " ClientAuthV3=" + blob 165 | } 166 | // Invoke and read response 167 | resp, err := c.SendRequest(cmd) 168 | if err != nil { 169 | return nil, err 170 | } 171 | ret := &AddOnionResponse{RawResponse: resp} 172 | for _, data := range resp.Data { 173 | key, val, _ := torutil.PartitionString(data, '=') 174 | switch key { 175 | case "ServiceID": 176 | ret.ServiceID = val 177 | case "PrivateKey": 178 | if ret.Key, err = KeyFromString(val); err != nil { 179 | return nil, err 180 | } 181 | } 182 | } 183 | return ret, nil 184 | } 185 | 186 | // DelOnion invokes DELONION. 187 | func (c *Conn) DelOnion(serviceID string) error { 188 | return c.sendRequestIgnoreResponse("DEL_ONION %v", serviceID) 189 | } 190 | -------------------------------------------------------------------------------- /torutil/key_test.go: -------------------------------------------------------------------------------- 1 | package torutil 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "encoding/base64" 7 | "math/big" 8 | "testing" 9 | 10 | "github.com/cretz/bine/torutil/ed25519" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func genRsa(t *testing.T, bits int) *rsa.PrivateKey { 15 | k, e := rsa.GenerateKey(rand.Reader, bits) 16 | require.NoError(t, e) 17 | return k 18 | } 19 | 20 | func genEd25519(t *testing.T) ed25519.KeyPair { 21 | k, e := ed25519.GenerateKey(nil) 22 | require.NoError(t, e) 23 | return k 24 | } 25 | 26 | func TestOnionServiceIDFromPrivateKey(t *testing.T) { 27 | assert := func(key interface{}, shouldPanic bool) { 28 | if shouldPanic { 29 | require.Panics(t, func() { OnionServiceIDFromPrivateKey(key) }) 30 | } else { 31 | require.NotPanics(t, func() { OnionServiceIDFromPrivateKey(key) }) 32 | } 33 | } 34 | assert(nil, true) 35 | assert("bad type", true) 36 | assert(genRsa(t, 512), true) 37 | assert(genRsa(t, 1024), false) 38 | assert(genEd25519(t), false) 39 | } 40 | 41 | func TestOnionServiceIDFromPublicKey(t *testing.T) { 42 | assert := func(key interface{}, shouldPanic bool) { 43 | if shouldPanic { 44 | require.Panics(t, func() { OnionServiceIDFromPublicKey(key) }) 45 | } else { 46 | require.NotPanics(t, func() { OnionServiceIDFromPublicKey(key) }) 47 | } 48 | } 49 | assert(nil, true) 50 | assert("bad type", true) 51 | assert(genRsa(t, 512).Public(), true) 52 | assert(genRsa(t, 1024), true) 53 | assert(genRsa(t, 1024).Public(), false) 54 | assert(genEd25519(t), true) 55 | assert(genEd25519(t).Public(), false) 56 | } 57 | 58 | func TestOnionServiceIDFromV2PublicKey(t *testing.T) { 59 | keyNs := []string{ 60 | // Good: 61 | "146324450904690776677821409274235322093351159271459646966603918354799061259062657420293876128692345182038558188684966983800327732948675482476772223940488746110835191444662533167597590461666544121677987412778085089886835490778554764504249900341150942052002951429704745527158573712253866271451928082512868548761", 62 | "128765593328258418045179848773717342016715415508670816023595649916344640363150769867803188216600032423256896646578968925175093584252663716464819945657362904808776223191437446288878991355616138261909405164010386485361833909203128674413041630645284466111155610996017814867550636519109125461589251061597106654453", 63 | "142816677715940797613971689484332187730646681999601531244837211468050734148365138492918019219363903243436898624689103727294808675158556496441738627693945143098034304873441312947712853824963023184593797741228534339590785521072446422663170639163836372239933736851693970208563926767141632739068954958552435402293", 64 | "145115352997770387235216741368218582671004692828327605746752722031765658311649572143281396789387808261614671508963042791801662334421789227429337599249357503724975792005849908733936522427330824294880823009884401313371327997012363609954851207630328042324027016587584799514594101157535904741483269310276131442141", 65 | "147719637109219630754585551462675301139659936682064979504052824885582296579356301771435242063159743126441027484306731955552256555531866636211668612294755914990702770530441483651548916585382488692381916953093261634746890673551241873307767188168965986976533243218915179497387875035829308609534245761833108189053", 66 | // Bad (512 bit): 67 | "12406459612976799354275054531003074054562219068852891594185203203668633138039185159716483674833390801567933368800574140712590387835931746258315639847176501", 68 | } 69 | matchingIDs := []string{ 70 | "kqxsrkmm272hqvbj", 71 | "75rzoc3nxzucidqb", 72 | "l2vxsdecx6yita6r", 73 | "hzma3bmo7mtyr5mq", 74 | "prek6tayypvteljb", 75 | "", 76 | } 77 | for i, keyN := range keyNs { 78 | pubKey := &rsa.PublicKey{E: 65537, N: new(big.Int)} 79 | pubKey.N, _ = pubKey.N.SetString(keyN, 10) 80 | matchingID := matchingIDs[i] 81 | if matchingID == "" { 82 | require.Panics(t, func() { OnionServiceIDFromV2PublicKey(pubKey) }) 83 | } else { 84 | require.Equal(t, matchingID, OnionServiceIDFromV2PublicKey(pubKey)) 85 | } 86 | } 87 | } 88 | 89 | func TestOnionServiceIDFromV3PublicKey(t *testing.T) { 90 | base64Keys := []string{ 91 | "SLne6D/uawqUj23619GbeYCd6HnzYPqyUvF8/xyz/3XNVpkgnonQI+J5NQVSGkppD1b0M87+qOtUBmVXsd7H3w", 92 | "kPUs5aPoqISZVbg0q7coW+mNCODlcL4O7k2QWFOCC0gOQBiDm+g4Xz48lqucA7o2HIQ3gBdL5rlB6+q1tFdJwQ", 93 | "YGzw/EwpcqfWb5UWIw652Ps4vTKu38VgX7Qo16XvOWjNWQK9YmfgARYiGQ1XYXEAKBJvoq8x+rKFbQN3FG1F6w", 94 | "IJIZcWE57n5WCvHU2x7GkpBCIw0S0vWd+QyrE5RifGwPtYsbtxjyOxlb754Z0zXLZc+yQUp9hMQt5dt/YNpMag", 95 | "SD7d4I6ZOjNlcqR2g4ptFJUw0tUHPQvfk92sExvnJ1uofPw9T9LUaaEs3rE/1yoGWKI4YejAzaTJXF9wrWQyuA", 96 | } 97 | matchingIDs := []string{ 98 | "2s2wk473fmotzgh6l2ycigrwegnurlzufatjm3bglrb36zbvlerskxad", 99 | "tmcpdbgklpbywqyjpr7fijvjl7qjihd7pyubosbeohefec2m2thvzoqd", 100 | "nrcan5uye2fwazixubug6pzrzp6ofjez43bjcyfoxhgxyygxbhgs4zqd", 101 | "g2csv4kavhunvs45vxxc5ljz775d5a4ycqo4m4nrwpk3b4gryvz2zdyd", 102 | "jviaiibaz7r6wqxttj5i2bi4zjfilmsevplwwtxdfyjph2sdmq5osdid", 103 | } 104 | for i, base64Key := range base64Keys { 105 | key, err := base64.RawStdEncoding.DecodeString(base64Key) 106 | require.NoError(t, err) 107 | pubKey := ed25519.PrivateKey(key).PublicKey() 108 | matchingID := matchingIDs[i] 109 | require.Equal(t, matchingID, OnionServiceIDFromV3PublicKey(pubKey)) 110 | // Check verify here too 111 | derivedPubKey, err := PublicKeyFromV3OnionServiceID(matchingID) 112 | require.NoError(t, err) 113 | require.Equal(t, pubKey, derivedPubKey) 114 | // Let's mangle the matchingID a bit 115 | tooLong := matchingID + "ddddd" 116 | _, err = PublicKeyFromV3OnionServiceID(tooLong) 117 | require.EqualError(t, err, "Invalid id length") 118 | badVersion := matchingID[:len(matchingID)-1] + "e" 119 | _, err = PublicKeyFromV3OnionServiceID(badVersion) 120 | require.EqualError(t, err, "Invalid version") 121 | badChecksum := []byte(matchingID) 122 | badChecksum[len(badChecksum)-3] = 'q' 123 | _, err = PublicKeyFromV3OnionServiceID(string(badChecksum)) 124 | require.EqualError(t, err, "Invalid checksum") 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /torutil/ed25519/ed25519_test.go: -------------------------------------------------------------------------------- 1 | package ed25519 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "compress/gzip" 7 | "crypto" 8 | "crypto/rand" 9 | "encoding/hex" 10 | "os" 11 | "strings" 12 | "testing" 13 | 14 | "github.com/cretz/bine/torutil/ed25519/internal/edwards25519" 15 | ) 16 | 17 | // Taken from https://github.com/golang/crypto/blob/1a580b3eff7814fc9b40602fd35256c63b50f491/ed25519/ed25519_test.go 18 | 19 | type zeroReader struct{} 20 | 21 | func (zeroReader) Read(buf []byte) (int, error) { 22 | for i := range buf { 23 | buf[i] = 0 24 | } 25 | return len(buf), nil 26 | } 27 | 28 | func TestUnmarshalMarshal(t *testing.T) { 29 | pair, _ := GenerateKey(rand.Reader) 30 | 31 | var A edwards25519.ExtendedGroupElement 32 | var pubBytes [32]byte 33 | copy(pubBytes[:], pair.PublicKey()) 34 | if !A.FromBytes(&pubBytes) { 35 | t.Fatalf("ExtendedGroupElement.FromBytes failed") 36 | } 37 | 38 | var pub2 [32]byte 39 | A.ToBytes(&pub2) 40 | 41 | if pubBytes != pub2 { 42 | t.Errorf("FromBytes(%v)->ToBytes does not round-trip, got %x\n", pubBytes, pub2) 43 | } 44 | } 45 | 46 | func TestSignVerify(t *testing.T) { 47 | var zero zeroReader 48 | pair, _ := GenerateKey(zero) 49 | 50 | message := []byte("test message") 51 | sig := Sign(pair, message) 52 | if !Verify(pair.PublicKey(), message, sig) { 53 | t.Errorf("valid signature rejected") 54 | } 55 | 56 | wrongMessage := []byte("wrong message") 57 | if Verify(pair.PublicKey(), wrongMessage, sig) { 58 | t.Errorf("signature of different message accepted") 59 | } 60 | } 61 | 62 | func TestCryptoSigner(t *testing.T) { 63 | var zero zeroReader 64 | pair, _ := GenerateKey(zero) 65 | 66 | signer := crypto.Signer(pair) 67 | 68 | publicInterface := signer.Public() 69 | public2, ok := publicInterface.(PublicKey) 70 | if !ok { 71 | t.Fatalf("expected PublicKey from Public() but got %T", publicInterface) 72 | } 73 | 74 | if !bytes.Equal(pair.PublicKey(), public2) { 75 | t.Errorf("public keys do not match: original:%x vs Public():%x", pair.PublicKey(), public2) 76 | } 77 | 78 | message := []byte("message") 79 | var noHash crypto.Hash 80 | signature, err := signer.Sign(zero, message, noHash) 81 | if err != nil { 82 | t.Fatalf("error from Sign(): %s", err) 83 | } 84 | 85 | if !Verify(pair.PublicKey(), message, signature) { 86 | t.Errorf("Verify failed on signature from Sign()") 87 | } 88 | } 89 | 90 | func TestGolden(t *testing.T) { 91 | // sign.input.gz is a selection of test cases from 92 | // https://ed25519.cr.yp.to/python/sign.input 93 | testDataZ, err := os.Open("testdata/sign.input.gz") 94 | if err != nil { 95 | t.Fatal(err) 96 | } 97 | defer testDataZ.Close() 98 | testData, err := gzip.NewReader(testDataZ) 99 | if err != nil { 100 | t.Fatal(err) 101 | } 102 | defer testData.Close() 103 | 104 | scanner := bufio.NewScanner(testData) 105 | lineNo := 0 106 | 107 | for scanner.Scan() { 108 | lineNo++ 109 | 110 | line := scanner.Text() 111 | parts := strings.Split(line, ":") 112 | if len(parts) != 5 { 113 | t.Fatalf("bad number of parts on line %d", lineNo) 114 | } 115 | 116 | privBytes, _ := hex.DecodeString(parts[0]) 117 | pubKey, _ := hex.DecodeString(parts[1]) 118 | msg, _ := hex.DecodeString(parts[2]) 119 | sig, _ := hex.DecodeString(parts[3]) 120 | // The signatures in the test vectors also include the message 121 | // at the end, but we just want R and S. 122 | sig = sig[:SignatureSize] 123 | 124 | if l := len(pubKey); l != PublicKeySize { 125 | t.Fatalf("bad public key length on line %d: got %d bytes", lineNo, l) 126 | } 127 | 128 | var otherPriv [PrivateKeySize]byte 129 | copy(otherPriv[:], privBytes) 130 | copy(otherPriv[32:], pubKey) 131 | priv := FromCryptoPrivateKey(otherPriv[:]) 132 | 133 | sig2 := Sign(priv, msg) 134 | if !bytes.Equal(sig, sig2[:]) { 135 | t.Errorf("different signature result on line %d: %x vs %x", lineNo, sig, sig2) 136 | } 137 | 138 | if !Verify(pubKey, msg, sig2) { 139 | t.Errorf("signature failed to verify on line %d", lineNo) 140 | } 141 | } 142 | 143 | if err := scanner.Err(); err != nil { 144 | t.Fatalf("error reading test data: %s", err) 145 | } 146 | } 147 | 148 | func TestMalleability(t *testing.T) { 149 | // https://tools.ietf.org/html/rfc8032#section-5.1.7 adds an additional test 150 | // that s be in [0, order). This prevents someone from adding a multiple of 151 | // order to s and obtaining a second valid signature for the same message. 152 | msg := []byte{0x54, 0x65, 0x73, 0x74} 153 | sig := []byte{ 154 | 0x7c, 0x38, 0xe0, 0x26, 0xf2, 0x9e, 0x14, 0xaa, 0xbd, 0x05, 0x9a, 155 | 0x0f, 0x2d, 0xb8, 0xb0, 0xcd, 0x78, 0x30, 0x40, 0x60, 0x9a, 0x8b, 156 | 0xe6, 0x84, 0xdb, 0x12, 0xf8, 0x2a, 0x27, 0x77, 0x4a, 0xb0, 0x67, 157 | 0x65, 0x4b, 0xce, 0x38, 0x32, 0xc2, 0xd7, 0x6f, 0x8f, 0x6f, 0x5d, 158 | 0xaf, 0xc0, 0x8d, 0x93, 0x39, 0xd4, 0xee, 0xf6, 0x76, 0x57, 0x33, 159 | 0x36, 0xa5, 0xc5, 0x1e, 0xb6, 0xf9, 0x46, 0xb3, 0x1d, 160 | } 161 | publicKey := []byte{ 162 | 0x7d, 0x4d, 0x0e, 0x7f, 0x61, 0x53, 0xa6, 0x9b, 0x62, 0x42, 0xb5, 163 | 0x22, 0xab, 0xbe, 0xe6, 0x85, 0xfd, 0xa4, 0x42, 0x0f, 0x88, 0x34, 164 | 0xb1, 0x08, 0xc3, 0xbd, 0xae, 0x36, 0x9e, 0xf5, 0x49, 0xfa, 165 | } 166 | 167 | if Verify(publicKey, msg, sig) { 168 | t.Fatal("non-canonical signature accepted") 169 | } 170 | } 171 | 172 | func BenchmarkKeyGeneration(b *testing.B) { 173 | var zero zeroReader 174 | for i := 0; i < b.N; i++ { 175 | if _, err := GenerateKey(zero); err != nil { 176 | b.Fatal(err) 177 | } 178 | } 179 | } 180 | 181 | func BenchmarkSigning(b *testing.B) { 182 | var zero zeroReader 183 | pair, err := GenerateKey(zero) 184 | if err != nil { 185 | b.Fatal(err) 186 | } 187 | message := []byte("Hello, world!") 188 | b.ResetTimer() 189 | for i := 0; i < b.N; i++ { 190 | Sign(pair, message) 191 | } 192 | } 193 | 194 | func BenchmarkVerification(b *testing.B) { 195 | var zero zeroReader 196 | pair, err := GenerateKey(zero) 197 | if err != nil { 198 | b.Fatal(err) 199 | } 200 | message := []byte("Hello, world!") 201 | signature := Sign(pair, message) 202 | b.ResetTimer() 203 | for i := 0; i < b.N; i++ { 204 | Verify(pair.PublicKey(), message, signature) 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /examples/grpc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "log" 8 | "net" 9 | "reflect" 10 | "strings" 11 | "time" 12 | 13 | "github.com/cretz/bine/examples/grpc/pb" 14 | "github.com/cretz/bine/tor" 15 | "google.golang.org/grpc" 16 | ) 17 | 18 | func main() { 19 | if err := run(); err != nil { 20 | log.Fatal(err) 21 | } 22 | } 23 | 24 | func run() error { 25 | log.Printf("Starting Tor") 26 | // We'll give it 5 minutes to run the whole thing (way too much of course, usually about 20 seconds) 27 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) 28 | defer cancel() 29 | t, err := tor.Start(ctx, nil) 30 | if err != nil { 31 | return err 32 | } 33 | defer t.Close() 34 | 35 | log.Printf("Starting onion service, please wait") 36 | server, onionID, err := startServer(ctx, t) 37 | if err != nil { 38 | return err 39 | } 40 | defer server.Stop() 41 | log.Printf("Onion service available at %v.onion", onionID) 42 | 43 | log.Printf("Connecting to onion service") 44 | conn, client, err := startClient(ctx, t, onionID+".onion:80") 45 | if err != nil { 46 | return err 47 | } 48 | defer conn.Close() 49 | 50 | log.Printf("Doing simple RPC") 51 | resp, err := client.JoinStrings(ctx, &pb.JoinStringsRequest{Strings: []string{"foo", "bar", "baz"}, Delimiter: "-"}) 52 | if err != nil { 53 | return err 54 | } else if resp.Joined != "foo-bar-baz" { 55 | return fmt.Errorf("Invalid response: %v", resp.Joined) 56 | } 57 | 58 | log.Printf("Doing server-side streaming RPC") 59 | pStream, err := client.ProvideStrings(ctx, &pb.ProvideStringsRequest{Count: 10}) 60 | if err != nil { 61 | return err 62 | } 63 | for i := 0; i < 10; i++ { 64 | if resp, err := pStream.Recv(); err != nil { 65 | return err 66 | } else if resp.String_ != fmt.Sprintf("string-%v", i+1) { 67 | return fmt.Errorf("Invalid response: %v", resp.String_) 68 | } 69 | } 70 | if _, err = pStream.Recv(); err != io.EOF { 71 | return fmt.Errorf("Expected EOF, got %v", err) 72 | } 73 | 74 | log.Printf("Doing client-side streaming RPC") 75 | rStream, err := client.ReceiveStrings(ctx) 76 | strs := []string{"foo", "bar", "baz"} 77 | for _, str := range strs { 78 | if err := rStream.Send(&pb.ReceiveStringsRequest{String_: str}); err != nil { 79 | return err 80 | } 81 | } 82 | if resp, err := rStream.CloseAndRecv(); err != nil { 83 | return err 84 | } else if !reflect.DeepEqual(resp.Received, strs) { 85 | return fmt.Errorf("Unexpected response: %v", resp.Received) 86 | } 87 | 88 | log.Printf("Doing bi-directional streaming RPC") 89 | eStream, err := client.ExchangeStrings(ctx) 90 | for _, str := range strs { 91 | if err := eStream.Send(&pb.ExchangeStringsRequest{String_: str, WantReturn: str == "baz"}); err != nil { 92 | return err 93 | } 94 | } 95 | if resp, err := eStream.Recv(); err != nil { 96 | return err 97 | } else if !reflect.DeepEqual(resp.Received, strs) { 98 | return fmt.Errorf("Unexpected response: %v", resp.Received) 99 | } 100 | err = eStream.Send(&pb.ExchangeStringsRequest{String_: "one"}) 101 | if err == nil { 102 | err = eStream.Send(&pb.ExchangeStringsRequest{String_: "two", WantReturn: true}) 103 | } 104 | if err == nil { 105 | err = eStream.CloseSend() 106 | } 107 | if err != nil { 108 | return err 109 | } 110 | if resp, err := eStream.Recv(); err != nil { 111 | return err 112 | } else if !reflect.DeepEqual(resp.Received, []string{"one", "two"}) { 113 | return fmt.Errorf("Unexpected response: %v", resp.Received) 114 | } 115 | if _, err = eStream.Recv(); err != io.EOF { 116 | return fmt.Errorf("Expected EOF, got %v", err) 117 | } 118 | 119 | log.Printf("All completed successfully, shutting down") 120 | return nil 121 | } 122 | 123 | func startServer(ctx context.Context, t *tor.Tor) (server *grpc.Server, onionID string, err error) { 124 | // Wait at most a few minutes to publish the service 125 | listenCtx, listenCancel := context.WithTimeout(ctx, 3*time.Minute) 126 | defer listenCancel() 127 | // Create an onion service to listen on a random local port but show as 80 128 | // We'll do version 3 since it's quicker 129 | onion, err := t.Listen(listenCtx, &tor.ListenConf{RemotePorts: []int{80}}) 130 | if err != nil { 131 | return nil, "", err 132 | } 133 | onionID = onion.ID 134 | // Create the grpc server and start it 135 | server = grpc.NewServer() 136 | pb.RegisterSimpleServiceServer(server, simpleService{}) 137 | go func() { 138 | if err := server.Serve(onion); err != nil { 139 | log.Printf("Error serving: %v", err) 140 | } 141 | }() 142 | return 143 | } 144 | 145 | func startClient( 146 | ctx context.Context, t *tor.Tor, addr string, 147 | ) (conn *grpc.ClientConn, client pb.SimpleServiceClient, err error) { 148 | // Wait at most a few minutes to connect to the service 149 | connCtx, connCancel := context.WithTimeout(ctx, 3*time.Minute) 150 | defer connCancel() 151 | // Make the dialer 152 | dialer, err := t.Dialer(connCtx, nil) 153 | if err != nil { 154 | return nil, nil, err 155 | } 156 | // Make the connection 157 | conn, err = grpc.DialContext(connCtx, addr, 158 | grpc.FailOnNonTempDialError(true), 159 | grpc.WithBlock(), 160 | grpc.WithInsecure(), 161 | grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) { 162 | dialCtx, dialCancel := context.WithTimeout(ctx, timeout) 163 | defer dialCancel() 164 | return dialer.DialContext(dialCtx, "tcp", addr) 165 | }), 166 | ) 167 | if err == nil { 168 | client = pb.NewSimpleServiceClient(conn) 169 | } 170 | return 171 | } 172 | 173 | type simpleService struct{} 174 | 175 | func (simpleService) JoinStrings(ctx context.Context, req *pb.JoinStringsRequest) (*pb.JoinStringsResponse, error) { 176 | return &pb.JoinStringsResponse{Joined: strings.Join(req.Strings, req.Delimiter)}, nil 177 | } 178 | 179 | func (simpleService) ProvideStrings(req *pb.ProvideStringsRequest, srv pb.SimpleService_ProvideStringsServer) error { 180 | for i := 0; uint32(i) < req.Count; i++ { 181 | if err := srv.Send(&pb.ProvideStringsResponse{String_: fmt.Sprintf("string-%v", i+1)}); err != nil { 182 | return err 183 | } 184 | } 185 | return nil 186 | } 187 | 188 | func (simpleService) ReceiveStrings(srv pb.SimpleService_ReceiveStringsServer) error { 189 | resp := &pb.ReceiveStringsResponse{} 190 | for { 191 | if req, err := srv.Recv(); err == io.EOF { 192 | return srv.SendAndClose(resp) 193 | } else if err != nil { 194 | return err 195 | } else { 196 | resp.Received = append(resp.Received, req.String_) 197 | } 198 | } 199 | } 200 | 201 | func (simpleService) ExchangeStrings(srv pb.SimpleService_ExchangeStringsServer) error { 202 | resp := &pb.ExchangeStringsResponse{} 203 | for { 204 | if req, err := srv.Recv(); err == io.EOF { 205 | return srv.Send(resp) 206 | } else if err != nil { 207 | return err 208 | } else { 209 | resp.Received = append(resp.Received, req.String_) 210 | if req.WantReturn { 211 | if err := srv.Send(resp); err != nil { 212 | return err 213 | } 214 | resp = &pb.ExchangeStringsResponse{} 215 | } 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /examples/httpaltsvc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "flag" 8 | "fmt" 9 | "io/ioutil" 10 | "log" 11 | "net/http" 12 | "os" 13 | "os/exec" 14 | "path/filepath" 15 | "strings" 16 | 17 | "github.com/cretz/bine/tor" 18 | "github.com/cretz/bine/torutil" 19 | ) 20 | 21 | var verbose bool 22 | 23 | func main() { 24 | flag.BoolVar(&verbose, "verbose", false, "Whether to have verbose logging") 25 | flag.Parse() 26 | if flag.NArg() != 1 { 27 | log.Fatal("Expecting single domain arg") 28 | } else if err := run(flag.Arg(0)); err != nil { 29 | log.Fatal(err) 30 | } 31 | } 32 | 33 | func run(domain string) error { 34 | fmt.Println("Please wait while generating services") 35 | ctx, cancelFn := context.WithCancel(context.Background()) 36 | defer cancelFn() 37 | // Make sure mkcert is available 38 | if _, err := exec.LookPath("mkcert"); err != nil { 39 | return fmt.Errorf("Unable to find mkcert on PATH: %v", err) 40 | } 41 | // Listen until enter pressed 42 | srv, err := start(ctx, domain, ":80") 43 | if err != nil { 44 | return err 45 | } 46 | defer srv.Close() 47 | fmt.Printf("Listening on all IPs on port 80, so http://%v will use second onion as alt-svc\n", domain) 48 | fmt.Printf("Listening on onion http://%v.onion that will use second onion as alt-svc\n", srv.onion1.ID) 49 | fmt.Printf("Created secure second onion at https://%v.onion\n", srv.onion2.ID) 50 | fmt.Println("Press enter to exit") 51 | // Wait for key asynchronously 52 | go func() { 53 | fmt.Scanln() 54 | cancelFn() 55 | }() 56 | select { 57 | case err := <-srv.Err(): 58 | return err 59 | case <-ctx.Done(): 60 | return nil 61 | } 62 | } 63 | 64 | type server struct { 65 | exitAddrs map[string]bool 66 | t *tor.Tor 67 | onion1 *tor.OnionService 68 | onion2 *tor.OnionService 69 | httpSrv *http.Server 70 | httpSrvErrCh chan error 71 | } 72 | 73 | func start(ctx context.Context, domain string, httpAddr string) (srv *server, err error) { 74 | srv = &server{} 75 | // // Get all exit addrs 76 | if srv.exitAddrs, err = getExitAddresses(); err != nil { 77 | return nil, err 78 | } 79 | // Start tor 80 | startConf := &tor.StartConf{DataDir: "tor-data"} 81 | if verbose { 82 | startConf.DebugWriter = os.Stdout 83 | } else { 84 | startConf.ExtraArgs = []string{"--quiet"} 85 | } 86 | if srv.t, err = tor.Start(ctx, startConf); err != nil { 87 | return nil, err 88 | } 89 | // Henceforth, any err needs to call close 90 | // Start Onion 1 91 | if srv.onion1, err = srv.t.Listen(ctx, &tor.ListenConf{RemotePorts: []int{80}}); err != nil { 92 | srv.Close() 93 | return nil, err 94 | } 95 | // Start Onion 2 96 | if srv.onion2, err = srv.t.Listen(ctx, &tor.ListenConf{RemotePorts: []int{443}}); err != nil { 97 | srv.Close() 98 | return nil, err 99 | } 100 | // Call mkcert for both onions 101 | cmd := exec.CommandContext(ctx, "mkcert", domain, srv.onion1.ID+".onion", srv.onion2.ID+".onion") 102 | cmd.Dir = "tor-data" 103 | output, err := cmd.CombinedOutput() 104 | if verbose { 105 | fmt.Printf("Output from mkcert:\n%v\n", string(output)) 106 | } 107 | if err != nil { 108 | srv.Close() 109 | return nil, fmt.Errorf("Failed running mkcert: %v", err) 110 | } 111 | cert := filepath.Join("tor-data", domain+"+2.pem") 112 | key := filepath.Join("tor-data", domain+"+2-key.pem") 113 | // Listen on the onions 114 | srv.httpSrvErrCh = make(chan error, 3) 115 | go func(errCh chan error) { 116 | errCh <- http.Serve(srv.onion1, 117 | srv.NewHandler(srv.onion1.ID+".onion", srv.onion2.ID+".onion:443")) 118 | }(srv.httpSrvErrCh) 119 | go func(errCh chan error) { 120 | errCh <- http.ServeTLS(srv.onion2, 121 | srv.NewHandler(srv.onion2.ID+".onion", "", "http://"+srv.onion1.ID+".onion", "https://"+domain, 122 | "http://"+domain), cert, key) 123 | }(srv.httpSrvErrCh) 124 | // Start HTTP server 125 | srv.httpSrv = &http.Server{Addr: httpAddr, Handler: srv.NewHandler(httpAddr, srv.onion2.ID+".onion:443")} 126 | go func(httpSrv *http.Server, errCh chan error) { errCh <- httpSrv.ListenAndServe() }(srv.httpSrv, srv.httpSrvErrCh) 127 | return 128 | } 129 | 130 | func (s *server) Err() <-chan error { return s.httpSrvErrCh } 131 | 132 | func (s *server) NewHandler(siteAddr string, altSvc string, origins ...string) http.Handler { 133 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 134 | // TODO: re-enable if necessary 135 | // if r.URL.Path == "/.well-known/http-opportunistic" { 136 | // s.handleOpportunistic(siteAddr, origins, w, r) 137 | // } else { 138 | s.handleRegularRequest(siteAddr, altSvc, w, r) 139 | // } 140 | }) 141 | } 142 | 143 | func (s *server) handleOpportunistic(siteAddr string, origins []string, w http.ResponseWriter, r *http.Request) { 144 | if verbose { 145 | fmt.Printf("-------\nAccessed %v, responding with origins %v, site info:\n%v-------\n", 146 | siteAddr, origins, string(s.requestInfo(siteAddr, r))) 147 | } 148 | w.Header().Add("Content-Type", "application/json") 149 | byts, _ := json.Marshal(origins) 150 | w.Write(byts) 151 | } 152 | 153 | func (s *server) handleRegularRequest(siteAddr string, altSvc string, w http.ResponseWriter, r *http.Request) { 154 | // Set an alt-svc header 155 | if altSvc != "" { 156 | w.Header().Add("Alt-Svc", "h2=\""+altSvc+"\"; ma=600") 157 | } 158 | // Respond 159 | resp := s.requestInfo(siteAddr, r) 160 | if verbose { 161 | fmt.Printf("-------\nAccessed %v, responding with:\n%v-------\n", siteAddr, string(resp)) 162 | } 163 | w.Write(resp) 164 | } 165 | 166 | func (s *server) requestInfo(siteAddr string, r *http.Request) []byte { 167 | remoteAddr := r.Header.Get("X-Forwarded-For") 168 | if remoteAddr == "" { 169 | remoteAddr, _, _ = torutil.PartitionString(r.RemoteAddr, ':') 170 | } 171 | exit := "" 172 | if !s.exitAddrs[remoteAddr] { 173 | exit = " NOT" 174 | } 175 | buf := &bytes.Buffer{} 176 | fmt.Fprintf(buf, "Server-side site addr: %v\n", siteAddr) 177 | fmt.Fprintf(buf, "You accessed %v on %v from %v which is%v an exit node\n", 178 | r.URL.Path, r.Host, remoteAddr, exit) 179 | fmt.Fprintf(buf, "Headers:\n") 180 | for h, vals := range r.Header { 181 | for _, val := range vals { 182 | fmt.Fprintf(buf, " %v: %v\n", h, val) 183 | } 184 | } 185 | if verbose { 186 | fmt.Printf("-------\nAccessed %v, responding with:\n%v-------\n", siteAddr, string(buf.Bytes())) 187 | } 188 | return buf.Bytes() 189 | } 190 | 191 | func (s *server) Close() { 192 | if s.httpSrv != nil { 193 | s.httpSrv.Close() 194 | } 195 | if s.onion1 != nil { 196 | s.onion1.Close() 197 | } 198 | if s.onion2 != nil { 199 | s.onion2.Close() 200 | } 201 | if s.t != nil { 202 | s.t.Close() 203 | } 204 | } 205 | 206 | func getExitAddresses() (map[string]bool, error) { 207 | resp, err := http.Get("https://check.torproject.org/exit-addresses") 208 | if err != nil { 209 | return nil, err 210 | } 211 | body, err := ioutil.ReadAll(resp.Body) 212 | resp.Body.Close() 213 | if err != nil { 214 | return nil, err 215 | } 216 | ret := map[string]bool{} 217 | for _, line := range strings.Split(string(body), "\n") { 218 | pieces := strings.Split(strings.TrimSpace(line), " ") 219 | if len(pieces) >= 2 && pieces[0] == "ExitAddress" { 220 | ret[pieces[1]] = true 221 | } 222 | } 223 | return ret, nil 224 | } 225 | -------------------------------------------------------------------------------- /torutil/ed25519/ed25519.go: -------------------------------------------------------------------------------- 1 | // Package ed25519 implements Tor/BitTorrent-like ed25519 keys. 2 | // 3 | // See the following stack overflow post for details on why 4 | // golang.org/x/crypto/ed25519 can't be used: 5 | // https://stackoverflow.com/questions/44810708/ed25519-public-result-is-different 6 | package ed25519 7 | 8 | import ( 9 | "crypto" 10 | "crypto/rand" 11 | "crypto/sha512" 12 | "errors" 13 | "io" 14 | 15 | "github.com/cretz/bine/torutil/ed25519/internal/edwards25519" 16 | "golang.org/x/crypto/ed25519" 17 | ) 18 | 19 | const ( 20 | // PublicKeySize is the size, in bytes, of public keys as used in this package. 21 | PublicKeySize = 32 22 | // PrivateKeySize is the size, in bytes, of private keys as used in this package. 23 | PrivateKeySize = 64 24 | // SignatureSize is the size, in bytes, of signatures generated and verified by this package. 25 | SignatureSize = 64 26 | ) 27 | 28 | // PrivateKey is a 64-byte Ed25519 private key. Unlike 29 | // golang.org/x/crypto/ed25519, this is just the digest and does not contain 30 | // the public key within it. Instead call PublicKey() or better, call KeyPair() 31 | // which stores the precomputed public key. 32 | type PrivateKey []byte 33 | 34 | // PublicKey is a 32-byte Ed25519 public key. 35 | type PublicKey []byte 36 | 37 | // FromCryptoPrivateKey converts a Go private key to the one in this package. 38 | func FromCryptoPrivateKey(key ed25519.PrivateKey) KeyPair { 39 | digest := sha512.Sum512(key[:32]) 40 | digest[0] &= 248 41 | digest[31] &= 127 42 | digest[31] |= 64 43 | return &precomputedKeyPair{PrivateKeyBytes: digest[:], PublicKeyBytes: PublicKey(key[32:])} 44 | } 45 | 46 | // FromCryptoPublicKey converts a Go public key to the one in this package. 47 | func FromCryptoPublicKey(key ed25519.PublicKey) PublicKey { 48 | return PublicKey(key) 49 | } 50 | 51 | // KeyPair returns a new key pair with the public key precomputed. 52 | func (p PrivateKey) KeyPair() KeyPair { 53 | return &precomputedKeyPair{PrivateKeyBytes: p, PublicKeyBytes: p.PublicKey()} 54 | } 55 | 56 | // PrivateKey simply returns itself. Implements KeyPair.PrivateKey. 57 | func (p PrivateKey) PrivateKey() PrivateKey { return p } 58 | 59 | // Public simply delegates to PublicKey() to satisfy crypto.Signer. This method 60 | // does a bit more work than the traditional Go ed25519's private key's Public() 61 | // method so developers are encouraged to reuse the result or use KeyPair() 62 | // which stores this value. 63 | func (p PrivateKey) Public() crypto.PublicKey { return p.PublicKey() } 64 | 65 | // PublicKey generates a public key for this private key. This method does a bit 66 | // more work than the traditional Go ed25519's private key's Public() method so 67 | // developers are encouraged to reuse the result or use KeyPair() which stores 68 | // this value. Implements KeyPair.PublicKey. 69 | func (p PrivateKey) PublicKey() PublicKey { 70 | var A edwards25519.ExtendedGroupElement 71 | var hBytes [32]byte 72 | copy(hBytes[:], p[:]) 73 | edwards25519.GeScalarMultBase(&A, &hBytes) 74 | var publicKeyBytes [32]byte 75 | A.ToBytes(&publicKeyBytes) 76 | return publicKeyBytes[:] 77 | } 78 | 79 | // Sign signs the given message with priv. Ed25519 performs two passes over 80 | // messages to be signed and therefore cannot handle pre-hashed messages. Thus 81 | // opts.HashFunc() must return zero to indicate the message hasn't been hashed. 82 | // This can be achieved by passing crypto.Hash(0) as the value for opts. 83 | func (p PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) ([]byte, error) { 84 | if opts.HashFunc() != crypto.Hash(0) { 85 | return nil, errors.New("ed25519: cannot sign hashed message") 86 | } 87 | return Sign(p, message), nil 88 | } 89 | 90 | // Verify simply calls PublicKey().Verify(). Callers are encouraged to instead 91 | // store a precomputed KeyPair (via KeyPair() or GenerateKey()) and call Verify 92 | // on that. 93 | func (p PrivateKey) Verify(message []byte, sig []byte) bool { 94 | return p.PublicKey().Verify(message, sig) 95 | } 96 | 97 | // Verify simply calls the package-level function Verify(). 98 | func (p PublicKey) Verify(message []byte, sig []byte) bool { 99 | return Verify(p, message, sig) 100 | } 101 | 102 | // KeyPair is an interface for types with both keys. While PrivateKey does 103 | // implement this, it generates the PublicKey on demand. For better performance, 104 | // use the result of GenerateKey directly or call PrivateKey.KeyPair(). 105 | type KeyPair interface { 106 | crypto.Signer 107 | PrivateKey() PrivateKey 108 | PublicKey() PublicKey 109 | Verify(message []byte, sig []byte) bool 110 | } 111 | 112 | type precomputedKeyPair struct { 113 | PrivateKeyBytes PrivateKey 114 | PublicKeyBytes PublicKey 115 | } 116 | 117 | func (p *precomputedKeyPair) PrivateKey() PrivateKey { return p.PrivateKeyBytes } 118 | func (p *precomputedKeyPair) PublicKey() PublicKey { return p.PublicKeyBytes } 119 | func (p *precomputedKeyPair) Public() crypto.PublicKey { return p.PublicKey() } 120 | func (p *precomputedKeyPair) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) ([]byte, error) { 121 | if opts.HashFunc() != crypto.Hash(0) { 122 | return nil, errors.New("ed25519: cannot sign hashed message") 123 | } 124 | return Sign(p, message), nil 125 | } 126 | func (p *precomputedKeyPair) Verify(message []byte, sig []byte) bool { 127 | return p.PublicKeyBytes.Verify(message, sig) 128 | } 129 | 130 | // GenerateKey generates a public/private key pair using entropy from rand. 131 | // If rand is nil, crypto/rand.Reader will be used. 132 | func GenerateKey(rnd io.Reader) (KeyPair, error) { 133 | if rnd == nil { 134 | rnd = rand.Reader 135 | } 136 | rndByts := make([]byte, 32) 137 | if _, err := io.ReadFull(rnd, rndByts); err != nil { 138 | return nil, err 139 | } 140 | digest := sha512.Sum512(rndByts) 141 | digest[0] &= 248 142 | digest[31] &= 127 143 | digest[31] |= 64 144 | return PrivateKey(digest[:]).KeyPair(), nil 145 | } 146 | 147 | // Sign signs the message with the given key pair. 148 | func Sign(keyPair KeyPair, message []byte) []byte { 149 | // Ref: https://stackoverflow.com/questions/44810708/ed25519-public-result-is-different 150 | 151 | var privateKeyA [32]byte 152 | copy(privateKeyA[:], keyPair.PrivateKey()) // we need this in an array later 153 | var messageDigest, hramDigest [64]byte 154 | 155 | h := sha512.New() 156 | h.Write(keyPair.PrivateKey()[32:]) 157 | h.Write(message) 158 | h.Sum(messageDigest[:0]) 159 | 160 | var messageDigestReduced [32]byte 161 | edwards25519.ScReduce(&messageDigestReduced, &messageDigest) 162 | var R edwards25519.ExtendedGroupElement 163 | edwards25519.GeScalarMultBase(&R, &messageDigestReduced) 164 | 165 | var encodedR [32]byte 166 | R.ToBytes(&encodedR) 167 | 168 | h.Reset() 169 | h.Write(encodedR[:]) 170 | h.Write(keyPair.PublicKey()) 171 | h.Write(message) 172 | h.Sum(hramDigest[:0]) 173 | var hramDigestReduced [32]byte 174 | edwards25519.ScReduce(&hramDigestReduced, &hramDigest) 175 | 176 | var s [32]byte 177 | edwards25519.ScMulAdd(&s, &hramDigestReduced, &privateKeyA, &messageDigestReduced) 178 | 179 | signature := make([]byte, 64) 180 | copy(signature[:], encodedR[:]) 181 | copy(signature[32:], s[:]) 182 | 183 | return signature 184 | } 185 | 186 | // Verify verifies a signed message. 187 | func Verify(p PublicKey, message []byte, sig []byte) bool { 188 | return ed25519.Verify(ed25519.PublicKey(p), message, sig) 189 | } 190 | -------------------------------------------------------------------------------- /tor/forward.go: -------------------------------------------------------------------------------- 1 | package tor 2 | 3 | import ( 4 | "context" 5 | "crypto" 6 | "fmt" 7 | "strconv" 8 | 9 | "github.com/cretz/bine/control" 10 | "github.com/cretz/bine/torutil/ed25519" 11 | othered25519 "golang.org/x/crypto/ed25519" 12 | ) 13 | 14 | // OnionForward describes a port forward to an onion service. 15 | type OnionForward struct { 16 | // ID is the service ID for this onion service. 17 | ID string 18 | 19 | // Key is the private key for this service. It is either the set key, the 20 | // generated key, or nil if asked to discard the key. If present, it is an 21 | // instance of github.com/cretz/bine/torutil/ed25519.KeyPair. 22 | Key crypto.PrivateKey 23 | 24 | // PortForwards defines the ports that will be forwarded to the onion 25 | // service. 26 | PortForwards map[string][]int 27 | 28 | // The Tor object that created this. Needed for Close. 29 | Tor *Tor 30 | } 31 | 32 | // ForwardConf is the configuration for Forward calls. 33 | type ForwardConf struct { 34 | // PortForwards defines the ports that will be forwarded to the onion 35 | // service. 36 | PortForwards map[string][]int 37 | 38 | // Key is the private key to use. If not present, a key is generated. If 39 | // present, it must be an instance of 40 | // github.com/cretz/bine/torutil/ed25519.KeyPair, a 41 | // golang.org/x/crypto/ed25519.PrivateKey, or a 42 | // github.com/cretz/bine/control.Key. 43 | Key crypto.PrivateKey 44 | 45 | // ClientAuths is the credential set for clients. The values are 46 | // base32-encoded ed25519 public keys. 47 | ClientAuths []string 48 | 49 | // MaxStreams is the maximum number of streams the service will accept. 0 50 | // means unlimited. 51 | MaxStreams int 52 | 53 | // DiscardKey, if true and Key is nil (meaning a private key is generated), 54 | // tells Tor not to return the generated private key. This value is ignored 55 | // if Key is not nil. 56 | DiscardKey bool 57 | 58 | // Detach, if true, prevents the default behavior of the onion service being 59 | // deleted when this controller connection is closed. 60 | Detach bool 61 | 62 | // NonAnonymous must be true if Tor options HiddenServiceSingleHopMode and 63 | // HiddenServiceNonAnonymousMode are set. Otherwise, it must be false. 64 | NonAnonymous bool 65 | 66 | // MaxStreamsCloseCircuit determines whether to close the circuit when the 67 | // maximum number of streams is exceeded. If true, the circuit is closed. If 68 | // false, the stream is simply not connected but the circuit stays open. 69 | MaxStreamsCloseCircuit bool 70 | 71 | // NoWait if true will not wait until the onion service is published. If 72 | // false, the network will be enabled if it's not and then we will wait 73 | // until the onion service is published. 74 | NoWait bool 75 | } 76 | 77 | // Forward creates an onion service which forwards to local ports. The context 78 | // can be nil. conf is required and cannot be nil. 79 | func (t *Tor) Forward(ctx context.Context, conf *ForwardConf) (*OnionForward, error) { 80 | if ctx == nil { 81 | ctx = context.Background() 82 | } 83 | // Create the forward up here and make sure we close it no matter the error within 84 | fwd := &OnionForward{Tor: t} 85 | var err error 86 | 87 | // Henceforth, any error requires we close the svc 88 | 89 | // Build the onion request 90 | req := &control.AddOnionRequest{MaxStreams: conf.MaxStreams, ClientAuths: conf.ClientAuths} 91 | // Set flags 92 | if conf.DiscardKey { 93 | req.Flags = append(req.Flags, "DiscardPK") 94 | } 95 | if conf.Detach { 96 | req.Flags = append(req.Flags, "Detach") 97 | } 98 | if len(conf.ClientAuths) > 0 { 99 | req.Flags = append(req.Flags, "V3Auth") 100 | } 101 | if conf.NonAnonymous { 102 | req.Flags = append(req.Flags, "NonAnonymous") 103 | } 104 | if conf.MaxStreamsCloseCircuit { 105 | req.Flags = append(req.Flags, "MaxStreamsCloseCircuit") 106 | } 107 | // Set the key 108 | switch key := conf.Key.(type) { 109 | case nil: 110 | req.Key = control.GenKey(control.KeyAlgoED25519V3) 111 | case control.GenKey: 112 | req.Key = key 113 | case ed25519.KeyPair: 114 | fwd.Key = key 115 | req.Key = &control.ED25519Key{key} 116 | case othered25519.PrivateKey: 117 | properKey := ed25519.FromCryptoPrivateKey(key) 118 | fwd.Key = properKey 119 | req.Key = &control.ED25519Key{properKey} 120 | case *control.ED25519Key: 121 | fwd.Key = key.KeyPair 122 | req.Key = key 123 | default: 124 | err = fmt.Errorf("Unrecognized key type: %T", key) 125 | } 126 | 127 | // Apply the remote ports 128 | fwd.PortForwards = conf.PortForwards 129 | for localPort, remotePorts := range fwd.PortForwards { 130 | if len(remotePorts) == 0 { 131 | continue 132 | } 133 | for _, remotePort := range remotePorts { 134 | req.Ports = append(req.Ports, &control.KeyVal{ 135 | Key: strconv.Itoa(remotePort), 136 | Val: localPort, 137 | }) 138 | } 139 | } 140 | 141 | // Create the onion service 142 | var resp *control.AddOnionResponse 143 | if err == nil { 144 | resp, err = t.Control.AddOnion(req) 145 | } 146 | 147 | // Apply the response to the service 148 | if err == nil { 149 | fwd.ID = resp.ServiceID 150 | switch key := resp.Key.(type) { 151 | case nil: 152 | // Do nothing 153 | case *control.ED25519Key: 154 | fwd.Key = key.KeyPair 155 | default: 156 | err = fmt.Errorf("Unrecognized result key type: %T", key) 157 | } 158 | } 159 | 160 | // Wait if necessary 161 | if err == nil && !conf.NoWait { 162 | t.Debugf("Enabling network before waiting for publication") 163 | // First make sure network is enabled 164 | if err = t.EnableNetwork(ctx, true); err == nil { 165 | t.Debugf("Waiting for publication") 166 | // Now we'll take a similar approach to Stem. Several UPLOADs are sent out, so we count em. If we see 167 | // UPLOADED, we succeeded. If we see failed, we count those. If there are as many failures as uploads, they 168 | // all failed and it's a failure. NOTE: unlike Stem's comments that say they don't, we are actually seeing 169 | // the service IDs for UPLOADED so we don't keep a map. 170 | uploadsAttempted := 0 171 | failures := []string{} 172 | _, err = t.Control.EventWait(ctx, []control.EventCode{control.EventCodeHSDesc}, 173 | func(evt control.Event) (bool, error) { 174 | hs, _ := evt.(*control.HSDescEvent) 175 | if hs != nil && hs.Address == fwd.ID { 176 | switch hs.Action { 177 | case "UPLOAD": 178 | uploadsAttempted++ 179 | case "FAILED": 180 | failures = append(failures, 181 | fmt.Sprintf("Failed uploading to dir %v - reason: %v", hs.HSDir, hs.Reason)) 182 | if len(failures) == uploadsAttempted { 183 | return false, fmt.Errorf("Failed all uploads, reasons: %v", failures) 184 | } 185 | case "UPLOADED": 186 | return true, nil 187 | } 188 | } 189 | return false, nil 190 | }) 191 | } 192 | } 193 | 194 | // Give back err and close if there is an err 195 | if err != nil { 196 | if closeErr := fwd.Close(); closeErr != nil { 197 | err = fmt.Errorf("Error on listen: %v (also got error trying to close: %v)", err, closeErr) 198 | } 199 | return nil, err 200 | } 201 | return fwd, nil 202 | } 203 | 204 | // String implements fmt.Stringer 205 | func (o *OnionForward) String() string { 206 | return fmt.Sprintf("%v.onion", o.ID) 207 | } 208 | 209 | // Close deletes the onion service. 210 | func (o *OnionForward) Close() (err error) { 211 | o.Tor.Debugf("Closing onion %v", o) 212 | // Delete the onion first 213 | if o.ID != "" { 214 | err = o.Tor.Control.DelOnion(o.ID) 215 | o.ID = "" 216 | } 217 | if err != nil { 218 | o.Tor.Debugf("Failed closing onion: %v", err) 219 | } 220 | return 221 | } 222 | -------------------------------------------------------------------------------- /tor/listen.go: -------------------------------------------------------------------------------- 1 | package tor 2 | 3 | import ( 4 | "context" 5 | "crypto" 6 | "fmt" 7 | "net" 8 | "strconv" 9 | 10 | "github.com/cretz/bine/control" 11 | "github.com/cretz/bine/torutil/ed25519" 12 | othered25519 "golang.org/x/crypto/ed25519" 13 | ) 14 | 15 | // OnionService implements net.Listener and net.Addr for an onion service. 16 | type OnionService struct { 17 | // ID is the service ID for this onion service. 18 | ID string 19 | 20 | // Key is the private key for this service. It is either the set key, the 21 | // generated key, or nil if asked to discard the key. If present, it is an 22 | // instance of github.com/cretz/bine/torutil/ed25519.KeyPair. 23 | Key crypto.PrivateKey 24 | 25 | // LocalListener is the local TCP listener. This is always present. 26 | LocalListener net.Listener 27 | 28 | // RemotePorts is the set of remote ports that are forwarded to the local 29 | // listener. This will always have at least one value. 30 | RemotePorts []int 31 | 32 | // CloseLocalListenerOnClose is true if the local listener should be closed 33 | // on Close. This is set to true if a listener was created by Listen and set 34 | // to false of an existing LocalListener was provided to Listen. 35 | CloseLocalListenerOnClose bool 36 | 37 | // The Tor object that created this. Needed for Close. 38 | Tor *Tor 39 | } 40 | 41 | // ListenConf is the configuration for Listen calls. 42 | type ListenConf struct { 43 | // LocalPort is the local port to create a TCP listener on. If the port is 44 | // 0, it is automatically chosen. This is ignored if LocalListener is set. 45 | LocalPort int 46 | 47 | // LocalListener is the specific local listener to back the onion service. 48 | // If this is nil (the default), then a listener is created with LocalPort. 49 | LocalListener net.Listener 50 | 51 | // RemotePorts are the remote ports to serve the onion service on. If empty, 52 | // it is the same as the local port or local listener. This must have at 53 | // least one value if the local listener is not a *net.TCPListener. 54 | RemotePorts []int 55 | 56 | // Key is the private key to use. If not present, a key is generated. If 57 | // present, it must be an instance of 58 | // github.com/cretz/bine/torutil/ed25519.KeyPair, a 59 | // golang.org/x/crypto/ed25519.PrivateKey, or a 60 | // github.com/cretz/bine/control.Key. 61 | Key crypto.PrivateKey 62 | 63 | // ClientAuths is the credential set for clients. The values are 64 | // base32-encoded ed25519 public keys. 65 | ClientAuths []string 66 | 67 | // MaxStreams is the maximum number of streams the service will accept. 0 68 | // means unlimited. 69 | MaxStreams int 70 | 71 | // DiscardKey, if true and Key is nil (meaning a private key is generated), 72 | // tells Tor not to return the generated private key. This value is ignored 73 | // if Key is not nil. 74 | DiscardKey bool 75 | 76 | // Detach, if true, prevents the default behavior of the onion service being 77 | // deleted when this controller connection is closed. 78 | Detach bool 79 | 80 | // NonAnonymous must be true if Tor options HiddenServiceSingleHopMode and 81 | // HiddenServiceNonAnonymousMode are set. Otherwise, it must be false. 82 | NonAnonymous bool 83 | 84 | // MaxStreamsCloseCircuit determines whether to close the circuit when the 85 | // maximum number of streams is exceeded. If true, the circuit is closed. If 86 | // false, the stream is simply not connected but the circuit stays open. 87 | MaxStreamsCloseCircuit bool 88 | 89 | // NoWait if true will not wait until the onion service is published. If 90 | // false, the network will be enabled if it's not and then we will wait 91 | // until the onion service is published. 92 | NoWait bool 93 | } 94 | 95 | // Listen creates an onion service and local listener. The context can be nil. 96 | // If conf is nil, the default struct value is used. Note, if this errors, any 97 | // listeners created here are closed but if a LocalListener is provided it may remain open. 98 | func (t *Tor) Listen(ctx context.Context, conf *ListenConf) (*OnionService, error) { 99 | if ctx == nil { 100 | ctx = context.Background() 101 | } 102 | // Create the service up here and make sure we close it no matter the error within 103 | svc := &OnionService{Tor: t, CloseLocalListenerOnClose: conf.LocalListener == nil} 104 | var err error 105 | 106 | // Create the local listener if necessary 107 | svc.LocalListener = conf.LocalListener 108 | if svc.LocalListener == nil { 109 | if svc.LocalListener, err = net.Listen("tcp", "127.0.0.1:"+strconv.Itoa(conf.LocalPort)); err != nil { 110 | return nil, err 111 | } 112 | } 113 | 114 | // Henceforth, any error requires we close the svc 115 | 116 | // Build the onion request 117 | req := &control.AddOnionRequest{MaxStreams: conf.MaxStreams, ClientAuths: conf.ClientAuths} 118 | // Set flags 119 | if conf.DiscardKey { 120 | req.Flags = append(req.Flags, "DiscardPK") 121 | } 122 | if conf.Detach { 123 | req.Flags = append(req.Flags, "Detach") 124 | } 125 | if len(conf.ClientAuths) > 0 { 126 | req.Flags = append(req.Flags, "V3Auth") 127 | } 128 | if conf.NonAnonymous { 129 | req.Flags = append(req.Flags, "NonAnonymous") 130 | } 131 | if conf.MaxStreamsCloseCircuit { 132 | req.Flags = append(req.Flags, "MaxStreamsCloseCircuit") 133 | } 134 | // Set the key 135 | switch key := conf.Key.(type) { 136 | case nil: 137 | req.Key = control.GenKey(control.KeyAlgoED25519V3) 138 | case control.GenKey: 139 | req.Key = key 140 | case ed25519.KeyPair: 141 | svc.Key = key 142 | req.Key = &control.ED25519Key{key} 143 | case othered25519.PrivateKey: 144 | properKey := ed25519.FromCryptoPrivateKey(key) 145 | svc.Key = properKey 146 | req.Key = &control.ED25519Key{properKey} 147 | case *control.ED25519Key: 148 | svc.Key = key.KeyPair 149 | req.Key = key 150 | default: 151 | err = fmt.Errorf("Unrecognized key type: %T", key) 152 | } 153 | 154 | // Apply the remote ports 155 | if err == nil { 156 | if len(conf.RemotePorts) == 0 { 157 | tcpAddr, ok := svc.LocalListener.Addr().(*net.TCPAddr) 158 | if !ok { 159 | err = fmt.Errorf("Unable to derive local TCP port") 160 | } else { 161 | svc.RemotePorts = []int{tcpAddr.Port} 162 | } 163 | } else { 164 | svc.RemotePorts = make([]int, len(conf.RemotePorts)) 165 | copy(svc.RemotePorts, conf.RemotePorts) 166 | } 167 | } 168 | // Apply the local ports with the remote ports 169 | if err == nil { 170 | localAddr := svc.LocalListener.Addr().String() 171 | if _, ok := svc.LocalListener.(*net.UnixListener); ok { 172 | localAddr = "unix:" + localAddr 173 | } 174 | for _, remotePort := range svc.RemotePorts { 175 | req.Ports = append(req.Ports, &control.KeyVal{Key: strconv.Itoa(remotePort), Val: localAddr}) 176 | } 177 | } 178 | 179 | // Create the onion service 180 | var resp *control.AddOnionResponse 181 | if err == nil { 182 | resp, err = t.Control.AddOnion(req) 183 | } 184 | 185 | // Apply the response to the service 186 | if err == nil { 187 | svc.ID = resp.ServiceID 188 | switch key := resp.Key.(type) { 189 | case nil: 190 | // Do nothing 191 | case *control.ED25519Key: 192 | svc.Key = key.KeyPair 193 | default: 194 | err = fmt.Errorf("Unrecognized result key type: %T", key) 195 | } 196 | } 197 | 198 | // Wait if necessary 199 | if err == nil && !conf.NoWait { 200 | t.Debugf("Enabling network before waiting for publication") 201 | // First make sure network is enabled 202 | if err = t.EnableNetwork(ctx, true); err == nil { 203 | t.Debugf("Waiting for publication") 204 | // Now we'll take a similar approach to Stem. Several UPLOADs are sent out, so we count em. If we see 205 | // UPLOADED, we succeeded. If we see failed, we count those. If there are as many failures as uploads, they 206 | // all failed and it's a failure. NOTE: unlike Stem's comments that say they don't, we are actually seeing 207 | // the service IDs for UPLOADED so we don't keep a map. 208 | uploadsAttempted := 0 209 | failures := []string{} 210 | _, err = t.Control.EventWait(ctx, []control.EventCode{control.EventCodeHSDesc}, 211 | func(evt control.Event) (bool, error) { 212 | hs, _ := evt.(*control.HSDescEvent) 213 | if hs != nil && hs.Address == svc.ID { 214 | switch hs.Action { 215 | case "UPLOAD": 216 | uploadsAttempted++ 217 | case "FAILED": 218 | failures = append(failures, 219 | fmt.Sprintf("Failed uploading to dir %v - reason: %v", hs.HSDir, hs.Reason)) 220 | if len(failures) == uploadsAttempted { 221 | return false, fmt.Errorf("Failed all uploads, reasons: %v", failures) 222 | } 223 | case "UPLOADED": 224 | return true, nil 225 | } 226 | } 227 | return false, nil 228 | }) 229 | } 230 | } 231 | 232 | // Give back err and close if there is an err 233 | if err != nil { 234 | if closeErr := svc.Close(); closeErr != nil { 235 | err = fmt.Errorf("Error on listen: %v (also got error trying to close: %v)", err, closeErr) 236 | } 237 | return nil, err 238 | } 239 | return svc, nil 240 | } 241 | 242 | // Accept implements net.Listener.Accept. 243 | func (o *OnionService) Accept() (net.Conn, error) { 244 | return o.LocalListener.Accept() 245 | } 246 | 247 | // Addr implements net.Listener.Addr just returning this object. 248 | func (o *OnionService) Addr() net.Addr { 249 | return o 250 | } 251 | 252 | // Network implements net.Addr.Network always returning "tcp". 253 | func (o *OnionService) Network() string { 254 | return "tcp" 255 | } 256 | 257 | // String implements net.Addr.String and returns "<serviceID>.onion:<virtport>". 258 | func (o *OnionService) String() string { 259 | return fmt.Sprintf("%v.onion:%v", o.ID, o.RemotePorts[0]) 260 | } 261 | 262 | // Close implements net.Listener.Close and deletes the onion service and closes 263 | // the LocalListener if CloseLocalListenerOnClose is true. 264 | func (o *OnionService) Close() (err error) { 265 | o.Tor.Debugf("Closing onion %v", o) 266 | // Delete the onion first 267 | if o.ID != "" { 268 | err = o.Tor.Control.DelOnion(o.ID) 269 | o.ID = "" 270 | } 271 | // Now if the local one needs to be closed, do it 272 | if o.CloseLocalListenerOnClose && o.LocalListener != nil { 273 | if closeErr := o.LocalListener.Close(); closeErr != nil { 274 | if err != nil { 275 | err = fmt.Errorf("Unable to close onion: %v (also unable to close local listener: %v)", err, closeErr) 276 | } else { 277 | err = closeErr 278 | } 279 | } 280 | o.LocalListener = nil 281 | } 282 | if err != nil { 283 | o.Tor.Debugf("Failed closing onion: %v", err) 284 | } 285 | return 286 | } 287 | -------------------------------------------------------------------------------- /examples/go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 4 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 5 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 6 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 9 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 10 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 11 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 12 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 13 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 14 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 15 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 16 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 17 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 18 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 19 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 20 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 21 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 22 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 23 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 24 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 25 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 26 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 27 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 28 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 29 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 30 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 31 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 32 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 33 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 34 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 35 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 36 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 37 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 38 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 39 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 40 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 41 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 42 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= 43 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= 44 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 45 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 46 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 47 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 48 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 49 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 50 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 51 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 52 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 53 | golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo= 54 | golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 55 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 56 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 57 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 58 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 59 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 60 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 61 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 62 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= 63 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 64 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 65 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 66 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 67 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= 68 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 69 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 70 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 71 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 72 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 73 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 74 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 75 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 76 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 77 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 78 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 79 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 80 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= 81 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 82 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 83 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 84 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 85 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 86 | google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= 87 | google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 88 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 89 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 90 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 91 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 92 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 93 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 94 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 95 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 96 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 97 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 98 | google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= 99 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 100 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 101 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 102 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 103 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 104 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 105 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 106 | -------------------------------------------------------------------------------- /tor/tor.go: -------------------------------------------------------------------------------- 1 | package tor 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "net/textproto" 9 | "os" 10 | "path/filepath" 11 | "strconv" 12 | "time" 13 | 14 | "github.com/cretz/bine/control" 15 | 16 | "github.com/cretz/bine/process" 17 | ) 18 | 19 | // Tor is the wrapper around the Tor process and control port connection. It 20 | // should be created with Start and developers should always call Close when 21 | // done. 22 | type Tor struct { 23 | // Process is the Tor instance that is running. 24 | Process process.Process 25 | 26 | // Control is the Tor controller connection. 27 | Control *control.Conn 28 | 29 | // ProcessCancelFunc is the context cancellation func for the Tor process. 30 | // It is used by Close and should not be called directly. This can be nil. 31 | ProcessCancelFunc context.CancelFunc 32 | 33 | // ControlPort is the port that Control is connected on. It is 0 if the 34 | // connection is an embedded control connection. 35 | ControlPort int 36 | 37 | // DataDir is the path to the data directory that Tor is using. 38 | DataDir string 39 | 40 | // DeleteDataDirOnClose is true if, when Close is invoked, the entire 41 | // directory will be deleted. 42 | DeleteDataDirOnClose bool 43 | 44 | // DebugWriter is the writer used for debug logs, or nil if debug logs 45 | // should not be emitted. 46 | DebugWriter io.Writer 47 | 48 | // StopProcessOnClose, if true, will attempt to halt the process on close. 49 | StopProcessOnClose bool 50 | 51 | // GeoIPCreatedFile is the path, relative to DataDir, that was created from 52 | // StartConf.GeoIPFileReader. It is empty if no file was created. 53 | GeoIPCreatedFile string 54 | 55 | // GeoIPv6CreatedFile is the path, relative to DataDir, that was created 56 | // from StartConf.GeoIPFileReader. It is empty if no file was created. 57 | GeoIPv6CreatedFile string 58 | } 59 | 60 | // StartConf is the configuration used for Start when starting a Tor instance. A 61 | // default instance with no fields set is the default used for Start. 62 | type StartConf struct { 63 | // ExePath is the path to the Tor executable. If it is not present, "tor" is 64 | // used either locally or on the PATH. This is ignored if ProcessCreator is 65 | // set. 66 | ExePath string 67 | 68 | // ProcessCreator is the override to use a specific process creator. If set, 69 | // ExePath is ignored. 70 | ProcessCreator process.Creator 71 | 72 | // UseEmbeddedControlConn can be set to true to use 73 | // process.Process.EmbeddedControlConn() instead of creating a connection 74 | // via ControlPort. Note, this only works when ProcessCreator is an 75 | // embedded Tor creator with version >= 0.3.5.x. 76 | UseEmbeddedControlConn bool 77 | 78 | // ControlPort is the port to use for the Tor controller. If it is 0, Tor 79 | // picks a port for use. This is ignored if UseEmbeddedControlConn is true. 80 | ControlPort int 81 | 82 | // DataDir is the directory used by Tor. If it is empty, a temporary 83 | // directory is created in TempDataDirBase. 84 | DataDir string 85 | 86 | // TempDataDirBase is the parent directory that a temporary data directory 87 | // will be created under for use by Tor. This is ignored if DataDir is not 88 | // empty. If empty it is assumed to be the current working directory. 89 | TempDataDirBase string 90 | 91 | // RetainTempDataDir, if true, will not set the created temporary data 92 | // directory to be deleted on close. This is ignored if DataDir is not 93 | // empty. 94 | RetainTempDataDir bool 95 | 96 | // DisableCookieAuth, if true, will not use the default SAFECOOKIE 97 | // authentication mechanism for the Tor controller. 98 | DisableCookieAuth bool 99 | 100 | // DisableEagerAuth, if true, will not authenticate on Start. 101 | DisableEagerAuth bool 102 | 103 | // EnableNetwork, if true, will connect to the wider Tor network on start. 104 | EnableNetwork bool 105 | 106 | // ExtraArgs is the set of extra args passed to the Tor instance when 107 | // started. 108 | ExtraArgs []string 109 | 110 | // TorrcFile is the torrc file to set on start. If empty, a blank torrc is 111 | // created in the data directory and is used instead. 112 | TorrcFile string 113 | 114 | // DebugWriter is the writer to use for debug logs, or nil for no debug 115 | // logs. 116 | DebugWriter io.Writer 117 | 118 | // NoHush if true does not set --hush. By default --hush is set. 119 | NoHush bool 120 | 121 | // NoAutoSocksPort if true does not set "--SocksPort auto" as is done by 122 | // default. This means the caller could set their own or just let it 123 | // default to 9050. 124 | NoAutoSocksPort bool 125 | 126 | // GeoIPReader, if present, is called before start to copy geo IP files to 127 | // the data directory. Errors are propagated. If the ReadCloser is present, 128 | // it is copied to the data dir, overwriting as necessary, and then closed 129 | // and the appropriate command line argument is added to reference it. If 130 | // both the ReadCloser and error are nil, no copy or command line argument 131 | // is used for that version. This is called twice, once with false and once 132 | // with true for ipv6. 133 | // 134 | // This can be set to torutil/geoipembed.GeoIPReader to use an embedded 135 | // source. 136 | GeoIPFileReader func(ipv6 bool) (io.ReadCloser, error) 137 | } 138 | 139 | // Start a Tor instance and connect to it. If ctx is nil, context.Background() 140 | // is used. If conf is nil, a default instance is used. 141 | func Start(ctx context.Context, conf *StartConf) (*Tor, error) { 142 | if ctx == nil { 143 | ctx = context.Background() 144 | } 145 | if conf == nil { 146 | conf = &StartConf{} 147 | } 148 | tor := &Tor{DataDir: conf.DataDir, DebugWriter: conf.DebugWriter, StopProcessOnClose: true} 149 | // Create the data dir and make it absolute 150 | if tor.DataDir == "" { 151 | tempBase := conf.TempDataDirBase 152 | if tempBase == "" { 153 | tempBase = "." 154 | } 155 | var err error 156 | if tempBase, err = filepath.Abs(tempBase); err != nil { 157 | return nil, err 158 | } 159 | if tor.DataDir, err = ioutil.TempDir(tempBase, "data-dir-"); err != nil { 160 | return nil, fmt.Errorf("Unable to create temp data dir: %v", err) 161 | } 162 | tor.Debugf("Created temp data directory at: %v", tor.DataDir) 163 | tor.DeleteDataDirOnClose = !conf.RetainTempDataDir 164 | } else if err := os.MkdirAll(tor.DataDir, 0700); err != nil { 165 | return nil, fmt.Errorf("Unable to create data dir: %v", err) 166 | } 167 | 168 | // !!!! From this point on, we must close tor if we error !!!! 169 | 170 | // Copy geoip stuff if necessary 171 | err := tor.copyGeoIPFiles(conf) 172 | // Start tor 173 | if err == nil { 174 | err = tor.startProcess(ctx, conf) 175 | } 176 | // Connect the controller 177 | if err == nil { 178 | err = tor.connectController(ctx, conf) 179 | } 180 | // Attempt eager auth w/ no password 181 | if err == nil && !conf.DisableEagerAuth { 182 | err = tor.Control.Authenticate("") 183 | } 184 | // If there was an error, we have to try to close here but it may leave the process open 185 | if err != nil { 186 | if closeErr := tor.Close(); closeErr != nil { 187 | err = fmt.Errorf("Error on start: %v (also got error trying to close: %v)", err, closeErr) 188 | } 189 | } 190 | return tor, err 191 | } 192 | 193 | func (t *Tor) copyGeoIPFiles(conf *StartConf) error { 194 | if conf.GeoIPFileReader == nil { 195 | return nil 196 | } 197 | if r, err := conf.GeoIPFileReader(false); err != nil { 198 | return fmt.Errorf("Unable to read geoip file: %v", err) 199 | } else if r != nil { 200 | t.GeoIPCreatedFile = "geoip" 201 | if err := createFile(filepath.Join(t.DataDir, "geoip"), r); err != nil { 202 | return fmt.Errorf("Unable to create geoip file: %v", err) 203 | } 204 | } 205 | if r, err := conf.GeoIPFileReader(true); err != nil { 206 | return fmt.Errorf("Unable to read geoip6 file: %v", err) 207 | } else if r != nil { 208 | t.GeoIPv6CreatedFile = "geoip6" 209 | if err := createFile(filepath.Join(t.DataDir, "geoip6"), r); err != nil { 210 | return fmt.Errorf("Unable to create geoip6 file: %v", err) 211 | } 212 | } 213 | return nil 214 | } 215 | 216 | func createFile(to string, from io.ReadCloser) error { 217 | f, err := os.Create(to) 218 | if err == nil { 219 | _, err = io.Copy(f, from) 220 | if closeErr := f.Close(); err == nil { 221 | err = closeErr 222 | } 223 | } 224 | if closeErr := from.Close(); err == nil { 225 | err = closeErr 226 | } 227 | return err 228 | } 229 | 230 | func (t *Tor) startProcess(ctx context.Context, conf *StartConf) error { 231 | // Get the creator 232 | creator := conf.ProcessCreator 233 | if creator == nil { 234 | torPath := conf.ExePath 235 | if torPath == "" { 236 | torPath = "tor" 237 | } 238 | creator = process.NewCreator(torPath) 239 | } 240 | // Build the args 241 | args := []string{"--DataDirectory", t.DataDir} 242 | if !conf.DisableCookieAuth { 243 | args = append(args, "--CookieAuthentication", "1") 244 | } 245 | if !conf.EnableNetwork { 246 | args = append(args, "--DisableNetwork", "1") 247 | } 248 | if !conf.NoHush { 249 | args = append(args, "--hush") 250 | } 251 | if !conf.NoAutoSocksPort { 252 | args = append(args, "--SocksPort", "auto") 253 | } 254 | if t.GeoIPCreatedFile != "" { 255 | args = append(args, "--GeoIPFile", filepath.Join(t.DataDir, t.GeoIPCreatedFile)) 256 | } 257 | if t.GeoIPv6CreatedFile != "" { 258 | args = append(args, "--GeoIPv6File", filepath.Join(t.DataDir, t.GeoIPv6CreatedFile)) 259 | } 260 | // If there is no Torrc file, create a blank temp one 261 | torrcFileName := conf.TorrcFile 262 | if torrcFileName == "" { 263 | torrcFile, err := ioutil.TempFile(t.DataDir, "torrc-") 264 | if err != nil { 265 | return err 266 | } 267 | torrcFileName = torrcFile.Name() 268 | if err = torrcFile.Close(); err != nil { 269 | return err 270 | } 271 | } 272 | args = append(args, "-f", torrcFileName) 273 | // Create file for Tor to write the control port to if it's not told to us and we're not embedded 274 | var controlPortFileName string 275 | var err error 276 | if !conf.UseEmbeddedControlConn { 277 | if conf.ControlPort == 0 { 278 | controlPortFile, err := ioutil.TempFile(t.DataDir, "control-port-") 279 | if err != nil { 280 | return err 281 | } 282 | controlPortFileName = controlPortFile.Name() 283 | if err = controlPortFile.Close(); err != nil { 284 | return err 285 | } 286 | args = append(args, "--ControlPort", "auto", "--ControlPortWriteToFile", controlPortFile.Name()) 287 | } else { 288 | args = append(args, "--ControlPort", strconv.Itoa(conf.ControlPort)) 289 | } 290 | } 291 | // Create process creator with args 292 | var processCtx context.Context 293 | processCtx, t.ProcessCancelFunc = context.WithCancel(ctx) 294 | args = append(args, conf.ExtraArgs...) 295 | p, err := creator.New(processCtx, args...) 296 | if err != nil { 297 | return err 298 | } 299 | // Use the embedded conn if requested 300 | if conf.UseEmbeddedControlConn { 301 | t.Debugf("Using embedded control connection") 302 | conn, err := p.EmbeddedControlConn() 303 | if err != nil { 304 | return fmt.Errorf("Unable to get embedded control conn: %v", err) 305 | } 306 | t.Control = control.NewConn(textproto.NewConn(conn)) 307 | t.Control.DebugWriter = t.DebugWriter 308 | } 309 | // Start process with the args 310 | t.Debugf("Starting tor with args %v", args) 311 | if err = p.Start(); err != nil { 312 | return err 313 | } 314 | t.Process = p 315 | // If not embedded, try a few times to read the control port file if we need to 316 | if !conf.UseEmbeddedControlConn { 317 | t.ControlPort = conf.ControlPort 318 | if t.ControlPort == 0 { 319 | ControlPortCheck: 320 | for i := 0; i < 10; i++ { 321 | select { 322 | case <-ctx.Done(): 323 | err = ctx.Err() 324 | break ControlPortCheck 325 | default: 326 | // Try to read the controlport file, or wait a bit 327 | var byts []byte 328 | if byts, err = ioutil.ReadFile(controlPortFileName); err != nil { 329 | break ControlPortCheck 330 | } else if t.ControlPort, err = process.ControlPortFromFileContents(string(byts)); err == nil { 331 | break ControlPortCheck 332 | } 333 | time.Sleep(200 * time.Millisecond) 334 | } 335 | } 336 | if err != nil { 337 | return fmt.Errorf("Unable to read control port file: %v", err) 338 | } 339 | } 340 | } 341 | return nil 342 | } 343 | 344 | func (t *Tor) connectController(ctx context.Context, conf *StartConf) error { 345 | // This doesn't apply if already connected (e.g. using embedded conn) 346 | if t.Control != nil { 347 | return nil 348 | } 349 | t.Debugf("Connecting to control port %v", t.ControlPort) 350 | textConn, err := textproto.Dial("tcp", "127.0.0.1:"+strconv.Itoa(t.ControlPort)) 351 | if err != nil { 352 | return err 353 | } 354 | t.Control = control.NewConn(textConn) 355 | t.Control.DebugWriter = t.DebugWriter 356 | return nil 357 | } 358 | 359 | // EnableNetwork sets DisableNetwork to 0 and optionally waits for bootstrap to 360 | // complete. The context can be nil. If DisableNetwork isnt 1, this does 361 | // nothing. 362 | func (t *Tor) EnableNetwork(ctx context.Context, wait bool) error { 363 | if ctx == nil { 364 | ctx = context.Background() 365 | } 366 | // Only enable if DisableNetwork is 1 367 | if vals, err := t.Control.GetConf("DisableNetwork"); err != nil { 368 | return err 369 | } else if len(vals) == 0 || vals[0].Key != "DisableNetwork" || vals[0].Val != "1" { 370 | return nil 371 | } 372 | // Enable the network 373 | if err := t.Control.SetConf(control.KeyVals("DisableNetwork", "0")...); err != nil { 374 | return nil 375 | } 376 | // If not waiting, leave 377 | if !wait { 378 | return nil 379 | } 380 | // Wait for progress to hit 100 381 | _, err := t.Control.EventWait(ctx, []control.EventCode{control.EventCodeStatusClient}, 382 | func(evt control.Event) (bool, error) { 383 | if status, _ := evt.(*control.StatusEvent); status != nil && status.Action == "BOOTSTRAP" { 384 | if status.Severity == "NOTICE" && status.Arguments["PROGRESS"] == "100" { 385 | return true, nil 386 | } else if status.Severity == "ERR" { 387 | return false, fmt.Errorf("Failing bootstrapping, Tor warning: %v", status.Arguments["WARNING"]) 388 | } 389 | } 390 | return false, nil 391 | }) 392 | return err 393 | } 394 | 395 | // Close sends a halt to the Tor process if it can, closes the controller 396 | // connection, and stops the process. 397 | func (t *Tor) Close() error { 398 | t.Debugf("Closing Tor") 399 | errs := []error{} 400 | // If controller is authenticated, send the quit signal to the process. Otherwise, just close the controller. 401 | sentHalt := false 402 | if t.Control != nil { 403 | if t.Control.Authenticated && t.StopProcessOnClose { 404 | if err := t.Control.Signal("HALT"); err != nil { 405 | errs = append(errs, fmt.Errorf("Unable to signal halt: %v", err)) 406 | } else { 407 | sentHalt = true 408 | } 409 | } 410 | // Now close the controller 411 | if err := t.Control.Close(); err != nil { 412 | errs = append(errs, fmt.Errorf("Unable to close contrlller: %v", err)) 413 | } else { 414 | t.Control = nil 415 | } 416 | } 417 | if t.Process != nil { 418 | // If we didn't halt, we have to force kill w/ the cancel func 419 | if !sentHalt && t.StopProcessOnClose { 420 | t.ProcessCancelFunc() 421 | } 422 | // Wait for a bit to make sure it stopped 423 | errCh := make(chan error, 1) 424 | var waitErr error 425 | go func() { errCh <- t.Process.Wait() }() 426 | select { 427 | case waitErr = <-errCh: 428 | if waitErr != nil { 429 | errs = append(errs, fmt.Errorf("Process wait failed: %v", waitErr)) 430 | } 431 | case <-time.After(300 * time.Millisecond): 432 | errs = append(errs, fmt.Errorf("Process did not exit after 300 ms")) 433 | } 434 | if waitErr == nil { 435 | t.Process = nil 436 | } 437 | } 438 | // Get rid of the entire data dir 439 | if t.DeleteDataDirOnClose { 440 | if err := os.RemoveAll(t.DataDir); err != nil { 441 | errs = append(errs, fmt.Errorf("Failed to remove data dir %v: %v", t.DataDir, err)) 442 | } 443 | } 444 | // Combine the errors if present 445 | if len(errs) == 0 { 446 | return nil 447 | } else if len(errs) == 1 { 448 | t.Debugf("Error while closing Tor: %v", errs[0]) 449 | return errs[0] 450 | } 451 | t.Debugf("Errors while closing Tor: %v", errs) 452 | return fmt.Errorf("Got %v errors while closing - %v", len(errs), errs) 453 | } 454 | -------------------------------------------------------------------------------- /examples/grpc/pb/service.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: service.proto 3 | 4 | /* 5 | Package pb is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | service.proto 9 | 10 | It has these top-level messages: 11 | JoinStringsRequest 12 | JoinStringsResponse 13 | ProvideStringsRequest 14 | ProvideStringsResponse 15 | ReceiveStringsRequest 16 | ReceiveStringsResponse 17 | ExchangeStringsRequest 18 | ExchangeStringsResponse 19 | */ 20 | package pb 21 | 22 | import proto "github.com/golang/protobuf/proto" 23 | import fmt "fmt" 24 | import math "math" 25 | 26 | import ( 27 | context "golang.org/x/net/context" 28 | grpc "google.golang.org/grpc" 29 | ) 30 | 31 | // Reference imports to suppress errors if they are not otherwise used. 32 | var _ = proto.Marshal 33 | var _ = fmt.Errorf 34 | var _ = math.Inf 35 | 36 | // This is a compile-time assertion to ensure that this generated file 37 | // is compatible with the proto package it is being compiled against. 38 | // A compilation error at this line likely means your copy of the 39 | // proto package needs to be updated. 40 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 41 | 42 | type JoinStringsRequest struct { 43 | Strings []string `protobuf:"bytes,1,rep,name=strings" json:"strings,omitempty"` 44 | Delimiter string `protobuf:"bytes,2,opt,name=delimiter" json:"delimiter,omitempty"` 45 | } 46 | 47 | func (m *JoinStringsRequest) Reset() { *m = JoinStringsRequest{} } 48 | func (m *JoinStringsRequest) String() string { return proto.CompactTextString(m) } 49 | func (*JoinStringsRequest) ProtoMessage() {} 50 | func (*JoinStringsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } 51 | 52 | func (m *JoinStringsRequest) GetStrings() []string { 53 | if m != nil { 54 | return m.Strings 55 | } 56 | return nil 57 | } 58 | 59 | func (m *JoinStringsRequest) GetDelimiter() string { 60 | if m != nil { 61 | return m.Delimiter 62 | } 63 | return "" 64 | } 65 | 66 | type JoinStringsResponse struct { 67 | Joined string `protobuf:"bytes,1,opt,name=joined" json:"joined,omitempty"` 68 | } 69 | 70 | func (m *JoinStringsResponse) Reset() { *m = JoinStringsResponse{} } 71 | func (m *JoinStringsResponse) String() string { return proto.CompactTextString(m) } 72 | func (*JoinStringsResponse) ProtoMessage() {} 73 | func (*JoinStringsResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } 74 | 75 | func (m *JoinStringsResponse) GetJoined() string { 76 | if m != nil { 77 | return m.Joined 78 | } 79 | return "" 80 | } 81 | 82 | type ProvideStringsRequest struct { 83 | Count uint32 `protobuf:"varint,1,opt,name=count" json:"count,omitempty"` 84 | } 85 | 86 | func (m *ProvideStringsRequest) Reset() { *m = ProvideStringsRequest{} } 87 | func (m *ProvideStringsRequest) String() string { return proto.CompactTextString(m) } 88 | func (*ProvideStringsRequest) ProtoMessage() {} 89 | func (*ProvideStringsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } 90 | 91 | func (m *ProvideStringsRequest) GetCount() uint32 { 92 | if m != nil { 93 | return m.Count 94 | } 95 | return 0 96 | } 97 | 98 | type ProvideStringsResponse struct { 99 | String_ string `protobuf:"bytes,1,opt,name=string" json:"string,omitempty"` 100 | } 101 | 102 | func (m *ProvideStringsResponse) Reset() { *m = ProvideStringsResponse{} } 103 | func (m *ProvideStringsResponse) String() string { return proto.CompactTextString(m) } 104 | func (*ProvideStringsResponse) ProtoMessage() {} 105 | func (*ProvideStringsResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } 106 | 107 | func (m *ProvideStringsResponse) GetString_() string { 108 | if m != nil { 109 | return m.String_ 110 | } 111 | return "" 112 | } 113 | 114 | type ReceiveStringsRequest struct { 115 | String_ string `protobuf:"bytes,1,opt,name=string" json:"string,omitempty"` 116 | } 117 | 118 | func (m *ReceiveStringsRequest) Reset() { *m = ReceiveStringsRequest{} } 119 | func (m *ReceiveStringsRequest) String() string { return proto.CompactTextString(m) } 120 | func (*ReceiveStringsRequest) ProtoMessage() {} 121 | func (*ReceiveStringsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } 122 | 123 | func (m *ReceiveStringsRequest) GetString_() string { 124 | if m != nil { 125 | return m.String_ 126 | } 127 | return "" 128 | } 129 | 130 | type ReceiveStringsResponse struct { 131 | Received []string `protobuf:"bytes,1,rep,name=received" json:"received,omitempty"` 132 | } 133 | 134 | func (m *ReceiveStringsResponse) Reset() { *m = ReceiveStringsResponse{} } 135 | func (m *ReceiveStringsResponse) String() string { return proto.CompactTextString(m) } 136 | func (*ReceiveStringsResponse) ProtoMessage() {} 137 | func (*ReceiveStringsResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } 138 | 139 | func (m *ReceiveStringsResponse) GetReceived() []string { 140 | if m != nil { 141 | return m.Received 142 | } 143 | return nil 144 | } 145 | 146 | type ExchangeStringsRequest struct { 147 | String_ string `protobuf:"bytes,1,opt,name=string" json:"string,omitempty"` 148 | WantReturn bool `protobuf:"varint,2,opt,name=want_return,json=wantReturn" json:"want_return,omitempty"` 149 | } 150 | 151 | func (m *ExchangeStringsRequest) Reset() { *m = ExchangeStringsRequest{} } 152 | func (m *ExchangeStringsRequest) String() string { return proto.CompactTextString(m) } 153 | func (*ExchangeStringsRequest) ProtoMessage() {} 154 | func (*ExchangeStringsRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } 155 | 156 | func (m *ExchangeStringsRequest) GetString_() string { 157 | if m != nil { 158 | return m.String_ 159 | } 160 | return "" 161 | } 162 | 163 | func (m *ExchangeStringsRequest) GetWantReturn() bool { 164 | if m != nil { 165 | return m.WantReturn 166 | } 167 | return false 168 | } 169 | 170 | type ExchangeStringsResponse struct { 171 | Received []string `protobuf:"bytes,1,rep,name=received" json:"received,omitempty"` 172 | } 173 | 174 | func (m *ExchangeStringsResponse) Reset() { *m = ExchangeStringsResponse{} } 175 | func (m *ExchangeStringsResponse) String() string { return proto.CompactTextString(m) } 176 | func (*ExchangeStringsResponse) ProtoMessage() {} 177 | func (*ExchangeStringsResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} } 178 | 179 | func (m *ExchangeStringsResponse) GetReceived() []string { 180 | if m != nil { 181 | return m.Received 182 | } 183 | return nil 184 | } 185 | 186 | func init() { 187 | proto.RegisterType((*JoinStringsRequest)(nil), "pb.JoinStringsRequest") 188 | proto.RegisterType((*JoinStringsResponse)(nil), "pb.JoinStringsResponse") 189 | proto.RegisterType((*ProvideStringsRequest)(nil), "pb.ProvideStringsRequest") 190 | proto.RegisterType((*ProvideStringsResponse)(nil), "pb.ProvideStringsResponse") 191 | proto.RegisterType((*ReceiveStringsRequest)(nil), "pb.ReceiveStringsRequest") 192 | proto.RegisterType((*ReceiveStringsResponse)(nil), "pb.ReceiveStringsResponse") 193 | proto.RegisterType((*ExchangeStringsRequest)(nil), "pb.ExchangeStringsRequest") 194 | proto.RegisterType((*ExchangeStringsResponse)(nil), "pb.ExchangeStringsResponse") 195 | } 196 | 197 | // Reference imports to suppress errors if they are not otherwise used. 198 | var _ context.Context 199 | var _ grpc.ClientConn 200 | 201 | // This is a compile-time assertion to ensure that this generated file 202 | // is compatible with the grpc package it is being compiled against. 203 | const _ = grpc.SupportPackageIsVersion4 204 | 205 | // Client API for SimpleService service 206 | 207 | type SimpleServiceClient interface { 208 | JoinStrings(ctx context.Context, in *JoinStringsRequest, opts ...grpc.CallOption) (*JoinStringsResponse, error) 209 | ProvideStrings(ctx context.Context, in *ProvideStringsRequest, opts ...grpc.CallOption) (SimpleService_ProvideStringsClient, error) 210 | ReceiveStrings(ctx context.Context, opts ...grpc.CallOption) (SimpleService_ReceiveStringsClient, error) 211 | ExchangeStrings(ctx context.Context, opts ...grpc.CallOption) (SimpleService_ExchangeStringsClient, error) 212 | } 213 | 214 | type simpleServiceClient struct { 215 | cc *grpc.ClientConn 216 | } 217 | 218 | func NewSimpleServiceClient(cc *grpc.ClientConn) SimpleServiceClient { 219 | return &simpleServiceClient{cc} 220 | } 221 | 222 | func (c *simpleServiceClient) JoinStrings(ctx context.Context, in *JoinStringsRequest, opts ...grpc.CallOption) (*JoinStringsResponse, error) { 223 | out := new(JoinStringsResponse) 224 | err := grpc.Invoke(ctx, "/pb.SimpleService/JoinStrings", in, out, c.cc, opts...) 225 | if err != nil { 226 | return nil, err 227 | } 228 | return out, nil 229 | } 230 | 231 | func (c *simpleServiceClient) ProvideStrings(ctx context.Context, in *ProvideStringsRequest, opts ...grpc.CallOption) (SimpleService_ProvideStringsClient, error) { 232 | stream, err := grpc.NewClientStream(ctx, &_SimpleService_serviceDesc.Streams[0], c.cc, "/pb.SimpleService/ProvideStrings", opts...) 233 | if err != nil { 234 | return nil, err 235 | } 236 | x := &simpleServiceProvideStringsClient{stream} 237 | if err := x.ClientStream.SendMsg(in); err != nil { 238 | return nil, err 239 | } 240 | if err := x.ClientStream.CloseSend(); err != nil { 241 | return nil, err 242 | } 243 | return x, nil 244 | } 245 | 246 | type SimpleService_ProvideStringsClient interface { 247 | Recv() (*ProvideStringsResponse, error) 248 | grpc.ClientStream 249 | } 250 | 251 | type simpleServiceProvideStringsClient struct { 252 | grpc.ClientStream 253 | } 254 | 255 | func (x *simpleServiceProvideStringsClient) Recv() (*ProvideStringsResponse, error) { 256 | m := new(ProvideStringsResponse) 257 | if err := x.ClientStream.RecvMsg(m); err != nil { 258 | return nil, err 259 | } 260 | return m, nil 261 | } 262 | 263 | func (c *simpleServiceClient) ReceiveStrings(ctx context.Context, opts ...grpc.CallOption) (SimpleService_ReceiveStringsClient, error) { 264 | stream, err := grpc.NewClientStream(ctx, &_SimpleService_serviceDesc.Streams[1], c.cc, "/pb.SimpleService/ReceiveStrings", opts...) 265 | if err != nil { 266 | return nil, err 267 | } 268 | x := &simpleServiceReceiveStringsClient{stream} 269 | return x, nil 270 | } 271 | 272 | type SimpleService_ReceiveStringsClient interface { 273 | Send(*ReceiveStringsRequest) error 274 | CloseAndRecv() (*ReceiveStringsResponse, error) 275 | grpc.ClientStream 276 | } 277 | 278 | type simpleServiceReceiveStringsClient struct { 279 | grpc.ClientStream 280 | } 281 | 282 | func (x *simpleServiceReceiveStringsClient) Send(m *ReceiveStringsRequest) error { 283 | return x.ClientStream.SendMsg(m) 284 | } 285 | 286 | func (x *simpleServiceReceiveStringsClient) CloseAndRecv() (*ReceiveStringsResponse, error) { 287 | if err := x.ClientStream.CloseSend(); err != nil { 288 | return nil, err 289 | } 290 | m := new(ReceiveStringsResponse) 291 | if err := x.ClientStream.RecvMsg(m); err != nil { 292 | return nil, err 293 | } 294 | return m, nil 295 | } 296 | 297 | func (c *simpleServiceClient) ExchangeStrings(ctx context.Context, opts ...grpc.CallOption) (SimpleService_ExchangeStringsClient, error) { 298 | stream, err := grpc.NewClientStream(ctx, &_SimpleService_serviceDesc.Streams[2], c.cc, "/pb.SimpleService/ExchangeStrings", opts...) 299 | if err != nil { 300 | return nil, err 301 | } 302 | x := &simpleServiceExchangeStringsClient{stream} 303 | return x, nil 304 | } 305 | 306 | type SimpleService_ExchangeStringsClient interface { 307 | Send(*ExchangeStringsRequest) error 308 | Recv() (*ExchangeStringsResponse, error) 309 | grpc.ClientStream 310 | } 311 | 312 | type simpleServiceExchangeStringsClient struct { 313 | grpc.ClientStream 314 | } 315 | 316 | func (x *simpleServiceExchangeStringsClient) Send(m *ExchangeStringsRequest) error { 317 | return x.ClientStream.SendMsg(m) 318 | } 319 | 320 | func (x *simpleServiceExchangeStringsClient) Recv() (*ExchangeStringsResponse, error) { 321 | m := new(ExchangeStringsResponse) 322 | if err := x.ClientStream.RecvMsg(m); err != nil { 323 | return nil, err 324 | } 325 | return m, nil 326 | } 327 | 328 | // Server API for SimpleService service 329 | 330 | type SimpleServiceServer interface { 331 | JoinStrings(context.Context, *JoinStringsRequest) (*JoinStringsResponse, error) 332 | ProvideStrings(*ProvideStringsRequest, SimpleService_ProvideStringsServer) error 333 | ReceiveStrings(SimpleService_ReceiveStringsServer) error 334 | ExchangeStrings(SimpleService_ExchangeStringsServer) error 335 | } 336 | 337 | func RegisterSimpleServiceServer(s *grpc.Server, srv SimpleServiceServer) { 338 | s.RegisterService(&_SimpleService_serviceDesc, srv) 339 | } 340 | 341 | func _SimpleService_JoinStrings_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 342 | in := new(JoinStringsRequest) 343 | if err := dec(in); err != nil { 344 | return nil, err 345 | } 346 | if interceptor == nil { 347 | return srv.(SimpleServiceServer).JoinStrings(ctx, in) 348 | } 349 | info := &grpc.UnaryServerInfo{ 350 | Server: srv, 351 | FullMethod: "/pb.SimpleService/JoinStrings", 352 | } 353 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 354 | return srv.(SimpleServiceServer).JoinStrings(ctx, req.(*JoinStringsRequest)) 355 | } 356 | return interceptor(ctx, in, info, handler) 357 | } 358 | 359 | func _SimpleService_ProvideStrings_Handler(srv interface{}, stream grpc.ServerStream) error { 360 | m := new(ProvideStringsRequest) 361 | if err := stream.RecvMsg(m); err != nil { 362 | return err 363 | } 364 | return srv.(SimpleServiceServer).ProvideStrings(m, &simpleServiceProvideStringsServer{stream}) 365 | } 366 | 367 | type SimpleService_ProvideStringsServer interface { 368 | Send(*ProvideStringsResponse) error 369 | grpc.ServerStream 370 | } 371 | 372 | type simpleServiceProvideStringsServer struct { 373 | grpc.ServerStream 374 | } 375 | 376 | func (x *simpleServiceProvideStringsServer) Send(m *ProvideStringsResponse) error { 377 | return x.ServerStream.SendMsg(m) 378 | } 379 | 380 | func _SimpleService_ReceiveStrings_Handler(srv interface{}, stream grpc.ServerStream) error { 381 | return srv.(SimpleServiceServer).ReceiveStrings(&simpleServiceReceiveStringsServer{stream}) 382 | } 383 | 384 | type SimpleService_ReceiveStringsServer interface { 385 | SendAndClose(*ReceiveStringsResponse) error 386 | Recv() (*ReceiveStringsRequest, error) 387 | grpc.ServerStream 388 | } 389 | 390 | type simpleServiceReceiveStringsServer struct { 391 | grpc.ServerStream 392 | } 393 | 394 | func (x *simpleServiceReceiveStringsServer) SendAndClose(m *ReceiveStringsResponse) error { 395 | return x.ServerStream.SendMsg(m) 396 | } 397 | 398 | func (x *simpleServiceReceiveStringsServer) Recv() (*ReceiveStringsRequest, error) { 399 | m := new(ReceiveStringsRequest) 400 | if err := x.ServerStream.RecvMsg(m); err != nil { 401 | return nil, err 402 | } 403 | return m, nil 404 | } 405 | 406 | func _SimpleService_ExchangeStrings_Handler(srv interface{}, stream grpc.ServerStream) error { 407 | return srv.(SimpleServiceServer).ExchangeStrings(&simpleServiceExchangeStringsServer{stream}) 408 | } 409 | 410 | type SimpleService_ExchangeStringsServer interface { 411 | Send(*ExchangeStringsResponse) error 412 | Recv() (*ExchangeStringsRequest, error) 413 | grpc.ServerStream 414 | } 415 | 416 | type simpleServiceExchangeStringsServer struct { 417 | grpc.ServerStream 418 | } 419 | 420 | func (x *simpleServiceExchangeStringsServer) Send(m *ExchangeStringsResponse) error { 421 | return x.ServerStream.SendMsg(m) 422 | } 423 | 424 | func (x *simpleServiceExchangeStringsServer) Recv() (*ExchangeStringsRequest, error) { 425 | m := new(ExchangeStringsRequest) 426 | if err := x.ServerStream.RecvMsg(m); err != nil { 427 | return nil, err 428 | } 429 | return m, nil 430 | } 431 | 432 | var _SimpleService_serviceDesc = grpc.ServiceDesc{ 433 | ServiceName: "pb.SimpleService", 434 | HandlerType: (*SimpleServiceServer)(nil), 435 | Methods: []grpc.MethodDesc{ 436 | { 437 | MethodName: "JoinStrings", 438 | Handler: _SimpleService_JoinStrings_Handler, 439 | }, 440 | }, 441 | Streams: []grpc.StreamDesc{ 442 | { 443 | StreamName: "ProvideStrings", 444 | Handler: _SimpleService_ProvideStrings_Handler, 445 | ServerStreams: true, 446 | }, 447 | { 448 | StreamName: "ReceiveStrings", 449 | Handler: _SimpleService_ReceiveStrings_Handler, 450 | ClientStreams: true, 451 | }, 452 | { 453 | StreamName: "ExchangeStrings", 454 | Handler: _SimpleService_ExchangeStrings_Handler, 455 | ServerStreams: true, 456 | ClientStreams: true, 457 | }, 458 | }, 459 | Metadata: "service.proto", 460 | } 461 | 462 | func init() { proto.RegisterFile("service.proto", fileDescriptor0) } 463 | 464 | var fileDescriptor0 = []byte{ 465 | // 333 bytes of a gzipped FileDescriptorProto 466 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x53, 0x4f, 0x4f, 0x3a, 0x31, 467 | 0x14, 0x4c, 0xf9, 0xe5, 0x87, 0xf0, 0x08, 0x9a, 0x54, 0x59, 0xd6, 0xd5, 0x44, 0xd2, 0x13, 0x17, 468 | 0x90, 0xf8, 0xe7, 0xea, 0xcd, 0x83, 0xc6, 0x18, 0x2d, 0x1f, 0xc0, 0xc0, 0xee, 0x0b, 0xd6, 0x40, 469 | 0xbb, 0xb6, 0x05, 0xfd, 0x62, 0x7e, 0x3f, 0x43, 0xbb, 0x88, 0x2c, 0x35, 0x7a, 0x9c, 0x79, 0xef, 470 | 0xcd, 0x34, 0x33, 0x29, 0x34, 0x0d, 0xea, 0x85, 0x48, 0xb1, 0x9f, 0x6b, 0x65, 0x15, 0xad, 0xe4, 471 | 0x63, 0x76, 0x07, 0xf4, 0x56, 0x09, 0x39, 0xb4, 0x5a, 0xc8, 0x89, 0xe1, 0xf8, 0x3a, 0x47, 0x63, 472 | 0x69, 0x0c, 0x3b, 0xc6, 0x33, 0x31, 0xe9, 0xfc, 0xeb, 0xd6, 0xf9, 0x0a, 0xd2, 0x63, 0xa8, 0x67, 473 | 0x38, 0x15, 0x33, 0x61, 0x51, 0xc7, 0x95, 0x0e, 0xe9, 0xd6, 0xf9, 0x9a, 0x60, 0x3d, 0xd8, 0xdf, 474 | 0x50, 0x33, 0xb9, 0x92, 0x06, 0x69, 0x04, 0xd5, 0x17, 0x25, 0x24, 0x66, 0x31, 0x71, 0x17, 0x05, 475 | 0x62, 0x3d, 0x68, 0x3d, 0x68, 0xb5, 0x10, 0x19, 0x96, 0xfc, 0x0f, 0xe0, 0x7f, 0xaa, 0xe6, 0xd2, 476 | 0xba, 0xfd, 0x26, 0xf7, 0x80, 0x0d, 0x20, 0x2a, 0xaf, 0xaf, 0x0d, 0xfc, 0x03, 0x57, 0x06, 0x1e, 477 | 0xb1, 0x53, 0x68, 0x71, 0x4c, 0x51, 0x2c, 0xca, 0x06, 0x3f, 0x1d, 0x5c, 0x40, 0x54, 0x3e, 0x28, 478 | 0x2c, 0x12, 0xa8, 0x69, 0x3f, 0xc9, 0x8a, 0x4c, 0xbe, 0x30, 0x7b, 0x84, 0xe8, 0xfa, 0x3d, 0x7d, 479 | 0x1e, 0xc9, 0xc9, 0x1f, 0x7d, 0xe8, 0x09, 0x34, 0xde, 0x46, 0xd2, 0x3e, 0x69, 0xb4, 0x73, 0x2d, 480 | 0x5d, 0x90, 0x35, 0x0e, 0x4b, 0x8a, 0x3b, 0x86, 0x5d, 0x42, 0x7b, 0x4b, 0xf2, 0xf7, 0x97, 0x9c, 481 | 0x7d, 0x54, 0xa0, 0x39, 0x14, 0xb3, 0x7c, 0x8a, 0x43, 0x5f, 0x35, 0xbd, 0x82, 0xc6, 0xb7, 0x4a, 482 | 0x68, 0xd4, 0xcf, 0xc7, 0xfd, 0xed, 0xc6, 0x93, 0xf6, 0x16, 0x5f, 0xb8, 0xdd, 0xc0, 0xee, 0x66, 483 | 0xe8, 0xf4, 0x70, 0xb9, 0x1a, 0xec, 0x2d, 0x49, 0x42, 0x23, 0x2f, 0x34, 0x20, 0x4b, 0xa9, 0xcd, 484 | 0x70, 0xbd, 0x54, 0xb0, 0x21, 0x2f, 0x15, 0xee, 0xa2, 0x4b, 0xe8, 0x3d, 0xec, 0x95, 0xe2, 0xa1, 485 | 0xee, 0x20, 0x5c, 0x43, 0x72, 0x14, 0x9c, 0xad, 0xd4, 0x06, 0x64, 0x5c, 0x75, 0x3f, 0xe2, 0xfc, 486 | 0x33, 0x00, 0x00, 0xff, 0xff, 0xb0, 0x5e, 0xf9, 0x7a, 0x22, 0x03, 0x00, 0x00, 487 | } 488 | --------------------------------------------------------------------------------