1
mirror of https://github.com/jlelse/GoBlog synced 2024-05-30 12:34:27 +00:00

Rework tor integration to work without library

This commit is contained in:
Jan-Lukas Else 2024-05-13 13:49:12 +02:00
parent d60e613bbd
commit 760e467803
8 changed files with 120 additions and 54 deletions

View File

@ -55,7 +55,6 @@ type configServer struct {
HttpsKey string `mapstructure:"httpsKey"`
HttpsRedirect bool `mapstructure:"httpsRedirect"`
Tor bool `mapstructure:"tor"`
TorSingleHop bool `mapstructure:"torSingleHop"`
SecurityHeaders bool `mapstructure:"securityHeaders"`
CSPDomains []string `mapstructure:"cspDomains"`
publicHostname string

View File

@ -38,7 +38,6 @@ Here's an (incomplete) list of features:
- Sitemap
- Automatic HTTPS using Let's Encrypt
- Tor Hidden Service
- Tailscale integration for private blogs with HTTPS
- Fast in-memory caching for even faster performance
- Automatic asset minification of HTML, CSS and JavaScript
- Statistics page with information about posts

View File

@ -40,7 +40,6 @@ server:
- media.example.com
# Tor
tor: true # Publish onion service, requires Tor to be installed and available in path
torSingleHop: true # Enable single hop mode (non-anonymous)
# Cache
cache:

2
go.mod
View File

@ -12,8 +12,6 @@ require (
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500
github.com/carlmjohnson/requests v0.23.5
// master
github.com/cretz/bine v0.2.1-0.20221201125941-b9d31d9c7866
github.com/dchest/captcha v1.0.0
github.com/dgraph-io/ristretto v0.1.1
github.com/disintegration/imaging v1.6.2

4
go.sum
View File

@ -37,8 +37,6 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cretz/bine v0.2.1-0.20221201125941-b9d31d9c7866 h1:8Cji9JDEuYibvDQrWHvc42r9/HxjlD2kahN67LECXFk=
github.com/cretz/bine v0.2.1-0.20221201125941-b9d31d9c7866/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@ -300,7 +298,6 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
@ -315,7 +312,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=

100
pkgs/tor/tor.go Normal file
View File

@ -0,0 +1,100 @@
package tor
import (
"context"
"crypto/ed25519"
"crypto/sha512"
"encoding/base32"
"fmt"
"net"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"golang.org/x/crypto/sha3"
)
// https://github.com/torproject/torspec/blob/8961bb4d83fccb2b987f9899ca83aa430f84ab0c/rend-spec-v3.txt#L2259-L2281
func onionAddressFromEd25519(publicKey ed25519.PublicKey) string {
checksum := sha3.Sum256(append(append([]byte(".onion checksum"), publicKey...), 0x03))
checkdigits := checksum[:2]
serviceID := base32.StdEncoding.EncodeToString(append(append(publicKey[:], checkdigits...), 0x03))
return strings.ToLower(serviceID) + ".onion"
}
// https://github.com/torproject/torspec/blob/8961bb4d83fccb2b987f9899ca83aa430f84ab0c/rend-spec-v3.txt#L2391-L2449
func deriveSecretKey(privateKey ed25519.PrivateKey) []byte {
hash := sha512.Sum512(privateKey[:32])
hash[0] &= 248
hash[31] &= 127
hash[31] |= 64
return hash[:]
}
// StartTor creates a temporary directory, writes the required files to it,
// and then creates a listener and starts the tor process to publish the onion service.
func StartTor(privateKey ed25519.PrivateKey, remotePort int) (net.Listener, string, func() error, error) {
publicKey := privateKey.Public().(ed25519.PublicKey)
onion := onionAddressFromEd25519(publicKey)
// Create the hidden service directory
dir, err := os.MkdirTemp("", "hidden_service_*")
if err != nil {
return nil, "", nil, fmt.Errorf("failed to create directory: %v", err)
}
// Write the keys to a file
secretKeyFilePath := filepath.Join(dir, "hs_ed25519_secret_key")
secretKeyFileContent := append([]byte("== ed25519v1-secret: type0 ==\x00\x00\x00"), deriveSecretKey(privateKey)...)
err = os.WriteFile(secretKeyFilePath, secretKeyFileContent, 0600)
if err != nil {
return nil, "", nil, fmt.Errorf("failed to write private key: %v", err)
}
publicKeyFilePath := filepath.Join(dir, "hs_ed25519_public_key")
publicKeyFileContent := append([]byte("== ed25519v1-public: type0 ==\x00\x00\x00"), publicKey...)
err = os.WriteFile(publicKeyFilePath, publicKeyFileContent, 0600)
if err != nil {
return nil, "", nil, fmt.Errorf("failed to write public key: %v", err)
}
hostnameFilePath := filepath.Join(dir, "hostname")
err = os.WriteFile(hostnameFilePath, []byte(onion), 0600)
if err != nil {
return nil, "", nil, fmt.Errorf("failed to write hostname: %v", err)
}
// Create listener
listener, err := net.Listen("tcp4", "127.0.0.1:")
if err != nil {
return nil, "", nil, fmt.Errorf("failed to create listener: %v", err)
}
// Start the Tor process
cmd := exec.CommandContext(
context.Background(),
"tor", "--ignore-missing-torrc",
"--HiddenServiceDir", dir,
"--HiddenServicePort", strconv.Itoa(remotePort)+" "+listener.Addr().String(),
"--HiddenServiceVersion", "3",
"--SocksPort", "0",
)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Start()
if err != nil {
return nil, "", nil, fmt.Errorf("failed to start Tor: %v", err)
}
// Wait
go cmd.Wait()
return listener, onion, func() error {
if err := os.RemoveAll(dir); err != nil {
return err
}
return nil
}, nil
}

61
tor.go
View File

@ -1,19 +1,17 @@
package main
import (
"context"
"crypto"
"crypto/ed25519"
"crypto/x509"
"encoding/pem"
"errors"
"net/http"
"net/url"
"os"
"path/filepath"
"time"
"github.com/cretz/bine/tor"
"github.com/go-chi/chi/v5/middleware"
"go.goblog.app/app/pkgs/tor"
)
const torUsedKey contextKey = "tor"
@ -34,35 +32,12 @@ func (a *goBlog) startOnionService(h http.Handler) error {
}
// Start tor
a.info("Starting and registering onion service")
t, err := tor.Start(context.Background(), &tor.StartConf{
TempDataDirBase: os.TempDir(),
NoAutoSocksPort: true,
ExtraArgs: a.torExtraArgs(),
})
listener, addr, cancel, err := tor.StartTor(torKey, 80)
if err != nil {
return err
}
defer func() {
_ = t.Close()
}()
// Wait at most a few minutes to publish the service
listenCtx, listenCancel := context.WithTimeout(context.Background(), 3*time.Minute)
defer listenCancel()
// Create a v3 onion service to listen on any port but show as 80
onion, err := t.Listen(listenCtx, &tor.ListenConf{
Key: torKey,
RemotePorts: []int{80},
NonAnonymous: a.cfg.Server.TorSingleHop,
})
if err != nil {
return err
}
defer func() {
_ = onion.Close()
}()
a.torAddress = "http://" + onion.String()
torUrl, _ := url.Parse(a.torAddress)
a.torHostname = torUrl.Hostname()
a.torAddress = "http://" + addr
a.torHostname = addr
a.info("Onion service published", "address", a.torAddress)
// Clear cache
a.cache.purge()
@ -74,15 +49,20 @@ func (a *goBlog) startOnionService(h http.Handler) error {
WriteTimeout: 5 * time.Minute,
}
a.shutdown.Add(a.shutdownServer(s, "tor"))
if err = s.Serve(onion); err != nil && err != http.ErrServerClosed {
a.shutdown.Add(func() {
if err := cancel(); err != nil {
a.error("failed to shutdown tor", "err", err)
}
})
if err = s.Serve(listener); err != nil && err != http.ErrServerClosed {
return err
}
return nil
}
func (*goBlog) createTorPrivateKey(torDataPath string) (crypto.PrivateKey, error) {
func (*goBlog) createTorPrivateKey(torDataPath string) (ed25519.PrivateKey, error) {
torKeyPath := filepath.Join(torDataPath, "onion.pk")
var torKey crypto.PrivateKey
var torKey ed25519.PrivateKey
if _, err := os.Stat(torKeyPath); os.IsNotExist(err) {
// Tor private key not found, create it
_, torKey, err = ed25519.GenerateKey(nil)
@ -103,18 +83,15 @@ func (*goBlog) createTorPrivateKey(torDataPath string) (crypto.PrivateKey, error
d, _ := os.ReadFile(torKeyPath)
block, _ := pem.Decode(d)
x509Encoded := block.Bytes
torKey, err = x509.ParsePKCS8PrivateKey(x509Encoded)
parsedTorKey, err := x509.ParsePKCS8PrivateKey(x509Encoded)
if err != nil {
return nil, err
}
ok := false
torKey, ok = parsedTorKey.(ed25519.PrivateKey)
if !ok {
return nil, errors.New("could not parse Tor key as ed25519 private key")
}
}
return torKey, nil
}
func (a *goBlog) torExtraArgs() []string {
s := []string{"--SocksPort", "0"}
if a.cfg.Server.TorSingleHop {
s = append(s, "--HiddenServiceNonAnonymousMode", "1", "--HiddenServiceSingleHopMode", "1")
}
return s
}

View File

@ -1,9 +1,7 @@
#!/bin/bash
FLAGS="-tags=linux,libsqlite3,sqlite_fts5"
EXTRA="
github.com/cretz/bine@master
"
EXTRA=""
# Update all direct dependencies to latest version
echo "Check for updates..."