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" ) // V3HostnameLength is the length of a Tor V3 Onion Address (without the .onion suffix) const V3HostnameLength = 56 // Hidden service version const version = byte(0x03) // Salt used to create checkdigits const salt = ".onion checksum" func getCheckdigits(pub ed25519.PublicKey) []byte { // Calculate checksum sha3(".onion checksum" || publicKey || version) checkstr := []byte(salt) checkstr = append(checkstr, pub...) checkstr = append(checkstr, version) checksum := sha3.Sum256(checkstr) return checksum[:2] } // GetTorV3Hostname converts an ed25519 public key to a valid tor onion hostname func GetTorV3Hostname(pub ed25519.PublicKey) string { // Construct onion address base32(publicKey || checkdigits || version) checkdigits := getCheckdigits(pub) combined := pub[:] combined = append(combined, checkdigits...) combined = append(combined, version) serviceID := base32.StdEncoding.EncodeToString(combined) return strings.ToLower(serviceID) } // IsValidHostname returns true if the given address is a valid onion v3 address func IsValidHostname(address string) bool { if len(address) == V3HostnameLength { data, err := base32.StdEncoding.DecodeString(strings.ToUpper(address)) if err == nil { pubkey := data[0:ed25519.PublicKeySize] if GetTorV3Hostname(ed25519.PublicKey(pubkey)) == address { return true } } } 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") } // 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) }