summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--config.json7
-rw-r--r--main.go2
-rw-r--r--pkg/app/app.go31
-rw-r--r--pkg/app/config.go21
-rw-r--r--pkg/app/tor.go44
-rw-r--r--pkg/onionkey/key.go22
-rw-r--r--pkg/onionkey/v3.go76
8 files changed, 204 insertions, 2 deletions
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)
+}