16 Commits

Author SHA1 Message Date
eyedeekay
00aafc2ede fix the broken tests 2024-11-13 14:55:42 -05:00
eyedeekay
8920b7c847 update version 2024-11-13 14:53:54 -05:00
eyedeekay
2791849c67 update modules 2024-11-13 14:52:44 -05:00
eyedeekay
0d9a9ee10a Fix up import paths 2024-11-09 11:54:29 -05:00
eyedeekay
867026628f setup auto-assign workflow 2024-11-08 15:01:15 -05:00
eyedeekay
56eed1e88f Merge branch 'main' of github.com:eyedeekay/onramp 2024-11-08 12:55:30 -05:00
eyedeekay
c4e23394dc change module path 2024-11-08 12:54:30 -05:00
idk
f9fc572938 Merge pull request #1 from hkh4n/logging
Logging
2024-11-05 00:03:47 +00:00
Haris Khan
e184eda3c1 replaced deprecated function 2024-11-04 15:20:23 -05:00
Haris Khan
a0a35dd81c added logging to tls.go 2024-11-04 15:17:48 -05:00
Haris Khan
7ef5793e5a added logging to proxy.go 2024-11-04 15:11:49 -05:00
Haris Khan
c2b8660d5e added logging to onion.go 2024-11-04 15:08:35 -05:00
Haris Khan
680d5c000b added logging to garlic.go 2024-11-04 14:52:43 -05:00
Haris Khan
c2b56d35e2 updated README.md to reflect new logging paradigm 2024-11-04 14:44:07 -05:00
Haris Khan
2a53e8347c added logging to common.go 2024-11-04 11:05:47 -05:00
Haris Khan
dcb997a327 added logging paradigm to log.go 2024-11-04 11:05:39 -05:00
14 changed files with 627 additions and 56 deletions

20
.github/workflows/auto-assign.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: Auto Assign
on:
issues:
types: [opened]
pull_request:
types: [opened]
jobs:
run:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- name: 'Auto-assign issue'
uses: pozil/auto-assign-issue@v1
with:
repo-token:${{ secrets.GITHUB_TOKEN }}
assignees: eyedeekay
numOfAssignee: 1

View File

@@ -1,6 +1,6 @@
USER_GH=eyedeekay USER_GH=eyedeekay
VERSION=0.33.8 VERSION=0.33.9
CREDIT='contributors to this release: @hkh4n, @eyedeekay' CREDIT='contributors to this release: @hkh4n, @eyedeekay'
packagename=onramp packagename=onramp

View File

@@ -77,3 +77,31 @@ func main() {
defer listener.Close() defer listener.Close()
} }
``` ```
## Verbosity ##
Logging can be enabled and configured using the DEBUG_I2P environment variable. By default, logging is disabled.
There are three available log levels:
- Debug
```shell
export DEBUG_I2P=debug
```
- Warn
```shell
export DEBUG_I2P=warn
```
- Error
```shell
export DEBUG_I2P=error
```
If DEBUG_I2P is set to an unrecognized variable, it will fall back to "debug".
## Contributing
See CONTRIBUTING.md for more information.
## License
This project is licensed under the MIT license, see LICENSE for more information.

View File

