Documentation
¶
Overview ¶
Package tor provides Tor network connectivity for OnionScan.
This package wraps the tornago library to provide SOCKS5 proxy connections through the Tor network. It handles connection management, proxy status verification, and provides HTTP clients configured for Tor.
Design decision: We use tornago instead of directly implementing SOCKS5 because tornago provides a well-tested, maintained implementation with additional features like automatic retry and connection pooling. This reduces our maintenance burden and leverages existing expertise in Tor connectivity.
The package is designed to be used with dependency injection - create a Client and pass it to components that need Tor connectivity rather than using global state.
Index ¶
- Constants
- Variables
- func ComputeV3AddressFromPublicKey(pubkey []byte) (string, error)
- func ExtractV2Addresses(content string) []string
- func ExtractV3Addresses(content string) []string
- func IsV2Address(address string) bool
- func IsValidV3Address(address string) bool
- func NormalizeAddress(address string) (string, error)
- type Client
- func (c *Client) CheckConnection(ctx context.Context) ProxyStatus
- func (c *Client) Dial(network, address string) (net.Conn, error)
- func (c *Client) DialContext(ctx context.Context, network, address string) (net.Conn, error)
- func (c *Client) Dialer() proxy.Dialer
- func (c *Client) HTTPClient() *http.Client
- func (c *Client) HTTPClientWithConfig(cookie string, headers map[string]string) *http.Client
- func (c *Client) NewHTTPClient() *http.Client
- func (c *Client) ProxyAddress() string
- type EmbeddedTor
- type EmbeddedTorOption
- type ProxyStatus
Constants ¶
const ( // OnionV3Length is the length of a v3 onion address without the ".onion" suffix. // V3 addresses are 56 characters of base32-encoded data. OnionV3Length = 56 // OnionV3TotalLength is the total length including the ".onion" suffix. OnionV3TotalLength = 62 // OnionV3Version is the version byte for v3 onion addresses. OnionV3Version = 0x03 // OnionV2Length is the length of a v2 onion address without the ".onion" suffix. // V2 addresses are 16 characters. Note: V2 was deprecated in 2021. OnionV2Length = 16 // OnionSuffix is the common suffix for all onion addresses. OnionSuffix = ".onion" )
Onion address constants.
Variables ¶
var ( // ErrProxyNotTor is returned when the configured proxy address responds // but is not a Tor SOCKS5 proxy. This typically happens when connecting // to a regular HTTP proxy or a different service on the expected port. ErrProxyNotTor = errors.New("proxy is not a Tor SOCKS5 proxy") // ErrProxyCannotConnect is returned when we cannot establish a TCP connection // to the proxy address. This usually means Tor is not running or the address // is incorrect. ErrProxyCannotConnect = errors.New("cannot connect to Tor proxy") // ErrProxyTimeout is returned when the connection to the proxy times out. // This may indicate network issues or an overloaded Tor daemon. ErrProxyTimeout = errors.New("timeout connecting to Tor proxy") // ErrInvalidProxyAddress is returned when the proxy address format is invalid. // Expected format is "host:port". ErrInvalidProxyAddress = errors.New("invalid proxy address format: expected host:port") )
Tor connectivity errors. These errors are returned when there are problems connecting to or through Tor.
Design decision: We define specific error types rather than wrapping all errors generically. This allows callers to handle different failure modes appropriately (e.g., retry on timeout, but fail fast on wrong proxy type).
var ( // ErrInvalidOnionAddress is returned when an address is not a valid onion address. ErrInvalidOnionAddress = newOnionError("invalid onion address") // ErrV2AddressDeprecated is returned when a v2 address is provided. // V2 addresses stopped working in October 2021. ErrV2AddressDeprecated = newOnionError("v2 onion addresses are deprecated and no longer functional") )
Onion address validation errors.
Functions ¶
func ComputeV3AddressFromPublicKey ¶
ComputeV3AddressFromPublicKey computes the v3 onion address from an ed25519 public key. This is useful for verifying that a discovered public key matches a known address.
The public key must be exactly 32 bytes (ed25519 public key size).
func ExtractV2Addresses ¶
ExtractV2Addresses finds all v2 onion addresses in the given text. Returns a deduplicated slice of addresses found.
These addresses are deprecated and non-functional, but detecting them is useful for reporting outdated content.
Design decision: We first extract all v3 addresses, then filter v2 matches to exclude any that are substrings of v3 addresses. This is necessary because the last 16 characters of a v3 address would otherwise match the v2 pattern.
func ExtractV3Addresses ¶
ExtractV3Addresses finds all v3 onion addresses in the given text. Returns a deduplicated slice of addresses found.
Design decision: We deduplicate results because the same address often appears multiple times in page content (links, text, etc.). Returning unique addresses simplifies processing for callers.
func IsV2Address ¶
IsV2Address checks if the given address matches the v2 onion address format. V2 addresses were deprecated in October 2021 and no longer work on the Tor network.
This function is provided to detect and warn about v2 addresses in content, not to validate them for use.
func IsValidV3Address ¶
IsValidV3Address checks if the given address is a valid v3 onion address. It performs both format validation and checksum verification.
Design decision: We perform full checksum validation rather than just pattern matching because: 1. It catches typos and corrupted addresses 2. It verifies the address was properly generated 3. It matches what Tor itself does when connecting
The address should be lowercase and include the ".onion" suffix.
func NormalizeAddress ¶
NormalizeAddress normalizes an onion address to lowercase with .onion suffix. Returns the normalized address or an error if invalid.
This function handles common input variations: - Uppercase letters - Missing .onion suffix - Extra whitespace - URL schemes (http://, https://) - Trailing paths or query strings
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client provides Tor network connectivity. It wraps a SOCKS5 dialer and provides methods for creating HTTP clients and raw TCP connections through Tor.
Design decision: We don't use tornago's higher-level Tor daemon management because OnionScan expects users to have their own Tor daemon running. We only use the SOCKS5 connectivity, which is standard Go functionality. If tornago provides optimizations, we can integrate them later.
func NewClient ¶
NewClient creates a new Tor client with the given proxy address and timeout.
The proxyAddress must be in "host:port" format (e.g., "127.0.0.1:9050"). The timeout is used as the default for HTTP clients created by this client.
This function validates the proxy address format but does not verify that the proxy is actually running. Call CheckConnection() to verify.
Design decision: We don't connect to the proxy in the constructor because: 1. It allows creating the client even when Tor isn't running yet 2. It separates object creation from network operations 3. It allows for better testing with mock proxies
func (*Client) CheckConnection ¶
func (c *Client) CheckConnection(ctx context.Context) ProxyStatus
CheckConnection verifies that the Tor proxy is running and accessible. It returns a ProxyStatus indicating the result of the check.
The check works by performing a SOCKS5 protocol handshake to verify: 1. The proxy speaks SOCKS5 protocol 2. The proxy accepts connections without authentication 3. The proxy can handle .onion domain connections
Security note: This is more robust than just checking HTTP response strings, as a fake proxy attack cannot easily mimic proper SOCKS5 protocol behavior.
func (*Client) Dial ¶
Dial establishes a TCP connection through Tor to the given address. This is useful for non-HTTP protocols like SSH, FTP, etc.
The address should be in "host:port" format. For hidden services, use the .onion address (e.g., "example.onion:22").
func (*Client) DialContext ¶
DialContext establishes a TCP connection through Tor with context support. This allows for timeout and cancellation control.
Design decision: We wrap the basic Dial with context support because the proxy.Dialer interface doesn't support context directly. If the context is cancelled, the goroutine returns the error but the underlying connection attempt may continue briefly. This is a known limitation of the approach.
func (*Client) Dialer ¶
Dialer returns the underlying proxy dialer. This is useful for protocol scanners that need direct TCP connections.
Design decision: We expose the dialer because: 1. Protocol scanners need it for non-HTTP connections 2. It allows for more flexible connection management 3. The caller can wrap it with additional functionality
func (*Client) HTTPClient ¶
HTTPClient returns a new HTTP client configured for Tor. This is a convenience method that calls NewHTTPClient().
func (*Client) HTTPClientWithConfig ¶
HTTPClientWithConfig creates an HTTP client with custom cookie and headers. This is useful for authenticated scanning where site-specific credentials are needed.
The cookie parameter is a raw cookie string (e.g., "session_id=abc123"). The headers parameter is a map of header names to values.
Design decision: We use a custom RoundTripper to inject headers/cookies rather than modifying each request. This ensures all requests (including redirects and subrequests) include the configured values.
func (*Client) NewHTTPClient ¶
NewHTTPClient creates an HTTP client configured to use the Tor proxy. The returned client routes all requests through Tor's SOCKS5 proxy.
Design decisions: - TLS verification is disabled because hidden services use self-signed certs - We enable cookies via a cookie jar for session management during crawling - Redirect limit is 10 to prevent redirect loops while allowing normal redirects - Idle connection timeout is shorter than default to manage Tor circuit resources
func (*Client) ProxyAddress ¶
ProxyAddress returns the configured proxy address.
type EmbeddedTor ¶
type EmbeddedTor struct {
// contains filtered or unexported fields
}
EmbeddedTor manages an embedded Tor daemon using tornago. It provides automatic startup and shutdown of the Tor process, eliminating the need for an external Tor installation.
Design decision: We use tornago's embedded Tor functionality because:
- It simplifies deployment - no external Tor daemon required
- It provides better control over the Tor process lifecycle
- It allows OnionScan to work "out of the box" without setup
Note: Starting the embedded Tor daemon takes 1-3 minutes as it needs to:
- Download directory information from the Tor network
- Build initial circuits through the relay network
- Establish SOCKS and control port listeners
func NewEmbeddedTor ¶
func NewEmbeddedTor(opts ...EmbeddedTorOption) *EmbeddedTor
NewEmbeddedTor creates a new embedded Tor manager. Call Start() to actually launch the Tor daemon.
func (*EmbeddedTor) ControlAddr ¶
func (e *EmbeddedTor) ControlAddr() string
ControlAddr returns the control port address of the running Tor daemon. Returns an empty string if Tor is not running.
The control port can be used for advanced Tor control operations, but is not required for basic scanning functionality.
func (*EmbeddedTor) IsRunning ¶
func (e *EmbeddedTor) IsRunning() bool
IsRunning returns true if the embedded Tor daemon is currently running.
func (*EmbeddedTor) NewClient ¶
func (e *EmbeddedTor) NewClient(timeout time.Duration) (*Client, error)
NewClient creates a new Tor client using the embedded daemon's SOCKS proxy. Returns an error if the embedded Tor daemon is not running.
func (*EmbeddedTor) SocksAddr ¶
func (e *EmbeddedTor) SocksAddr() string
SocksAddr returns the SOCKS5 proxy address of the running Tor daemon. Returns an empty string if Tor is not running.
The format is "host:port" (e.g., "127.0.0.1:42715"). This address can be passed to NewClient() to create a Tor client.
func (*EmbeddedTor) Start ¶
func (e *EmbeddedTor) Start(ctx context.Context) error
Start launches the embedded Tor daemon and waits for it to bootstrap. This typically takes 1-3 minutes depending on network conditions.
The context can be used to cancel the startup if needed. Returns an error if Tor fails to start within the timeout period.
func (*EmbeddedTor) Stop ¶
func (e *EmbeddedTor) Stop() error
Stop gracefully shuts down the embedded Tor daemon. This should be called when the application exits to clean up resources.
It's safe to call Stop() multiple times or on an unstarted instance.
type EmbeddedTorOption ¶
type EmbeddedTorOption func(*EmbeddedTor)
EmbeddedTorOption configures an EmbeddedTor instance.
func WithStartupTimeout ¶
func WithStartupTimeout(timeout time.Duration) EmbeddedTorOption
WithStartupTimeout sets the maximum time to wait for Tor to bootstrap.
type ProxyStatus ¶
type ProxyStatus int
ProxyStatus represents the result of checking the Tor proxy connection. This enum allows for easy status reporting and programmatic handling of different proxy states.
const ( // ProxyStatusOK indicates the proxy is a working Tor SOCKS5 proxy. ProxyStatusOK ProxyStatus = iota // ProxyStatusWrongType indicates the proxy is not a Tor proxy. // The connection succeeded but the response indicates a different type of proxy. ProxyStatusWrongType // ProxyStatusCannotConnect indicates we could not establish a connection. // Tor may not be running or the address may be wrong. ProxyStatusCannotConnect // ProxyStatusTimeout indicates the connection attempt timed out. // This may be a temporary network issue or an overloaded Tor daemon. ProxyStatusTimeout )
func (ProxyStatus) Error ¶
func (s ProxyStatus) Error() error
Error returns the appropriate error for this status, or nil if OK.
func (ProxyStatus) String ¶
func (s ProxyStatus) String() string
String returns a human-readable description of the proxy status.