mirror of
https://github.com/go-i2p/onramp.git
synced 2025-07-12 10:55:29 -04:00
381 lines
10 KiB
Go
381 lines
10 KiB
Go
//go:build !gen
|
|
// +build !gen
|
|
|
|
package onramp
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"github.com/cretz/bine/tor"
|
|
"github.com/cretz/bine/torutil/ed25519"
|
|
)
|
|
|
|
var torp *tor.Tor
|
|
|
|
// Onion represents a structure which manages an onion service and
|
|
// a Tor client. The onion service will automatically have persistent
|
|
// keys.
|
|
type Onion struct {
|
|
*tor.StartConf
|
|
*tor.ListenConf
|
|
*tor.DialConf
|
|
context.Context
|
|
name string
|
|
}
|
|
|
|
func (o *Onion) getStartConf() *tor.StartConf {
|
|
if o.StartConf == nil {
|
|
o.StartConf = &tor.StartConf{}
|
|
}
|
|
return o.StartConf
|
|
}
|
|
|
|
func (o *Onion) getContext() context.Context {
|
|
if o.Context == nil {
|
|
o.Context = context.Background()
|
|
}
|
|
return o.Context
|
|
}
|
|
|
|
func (o *Onion) getListenConf() *tor.ListenConf {
|
|
keys, err := o.Keys()
|
|
if err != nil {
|
|
log.Fatalf("Unable to get onion service keys, %s", err)
|
|
}
|
|
if o.ListenConf == nil {
|
|
o.ListenConf = &tor.ListenConf{
|
|
Key: keys,
|
|
}
|
|
}
|
|
return o.ListenConf
|
|
}
|
|
|
|
func (o *Onion) getDialConf() *tor.DialConf {
|
|
if o.DialConf == nil {
|
|
o.DialConf = &tor.DialConf{}
|
|
}
|
|
return o.DialConf
|
|
}
|
|
|
|
func (o *Onion) getTor() *tor.Tor {
|
|
if torp == nil {
|
|
log.Debug("Initializing new Tor instance")
|
|
var err error
|
|
torp, err = tor.Start(o.getContext(), o.getStartConf())
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to start Tor")
|
|
panic(err) // return nil instead?
|
|
}
|
|
log.Debug("Tor instance started successfully")
|
|
}
|
|
return torp
|
|
}
|
|
|
|
func (o *Onion) getDialer() *tor.Dialer {
|
|
// if o.Dialer == nil {
|
|
// var err error
|
|
// o.Dialer, err
|
|
log.Debug("Creating new Tor dialer")
|
|
dialer, err := o.getTor().Dialer(o.getContext(), o.getDialConf())
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to create Tor dialer")
|
|
panic(err)
|
|
}
|
|
log.Debug("Tor dialer created successfully")
|
|
//}
|
|
//return o.Dialer
|
|
return dialer
|
|
}
|
|
|
|
func (o *Onion) getName() string {
|
|
if o.name == "" {
|
|
o.name = "onramp-onion"
|
|
}
|
|
return o.name
|
|
}
|
|
|
|
// NewListener returns a net.Listener which will listen on an onion
|
|
// address, and will automatically generate a keypair and store it.
|
|
// the args are always ignored
|
|
func (o *Onion) NewListener(n, addr string) (net.Listener, error) {
|
|
return o.Listen(n)
|
|
}
|
|
|
|
// Listen returns a net.Listener which will listen on an onion
|
|
// address, and will automatically generate a keypair and store it.
|
|
// the args are always ignored
|
|
func (o *Onion) Listen(args ...string) (net.Listener, error) {
|
|
log.WithFields(logrus.Fields{
|
|
"args": args,
|
|
"name": o.getName(),
|
|
}).Debug("Setting up Onion listener")
|
|
listener, err := o.OldListen(args...)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to create Onion listener")
|
|
return nil, err
|
|
}
|
|
|
|
log.Debug("Successfully created Onion listener")
|
|
return listener, nil
|
|
// return o.OldListen(args...)
|
|
}
|
|
|
|
// OldListen returns a net.Listener which will listen on an onion
|
|
// address, and will automatically generate a keypair and store it.
|
|
// the args are always ignored
|
|
func (o *Onion) OldListen(args ...string) (net.Listener, error) {
|
|
log.WithField("name", o.getName()).Debug("Creating Tor listener")
|
|
|
|
listener, err := o.getTor().Listen(o.getContext(), o.getListenConf())
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to create Tor listener")
|
|
return nil, err
|
|
}
|
|
|
|
log.Debug("Successfully created Tor listener")
|
|
return listener, nil
|
|
// return o.getTor().Listen(o.getContext(), o.getListenConf())
|
|
}
|
|
|
|
// ListenTLS returns a net.Listener which will apply TLS encryption
|
|
// to the onion listener, which will not be decrypted until it reaches
|
|
// the browser
|
|
func (o *Onion) ListenTLS(args ...string) (net.Listener, error) {
|
|
log.WithField("args", args).Debug("Setting up TLS Onion listener")
|
|
cert, err := o.TLSKeys()
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to get TLS keys")
|
|
return nil, fmt.Errorf("onramp ListenTLS: %v", err)
|
|
}
|
|
log.Debug("Creating base Tor listener")
|
|
l, err := o.getTor().Listen(o.getContext(), o.getListenConf())
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to create base Tor listener")
|
|
return nil, err
|
|
}
|
|
log.Debug("Wrapping Tor listener with TLS")
|
|
return tls.NewListener(
|
|
l,
|
|
&tls.Config{
|
|
Certificates: []tls.Certificate{cert},
|
|
},
|
|
), nil
|
|
}
|
|
|
|
// Dial returns a net.Conn to the given onion address or clearnet address.
|
|
func (o *Onion) Dial(net, addr string) (net.Conn, error) {
|
|
log.WithFields(logrus.Fields{
|
|
"network": net,
|
|
"address": addr,
|
|
}).Debug("Attempting to dial via Tor")
|
|
conn, err := o.getDialer().DialContext(o.getContext(), net, addr)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to establish Tor connection")
|
|
return nil, err
|
|
}
|
|
|
|
log.Debug("Successfully established Tor connection")
|
|
return conn, nil
|
|
// return o.getDialer().DialContext(o.getContext(), net, addr)
|
|
}
|
|
|
|
// Close closes the Onion Service and all associated resources.
|
|
func (o *Onion) Close() error {
|
|
log.WithField("name", o.getName()).Debug("Closing Onion service")
|
|
|
|
err := o.getTor().Close()
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to close Tor instance")
|
|
return err
|
|
}
|
|
|
|
log.Debug("Successfully closed Onion service")
|
|
return nil
|
|
// return o.getTor().Close()
|
|
}
|
|
|
|
// Keys returns the keys for the Onion
|
|
func (o *Onion) Keys() (ed25519.KeyPair, error) {
|
|
log.WithField("name", o.getName()).Debug("Retrieving Onion keys")
|
|
|
|
keys, err := TorKeys(o.getName())
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to get Tor keys")
|
|
return nil, err
|
|
}
|
|
|
|
log.Debug("Successfully retrieved Onion keys")
|
|
return keys, nil
|
|
// return TorKeys(o.getName())
|
|
}
|
|
|
|
// DeleteKeys deletes the keys at the given key name in the key store.
|
|
// This is permanent and irreversible, and will change the onion service
|
|
// address.
|
|
func (g *Onion) DeleteKeys() error {
|
|
log.WithField("Onion keys", g.getName()).Debug("Deleting Onion keys")
|
|
return DeleteOnionKeys(g.getName())
|
|
}
|
|
|
|
// NewOnion returns a new Onion object.
|
|
func NewOnion(name string) (*Onion, error) {
|
|
return &Onion{
|
|
name: name,
|
|
}, nil
|
|
}
|
|
|
|
// TorKeys returns a key pair which will be stored at the given key
|
|
// name in the key store. If the key already exists, it will be
|
|
// returned. If it does not exist, it will be generated.
|
|
func TorKeys(keyName string) (ed25519.KeyPair, error) {
|
|
log.WithField("key_name", keyName).Debug("Getting Tor keys")
|
|
keystore, err := TorKeystorePath()
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to get keystore path")
|
|
return nil, fmt.Errorf("onramp OnionKeys: discovery error %v", err)
|
|
}
|
|
var keys ed25519.KeyPair
|
|
keysPath := filepath.Join(keystore, keyName+".tor.private")
|
|
log.WithField("path", keysPath).Debug("Checking for existing keys")
|
|
if _, err := os.Stat(keysPath); os.IsNotExist(err) {
|
|
log.Debug("Generating new Tor keys")
|
|
tkeys, err := ed25519.GenerateKey(nil)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to generate onion service key")
|
|
log.Fatal("Unable to generate onion service key")
|
|
}
|
|
keys = tkeys
|
|
|
|
log.WithField("path", keysPath).Debug("Creating key file")
|
|
f, err := os.Create(keysPath)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to create Tor keys file")
|
|
log.Fatal("Unable to create Tor keys file for writing")
|
|
}
|
|
defer f.Close()
|
|
_, err = f.Write(tkeys.PrivateKey())
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to write Tor keys to disk")
|
|
log.Fatal("Unable to write Tor keys to disk")
|
|
}
|
|
log.Debug("Successfully generated and stored new keys")
|
|
} else if err == nil {
|
|
log.Debug("Loading existing Tor keys")
|
|
tkeys, err := os.ReadFile(keysPath)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to read Tor keys from disk")
|
|
log.Fatal("Unable to read Tor keys from disk")
|
|
}
|
|
k := ed25519.FromCryptoPrivateKey(tkeys)
|
|
keys = k
|
|
log.Debug("Successfully loaded existing keys")
|
|
} else {
|
|
log.WithError(err).Error("Failed to set up Tor keys")
|
|
log.Fatal("Unable to set up Tor keys")
|
|
}
|
|
return keys, nil
|
|
}
|
|
|
|
var onions map[string]*Onion
|
|
|
|
// CloseAllOnion closes all onions managed by the onramp package. It does not
|
|
// affect objects instantiated by an app.
|
|
func CloseAllOnion() {
|
|
log.WithField("count", len(onions)).Debug("Closing all Onion services")
|
|
for i, g := range onions {
|
|
log.WithFields(logrus.Fields{
|
|
"index": i,
|
|
"name": g.name,
|
|
}).Debug("Closing Onion service")
|
|
CloseOnion(i)
|
|
}
|
|
|
|
log.Debug("All Onion services closed")
|
|
}
|
|
|
|
// CloseOnion closes the Onion at the given index. It does not affect Onion
|
|
// objects instantiated by an app.
|
|
func CloseOnion(tunName string) {
|
|
log.WithField("tunnel_name", tunName).Debug("Attempting to close Onion service")
|
|
|
|
g, ok := onions[tunName]
|
|
if ok {
|
|
log.WithField("name", g.name).Debug("Found Onion service, closing")
|
|
err := g.Close()
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to close Onion service")
|
|
} else {
|
|
log.Debug("Successfully closed Onion service")
|
|
}
|
|
} else {
|
|
log.Debug("No Onion service found for tunnel name")
|
|
}
|
|
}
|
|
|
|
// ListenOnion returns a net.Listener for a onion structure's keys
|
|
// corresponding to a structure managed by the onramp library
|
|
// and not instantiated by an app.
|
|
func ListenOnion(network, keys string) (net.Listener, error) {
|
|
log.WithFields(logrus.Fields{
|
|
"network": network,
|
|
"keys": keys,
|
|
}).Debug("Creating new Onion listener")
|
|
|
|
g, err := NewOnion(keys)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to create new Onion")
|
|
return nil, fmt.Errorf("onramp Listen: %v", err)
|
|
}
|
|
onions[keys] = g
|
|
log.Debug("Onion service registered, creating listener")
|
|
|
|
listener, err := g.Listen()
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to create Onion listener")
|
|
return nil, err
|
|
}
|
|
|
|
log.Debug("Successfully created Onion listener")
|
|
return listener, nil
|
|
// return g.Listen()
|
|
}
|
|
|
|
// DialOnion returns a net.Conn for a onion structure's keys
|
|
// corresponding to a structure managed by the onramp library
|
|
// and not instantiated by an app.
|
|
func DialOnion(network, addr string) (net.Conn, error) {
|
|
g, err := NewOnion(addr)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("onramp Dial: %v", err)
|
|
}
|
|
onions[addr] = g
|
|
return g.Dial(network, addr)
|
|
}
|
|
|
|
// DeleteOnionKeys deletes the key file at the given path as determined by
|
|
// keystore + tunName.
|
|
func DeleteOnionKeys(tunName string) error {
|
|
log.WithField("tunnel_name", tunName).Debug("Attempting to delete Onion keys")
|
|
|
|
keystore, err := TorKeystorePath()
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to get keystore path")
|
|
return fmt.Errorf("onramp DeleteOnionKeys: discovery error %v", err)
|
|
}
|
|
keyspath := filepath.Join(keystore, tunName+".i2p.private")
|
|
log.WithError(err).Error("Failed to get keystore path")
|
|
if err := os.Remove(keyspath); err != nil {
|
|
log.WithError(err).WithField("path", keyspath).Error("Failed to delete key file")
|
|
return fmt.Errorf("onramp DeleteOnionKeys: %v", err)
|
|
}
|
|
log.Debug("Successfully deleted Onion keys")
|
|
return nil
|
|
}
|