diff --git a/.gitignore b/.gitignore index 2764e01..9e85f5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ .idea/ coverage.* tor/tor/ +vendor/ +*.cover.out +tmp/ diff --git a/README.md b/README.md index 9366265..e992533 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,53 @@ # connectivity -A library providing an ACN (Anonymous Communication Network) abstraction and a tor (for now) implementation. +A library providing an ACN (Anonymous Communication Network +) networking abstraction + +## Supported ACNs + +* Tor v3 Onion Services + +## Requirements for ACN Support + +* Reference an EndPoint via a string / hostname +* Maintain an endpoint via a PublicKey (the underlying crypto is the + responsibility of the implementation) + +## Using + +Each ACN implementation provides a specific start function that takes in the +required parameters to e.g. find a specific binary on the system, attempt to + talk to a specific system service or launch an in-memory networking manager: + + acn, err := NewTorACN(".", "", 9051, HashedPasswordAuthenticator{"examplehasedpassword"}) + if err != nil { + t.Error(err) + return + } + +At this point the ACN is responsible for setting up the networking interface, +the result of which can be checked via the Status callback: + + + acn.SetStatusCallback(getStatusCallback(progChan)) + + progress := 0 + for progress < 100 { + progress = <-progChan + } + +Once initialized the ACN can be used to open new connections: + + conn,err := acn.Open(hostname); + +Or host a service on the ACN: + + ls,err := acn.Listen(identity, port) ; + +We also provide closing and restart functionality for managing the networking +service: + + acn.Restart() +and + + acn.Close() \ No newline at end of file diff --git a/acn.go b/acn.go index ab64a64..c8d221b 100644 --- a/acn.go +++ b/acn.go @@ -47,10 +47,6 @@ type ACN interface { // Open takes a hostname and returns a net.conn to the derived endpoint // Open allows a client to resolve various hostnames to connections - // The supported types are onions address are: - // * ricochet:jlq67qzo6s4yp3sp - // * jlq67qzo6s4yp3sp - // * 127.0.0.1:55555|jlq67qzo6s4yp3sp - Localhost Connection Open(hostname string) (net.Conn, string, error) // Listen takes a private key and a port and returns a ListenService for it diff --git a/go.mod b/go.mod index 102d5b4..7d7012b 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,11 @@ go 1.13 require ( git.openprivacy.ca/openprivacy/log v1.0.0 + github.com/client9/misspell v0.3.4 // indirect github.com/cretz/bine v0.1.1-0.20200124154328-f9f678b84cca + github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf // indirect github.com/stretchr/testify v1.3.0 // indirect golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72 + golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect + golang.org/x/tools v0.0.0-20200625195345-7480c7b4547d // indirect ) diff --git a/go.sum b/go.sum index 545fb88..ab0cb51 100644 --- a/go.sum +++ b/go.sum @@ -1,20 +1,43 @@ git.openprivacy.ca/openprivacy/log v1.0.0 h1:Rvqm1weUdR4AOnJ79b1upHCc9vC/QF1rhSD2Um7sr1Y= git.openprivacy.ca/openprivacy/log v1.0.0/go.mod h1:gGYK8xHtndRLDymFtmjkG26GaMQNgyhioNS82m812Iw= +github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cretz/bine v0.1.1-0.20200124154328-f9f678b84cca h1:Q2r7AxHdJwWfLtBZwvW621M3sPqxPc6ITv2j1FGsYpw= github.com/cretz/bine v0.1.1-0.20200124154328-f9f678b84cca/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf h1:vc7Dmrk4JwS0ZPS6WZvWlwDflgDTA26jItmbSj83nug= +github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72 h1:+ELyKg6m8UBf0nPFSqD0mi7zUfwPyXo23HNjMnXPz7w= golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 h1:EBZoQjiKKPaLbPrbpssUfuHtwM6KV/vb4U85g/cigFY= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200625195345-7480c7b4547d h1:V1BGE5ZHrUIYZYNEm0i7jrPwSo3ks0HSn1TrartSqME= +golang.org/x/tools v0.0.0-20200625195345-7480c7b4547d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/testing/torrc b/testing/torrc index 513c094..b190420 100644 --- a/testing/torrc +++ b/testing/torrc @@ -1,5 +1,6 @@ SOCKSPort 9050 ControlPort 9051 -CookieAuthentication 0 +# "examplehashedpassword" - used for testing +HashedControlPassword 16:C15305F97789414B601259E3EC5E76B8E55FC56A9F562B713F3D2BA257 # Needed for integ tests RunAsDaemon 1 \ No newline at end of file diff --git a/tor/torProvider.go b/tor/torProvider.go index 703e8ab..672e911 100644 --- a/tor/torProvider.go +++ b/tor/torProvider.go @@ -47,6 +47,7 @@ type onionListenService struct { } type torProvider struct { + controlPort int t *tor.Tor dialer *tor.Dialer appDirectory string @@ -56,6 +57,7 @@ type torProvider struct { childListeners map[string]*onionListenService statusCallback func(int, string) lastRestartTime time.Time + authenticator Authenticator } func (ols *onionListenService) AddressFull() string { @@ -229,9 +231,9 @@ func (tp *torProvider) callStatusCallback(prog int, status string) { tp.lock.Unlock() } -// NewTorACN creates/starts a Tor ACN and returns a usable ACN object -func NewTorACN(appDirectory string, bundledTorPath string) (connectivity.ACN, error) { - tp, err := startTor(appDirectory, bundledTorPath) +// NewTorACNWithAuth creates/starts a Tor ACN and returns a usable ACN object +func NewTorACNWithAuth(appDirectory string, bundledTorPath string, controlPort int, authenticator Authenticator) (connectivity.ACN, error) { + tp, err := startTor(appDirectory, bundledTorPath, controlPort, authenticator) if err == nil { tp.dialer, err = tp.t.Dialer(nil, &tor.DialConf{}) if err == nil { @@ -241,6 +243,11 @@ func NewTorACN(appDirectory string, bundledTorPath string) (connectivity.ACN, er return tp, err } +// NewTorACN creates/starts a Tor ACN and returns a usable ACN object with a NullAuthenticator - this will fail. +func NewTorACN(appDirectory string, bundledTorPath string) (connectivity.ACN, error) { + return NewTorACNWithAuth(appDirectory, bundledTorPath, 9051, NullAuthenticator{}) +} + // newHideCmd creates a Creator function for bine which generates a cmd that one windows will hide the dosbox func newHideCmd(exePath string) process.Creator { return process.CmdCreatorFunc(func(ctx context.Context, args ...string) (*exec.Cmd, error) { @@ -252,18 +259,18 @@ func newHideCmd(exePath string) process.Creator { }) } -func startTor(appDirectory string, bundledTorPath string) (*torProvider, error) { +func startTor(appDirectory string, bundledTorPath string, controlPort int, authenticator Authenticator) (*torProvider, error) { dataDir := path.Join(appDirectory, "tor") os.MkdirAll(dataDir, 0700) - tp := &torProvider{appDirectory: appDirectory, bundeledTorPath: bundledTorPath, childListeners: make(map[string]*onionListenService), breakChan: make(chan bool), statusCallback: nil, lastRestartTime: time.Now().Add(-restartCooldown)} + tp := &torProvider{authenticator: authenticator, controlPort: controlPort, appDirectory: appDirectory, bundeledTorPath: bundledTorPath, childListeners: make(map[string]*onionListenService), breakChan: make(chan bool), statusCallback: nil, lastRestartTime: time.Now().Add(-restartCooldown)} // attempt connect to system tor log.Debugf("dialing system tor control port\n") - controlport, err := dialControlPort(9051) + controlport, err := dialControlPort(tp.controlPort) if err == nil { // TODO: configurable auth - err := controlport.Authenticate("") + err := authenticator.Authenticate(controlport) if err == nil { log.Debugln("connected to control port") pinfo, err := controlport.ProtocolInfo() @@ -352,7 +359,7 @@ func (tp *torProvider) restart() { tp.t = nil for { - newTp, err := startTor(tp.appDirectory, tp.bundeledTorPath) + newTp, err := startTor(tp.appDirectory, tp.bundeledTorPath, tp.controlPort, tp.authenticator) if err == nil { tp.t = newTp.t tp.dialer, _ = tp.t.Dialer(nil, &tor.DialConf{}) diff --git a/tor/torProvider_test.go b/tor/torProvider_test.go index cce27a7..66a840f 100644 --- a/tor/torProvider_test.go +++ b/tor/torProvider_test.go @@ -14,7 +14,7 @@ func getStatusCallback(progChan chan int) func(int, string) { func TestTorProvider(t *testing.T) { progChan := make(chan int) - acn, err := NewTorACN(".", "") + acn, err := NewTorACNWithAuth(".", "", 9051, HashedPasswordAuthenticator{"examplehashedpassword"}) if err != nil { t.Error(err) return diff --git a/tor/torUtils.go b/tor/torUtils.go index 2da1ec1..0dc4172 100644 --- a/tor/torUtils.go +++ b/tor/torUtils.go @@ -2,6 +2,8 @@ package tor import ( "encoding/base32" + "errors" + "github.com/cretz/bine/control" "golang.org/x/crypto/ed25519" "golang.org/x/crypto/sha3" "strings" @@ -49,3 +51,33 @@ func IsValidHostname(address string) bool { } return false } + +// Authenticator provides a facade over various Tor control port authentication methods. +type Authenticator interface { + Authenticate(controlport *control.Conn) error +} + +// HashedPasswordAuthenticator authenticates to a Tor control port using a hashed password. +// Note: This method is vulnerable to replay attacks by the host system (but so is cookie auth) +type HashedPasswordAuthenticator struct { + password string +} + +// Authenticate uses the given hashed password to authenticate to the control port +func (h HashedPasswordAuthenticator) Authenticate(controlport *control.Conn) error { + return controlport.Authenticate(h.password) +} + +// NewHashedPasswordAuthenticator creates a new hashed password authenticator +func NewHashedPasswordAuthenticator(password string) Authenticator { + return HashedPasswordAuthenticator{password: password} +} + +// NullAuthenticator exists to force always authenticating to a system tor. +type NullAuthenticator struct { +} + +// Authenticate on a NullAuthenticator always results in failure. +func (n NullAuthenticator) Authenticate(controlport *control.Conn) error { + return errors.New("null authenticator provided, this control port is unsafe, start a new tor process instead") +}