package tor import ( "encoding/base32" "errors" "filippo.io/edwards25519" "git.openprivacy.ca/openprivacy/bine/control" "git.openprivacy.ca/openprivacy/bine/tor" "golang.org/x/crypto/ed25519" "golang.org/x/crypto/sha3" "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] // Tor won't allow us to connect to a hostname containing a torsion component // However because we permit authentication over inbound connections we would like to // be extra safe and reject all *invalid* hostnames that contain a torsion component... // to do this we need to multiply the point by the order of the group and check that the // result is the ed25519 identity element. // l = order of the group (minus 1) lBytes := []byte{236, 211, 245, 92, 26, 99, 18, 88, 214, 156, 247, 162, 222, 249, 222, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16} l, _ := edwards25519.NewScalar().SetCanonicalBytes(lBytes) // construct a curve point from the public key // if this fails then the hostname is invalid p, err := new(edwards25519.Point).SetBytes(pubkey) if err != nil { return false } // Calculate l*P (actually (l-1)P + P because of the limitations of the scalar library... result := new(edwards25519.Point).ScalarMult(l, p) result = new(edwards25519.Point).Add(result, p) // The result should be the identity point..assuming the hostname contains no torsion components... if result.Equal(edwards25519.NewIdentityPoint()) == 0 { return false } // Finally check that we arrive at the same hostname as the one we were given... if GetTorV3Hostname(ed25519.PublicKey(pubkey)) == address { return true } } } return false } // 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) tor.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") }