ipsn / go-libtor

Self-contained Tor from Go
BSD 3-Clause "New" or "Revised" License
548 stars 47 forks source link

How is it possible to persist V3 .onion address? #38

Open qwertyqop opened 2 years ago

qwertyqop commented 2 years ago

I would like to keep my onion address static after I close the program and reopen it. this is done with V2 addressed as described in issue #19, however it is not applicable with V3 addresses. how do I make V3 addresses persistent after reopening the program?

Danukeru commented 2 years ago

Working off of the libtor example, observe these comments and changes.

import  (
...
        "github.com/cretz/bine/torutil/ed25519"
         b64 "encoding/base64"
)

...

    // $ dd if=/dev/urandom bs=64 count=1 status=none| base64 -w100
    // OR if you generated a vanity address, use this to dump the last 64 bytes from a stored onion-V3 :
    // $ dd if=hs_ed25519_secret_key skip=32 bs=1 status=none | base64 -w 100
    _b64Ed25519 := "OUyeOZtOT8JgE2JrjDsoFTCGWYdz+rc1CTEbgGtD6swEQcD1jmWkWUrkmFiRLkcBVvio5/yUyRJ24fqNyei57Q=="
    _key, _ := b64.StdEncoding.DecodeString(_b64Ed25519)

    // Create an onion service to listen on any port but show as 80
    onion, err := t.Listen(ctx, &tor.ListenConf{RemotePorts: []int{80}, Version3: true, Key: ed25519.PrivateKey(_key)})
    if err != nil {
        log.Panicf("Failed to create onion service: %v", err)
    }
    defer onion.Close()
ciehanski commented 2 years ago

As @Danukeru's example shows, you can utilize a vanity address as well. There is a nice little Go package here that can assist with generating that if you'd like to specify a reusable prefix for your V3 address.

mh-cbon commented 2 years ago

I use this scheme based on ciehanski post.

To create a key,

func createKey(path string) (crypto.PrivateKey, error) {
    path = strings.TrimSuffix(path, "hs_ed25519_secret_key")
    err := os.MkdirAll(path, 0700)
    if err != nil {
        return nil, err
    }

    publicKey, secretKey, err := ed25519.GenerateKey(nil)
    if err != nil {
        return nil, err
    }
    onionAddress := encodePublicKey(publicKey)

    expandedSecretKey := expandSecretKey(secretKey)
    secretKeyFile := append([]byte("== ed25519v1-secret: type0 ==\x00\x00\x00"), expandedSecretKey[:]...)
    secretKeyPath := filepath.Join(path, "/hs_ed25519_secret_key")
    err = os.WriteFile(secretKeyPath, secretKeyFile, 0600)
    if err != nil {
        return nil, err
    }
    publicKeyFile := append([]byte("== ed25519v1-public: type0 ==\x00\x00\x00"), publicKey...)
    publicKeyPath := filepath.Join(path, "/hs_ed25519_public_key")
    err = os.WriteFile(publicKeyPath, publicKeyFile, 0600)
    if err != nil {
        os.Remove(secretKeyPath)
        return nil, err
    }
    hostnameFile := []byte(onionAddress + ".onion\n")
    hostnamePath := filepath.Join(path, "/hostname")
    err = os.WriteFile(hostnamePath, hostnameFile, 0600)
    if err != nil {
        os.Remove(secretKeyPath)
        os.Remove(publicKeyPath)
        return nil, err
    }
    return bed25519.PrivateKey(expandedSecretKey[:]), nil
}
func expandSecretKey(secretKey ed25519.PrivateKey) [64]byte {
    hash := sha512.Sum512(secretKey[:32])
    hash[0] &= 248
    hash[31] &= 127
    hash[31] |= 64
    return hash
}
func encodePublicKey(publicKey ed25519.PublicKey) string {
    // checksum = H(".onion checksum" || pubkey || version)
    var checksumBytes bytes.Buffer
    checksumBytes.Write([]byte(".onion checksum"))
    checksumBytes.Write([]byte(publicKey))
    checksumBytes.Write([]byte{0x03})
    checksum := sha3.Sum256(checksumBytes.Bytes())

    // onion_address = base32(pubkey || checksum || version)
    var onionAddressBytes bytes.Buffer
    onionAddressBytes.Write([]byte(publicKey))
    onionAddressBytes.Write([]byte(checksum[:2]))
    onionAddressBytes.Write([]byte{0x03})
    onionAddress := base32.StdEncoding.EncodeToString(onionAddressBytes.Bytes())

    return strings.ToLower(onionAddress)
}

to load a key

func loadKey(path string) (crypto.PrivateKey, error) {
    var key crypto.PrivateKey
    if path != "" {
        if !strings.HasSuffix(path, "hs_ed25519_secret_key") {
            path = filepath.Join(path, "hs_ed25519_secret_key")
        }
        d, err := os.ReadFile(path)
        if err != nil {
            return nil, fmt.Errorf("failed to load the private key: %v", err)
        }
        d = bytes.TrimPrefix(d, []byte("== ed25519v1-secret: type0 ==\x00\x00\x00"))
        key = bed25519.PrivateKey(d)
    }
    return key, nil
}

The bed25519 import is github.com/cretz/bine/torutil/ed25519

Then I give the key to the Tor process creator and it works upon restart. smooth.