@@ -4,6 +4,7 @@
package onramp package onramp
import ( import (
"github.com/sirupsen/logrus"
"net" "net"
"net/url" "net/url"
"os" "os"
@@ -17,16 +18,23 @@ import (
func GetJoinedWD(dir string) (string, error) { func GetJoinedWD(dir string) (string, error) {
wd, err := os.Getwd() wd, err := os.Getwd()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get working directory")
return "", err return "", err
} }
jwd := filepath.Join(wd, dir) jwd := filepath.Join(wd, dir)
ajwd, err := filepath.Abs(jwd) ajwd, err := filepath.Abs(jwd)
if err != nil { if err != nil {
log.WithError(err).WithField("path", jwd).Error("Failed to get absolute path")
return "", err return "", err
} }
if _, err := os.Stat(ajwd); err != nil { if _, err := os.Stat(ajwd); err != nil {
os.MkdirAll(ajwd, 0755) log.WithField("path", ajwd).Debug("Directory does not exist, creating")
if err := os.MkdirAll(ajwd, 0755); err != nil {
log.WithError(err).WithField("path", ajwd).Error("Failed to create directory")
return "", err
}
} }
log.WithField("path", ajwd).Debug("Successfully got joined working directory")
return ajwd, nil return ajwd, nil
} }
@@ -53,68 +61,112 @@ var TLS_KEYSTORE_PATH = tlsdefault
// path is not set, it returns the default path. If the path does // path is not set, it returns the default path. If the path does
// not exist, it creates it. // not exist, it creates it.
func I2PKeystorePath() (string, error) { func I2PKeystorePath() (string, error) {
log.WithField("path", I2P_KEYSTORE_PATH).Debug("Checking I2P keystore path")
if _, err := os.Stat(I2P_KEYSTORE_PATH); err != nil { if _, err := os.Stat(I2P_KEYSTORE_PATH); err != nil {
log.WithField("path", I2P_KEYSTORE_PATH).Debug("I2P keystore directory does not exist, creating")
err := os.MkdirAll(I2P_KEYSTORE_PATH, 0755) err := os.MkdirAll(I2P_KEYSTORE_PATH, 0755)
if err != nil { if err != nil {
log.WithError(err).WithField("path", I2P_KEYSTORE_PATH).Error("Failed to create I2P keystore directory")
return "", err return "", err
} }
} }
log.WithField("path", I2P_KEYSTORE_PATH).Debug("I2P keystore path verified")
return I2P_KEYSTORE_PATH, nil return I2P_KEYSTORE_PATH, nil
} }
// DeleteI2PKeyStore deletes the I2P Keystore. // DeleteI2PKeyStore deletes the I2P Keystore.
func DeleteI2PKeyStore() error { func DeleteI2PKeyStore() error {
return os.RemoveAll(I2P_KEYSTORE_PATH) log.WithField("path", I2P_KEYSTORE_PATH).Debug("Attempting to delete I2P keystore")
err := os.RemoveAll(I2P_KEYSTORE_PATH)
if err != nil {
log.WithError(err).WithField("path", I2P_KEYSTORE_PATH).Error("Failed to delete I2P keystore")
return err
}
log.WithField("path", I2P_KEYSTORE_PATH).Debug("Successfully deleted I2P keystore")
return nil
//return os.RemoveAll(I2P_KEYSTORE_PATH)
} }
// TorKeystorePath returns the path to the Onion Keystore. If the // TorKeystorePath returns the path to the Onion Keystore. If the
// path is not set, it returns the default path. If the path does // path is not set, it returns the default path. If the path does
// not exist, it creates it. // not exist, it creates it.
func TorKeystorePath() (string, error) { func TorKeystorePath() (string, error) {
log.WithField("path", ONION_KEYSTORE_PATH).Debug("Checking Tor keystore path")
if _, err := os.Stat(ONION_KEYSTORE_PATH); err != nil { if _, err := os.Stat(ONION_KEYSTORE_PATH); err != nil {
log.WithField("path", ONION_KEYSTORE_PATH).Debug("Tor keystore directory does not exist, creating")
err := os.MkdirAll(ONION_KEYSTORE_PATH, 0755) err := os.MkdirAll(ONION_KEYSTORE_PATH, 0755)
if err != nil { if err != nil {
log.WithError(err).WithField("path", ONION_KEYSTORE_PATH).Error("Failed to create Tor keystore directory")
return "", err return "", err
} }
} }
log.WithField("path", ONION_KEYSTORE_PATH).Debug("Tor keystore path verified")
return ONION_KEYSTORE_PATH, nil return ONION_KEYSTORE_PATH, nil
} }
// DeleteTorKeyStore deletes the Onion Keystore. // DeleteTorKeyStore deletes the Onion Keystore.
func DeleteTorKeyStore() error { func DeleteTorKeyStore() error {
return os.RemoveAll(ONION_KEYSTORE_PATH) log.WithField("path", ONION_KEYSTORE_PATH).Debug("Attempting to delete Tor keystore")
err := os.RemoveAll(ONION_KEYSTORE_PATH)
if err != nil {
log.WithError(err).WithField("path", ONION_KEYSTORE_PATH).Error("Failed to delete Tor keystore")
return err
}
log.WithField("path", ONION_KEYSTORE_PATH).Debug("Successfully deleted Tor keystore")
return nil
//return os.RemoveAll(ONION_KEYSTORE_PATH)
} }
// TLSKeystorePath returns the path to the TLS Keystore. If the // TLSKeystorePath returns the path to the TLS Keystore. If the
// path is not set, it returns the default path. If the path does // path is not set, it returns the default path. If the path does
// not exist, it creates it. // not exist, it creates it.
func TLSKeystorePath() (string, error) { func TLSKeystorePath() (string, error) {
log.WithField("path", TLS_KEYSTORE_PATH).Debug("Checking TLS keystore path")
if _, err := os.Stat(TLS_KEYSTORE_PATH); err != nil { if _, err := os.Stat(TLS_KEYSTORE_PATH); err != nil {
log.WithField("path", TLS_KEYSTORE_PATH).Debug("TLS keystore directory does not exist, creating")
err := os.MkdirAll(TLS_KEYSTORE_PATH, 0755) err := os.MkdirAll(TLS_KEYSTORE_PATH, 0755)
if err != nil { if err != nil {
log.WithError(err).WithField("path", TLS_KEYSTORE_PATH).Error("Failed to create TLS keystore directory")
return "", err return "", err
} }
} }
log.WithField("path", TLS_KEYSTORE_PATH).Debug("TLS keystore path verified")
return TLS_KEYSTORE_PATH, nil return TLS_KEYSTORE_PATH, nil
} }
// DeleteTLSKeyStore deletes the TLS Keystore. // DeleteTLSKeyStore deletes the TLS Keystore.
func DeleteTLSKeyStore() error { func DeleteTLSKeyStore() error {
return os.RemoveAll(TLS_KEYSTORE_PATH) log.WithField("path", TLS_KEYSTORE_PATH).Debug("Attempting to delete TLS keystore")
err := os.RemoveAll(TLS_KEYSTORE_PATH)
if err != nil {
log.WithError(err).WithField("path", TLS_KEYSTORE_PATH).Error("Failed to delete TLS keystore")
return err
}
log.WithField("path", TLS_KEYSTORE_PATH).Debug("Successfully deleted TLS keystore")
return nil
//return os.RemoveAll(TLS_KEYSTORE_PATH)
} }
// Dial returns a connection for the given network and address. // Dial returns a connection for the given network and address.
// network is ignored. If the address ends in i2p, it returns an I2P connection. // network is ignored. If the address ends in i2p, it returns an I2P connection.
// if the address ends in anything else, it returns a Tor connection. // if the address ends in anything else, it returns a Tor connection.
func Dial(network, addr string) (net.Conn, error) { func Dial(network, addr string) (net.Conn, error) {
log.WithFields(logrus.Fields{
"network": network,
"address": addr,
}).Debug("Attempting to dial")
url, err := url.Parse(addr) url, err := url.Parse(addr)
if err != nil { if err != nil {
log.WithError(err).WithField("address", addr).Error("Failed to parse address")
return nil, err return nil, err
} }
hostname := url.Hostname() hostname := url.Hostname()
if strings.HasSuffix(hostname, ".i2p") { if strings.HasSuffix(hostname, ".i2p") {
log.WithField("hostname", hostname).Debug("Using I2P connection for .i2p address")
return DialGarlic(network, addr) return DialGarlic(network, addr)
} }
log.WithField("hostname", hostname).Debug("Using Tor connection for non-i2p address")
return DialOnion(network, addr) return DialOnion(network, addr)
} }
@@ -123,19 +175,31 @@ func Dial(network, addr string) (net.Conn, error) {
// if network is tor or onion, it returns an Onion listener. // if network is tor or onion, it returns an Onion listener.
// if keys ends with ".i2p", it returns an I2P listener. // if keys ends with ".i2p", it returns an I2P listener.
func Listen(network, keys string) (net.Listener, error) { func Listen(network, keys string) (net.Listener, error) {
log.WithFields(logrus.Fields{
"network": network,
"keys": keys,
}).Debug("Attempting to create listener")
if network == "i2p" || network == "garlic" { if network == "i2p" || network == "garlic" {
log.Debug("Creating I2P listener based on network type")
return ListenGarlic(network, keys) return ListenGarlic(network, keys)
} }
if network == "tor" || network == "onion" { if network == "tor" || network == "onion" {
log.Debug("Creating Tor listener based on network type")
return ListenOnion(network, keys) return ListenOnion(network, keys)
} }
url, err := url.Parse(keys) url, err := url.Parse(keys)
if err != nil { if err != nil {
log.WithError(err).WithField("keys", keys).Error("Failed to parse keys URL")
return nil, err return nil, err
} }
hostname := url.Hostname() hostname := url.Hostname()
if strings.HasSuffix(hostname, ".i2p") { if strings.HasSuffix(hostname, ".i2p") {
log.WithField("hostname", hostname).Debug("Creating I2P listener based on .i2p hostname")
return ListenGarlic(network, keys) return ListenGarlic(network, keys)
} }
log.WithField("hostname", hostname).Debug("Creating Tor listener for non-i2p hostname")
return ListenOnion(network, keys) return ListenOnion(network, keys)
} }

233
garlic.go
View File

@@ -7,14 +7,14 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"log" "github.com/sirupsen/logrus"
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/eyedeekay/i2pkeys" "github.com/go-i2p/i2pkeys"
"github.com/eyedeekay/sam3" "github.com/go-i2p/sam3"
) )
// Garlic is a ready-made I2P streaming manager. Once initialized it always // Garlic is a ready-made I2P streaming manager. Once initialized it always
@@ -101,27 +101,35 @@ func (g *Garlic) getOptions() []string {
func (g *Garlic) samSession() (*sam3.SAM, error) { func (g *Garlic) samSession() (*sam3.SAM, error) {
if g.SAM == nil { if g.SAM == nil {
log.WithField("address", g.getAddr()).Debug("Creating new SAM session")
var err error var err error
g.SAM, err = sam3.NewSAM(g.getAddr()) g.SAM, err = sam3.NewSAM(g.getAddr())
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create SAM session")
return nil, fmt.Errorf("onramp samSession: %v", err) return nil, fmt.Errorf("onramp samSession: %v", err)
} }
log.Debug("SAM session created successfully")
} }
return g.SAM, nil return g.SAM, nil
} }
func (g *Garlic) setupStreamSession() (*sam3.StreamSession, error) { func (g *Garlic) setupStreamSession() (*sam3.StreamSession, error) {
if g.StreamSession == nil { if g.StreamSession == nil {
log.WithField("name", g.getName()).Debug("Setting up stream session")
var err error var err error
g.ServiceKeys, err = g.Keys() g.ServiceKeys, err = g.Keys()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get keys for stream session")
return nil, fmt.Errorf("onramp setupStreamSession: %v", err) return nil, fmt.Errorf("onramp setupStreamSession: %v", err)
} }
log.WithField("address", g.ServiceKeys.Address.Base32()).Debug("Creating stream session with keys")
log.Println("Creating stream session with keys:", g.ServiceKeys.Address.Base32()) log.Println("Creating stream session with keys:", g.ServiceKeys.Address.Base32())
g.StreamSession, err = g.SAM.NewStreamSession(g.getName(), *g.ServiceKeys, g.getOptions()) g.StreamSession, err = g.SAM.NewStreamSession(g.getName(), *g.ServiceKeys, g.getOptions())
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create stream session")
return nil, fmt.Errorf("onramp setupStreamSession: %v", err) return nil, fmt.Errorf("onramp setupStreamSession: %v", err)
} }
log.Debug("Stream session created successfully")
return g.StreamSession, nil return g.StreamSession, nil
} }
return g.StreamSession, nil return g.StreamSession, nil
@@ -129,78 +137,131 @@ func (g *Garlic) setupStreamSession() (*sam3.StreamSession, error) {
func (g *Garlic) setupDatagramSession() (*sam3.DatagramSession, error) { func (g *Garlic) setupDatagramSession() (*sam3.DatagramSession, error) {
if g.DatagramSession == nil { if g.DatagramSession == nil {
log.WithField("name", g.getName()).Debug("Setting up datagram session")
var err error var err error
g.ServiceKeys, err = g.Keys() g.ServiceKeys, err = g.Keys()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get keys for datagram session")
return nil, fmt.Errorf("onramp setupDatagramSession: %v", err) return nil, fmt.Errorf("onramp setupDatagramSession: %v", err)
} }
log.WithField("address", g.ServiceKeys.Address.Base32()).Debug("Creating datagram session with keys")
log.Println("Creating datagram session with keys:", g.ServiceKeys.Address.Base32()) log.Println("Creating datagram session with keys:", g.ServiceKeys.Address.Base32())
g.DatagramSession, err = g.SAM.NewDatagramSession(g.getName(), *g.ServiceKeys, g.getOptions(), 0) g.DatagramSession, err = g.SAM.NewDatagramSession(g.getName(), *g.ServiceKeys, g.getOptions(), 0)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create datagram session")
return nil, fmt.Errorf("onramp setupDatagramSession: %v", err) return nil, fmt.Errorf("onramp setupDatagramSession: %v", err)
} }
log.Debug("Datagram session created successfully")
return g.DatagramSession, nil return g.DatagramSession, nil
} }
log.Debug("Using existing datagram session")
return g.DatagramSession, nil return g.DatagramSession, nil
} }
// NewListener returns a net.Listener for the Garlic structure's I2P keys. // NewListener returns a net.Listener for the Garlic structure's I2P keys.
// accepts a variable list of arguments, arguments after the first one are ignored. // accepts a variable list of arguments, arguments after the first one are ignored.
func (g *Garlic) NewListener(n, addr string) (net.Listener, error) { func (g *Garlic) NewListener(n, addr string) (net.Listener, error) {
return g.Listen(n) log.WithFields(logrus.Fields{
"network": n,
"address": addr,
"name": g.getName(),
}).Debug("Creating new listener")
listener, err := g.Listen(n)
if err != nil {
log.WithError(err).Error("Failed to create listener")
return nil, err
}
log.Debug("Successfully created listener")
return listener, nil
//return g.Listen(n)
} }
// Listen returns a net.Listener for the Garlic structure's I2P keys. // Listen returns a net.Listener for the Garlic structure's I2P keys.
// accepts a variable list of arguments, arguments after the first one are ignored. // accepts a variable list of arguments, arguments after the first one are ignored.
func (g *Garlic) Listen(args ...string) (net.Listener, error) { func (g *Garlic) Listen(args ...string) (net.Listener, error) {
return g.OldListen(args...) log.WithFields(logrus.Fields{
"args": args,
"name": g.getName(),
}).Debug("Setting up listener")
listener, err := g.OldListen(args...)
if err != nil {
log.WithError(err).Error("Failed to create listener")
return nil, err
}
log.Debug("Successfully created listener")
return listener, nil
//return g.OldListen(args...)
} }
// OldListen returns a net.Listener for the Garlic structure's I2P keys. // OldListen returns a net.Listener for the Garlic structure's I2P keys.
// accepts a variable list of arguments, arguments after the first one are ignored. // accepts a variable list of arguments, arguments after the first one are ignored.
func (g *Garlic) OldListen(args ...string) (net.Listener, error) { func (g *Garlic) OldListen(args ...string) (net.Listener, error) {
log.WithField("args", args).Debug("Starting OldListen")
if len(args) > 0 { if len(args) > 0 {
if args[0] == "tcp" || args[0] == "tcp6" || args[0] == "st" || args[0] == "st6" { protocol := args[0]
log.WithField("protocol", protocol).Debug("Checking protocol type")
//if args[0] == "tcp" || args[0] == "tcp6" || args[0] == "st" || args[0] == "st6" {
if protocol == "tcp" || protocol == "tcp6" || protocol == "st" || protocol == "st6" {
log.Debug("Using TCP stream listener")
return g.ListenStream() return g.ListenStream()
} else if args[0] == "udp" || args[0] == "udp6" || args[0] == "dg" || args[0] == "dg6" { //} else if args[0] == "udp" || args[0] == "udp6" || args[0] == "dg" || args[0] == "dg6" {
} else if protocol == "udp" || protocol == "udp6" || protocol == "dg" || protocol == "dg6" {
log.Debug("Using UDP datagram listener")
pk, err := g.ListenPacket() pk, err := g.ListenPacket()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create packet listener")
return nil, err return nil, err
} }
log.Debug("Successfully created datagram session")
return pk.(*sam3.DatagramSession), nil return pk.(*sam3.DatagramSession), nil
} }
} }
log.Debug("No protocol specified, defaulting to stream listener")
return g.ListenStream() return g.ListenStream()
} }
// Listen returns a net.Listener for the Garlic structure's I2P keys. // Listen returns a net.Listener for the Garlic structure's I2P keys.
func (g *Garlic) ListenStream() (net.Listener, error) { func (g *Garlic) ListenStream() (net.Listener, error) {
log.Debug("Setting up stream listener")
var err error var err error
if g.SAM, err = g.samSession(); err != nil { if g.SAM, err = g.samSession(); err != nil {
log.WithError(err).Error("Failed to create SAM session for stream listener")
return nil, fmt.Errorf("onramp NewGarlic: %v", err) return nil, fmt.Errorf("onramp NewGarlic: %v", err)
} }
if g.StreamSession, err = g.setupStreamSession(); err != nil { if g.StreamSession, err = g.setupStreamSession(); err != nil {
log.WithError(err).Error("Failed to setup stream session")
return nil, fmt.Errorf("onramp Listen: %v", err) return nil, fmt.Errorf("onramp Listen: %v", err)
} }
if g.StreamListener == nil { if g.StreamListener == nil {
log.Debug("Creating new stream listener")
g.StreamListener, err = g.StreamSession.Listen() g.StreamListener, err = g.StreamSession.Listen()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create stream listener")
return nil, fmt.Errorf("onramp Listen: %v", err) return nil, fmt.Errorf("onramp Listen: %v", err)
} }
log.Debug("Stream listener created successfully")
} }
return g.StreamListener, nil return g.StreamListener, nil
} }
// ListenPacket returns a net.PacketConn for the Garlic structure's I2P keys. // ListenPacket returns a net.PacketConn for the Garlic structure's I2P keys.
func (g *Garlic) ListenPacket() (net.PacketConn, error) { func (g *Garlic) ListenPacket() (net.PacketConn, error) {
log.Debug("Setting up packet connection")
var err error var err error
if g.SAM, err = g.samSession(); err != nil { if g.SAM, err = g.samSession(); err != nil {
log.WithError(err).Error("Failed to create SAM session for packet connection")
return nil, fmt.Errorf("onramp NewGarlic: %v", err) return nil, fmt.Errorf("onramp NewGarlic: %v", err)
} }
if g.DatagramSession, err = g.setupDatagramSession(); err != nil { if g.DatagramSession, err = g.setupDatagramSession(); err != nil {
log.WithError(err).Error("Failed to setup datagram session")
return nil, fmt.Errorf("onramp Listen: %v", err) return nil, fmt.Errorf("onramp Listen: %v", err)
} }
log.Debug("Packet connection successfully established")
return g.DatagramSession, nil return g.DatagramSession, nil
} }
@@ -208,23 +269,33 @@ func (g *Garlic) ListenPacket() (net.PacketConn, error) {
// which also uses TLS either for additional encryption, authentication, // which also uses TLS either for additional encryption, authentication,
// or browser-compatibility. // or browser-compatibility.
func (g *Garlic) ListenTLS(args ...string) (net.Listener, error) { func (g *Garlic) ListenTLS(args ...string) (net.Listener, error) {
log.WithField("args", args).Debug("Starting TLS listener")
listener, err := g.Listen(args...) listener, err := g.Listen(args...)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create base listener")
return nil, err return nil, err
} }
cert, err := g.TLSKeys() cert, err := g.TLSKeys()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get TLS keys")
return nil, fmt.Errorf("onramp ListenTLS: %v", err) return nil, fmt.Errorf("onramp ListenTLS: %v", err)
} }
if len(args) > 0 { if len(args) > 0 {
if args[0] == "tcp" || args[0] == "tcp6" || args[0] == "st" || args[0] == "st6" { protocol := args[0]
log.WithField("protocol", protocol).Debug("Creating TLS listener for protocol")
//if args[0] == "tcp" || args[0] == "tcp6" || args[0] == "st" || args[0] == "st6" {
if protocol == "tcp" || protocol == "tcp6" || protocol == "st" || protocol == "st6" {
log.Debug("Creating TLS stream listener")
return tls.NewListener( return tls.NewListener(
g.StreamListener, g.StreamListener,
&tls.Config{ &tls.Config{
Certificates: []tls.Certificate{cert}, Certificates: []tls.Certificate{cert},
}, },
), nil ), nil
} else if args[0] == "udp" || args[0] == "udp6" || args[0] == "dg" || args[0] == "dg6" { //} else if args[0] == "udp" || args[0] == "udp6" || args[0] == "dg" || args[0] == "dg6" {
} else if protocol == "udp" || protocol == "udp6" || protocol == "dg" || protocol == "dg6" {
log.Debug("Creating TLS datagram listener")
return tls.NewListener( return tls.NewListener(
g.DatagramSession, g.DatagramSession,
&tls.Config{ &tls.Config{
@@ -234,8 +305,10 @@ func (g *Garlic) ListenTLS(args ...string) (net.Listener, error) {
} }
} else { } else {
log.Debug("No protocol specified, using stream listener")
g.StreamListener = listener.(*sam3.StreamListener) g.StreamListener = listener.(*sam3.StreamListener)
} }
log.Debug("Successfully created TLS listener")
return tls.NewListener( return tls.NewListener(
g.StreamListener, g.StreamListener,
&tls.Config{ &tls.Config{
@@ -246,76 +319,143 @@ func (g *Garlic) ListenTLS(args ...string) (net.Listener, error) {
// Dial returns a net.Conn for the Garlic structure's I2P keys. // Dial returns a net.Conn for the Garlic structure's I2P keys.
func (g *Garlic) Dial(net, addr string) (net.Conn, error) { func (g *Garlic) Dial(net, addr string) (net.Conn, error) {
log.WithFields(logrus.Fields{
"network": net,
"address": addr,
}).Debug("Attempting to dial")
if !strings.Contains(addr, ".i2p") { if !strings.Contains(addr, ".i2p") {
log.Debug("Non-I2P address detected, returning null connection")
return &NullConn{}, nil return &NullConn{}, nil
} }
var err error var err error
if g.SAM, err = g.samSession(); err != nil { if g.SAM, err = g.samSession(); err != nil {
log.WithError(err).Error("Failed to create SAM session")
return nil, fmt.Errorf("onramp NewGarlic: %v", err) return nil, fmt.Errorf("onramp NewGarlic: %v", err)
} }
if g.StreamSession, err = g.setupStreamSession(); err != nil { if g.StreamSession, err = g.setupStreamSession(); err != nil {
log.WithError(err).Error("Failed to setup stream session")
return nil, fmt.Errorf("onramp Dial: %v", err) return nil, fmt.Errorf("onramp Dial: %v", err)
} }
return g.StreamSession.Dial(net, addr) log.Debug("Attempting to establish connection")
conn, err := g.StreamSession.Dial(net, addr)
if err != nil {
log.WithError(err).Error("Failed to establish connection")
return nil, err
}
log.Debug("Successfully established connection")
return conn, nil
//return g.StreamSession.Dial(net, addr)
} }
// DialContext returns a net.Conn for the Garlic structure's I2P keys. // DialContext returns a net.Conn for the Garlic structure's I2P keys.
func (g *Garlic) DialContext(ctx context.Context, net, addr string) (net.Conn, error) { func (g *Garlic) DialContext(ctx context.Context, net, addr string) (net.Conn, error) {
log.WithFields(logrus.Fields{
"network": net,
"address": addr,
}).Debug("Attempting to dial with context")
if !strings.Contains(addr, ".i2p") { if !strings.Contains(addr, ".i2p") {
log.Debug("Non-I2P address detected, returning null connection")
return &NullConn{}, nil return &NullConn{}, nil
} }
var err error var err error
if g.SAM, err = g.samSession(); err != nil { if g.SAM, err = g.samSession(); err != nil {
log.WithError(err).Error("Failed to create SAM session")
return nil, fmt.Errorf("onramp NewGarlic: %v", err) return nil, fmt.Errorf("onramp NewGarlic: %v", err)
} }
if g.StreamSession, err = g.setupStreamSession(); err != nil { if g.StreamSession, err = g.setupStreamSession(); err != nil {
log.WithError(err).Error("Failed to setup stream session")
return nil, fmt.Errorf("onramp Dial: %v", err) return nil, fmt.Errorf("onramp Dial: %v", err)
} }
return g.StreamSession.DialContext(ctx, net, addr) log.Debug("Attempting to establish connection with context")
conn, err := g.StreamSession.DialContext(ctx, net, addr)
if err != nil {
log.WithError(err).Error("Failed to establish connection")
return nil, err
}
log.Debug("Successfully established connection")
return conn, nil
//return g.StreamSession.DialContext(ctx, net, addr)
} }
// Close closes the Garlic structure's sessions and listeners. // Close closes the Garlic structure's sessions and listeners.
func (g *Garlic) Close() error { func (g *Garlic) Close() error {
log.WithField("name", g.getName()).Debug("Closing Garlic sessions")
e1 := g.StreamSession.Close() e1 := g.StreamSession.Close()
var err error var err error
if e1 != nil { if e1 != nil {
log.WithError(e1).Error("Failed to close stream session")
err = fmt.Errorf("onramp Close: %v", e1) err = fmt.Errorf("onramp Close: %v", e1)
} else {
log.Debug("Stream session closed successfully")
} }
e2 := g.SAM.Close() e2 := g.SAM.Close()
if e2 != nil { if e2 != nil {
log.WithError(e2).Error("Failed to close SAM session")
err = fmt.Errorf("onramp Close: %v %v", e1, e2) err = fmt.Errorf("onramp Close: %v %v", e1, e2)
} else {
log.Debug("SAM session closed successfully")
} }
if err == nil {
log.Debug("All sessions closed successfully")
}
return err return err
} }
// Keys returns the I2PKeys for the Garlic structure. If none // Keys returns the I2PKeys for the Garlic structure. If none
// exist, they are created and stored. // exist, they are created and stored.
func (g *Garlic) Keys() (*i2pkeys.I2PKeys, error) { func (g *Garlic) Keys() (*i2pkeys.I2PKeys, error) {
log.WithFields(logrus.Fields{
"name": g.getName(),
"address": g.getAddr(),
}).Debug("Retrieving I2P keys")
keys, err := I2PKeys(g.getName(), g.getAddr()) keys, err := I2PKeys(g.getName(), g.getAddr())
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get I2P keys")
return &i2pkeys.I2PKeys{}, fmt.Errorf("onramp Keys: %v", err) return &i2pkeys.I2PKeys{}, fmt.Errorf("onramp Keys: %v", err)
} }
log.Debug("Successfully retrieved I2P keys")
return &keys, nil return &keys, nil
} }
func (g *Garlic) DeleteKeys() error { func (g *Garlic) DeleteKeys() error {
return DeleteGarlicKeys(g.getName()) //return DeleteGarlicKeys(g.getName())
log.WithField("name", g.getName()).Debug("Attempting to delete Garlic keys")
err := DeleteGarlicKeys(g.getName())
if err != nil {
log.WithError(err).Error("Failed to delete Garlic keys")
}
log.Debug("Successfully deleted Garlic keys")
return err
} }
// NewGarlic returns a new Garlic struct. It is immediately ready to use with // NewGarlic returns a new Garlic struct. It is immediately ready to use with
// I2P streaming. // I2P streaming.
func NewGarlic(tunName, samAddr string, options []string) (*Garlic, error) { func NewGarlic(tunName, samAddr string, options []string) (*Garlic, error) {
log.WithFields(logrus.Fields{
"tunnel_name": tunName,
"sam_address": samAddr,
"options": options,
}).Debug("Creating new Garlic instance")
g := new(Garlic) g := new(Garlic)
g.name = tunName g.name = tunName
g.addr = samAddr g.addr = samAddr
g.opts = options g.opts = options
var err error var err error
if g.SAM, err = g.samSession(); err != nil { if g.SAM, err = g.samSession(); err != nil {
log.WithError(err).Error("Failed to create SAM session")
return nil, fmt.Errorf("onramp NewGarlic: %v", err) return nil, fmt.Errorf("onramp NewGarlic: %v", err)
} }
if g.StreamSession, err = g.setupStreamSession(); err != nil { if g.StreamSession, err = g.setupStreamSession(); err != nil {
log.WithError(err).Error("Failed to setup stream session")
return nil, fmt.Errorf("onramp NewGarlic: %v", err) return nil, fmt.Errorf("onramp NewGarlic: %v", err)
} }
log.Debug("Successfully created new Garlic instance")
return g, nil return g, nil
} }
@@ -324,49 +464,74 @@ func NewGarlic(tunName, samAddr string, options []string) (*Garlic, error) {
// This is permanent and irreversible, and will change the onion service // This is permanent and irreversible, and will change the onion service
// address. // address.
func DeleteGarlicKeys(tunName string) error { func DeleteGarlicKeys(tunName string) error {
log.WithField("tunnel_name", tunName).Debug("Attempting to delete Garlic keys")
keystore, err := I2PKeystorePath() keystore, err := I2PKeystorePath()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get keystore path")
return fmt.Errorf("onramp DeleteGarlicKeys: discovery error %v", err) return fmt.Errorf("onramp DeleteGarlicKeys: discovery error %v", err)
} }
keyspath := filepath.Join(keystore, tunName+".i2p.private") keyspath := filepath.Join(keystore, tunName+".i2p.private")
log.WithField("path", keyspath).Debug("Deleting key file")
if err := os.Remove(keyspath); err != nil { if err := os.Remove(keyspath); err != nil {
log.WithError(err).WithField("path", keyspath).Error("Failed to delete key file")
return fmt.Errorf("onramp DeleteGarlicKeys: %v", err) return fmt.Errorf("onramp DeleteGarlicKeys: %v", err)
} }
log.Debug("Successfully deleted Garlic keys")
return nil return nil
} }
// I2PKeys returns the I2PKeys at the keystore directory for the given // I2PKeys returns the I2PKeys at the keystore directory for the given
// tunnel name. If none exist, they are created and stored. // tunnel name. If none exist, they are created and stored.
func I2PKeys(tunName, samAddr string) (i2pkeys.I2PKeys, error) { func I2PKeys(tunName, samAddr string) (i2pkeys.I2PKeys, error) {
log.WithFields(logrus.Fields{
"tunnel_name": tunName,
"sam_address": samAddr,
}).Debug("Looking up I2P keys")
keystore, err := I2PKeystorePath() keystore, err := I2PKeystorePath()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get keystore path")
return i2pkeys.I2PKeys{}, fmt.Errorf("onramp I2PKeys: discovery error %v", err) return i2pkeys.I2PKeys{}, fmt.Errorf("onramp I2PKeys: discovery error %v", err)
} }
keyspath := filepath.Join(keystore, tunName+".i2p.private") keyspath := filepath.Join(keystore, tunName+".i2p.private")
log.WithField("path", keyspath).Debug("Checking for existing keys")
info, err := os.Stat(keyspath) info, err := os.Stat(keyspath)
if info != nil { if info != nil {
if info.Size() == 0 { if info.Size() == 0 {
log.WithField("path", keyspath).Debug("Keystore empty, will regenerate keys")
log.Println("onramp I2PKeys: keystore empty, re-generating keys") log.Println("onramp I2PKeys: keystore empty, re-generating keys")
} else {
log.WithField("path", keyspath).Debug("Found existing keystore")
} }
} }
if err != nil { if err != nil {
log.WithField("path", keyspath).Debug("Keys not found, generating new keys")
sam, err := sam3.NewSAM(samAddr) sam, err := sam3.NewSAM(samAddr)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create SAM connection")
return i2pkeys.I2PKeys{}, fmt.Errorf("onramp I2PKeys: SAM error %v", err) return i2pkeys.I2PKeys{}, fmt.Errorf("onramp I2PKeys: SAM error %v", err)
} }
log.Debug("SAM connection established")
keys, err := sam.NewKeys(tunName) keys, err := sam.NewKeys(tunName)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to generate new keys")
return i2pkeys.I2PKeys{}, fmt.Errorf("onramp I2PKeys: keygen error %v", err) return i2pkeys.I2PKeys{}, fmt.Errorf("onramp I2PKeys: keygen error %v", err)
} }
log.Debug("New keys generated successfully")
if err = i2pkeys.StoreKeys(keys, keyspath); err != nil { if err = i2pkeys.StoreKeys(keys, keyspath); err != nil {
log.WithError(err).WithField("path", keyspath).Error("Failed to store generated keys")
return i2pkeys.I2PKeys{}, fmt.Errorf("onramp I2PKeys: store error %v", err) return i2pkeys.I2PKeys{}, fmt.Errorf("onramp I2PKeys: store error %v", err)
} }
log.WithField("path", keyspath).Debug("Successfully stored new keys")
return keys, nil return keys, nil
} else { } else {
log.WithField("path", keyspath).Debug("Loading existing keys")
keys, err := i2pkeys.LoadKeys(keyspath) keys, err := i2pkeys.LoadKeys(keyspath)
if err != nil { if err != nil {
log.WithError(err).WithField("path", keyspath).Error("Failed to load existing keys")
return i2pkeys.I2PKeys{}, fmt.Errorf("onramp I2PKeys: load error %v", err) return i2pkeys.I2PKeys{}, fmt.Errorf("onramp I2PKeys: load error %v", err)
} }
log.Debug("Successfully loaded existing keys")
return keys, nil return keys, nil
} }
} }
@@ -376,18 +541,35 @@ var garlics map[string]*Garlic
// CloseAllGarlic closes all garlics managed by the onramp package. It does not // CloseAllGarlic closes all garlics managed by the onramp package. It does not
// affect objects instantiated by an app. // affect objects instantiated by an app.
func CloseAllGarlic() { func CloseAllGarlic() {
log.WithField("count", len(garlics)).Debug("Closing all Garlic connections")
for i, g := range garlics { for i, g := range garlics {
log.WithFields(logrus.Fields{
"index": i,
"name": g.name,
}).Debug("Closing Garlic connection")
log.Println("Closing garlic", g.name) log.Println("Closing garlic", g.name)
CloseGarlic(i) CloseGarlic(i)
} }
log.Debug("All Garlic connections closed")
} }
// CloseGarlic closes the Garlic at the given index. It does not affect Garlic // CloseGarlic closes the Garlic at the given index. It does not affect Garlic
// objects instantiated by an app. // objects instantiated by an app.
func CloseGarlic(tunName string) { func CloseGarlic(tunName string) {
log.WithField("tunnel_name", tunName).Debug("Attempting to close Garlic connection")
g, ok := garlics[tunName] g, ok := garlics[tunName]
if ok { if ok {
g.Close() log.Debug("Found Garlic connection, closing")
//g.Close()
err := g.Close()
if err != nil {
log.WithError(err).Error("Error closing Garlic connection")
} else {
log.Debug("Successfully closed Garlic connection")
}
} else {
log.Debug("No Garlic connection found for tunnel name")
} }
} }
@@ -399,11 +581,18 @@ var SAM_ADDR = "127.0.0.1:7656"
// corresponding to a structure managed by the onramp library // corresponding to a structure managed by the onramp library
// and not instantiated by an app. // and not instantiated by an app.
func ListenGarlic(network, keys string) (net.Listener, error) { func ListenGarlic(network, keys string) (net.Listener, error) {
log.WithFields(logrus.Fields{
"network": network,
"keys": keys,
"sam_addr": SAM_ADDR,
}).Debug("Creating new Garlic listener")
g, err := NewGarlic(keys, SAM_ADDR, OPT_DEFAULTS) g, err := NewGarlic(keys, SAM_ADDR, OPT_DEFAULTS)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create new Garlic")
return nil, fmt.Errorf("onramp Listen: %v", err) return nil, fmt.Errorf("onramp Listen: %v", err)
} }
garlics[keys] = g garlics[keys] = g
log.Debug("Successfully created Garlic listener")
return g.Listen() return g.Listen()
} }
@@ -411,10 +600,26 @@ func ListenGarlic(network, keys string) (net.Listener, error) {
// corresponding to a structure managed by the onramp library // corresponding to a structure managed by the onramp library
// and not instantiated by an app. // and not instantiated by an app.
func DialGarlic(network, addr string) (net.Conn, error) { func DialGarlic(network, addr string) (net.Conn, error) {
log.WithFields(logrus.Fields{
"network": network,
"address": addr,
"sam_addr": SAM_ADDR,
}).Debug("Creating new Garlic connection")
g, err := NewGarlic(addr, SAM_ADDR, OPT_DEFAULTS) g, err := NewGarlic(addr, SAM_ADDR, OPT_DEFAULTS)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create new Garlic")
return nil, fmt.Errorf("onramp Dial: %v", err) return nil, fmt.Errorf("onramp Dial: %v", err)
} }
garlics[addr] = g garlics[addr] = g
return g.Dial(network, addr) log.WithField("address", addr).Debug("Attempting to dial")
conn, err := g.Dial(network, addr)
if err != nil {
log.WithError(err).Error("Failed to dial connection")
return nil, err
}
log.Debug("Successfully established Garlic connection")
return conn, nil
//return g.Dial(network, addr)
} }

