From a8d31e2adb73ecdb596f9b493633e4cc8e753fa8 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Mon, 29 Jun 2020 13:21:21 -0700 Subject: [PATCH 1/2] Generate Torrc and Integration test for Launching Tor with Generated Torrc --- .drone.yml | 8 +++++ testing/launch_tor_integration_test.go | 29 ++++++++++++++++ tor/torProvider.go | 8 ++--- tor/torUtils.go | 48 ++++++++++++++++++++++++-- tor/torUtils_test.go | 23 ++++++++++++ 5 files changed, 109 insertions(+), 7 deletions(-) create mode 100644 testing/launch_tor_integration_test.go create mode 100644 tor/torUtils_test.go diff --git a/.drone.yml b/.drone.yml index fe85d4a..e7b7823 100644 --- a/.drone.yml +++ b/.drone.yml @@ -35,6 +35,14 @@ pipeline: - ./tmp/tor -f ./testing/torrc - sleep 15 - sh testing/tests.sh + integration-tests: + when: + repo: openprivacy/connectivity + branch: master + event: [ push, pull_request ] + image: golang + commands: + - go test -race -v ./testing/launch_tor_integration_test.go notify-email: image: drillster/drone-email host: build.openprivacy.ca diff --git a/testing/launch_tor_integration_test.go b/testing/launch_tor_integration_test.go new file mode 100644 index 0000000..1f52cbe --- /dev/null +++ b/testing/launch_tor_integration_test.go @@ -0,0 +1,29 @@ +package testing + +import ( + "git.openprivacy.ca/openprivacy/connectivity/tor" + "git.openprivacy.ca/openprivacy/log" + "os" + "path" + "testing" +) + +func TestLaunchTor(t *testing.T) { + log.SetLevel(log.LevelDebug) + err := tor.GenerateTorrc("examplehashedpassword", "../tmp/torrc") + + if err != nil { + t.Fatalf("failed to create torrc file: %v", err) + } + + // Get the current working director, clean the paths to remove relative references + wd, _ := os.Getwd() + t.Logf("Launching bundled tor at %v", path.Clean(wd+"/../tmp/tor")) + acn, err := tor.NewTorACNWithAuth(path.Clean(wd+"/../tmp/data"), path.Clean(wd+"/../tmp/tor"), 9051, tor.HashedPasswordAuthenticator{Password: "examplehashedpassword"}) + if err != nil { + t.Fatalf("tor failed to start: %v", err) + } + acn.WaitTillBootstrapped() + t.Log("we have bootstrapped!") + acn.Close() +} diff --git a/tor/torProvider.go b/tor/torProvider.go index 672e911..2f04cd5 100644 --- a/tor/torProvider.go +++ b/tor/torProvider.go @@ -269,7 +269,6 @@ func startTor(appDirectory string, bundledTorPath string, controlPort int, authe controlport, err := dialControlPort(tp.controlPort) if err == nil { - // TODO: configurable auth err := authenticator.Authenticate(controlport) if err == nil { log.Debugln("connected to control port") @@ -282,10 +281,10 @@ func startTor(appDirectory string, bundledTorPath string, controlPort int, authe controlport.Close() } } - + log.Debugf("launching system tor\n") // if not, try running system tor if checkCmdlineTorVersion("tor") { - t, err := tor.Start(nil, &tor.StartConf{EnableNetwork: true, DataDir: dataDir, DebugWriter: nil, ProcessCreator: newHideCmd("tor")}) + t, err := tor.Start(nil, &tor.StartConf{EnableNetwork: true, DataDir: dataDir, TorrcFile: "torrc", DebugWriter: nil, ProcessCreator: newHideCmd("tor")}) if err == nil { tp.t = t return tp, nil @@ -293,10 +292,11 @@ func startTor(appDirectory string, bundledTorPath string, controlPort int, authe log.Debugf("Error connecting to self-run system tor: %v\n", err) } + log.Debugf("launching bundled tor\n") // try running bundledTor if bundledTorPath != "" && checkCmdlineTorVersion(bundledTorPath) { log.Debugln("using bundled tor '" + bundledTorPath + "'") - t, err := tor.Start(nil, &tor.StartConf{EnableNetwork: true, DataDir: dataDir, ExePath: bundledTorPath, DebugWriter: nil, ProcessCreator: newHideCmd(bundledTorPath)}) + t, err := tor.Start(nil, &tor.StartConf{EnableNetwork: true, DataDir: dataDir, TorrcFile: "torrc", ExePath: bundledTorPath, DebugWriter: nil, ProcessCreator: newHideCmd(bundledTorPath)}) if err != nil { log.Debugf("Error running bundled tor: %v\n", err) } diff --git a/tor/torUtils.go b/tor/torUtils.go index 0dc4172..350025e 100644 --- a/tor/torUtils.go +++ b/tor/torUtils.go @@ -1,11 +1,17 @@ package tor import ( + "crypto/rand" + "crypto/sha1" "encoding/base32" + "encoding/hex" "errors" + "fmt" "github.com/cretz/bine/control" "golang.org/x/crypto/ed25519" "golang.org/x/crypto/sha3" + "io" + "io/ioutil" "strings" ) @@ -60,17 +66,17 @@ type Authenticator interface { // 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 + 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) + return controlport.Authenticate(h.Password) } // NewHashedPasswordAuthenticator creates a new hashed password authenticator func NewHashedPasswordAuthenticator(password string) Authenticator { - return HashedPasswordAuthenticator{password: password} + return HashedPasswordAuthenticator{Password: password} } // NullAuthenticator exists to force always authenticating to a system tor. @@ -81,3 +87,39 @@ type NullAuthenticator struct { 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") } + +// GenerateHashedPassword calculates a hash in the same waay tha tor --hash-password does +// this function takes a salt as input which is not great from an api-misuse perspective, but +// we make it private. +func generateHashedPassword(salt [8]byte, password string) string { + c := 96 + count := (16 + (c & 15)) << ((c >> 4) + 6) + tmp := append(salt[:], []byte(password)...) + slen := len(tmp) + d := sha1.New() + for count != 0 { + if count > slen { + d.Write(tmp) + count -= slen + } else { + d.Write(tmp[:count]) + count = 0 + } + } + hashed := d.Sum([]byte{}) + return fmt.Sprintf("16:%s%s%s", strings.ToUpper(hex.EncodeToString(salt[:])), + strings.ToUpper(hex.EncodeToString([]byte{byte(c)})), + strings.ToUpper(hex.EncodeToString(hashed))) +} + +// GenerateTorrc generates a Tor config file with the control port bieng guarded by a hash of the given +// password. +func GenerateTorrc(password string, path string) error { + var salt [8]byte + if _, err := io.ReadFull(rand.Reader, salt[:]); err != nil { + return err + } + hashedpassword := generateHashedPassword(salt, password) + contents := fmt.Sprintf("SOCKSPort 9050\nControlPort 9051\nHashedControlPassword %s", hashedpassword) + return ioutil.WriteFile(path, []byte(contents), 0600) +} diff --git a/tor/torUtils_test.go b/tor/torUtils_test.go new file mode 100644 index 0000000..f3e46fa --- /dev/null +++ b/tor/torUtils_test.go @@ -0,0 +1,23 @@ +package tor + +import ( + "os" + "testing" +) + +func TestGenerateHashedPassword(t *testing.T) { + // 16:C15305F97789414B601259E3EC5E76B8E55FC56A9F562B713F3D2BA257 + hp := generateHashedPassword([8]byte{0xC1, 0x53, 0x05, 0xF9, 0x77, 0x89, 0x41, 0x4B}, "examplehashedpassword") + if hp != "16:C15305F97789414B601259E3EC5E76B8E55FC56A9F562B713F3D2BA257" { + t.Fatalf("hashed passwords do not match. Expected %s, got %s", "16:C15305F97789414B601259E3EC5E76B8E55FC56A9F562B713F3D2BA257", hp) + } +} + +func TestGenerateTorrc(t *testing.T) { + path := "./torrc.test" + err := GenerateTorrc("examplehashedpassword", path) + if err != nil { + t.Errorf("Torrc file could not be written") + } + os.Remove(path) +} From 3888ece4d3d42b69369dcf79639c1ac4bc92cc84 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Mon, 29 Jun 2020 13:25:26 -0700 Subject: [PATCH 2/2] Kill Tor once the unit tests have finsihed --- .drone.yml | 1 + testing/launch_tor_integration_test.go | 4 +++- tor/torProvider.go | 12 ++++++++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.drone.yml b/.drone.yml index e7b7823..d94103f 100644 --- a/.drone.yml +++ b/.drone.yml @@ -35,6 +35,7 @@ pipeline: - ./tmp/tor -f ./testing/torrc - sleep 15 - sh testing/tests.sh + - pkill -9 tor integration-tests: when: repo: openprivacy/connectivity diff --git a/testing/launch_tor_integration_test.go b/testing/launch_tor_integration_test.go index 1f52cbe..8043796 100644 --- a/testing/launch_tor_integration_test.go +++ b/testing/launch_tor_integration_test.go @@ -10,7 +10,9 @@ import ( func TestLaunchTor(t *testing.T) { log.SetLevel(log.LevelDebug) - err := tor.GenerateTorrc("examplehashedpassword", "../tmp/torrc") + // Create the tor data directory if it doesn't already exist.. + os.MkdirAll("../tmp/data/tor", 0700) + err := tor.GenerateTorrc("examplehashedpassword", "../tmp/data/tor/torrc") if err != nil { t.Fatalf("failed to create torrc file: %v", err) diff --git a/tor/torProvider.go b/tor/torProvider.go index 2f04cd5..7b90396 100644 --- a/tor/torProvider.go +++ b/tor/torProvider.go @@ -3,6 +3,7 @@ package tor import ( "context" "errors" + "fmt" "git.openprivacy.ca/openprivacy/connectivity" "git.openprivacy.ca/openprivacy/log" "github.com/cretz/bine/control" @@ -282,9 +283,16 @@ func startTor(appDirectory string, bundledTorPath string, controlPort int, authe } } log.Debugf("launching system tor\n") + + // check if the torrc file is present where expected + if _, err := os.Stat(path.Join(dataDir, "torrc")); os.IsNotExist(err) { + log.Debugf("torrc file does not exist at %v", path.Join(dataDir, "torrc")) + return nil, fmt.Errorf("torrc file does not exist at %v", path.Join(dataDir, "torrc")) + } + // if not, try running system tor if checkCmdlineTorVersion("tor") { - t, err := tor.Start(nil, &tor.StartConf{EnableNetwork: true, DataDir: dataDir, TorrcFile: "torrc", DebugWriter: nil, ProcessCreator: newHideCmd("tor")}) + t, err := tor.Start(nil, &tor.StartConf{EnableNetwork: true, DataDir: dataDir, TorrcFile: path.Join(dataDir, "torrc"), DebugWriter: nil, ProcessCreator: newHideCmd("tor")}) if err == nil { tp.t = t return tp, nil @@ -296,7 +304,7 @@ func startTor(appDirectory string, bundledTorPath string, controlPort int, authe // try running bundledTor if bundledTorPath != "" && checkCmdlineTorVersion(bundledTorPath) { log.Debugln("using bundled tor '" + bundledTorPath + "'") - t, err := tor.Start(nil, &tor.StartConf{EnableNetwork: true, DataDir: dataDir, TorrcFile: "torrc", ExePath: bundledTorPath, DebugWriter: nil, ProcessCreator: newHideCmd(bundledTorPath)}) + t, err := tor.Start(nil, &tor.StartConf{EnableNetwork: true, DataDir: dataDir, TorrcFile: path.Join(dataDir, "torrc"), ExePath: bundledTorPath, DebugWriter: nil, ProcessCreator: newHideCmd(bundledTorPath)}) if err != nil { log.Debugf("Error running bundled tor: %v\n", err) }