From d38a88be8e94873187c3b82e9b3827faa3875541 Mon Sep 17 00:00:00 2001 From: davy wybiral Date: Wed, 3 Jul 2019 16:46:59 -0500 Subject: [PATCH] Tor (#12) * added tor support * change text --- .gitignore | 3 +- config.json | 7 +++++ main.go | 2 +- pkg/app/app.go | 31 ++++++++++++++++++ pkg/app/config.go | 21 +++++++++++++ pkg/app/tor.go | 44 ++++++++++++++++++++++++++ pkg/onionkey/key.go | 22 +++++++++++++ pkg/onionkey/v3.go | 76 +++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 pkg/app/tor.go create mode 100644 pkg/onionkey/key.go create mode 100644 pkg/onionkey/v3.go diff --git a/.gitignore b/.gitignore index d51813c..b84fc54 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ videos/* -!videos/README.md \ No newline at end of file +!videos/README.md +onion.key diff --git a/config.json b/config.json index 4973bf0..fac957f 100644 --- a/config.json +++ b/config.json @@ -19,5 +19,12 @@ "email": "author@somewhere.example" }, "copyright": "Copyright Text" + }, + "tor": { + "enable": true, + "controller": { + "host": "127.0.0.1", + "port": 9051 + } } } diff --git a/main.go b/main.go index 5719274..4249353 100644 --- a/main.go +++ b/main.go @@ -19,7 +19,7 @@ func main() { log.Fatal(err) } addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port) - log.Printf("Serving at http://%s", addr) + log.Printf("Local server: http://%s", addr) err = a.Run() if err != nil { log.Fatal(err) diff --git a/pkg/app/app.go b/pkg/app/app.go index 22ad91f..57c0aa4 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -16,6 +16,7 @@ import ( "github.com/gorilla/feeds" "github.com/gorilla/mux" "github.com/wybiral/tube/pkg/media" + "github.com/wybiral/tube/pkg/onionkey" ) // App represents main application. @@ -24,6 +25,7 @@ type App struct { Library *media.Library Watcher *fsnotify.Watcher Templates *template.Template + Tor *tor Listener net.Listener Router *mux.Router } @@ -52,6 +54,14 @@ func NewApp(cfg *Config) (*App, error) { a.Listener = ln // Setup Templates a.Templates = template.Must(template.ParseGlob("templates/*")) + // Setup Tor + if cfg.Tor.Enable { + t, err := newTor(cfg.Tor) + if err != nil { + return nil, err + } + a.Tor = t + } // Setup Router r := mux.NewRouter().StrictSlash(true) r.HandleFunc("/", a.indexHandler).Methods("GET") @@ -74,6 +84,27 @@ func NewApp(cfg *Config) (*App, error) { // Run imports the library and starts server. func (a *App) Run() error { + if a.Tor != nil { + var err error + cs := a.Config.Server + key := a.Tor.OnionKey + if key == nil { + key, err = onionkey.GenerateKey() + if err != nil { + return err + } + } + onion, err := key.Onion() + if err != nil { + return err + } + onion.Ports[80] = fmt.Sprintf("%s:%d", cs.Host, cs.Port) + err = a.Tor.Controller.AddOnion(onion) + if err != nil { + return err + } + log.Printf("Onion service: http://%s.onion", onion.ServiceID) + } for _, pc := range a.Config.Library { p := &media.Path{ Path: pc.Path, diff --git a/pkg/app/config.go b/pkg/app/config.go index c2ecb45..ec27435 100644 --- a/pkg/app/config.go +++ b/pkg/app/config.go @@ -10,6 +10,7 @@ type Config struct { Library []*PathConfig `json:"library"` Server *ServerConfig `json:"server"` Feed *FeedConfig `json:"feed"` + Tor *TorConfig `json:"tor,omitempty"` } // PathConfig settings for media library path. @@ -37,6 +38,19 @@ type FeedConfig struct { Copyright string `json:"copyright"` } +// TorConfig stores tor configuration. +type TorConfig struct { + Enable bool `json:"enable"` + Controller *TorControllerConfig `json:"controller"` +} + +// TorControllerConfig stores tor controller configuration. +type TorControllerConfig struct { + Host string `json:"host"` + Port int `json:"port"` + Password string `json:"password,omitempty"` +} + // DefaultConfig returns Config initialized with default values. func DefaultConfig() *Config { return &Config{ @@ -53,6 +67,13 @@ func DefaultConfig() *Config { Feed: &FeedConfig{ ExternalURL: "http://localhost", }, + Tor: &TorConfig{ + Enable: false, + Controller: &TorControllerConfig{ + Host: "127.0.0.1", + Port: 9051, + }, + }, } } diff --git a/pkg/app/tor.go b/pkg/app/tor.go new file mode 100644 index 0000000..88f2db2 --- /dev/null +++ b/pkg/app/tor.go @@ -0,0 +1,44 @@ +package app + +import ( + "fmt" + "os" + + "github.com/wybiral/torgo" + "github.com/wybiral/tube/pkg/onionkey" +) + +type tor struct { + OnionKey onionkey.Key + Controller *torgo.Controller +} + +func newTor(ct *TorConfig) (*tor, error) { + addr := fmt.Sprintf("%s:%d", ct.Controller.Host, ct.Controller.Port) + ctrl, err := torgo.NewController(addr) + if err != nil { + return nil, err + } + if len(ct.Controller.Password) > 0 { + err = ctrl.AuthenticatePassword(ct.Controller.Password) + } else { + err = ctrl.AuthenticateCookie() + if err != nil { + err = ctrl.AuthenticateNone() + } + } + if err != nil { + return nil, err + } + key, err := onionkey.ReadFile("onion.key") + if os.IsNotExist(err) { + key = nil + } else if err != nil { + return nil, err + } + t := &tor{ + Controller: ctrl, + OnionKey: key, + } + return t, nil +} diff --git a/pkg/onionkey/key.go b/pkg/onionkey/key.go new file mode 100644 index 0000000..895edae --- /dev/null +++ b/pkg/onionkey/key.go @@ -0,0 +1,22 @@ +package onionkey + +import ( + "github.com/wybiral/torgo" +) + +// Key is generic interface type for Tor onion keys. +type Key interface { + WriteFile(path string) error + Onion() (*torgo.Onion, error) + ServiceID() string +} + +// GenerateKey generates a Tor onion key. +func GenerateKey() (Key, error) { + return generateV3() +} + +// ReadFile reads a Tor onion key from file path. +func ReadFile(path string) (Key, error) { + return readV3(path) +} diff --git a/pkg/onionkey/v3.go b/pkg/onionkey/v3.go new file mode 100644 index 0000000..155c6b1 --- /dev/null +++ b/pkg/onionkey/v3.go @@ -0,0 +1,76 @@ +package onionkey + +import ( + "crypto/rand" + "encoding/base32" + "encoding/base64" + "errors" + "io/ioutil" + "os" + "strings" + + "github.com/wybiral/torgo" + "golang.org/x/crypto/ed25519" + "golang.org/x/crypto/sha3" +) + +type v3Key ed25519.PrivateKey + +func generateV3() (v3Key, error) { + _, key, err := ed25519.GenerateKey(rand.Reader) + return v3Key(key), err +} + +func readV3(path string) (v3Key, error) { + raw, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + pk := strings.TrimSpace(string(raw)) + parts := strings.SplitN(pk, ":", 2) + if parts[0] != "v3" { + return nil, errors.New("Invalid key type") + } + seed, err := base64.StdEncoding.DecodeString(parts[1]) + if err != nil { + return nil, err + } + key := ed25519.NewKeyFromSeed(seed) + return v3Key(key), nil +} + +func (k v3Key) Onion() (*torgo.Onion, error) { + return torgo.OnionFromEd25519(ed25519.PrivateKey(k)) +} + +func (k v3Key) WriteFile(path string) error { + seed := ed25519.PrivateKey(k).Seed() + b64 := base64.StdEncoding.EncodeToString(seed) + f, err := os.Create(path) + if err != nil { + return err + } + defer f.Close() + _, err = f.WriteString("v3:" + b64) + if err != nil { + return err + } + return nil +} + +func (k v3Key) ServiceID() string { + // Get ed25519 public key + pub := ed25519.PrivateKey(k).Public().(ed25519.PublicKey) + // Calculate check digits + checkstr := []byte(".onion checksum") + checkstr = append(checkstr, pub...) + checkstr = append(checkstr, 0x03) + checksum := sha3.Sum256(checkstr) + checkdigits := checksum[:2] + // Calculate service ID + combined := pub[:] + combined = append(combined, checkdigits...) + combined = append(combined, 0x03) + serviceID := base32.StdEncoding.EncodeToString(combined) + return strings.ToLower(serviceID) +}