View File

@@ -1,6 +1,6 @@
package onramp package onramp
import "github.com/eyedeekay/sam3" import "github.com/go-i2p/sam3"
var OPT_DEFAULTS = sam3.Options_Default var OPT_DEFAULTS = sam3.Options_Default
var OPT_WIDE = sam3.Options_Wide var OPT_WIDE = sam3.Options_Wide

View File

@@ -7,7 +7,6 @@ import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"net" "net"
"net/http" "net/http"
"testing" "testing"

9
go.mod
View File

@@ -1,16 +1,17 @@
module github.com/eyedeekay/onramp module github.com/go-i2p/onramp
go 1.18 go 1.18
require ( require (
github.com/cretz/bine v0.2.0 github.com/cretz/bine v0.2.0
github.com/eyedeekay/i2pkeys v0.33.8 github.com/go-i2p/i2pkeys v0.33.10-0.20241113193422-e10de5e60708
github.com/eyedeekay/sam3 v0.33.8 github.com/go-i2p/sam3 v0.33.9
github.com/sirupsen/logrus v1.9.3
) )
require ( require (
github.com/stretchr/testify v1.8.4 // indirect github.com/stretchr/testify v1.8.4 // indirect
golang.org/x/crypto v0.11.0 // indirect golang.org/x/crypto v0.11.0 // indirect
golang.org/x/net v0.12.0 // indirect golang.org/x/net v0.12.0 // indirect
golang.org/x/sys v0.10.0 // indirect golang.org/x/sys v0.27.0 // indirect
) )

