126 lines
3.9 KiB
Go
126 lines
3.9 KiB
Go
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)
|
|
}
|