From ba56a6cb4849fde711a4dbc6979f49cc344455a7 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Tue, 13 Oct 2020 11:10:45 -0700 Subject: [PATCH] Better Torrc Builder --- testing/launch_tor_integration_test.go | 9 ++- tor/torUtils.go | 42 ------------- tor/torUtils_test.go | 3 +- tor/torrcBuilder.go | 86 ++++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 45 deletions(-) create mode 100644 tor/torrcBuilder.go diff --git a/testing/launch_tor_integration_test.go b/testing/launch_tor_integration_test.go index 8043796..a1f124b 100644 --- a/testing/launch_tor_integration_test.go +++ b/testing/launch_tor_integration_test.go @@ -3,6 +3,7 @@ package testing import ( "git.openprivacy.ca/openprivacy/connectivity/tor" "git.openprivacy.ca/openprivacy/log" + "math/rand" "os" "path" "testing" @@ -10,9 +11,13 @@ import ( func TestLaunchTor(t *testing.T) { log.SetLevel(log.LevelDebug) + + controlPort := rand.Intn(1000) + 9052 + password := "examplehashedpassword" + // 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") + err := tor.NewTorrc().WithHashedPassword(password).WithControlPort(controlPort).Build("../tmp/data/tor/torrc") if err != nil { t.Fatalf("failed to create torrc file: %v", err) @@ -21,7 +26,7 @@ func TestLaunchTor(t *testing.T) { // 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"}) + acn, err := tor.NewTorACNWithAuth(path.Clean(wd+"/../tmp/data"), path.Clean(wd+"/../tmp/tor"), controlPort, tor.HashedPasswordAuthenticator{Password: password}) if err != nil { t.Fatalf("tor failed to start: %v", err) } diff --git a/tor/torUtils.go b/tor/torUtils.go index 350025e..b378125 100644 --- a/tor/torUtils.go +++ b/tor/torUtils.go @@ -1,17 +1,11 @@ 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" ) @@ -87,39 +81,3 @@ 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 index f3e46fa..e5bc476 100644 --- a/tor/torUtils_test.go +++ b/tor/torUtils_test.go @@ -15,7 +15,8 @@ func TestGenerateHashedPassword(t *testing.T) { func TestGenerateTorrc(t *testing.T) { path := "./torrc.test" - err := GenerateTorrc("examplehashedpassword", path) + password := "examplehashedpassword" + err := NewTorrc().WithHashedPassword(password).Build(path) if err != nil { t.Errorf("Torrc file could not be written") } diff --git a/tor/torrcBuilder.go b/tor/torrcBuilder.go new file mode 100644 index 0000000..1b4ea2f --- /dev/null +++ b/tor/torrcBuilder.go @@ -0,0 +1,86 @@ +package tor + +import ( + "crypto/rand" + "crypto/sha1" + "encoding/hex" + "fmt" + "io" + "io/ioutil" + "strings" +) + +// TorrcBuilder is a a helper for building torrc files +type TorrcBuilder struct { + lines []string +} + +// NewTorrc creates a new torrc builder +func NewTorrc() *TorrcBuilder { + return &TorrcBuilder{ + lines: []string{}, + } +} + +// WithSocksPort sets the SOCKS port of the tor process +func (tb *TorrcBuilder) WithSocksPort(port int) *TorrcBuilder { + tb.lines = append(tb.lines, fmt.Sprintf("SocksPort %v", port)) + return tb +} + +// WithControlPort sets the control port of the tor process +func (tb *TorrcBuilder) WithControlPort(port int) *TorrcBuilder { + tb.lines = append(tb.lines, fmt.Sprintf("ControlPort %v", port)) + return tb +} + +// WithOnionTrafficOnly ensures that the tor process only routes tor onion traffic. +func (tb *TorrcBuilder) WithOnionTrafficOnly() *TorrcBuilder { + for i, line := range tb.lines { + if strings.HasPrefix(line, "SocksPort") { + tb.lines[i] = fmt.Sprintf("%v OnionTrafficOnly", line) + return tb + } + } + return tb +} + +// WithHashedPassword sets a password for the control port. +func (tb *TorrcBuilder) WithHashedPassword(password string) *TorrcBuilder { + var salt [8]byte + if _, err := io.ReadFull(rand.Reader, salt[:]); err != nil { + panic("no randomness") + } + hashedpassword := generateHashedPassword(salt, password) + tb.lines = append(tb.lines, fmt.Sprintf("HashedControlPassword %s", hashedpassword)) + return tb +} + +// Build finalizes the torrc contents and write a file +func (tb *TorrcBuilder) Build(path string) error { + return ioutil.WriteFile(path, []byte(strings.Join(tb.lines, "\n")), 0600) +} + +// GenerateHashedPassword calculates a hash in the same way 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))) +}