23
go.sum
View File

@@ -2,16 +2,18 @@ github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= github.com/cretz/bine v0.2.0/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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/eyedeekay/i2pkeys v0.33.7 h1:cxqHSkl6b2lHyPJUtIQZBiipYf7NQVYqM1d3ub0MI4k= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eyedeekay/i2pkeys v0.33.7/go.mod h1:W9KCm9lqZ+Ozwl3dwcgnpPXAML97+I8Jiht7o5A8YBM= github.com/go-i2p/i2pkeys v0.0.0-20241108200332-e4f5ccdff8c4 h1:LRjaRCzg1ieGKZjELlaIg06Fx04RHzQLsWMYp1H6PQ4=
github.com/eyedeekay/i2pkeys v0.33.8 h1:f3llyruchFqs1QwCacBYbShArKPpMSSOqo/DVZXcfVs= github.com/go-i2p/i2pkeys v0.0.0-20241108200332-e4f5ccdff8c4/go.mod h1:m5TlHjPZrU5KbTd7Lr+I2rljyC6aJ88HdkeMQXV0U0E=
github.com/eyedeekay/i2pkeys v0.33.8/go.mod h1:W9KCm9lqZ+Ozwl3dwcgnpPXAML97+I8Jiht7o5A8YBM= github.com/go-i2p/i2pkeys v0.33.9/go.mod h1:Apt0rKbwylG37GoTAmovuJvB4lu0yFM2sgfIUefbMK8=
github.com/eyedeekay/sam3 v0.33.7 h1:GPYHG4NHxvhqPbGNJ3wKvUQyZSTCmX17f5L5QvyefGs= github.com/go-i2p/i2pkeys v0.33.10-0.20241113193422-e10de5e60708 h1:Tiy9IBwi21maNpK74yCdHursJJMkyH7w87tX1nXGWzg=
github.com/eyedeekay/sam3 v0.33.7/go.mod h1:25cRGEFawSkbiPNSh7vTUIpRtEYLVLg/4J4He6LndAY= github.com/go-i2p/i2pkeys v0.33.10-0.20241113193422-e10de5e60708/go.mod h1:m5TlHjPZrU5KbTd7Lr+I2rljyC6aJ88HdkeMQXV0U0E=
github.com/eyedeekay/sam3 v0.33.8 h1:emuSZ4qSyoqc1EDjIBFbJ3GXNHOXw6hjbNp2OqdOpgI= github.com/go-i2p/sam3 v0.33.9 h1:3a+gunx75DFc6jxloUZTAVJbdP6736VU1dy2i7I9fKA=
github.com/eyedeekay/sam3 v0.33.8/go.mod h1:ytbwLYLJlW6UA92Ffyc6oioWTKnGeeUMr9CLuJbtqSA= github.com/go-i2p/sam3 v0.33.9/go.mod h1:oDuV145l5XWKKafeE4igJHTDpPwA0Yloz9nyKKh92eo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
@@ -25,8 +27,9 @@ golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

50
log.go Normal file
View File

@@ -0,0 +1,50 @@
package onramp
import (
"github.com/sirupsen/logrus"
"io"
"os"
"strings"
"sync"
)
var (
log *logrus.Logger
once sync.Once
)
func InitializeOnrampLogger() {
once.Do(func() {
log = logrus.New()
// We do not want to log by default
log.SetOutput(io.Discard)
log.SetLevel(logrus.PanicLevel)
// Check if DEBUG_I2P is set
if logLevel := os.Getenv("DEBUG_I2P"); logLevel != "" {
log.SetOutput(os.Stdout)
switch strings.ToLower(logLevel) {
case "debug":
log.SetLevel(logrus.DebugLevel)
case "warn":
log.SetLevel(logrus.WarnLevel)
case "error":
log.SetLevel(logrus.ErrorLevel)
default:
log.SetLevel(logrus.DebugLevel)
}
log.WithField("level", log.GetLevel()).Debug("Logging enabled.")
}
})
}
// GetI2PKeysLogger returns the initialized logger
func GetOnrampLogger() *logrus.Logger {
if log == nil {
InitializeOnrampLogger()
}
return log
}
func init() {
GetOnrampLogger()
}

154
onion.go
View File

@@ -7,8 +7,7 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"io/ioutil" "github.com/sirupsen/logrus"
"log"
"net" "net"
"os" "os"
"path/filepath" "path/filepath"
@@ -66,11 +65,14 @@ func (o *Onion) getDialConf() *tor.DialConf {
func (o *Onion) getTor() *tor.Tor { func (o *Onion) getTor() *tor.Tor {
if torp == nil { if torp == nil {
log.Debug("Initializing new Tor instance")
var err error var err error
torp, err = tor.Start(o.getContext(), o.getStartConf()) torp, err = tor.Start(o.getContext(), o.getStartConf())
if err != nil { if err != nil {
panic(err) log.WithError(err).Error("Failed to start Tor")
panic(err) //return nil instead?
} }
log.Debug("Tor instance started successfully")
} }
return torp return torp
} }
@@ -79,10 +81,13 @@ func (o *Onion) getDialer() *tor.Dialer {
//if o.Dialer == nil { //if o.Dialer == nil {
//var err error //var err error
//o.Dialer, err //o.Dialer, err
log.Debug("Creating new Tor dialer")
dialer, err := o.getTor().Dialer(o.getContext(), o.getDialConf()) dialer, err := o.getTor().Dialer(o.getContext(), o.getDialConf())
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create Tor dialer")
panic(err) panic(err)
} }
log.Debug("Tor dialer created successfully")
//} //}
//return o.Dialer //return o.Dialer
return dialer return dialer
@@ -106,25 +111,55 @@ func (o *Onion) NewListener(n, addr string) (net.Listener, error) {
// address, and will automatically generate a keypair and store it. // address, and will automatically generate a keypair and store it.
// the args are always ignored // the args are always ignored
func (o *Onion) Listen(args ...string) (net.Listener, error) { func (o *Onion) Listen(args ...string) (net.Listener, error) {
return o.OldListen(args...) 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 // OldListen returns a net.Listener which will listen on an onion
// address, and will automatically generate a keypair and store it. // address, and will automatically generate a keypair and store it.
// the args are always ignored // the args are always ignored
func (o *Onion) OldListen(args ...string) (net.Listener, error) { func (o *Onion) OldListen(args ...string) (net.Listener, error) {
return o.getTor().Listen(o.getContext(), o.getListenConf()) 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 // ListenTLS returns a net.Listener which will apply TLS encryption
// to the onion listener, which will not be decrypted until it reaches // to the onion listener, which will not be decrypted until it reaches
// the browser // the browser
func (o *Onion) ListenTLS(args ...string) (net.Listener, error) { func (o *Onion) ListenTLS(args ...string) (net.Listener, error) {
log.WithField("args", args).Debug("Setting up TLS Onion listener")
cert, err := o.TLSKeys() cert, err := o.TLSKeys()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get TLS keys")
return nil, fmt.Errorf("onramp ListenTLS: %v", err) return nil, fmt.Errorf("onramp ListenTLS: %v", err)
} }
log.Debug("Creating base Tor listener")
l, err := o.getTor().Listen(o.getContext(), o.getListenConf()) 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( return tls.NewListener(
l, l,
&tls.Config{ &tls.Config{
@@ -135,23 +170,56 @@ func (o *Onion) ListenTLS(args ...string) (net.Listener, error) {
// Dial returns a net.Conn to the given onion address or clearnet address. // Dial returns a net.Conn to the given onion address or clearnet address.
func (o *Onion) Dial(net, addr string) (net.Conn, error) { func (o *Onion) Dial(net, addr string) (net.Conn, error) {
return o.getDialer().DialContext(o.getContext(), net, addr) 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. // Close closes the Onion Service and all associated resources.
func (o *Onion) Close() error { func (o *Onion) Close() error {
return o.getTor().Close() 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 // Keys returns the keys for the Onion
func (o *Onion) Keys() (ed25519.KeyPair, error) { func (o *Onion) Keys() (ed25519.KeyPair, error) {
return TorKeys(o.getName()) 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. // DeleteKeys deletes the keys at the given key name in the key store.
// This is permanent and irreversible, and will change the onion service // This is permanent and irreversible, and will change the onion service
// address. // address.
func (g *Onion) DeleteKeys() error { func (g *Onion) DeleteKeys() error {
log.WithField("Onion keys", g.getName()).Debug("Deleting Onion keys")
return DeleteOnionKeys(g.getName()) return DeleteOnionKeys(g.getName())
} }
@@ -166,36 +234,50 @@ func NewOnion(name string) (*Onion, error) {
// name in the key store. If the key already exists, it will be // name in the key store. If the key already exists, it will be
// returned. If it does not exist, it will be generated. // returned. If it does not exist, it will be generated.
func TorKeys(keyName string) (ed25519.KeyPair, error) { func TorKeys(keyName string) (ed25519.KeyPair, error) {
log.WithField("key_name", keyName).Debug("Getting Tor keys")
keystore, err := TorKeystorePath() keystore, err := TorKeystorePath()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get keystore path")
return nil, fmt.Errorf("onramp OnionKeys: discovery error %v", err) return nil, fmt.Errorf("onramp OnionKeys: discovery error %v", err)
} }
var keys ed25519.KeyPair var keys ed25519.KeyPair
keysPath := filepath.Join(keystore, keyName+".tor.private") keysPath := filepath.Join(keystore, keyName+".tor.private")
log.WithField("path", keysPath).Debug("Checking for existing keys")
if _, err := os.Stat(keysPath); os.IsNotExist(err) { if _, err := os.Stat(keysPath); os.IsNotExist(err) {
log.Debug("Generating new Tor keys")
tkeys, err := ed25519.GenerateKey(nil) tkeys, err := ed25519.GenerateKey(nil)
if err != nil { if err != nil {
log.Fatalf("Unable to generate onion service key, %s", err) log.WithError(err).Error("Failed to generate onion service key")
log.Fatal("Unable to generate onion service key")
} }
keys = tkeys keys = tkeys
log.WithField("path", keysPath).Debug("Creating key file")
f, err := os.Create(keysPath) f, err := os.Create(keysPath)
if err != nil { if err != nil {
log.Fatalf("Unable to create Tor keys file for writing, %s", err) log.WithError(err).Error("Failed to create Tor keys file")
log.Fatal("Unable to create Tor keys file for writing")
} }
defer f.Close() defer f.Close()
_, err = f.Write(tkeys.PrivateKey()) _, err = f.Write(tkeys.PrivateKey())
if err != nil { if err != nil {
log.Fatalf("Unable to write Tor keys to disk, %s", err) 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 { } else if err == nil {
tkeys, err := ioutil.ReadFile(keysPath) log.Debug("Loading existing Tor keys")
tkeys, err := os.ReadFile(keysPath)
if err != nil { if err != nil {
log.Fatalf("Unable to read Tor keys from disk") log.WithError(err).Error("Failed to read Tor keys from disk")
log.Fatal("Unable to read Tor keys from disk")
} }
k := ed25519.FromCryptoPrivateKey(tkeys) k := ed25519.FromCryptoPrivateKey(tkeys)
keys = k keys = k
log.Debug("Successfully loaded existing keys")
} else { } else {
log.Fatalf("Unable to set up Tor keys, %s", err) log.WithError(err).Error("Failed to set up Tor keys")
log.Fatal("Unable to set up Tor keys")
} }
return keys, nil return keys, nil
} }
@@ -205,18 +287,34 @@ var onions map[string]*Onion
// CloseAllOnion closes all onions managed by the onramp package. It does not // CloseAllOnion closes all onions managed by the onramp package. It does not
// affect objects instantiated by an app. // affect objects instantiated by an app.
func CloseAllOnion() { func CloseAllOnion() {
log.WithField("count", len(onions)).Debug("Closing all Onion services")
for i, g := range onions { for i, g := range onions {
log.Println("Closing onion", g.name) log.WithFields(logrus.Fields{
"index": i,
"name": g.name,
}).Debug("Closing Onion service")
CloseOnion(i) CloseOnion(i)
} }
log.Debug("All Onion services closed")
} }
// CloseOnion closes the Onion at the given index. It does not affect Onion // CloseOnion closes the Onion at the given index. It does not affect Onion
// objects instantiated by an app. // objects instantiated by an app.
func CloseOnion(tunName string) { func CloseOnion(tunName string) {
log.WithField("tunnel_name", tunName).Debug("Attempting to close Onion service")
g, ok := onions[tunName] g, ok := onions[tunName]
if ok { if ok {
g.Close() 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")
} }
} }
@@ -224,12 +322,28 @@ func CloseOnion(tunName string) {
// corresponding to a structure managed by the onramp library // corresponding to a structure managed by the onramp library
// and not instantiated by an app. // and not instantiated by an app.
func ListenOnion(network, keys string) (net.Listener, error) { 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) g, err := NewOnion(keys)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create new Onion")
return nil, fmt.Errorf("onramp Listen: %v", err) return nil, fmt.Errorf("onramp Listen: %v", err)
} }
onions[keys] = g onions[keys] = g
return g.Listen() 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 // DialOnion returns a net.Conn for a onion structure's keys
@@ -247,13 +361,19 @@ func DialOnion(network, addr string) (net.Conn, error) {
// DeleteOnionKeys deletes the key file at the given path as determined by // DeleteOnionKeys deletes the key file at the given path as determined by
// keystore + tunName. // keystore + tunName.
func DeleteOnionKeys(tunName string) error { func DeleteOnionKeys(tunName string) error {
log.WithField("tunnel_name", tunName).Debug("Attempting to delete Onion keys")
keystore, err := TorKeystorePath() keystore, err := TorKeystorePath()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get keystore path")
return fmt.Errorf("onramp DeleteOnionKeys: discovery error %v", err) return fmt.Errorf("onramp DeleteOnionKeys: discovery error %v", err)
} }
keyspath := filepath.Join(keystore, tunName+".i2p.private") keyspath := filepath.Join(keystore, tunName+".i2p.private")
log.WithError(err).Error("Failed to get keystore path")
if err := os.Remove(keyspath); err != nil { 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) return fmt.Errorf("onramp DeleteOnionKeys: %v", err)
} }
log.Debug("Successfully deleted Onion keys")
return nil return nil
} }

View File

@@ -7,7 +7,6 @@ import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"net/http" "net/http"
"testing" "testing"
) )

View File

@@ -1,8 +1,8 @@
package onramp package onramp
import ( import (
"github.com/sirupsen/logrus"
"io" "io"
"log"
"net" "net"
"strings" "strings"
) )
@@ -21,30 +21,59 @@ type OnrampProxy struct {
// and an I2P or Onion address, and it will act as a tunnel to a // and an I2P or Onion address, and it will act as a tunnel to a
// listening hidden service somewhere. // listening hidden service somewhere.
func (p *OnrampProxy) Proxy(list net.Listener, raddr string) error { func (p *OnrampProxy) Proxy(list net.Listener, raddr string) error {
log.WithFields(logrus.Fields{
"remote_address": raddr,
"local_address": list.Addr().String(),
}).Debug("Starting proxy service")
for { for {
log.Debug("Waiting for incoming connection")
conn, err := list.Accept() conn, err := list.Accept()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to accept connection")
return err return err
} }
log.WithFields(logrus.Fields{
"local_addr": conn.LocalAddr().String(),
"remote_addr": conn.RemoteAddr().String(),
}).Debug("Accepted new connection, starting proxy routine")
go p.proxy(conn, raddr) go p.proxy(conn, raddr)
} }
} }
func (p *OnrampProxy) proxy(conn net.Conn, raddr string) { func (p *OnrampProxy) proxy(conn net.Conn, raddr string) {
log.WithFields(logrus.Fields{
"remote_address": raddr,
"local_addr": conn.LocalAddr().String(),
"remote_addr": conn.RemoteAddr().String(),
}).Debug("Setting up proxy connection")
var remote net.Conn var remote net.Conn
var err error var err error
checkaddr := strings.Split(raddr, ":")[0] checkaddr := strings.Split(raddr, ":")[0]
if strings.HasSuffix(checkaddr, ".i2p") { if strings.HasSuffix(checkaddr, ".i2p") {
log.Debug("Detected I2P address, using Garlic connection")
remote, err = p.Garlic.Dial("tcp", raddr) remote, err = p.Garlic.Dial("tcp", raddr)
} else if strings.HasSuffix(checkaddr, ".onion") { } else if strings.HasSuffix(checkaddr, ".onion") {
log.Debug("Detected Onion address, using Tor connection")
remote, err = p.Onion.Dial("tcp", raddr) remote, err = p.Onion.Dial("tcp", raddr)
} else { } else {
log.Debug("Using standard TCP connection")
remote, err = net.Dial("tcp", raddr) remote, err = net.Dial("tcp", raddr)
} }
if err != nil { if err != nil {
log.Fatalf("cannot dial to remote: %v", err) log.WithError(err).Error("Failed to establish remote connection")
log.Fatal("Cannot dial to remote")
} }
defer remote.Close() defer remote.Close()
log.WithFields(logrus.Fields{
"local_addr": remote.LocalAddr().String(),
"remote_addr": remote.RemoteAddr().String(),
}).Debug("Remote connection established, starting bidirectional copy")
go io.Copy(remote, conn) go io.Copy(remote, conn)
io.Copy(conn, remote) io.Copy(conn, remote)
} }

53
tls.go
View File

@@ -10,6 +10,7 @@ import (
"encoding/asn1" "encoding/asn1"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"github.com/sirupsen/logrus"
"math/big" "math/big"
"net" "net"
"os" "os"
@@ -24,11 +25,14 @@ import (
// if no TLS keys exist, they will be generated. They will be valid for // if no TLS keys exist, they will be generated. They will be valid for
// the .b32.i2p domain. // the .b32.i2p domain.
func (g *Garlic) TLSKeys() (tls.Certificate, error) { func (g *Garlic) TLSKeys() (tls.Certificate, error) {
log.WithField("name", g.getName()).Debug("Getting TLS keys for Garlic service")
keys, err := g.Keys() keys, err := g.Keys()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get I2P keys")
return tls.Certificate{}, err return tls.Certificate{}, err
} }
base32 := keys.Addr().Base32() base32 := keys.Addr().Base32()
log.WithField("base32", base32).Debug("Retrieving TLS certificate for base32 address")
return TLSKeys(base32) return TLSKeys(base32)
} }
@@ -36,31 +40,45 @@ func (g *Garlic) TLSKeys() (tls.Certificate, error) {
// if no TLS keys exist, they will be generated. They will be valid for // if no TLS keys exist, they will be generated. They will be valid for
// the .onion domain. // the .onion domain.
func (o *Onion) TLSKeys() (tls.Certificate, error) { func (o *Onion) TLSKeys() (tls.Certificate, error) {
log.WithField("name", o.getName()).Debug("Getting TLS keys for Onion service")
keys, err := o.Keys() keys, err := o.Keys()
if err != nil { if err != nil {
return tls.Certificate{}, err return tls.Certificate{}, err
} }
onionService := torutil.OnionServiceIDFromPrivateKey(keys) onionService := torutil.OnionServiceIDFromPrivateKey(keys)
log.WithField("onion_service", onionService).Debug("Retrieving TLS certificate for onion service")
return TLSKeys(onionService) return TLSKeys(onionService)
} }
// TLSKeys returns the TLS certificate and key for the given hostname. // TLSKeys returns the TLS certificate and key for the given hostname.
func TLSKeys(tlsHost string) (tls.Certificate, error) { func TLSKeys(tlsHost string) (tls.Certificate, error) {
log.WithField("host", tlsHost).Debug("Getting TLS certificate and key")
tlsCert := tlsHost + ".crt" tlsCert := tlsHost + ".crt"
tlsKey := tlsHost + ".pem" tlsKey := tlsHost + ".pem"
if err := CreateTLSCertificate(tlsHost); nil != err { if err := CreateTLSCertificate(tlsHost); nil != err {
log.WithError(err).Error("Failed to create TLS certificate")
return tls.Certificate{}, err return tls.Certificate{}, err
} }
tlsKeystorePath, err := TLSKeystorePath() tlsKeystorePath, err := TLSKeystorePath()
if err != nil { if err != nil {
log.WithError(err).Error("Failed to get TLS keystore path")
return tls.Certificate{}, err return tls.Certificate{}, err
} }
tlsCertPath := filepath.Join(tlsKeystorePath, tlsCert) tlsCertPath := filepath.Join(tlsKeystorePath, tlsCert)
tlsKeyPath := filepath.Join(tlsKeystorePath, tlsKey) tlsKeyPath := filepath.Join(tlsKeystorePath, tlsKey)
log.WithFields(logrus.Fields{
"cert_path": tlsCertPath,
"key_path": tlsKeyPath,
}).Debug("Loading TLS certificate pair")
cert, err := tls.LoadX509KeyPair(tlsCertPath, tlsKeyPath) cert, err := tls.LoadX509KeyPair(tlsCertPath, tlsKeyPath)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to load TLS certificate pair")
return cert, err return cert, err
} }
log.Debug("Successfully loaded TLS certificate and key")
return cert, nil return cert, nil
} }
@@ -68,6 +86,7 @@ func TLSKeys(tlsHost string) (tls.Certificate, error) {
// and stores it in the TLS keystore for the application. If the keys already // and stores it in the TLS keystore for the application. If the keys already
// exist, generation is skipped. // exist, generation is skipped.
func CreateTLSCertificate(tlsHost string) error { func CreateTLSCertificate(tlsHost string) error {
log.WithField("host", tlsHost).Debug("Creating TLS certificate")
tlsCertName := tlsHost + ".crt" tlsCertName := tlsHost + ".crt"
tlsKeyName := tlsHost + ".pem" tlsKeyName := tlsHost + ".pem"
tlsKeystorePath, err := TLSKeystorePath() tlsKeystorePath, err := TLSKeystorePath()
@@ -79,70 +98,102 @@ func CreateTLSCertificate(tlsHost string) error {
_, certErr := os.Stat(tlsCert) _, certErr := os.Stat(tlsCert)
_, keyErr := os.Stat(tlsKey) _, keyErr := os.Stat(tlsKey)
if certErr != nil || keyErr != nil { if certErr != nil || keyErr != nil {
log.WithFields(logrus.Fields{
"cert_exists": certErr == nil,
"key_exists": keyErr == nil,
"cert_path": tlsCert,
"key_path": tlsKey,
}).Debug("Certificate or key missing, generating new ones")
if certErr != nil { if certErr != nil {
log.WithField("path", tlsCert).Debug("TLS certificate not found")
fmt.Printf("Unable to read TLS certificate '%s'\n", tlsCert) fmt.Printf("Unable to read TLS certificate '%s'\n", tlsCert)
} }
if keyErr != nil { if keyErr != nil {
log.WithField("path", tlsKey).Debug("TLS key not found")
fmt.Printf("Unable to read TLS key '%s'\n", tlsKey) fmt.Printf("Unable to read TLS key '%s'\n", tlsKey)
} }
if err := createTLSCertificate(tlsHost); nil != err { if err := createTLSCertificate(tlsHost); nil != err {
log.WithError(err).Error("Failed to create TLS certificate")
return err return err
} }
} else {
log.Debug("TLS certificate and key already exist")
} }
return nil return nil
} }
func createTLSCertificate(host string) error { func createTLSCertificate(host string) error {
log.WithField("host", host).Debug("Generating new TLS certificate")
fmt.Println("Generating TLS keys. This may take a minute...") fmt.Println("Generating TLS keys. This may take a minute...")
priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to generate private key")
return err return err
} }
tlsCert, err := NewTLSCertificate(host, priv) tlsCert, err := NewTLSCertificate(host, priv)
if nil != err { if nil != err {
log.WithError(err).Error("Failed to create new TLS certificate")
return err return err
} }
privStore, err := TLSKeystorePath() privStore, err := TLSKeystorePath()
if nil != err { if nil != err {
log.WithError(err).Error("Failed to get keystore path")
return err return err
} }
certFile := filepath.Join(privStore, host+".crt") certFile := filepath.Join(privStore, host+".crt")
log.WithField("path", certFile).Debug("Saving TLS certificate")
// save the TLS certificate // save the TLS certificate
certOut, err := os.Create(certFile) certOut, err := os.Create(certFile)
if err != nil { if err != nil {
log.WithError(err).WithField("path", certFile).Error("Failed to create certificate file")
return fmt.Errorf("failed to open %s for writing: %s", host+".crt", err) return fmt.Errorf("failed to open %s for writing: %s", host+".crt", err)
} }
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: tlsCert}) pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: tlsCert})
certOut.Close() certOut.Close()
log.WithField("path", certFile).Debug("TLS certificate saved successfully")
fmt.Printf("\tTLS certificate saved to: %s\n", host+".crt") fmt.Printf("\tTLS certificate saved to: %s\n", host+".crt")
// save the TLS private key // save the TLS private key
privFile := filepath.Join(privStore, host+".pem") privFile := filepath.Join(privStore, host+".pem")
log.WithField("path", privFile).Debug("Saving TLS private key")
keyOut, err := os.OpenFile(privFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) keyOut, err := os.OpenFile(privFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil { if err != nil {
log.WithError(err).WithField("path", privFile).Error("Failed to create private key file")
return fmt.Errorf("failed to open %s for writing: %v", privFile, err) return fmt.Errorf("failed to open %s for writing: %v", privFile, err)
} }
secp384r1, err := asn1.Marshal(asn1.ObjectIdentifier{1, 3, 132, 0, 34}) // http://www.ietf.org/rfc/rfc5480.txt secp384r1, err := asn1.Marshal(asn1.ObjectIdentifier{1, 3, 132, 0, 34}) // http://www.ietf.org/rfc/rfc5480.txt
if err != nil {
log.WithError(err).Error("Failed to marshal EC parameters")
return err
}
pem.Encode(keyOut, &pem.Block{Type: "EC PARAMETERS", Bytes: secp384r1}) pem.Encode(keyOut, &pem.Block{Type: "EC PARAMETERS", Bytes: secp384r1})
ecder, err := x509.MarshalECPrivateKey(priv) ecder, err := x509.MarshalECPrivateKey(priv)
if err != nil {
log.WithError(err).Error("Failed to marshal private key")
return err
}
pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: ecder}) pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: ecder})
pem.Encode(keyOut, &pem.Block{Type: "CERTIFICATE", Bytes: tlsCert}) pem.Encode(keyOut, &pem.Block{Type: "CERTIFICATE", Bytes: tlsCert})
keyOut.Close() keyOut.Close()
log.WithField("path", privFile).Debug("TLS private key saved successfully")
fmt.Printf("\tTLS private key saved to: %s\n", privFile) fmt.Printf("\tTLS private key saved to: %s\n", privFile)
// CRL // CRL
crlFile := filepath.Join(privStore, host+".crl") crlFile := filepath.Join(privStore, host+".crl")
log.WithField("path", crlFile).Debug("Creating CRL")
crlOut, err := os.OpenFile(crlFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) crlOut, err := os.OpenFile(crlFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil { if err != nil {
log.WithError(err).WithField("path", crlFile).Error("Failed to create CRL file")
return fmt.Errorf("failed to open %s for writing: %s", crlFile, err) return fmt.Errorf("failed to open %s for writing: %s", crlFile, err)
} }
crlcert, err := x509.ParseCertificate(tlsCert) crlcert, err := x509.ParseCertificate(tlsCert)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to parse certificate for CRL creation")
return fmt.Errorf("Certificate with unknown critical extension was not parsed: %s", err) return fmt.Errorf("Certificate with unknown critical extension was not parsed: %s", err)
} }
@@ -156,10 +207,12 @@ func createTLSCertificate(host string) error {
crlBytes, err := crlcert.CreateCRL(rand.Reader, priv, revokedCerts, now, now) crlBytes, err := crlcert.CreateCRL(rand.Reader, priv, revokedCerts, now, now)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to create CRL")
return fmt.Errorf("error creating CRL: %s", err) return fmt.Errorf("error creating CRL: %s", err)
} }
_, err = x509.ParseDERCRL(crlBytes) _, err = x509.ParseDERCRL(crlBytes)
if err != nil { if err != nil {
log.WithError(err).Error("Failed to validate generated CRL")
return fmt.Errorf("error reparsing CRL: %s", err) return fmt.Errorf("error reparsing CRL: %s", err)
} }
pem.Encode(crlOut, &pem.Block{Type: "X509 CRL", Bytes: crlBytes}) pem.Encode(crlOut, &pem.Block{Type: "X509 CRL", Bytes: crlBytes})