167 lines
9.2 KiB
Markdown
167 lines
9.2 KiB
Markdown
# Security
|
|
|
|
## Certificate Authority
|
|
|
|
### What it is
|
|
|
|
Holesail Browser generates a local root CA called **Holesail Browser CA** and installs it into your OS trust store. This CA is used to sign per-TLD wildcard TLS certificates that the HTTPS proxy presents to your browser — one certificate per wildcard parent domain, generated on demand.
|
|
|
|
### Why it's needed
|
|
|
|
Browsers require HTTPS for modern web features and refuse to load mixed content. The HTTPS proxy must present a valid TLS certificate for each virtual host's domain — but no public CA will sign certificates for non-public domains. A locally-trusted CA is the standard approach used by tools like mkcert, Burp Suite, and corporate proxies.
|
|
|
|
### What the CA can do
|
|
|
|
The CA is a standard X.509 root CA. In principle, it could be used to sign certificates for any domain — not just Holesail virtual host domains. However:
|
|
|
|
- The CA private key (`ca.key.pem`) is stored at `~/.holesail-browser/holesail-browser-certs/ca.key.pem`, readable only by your user account
|
|
- The native host only ever uses the CA to sign wildcard certificates for configured virtual host TLDs
|
|
- The CA is installed with the SSL trust policy only (macOS: `-p ssl`), limiting its scope
|
|
|
|
### Certificate issuance
|
|
|
|
A separate wildcard certificate is generated for each unique wildcard parent domain:
|
|
|
|
| Virtual host hostname | Wildcard parent | Certificate covers |
|
|
|-----------------------|-----------------|-------------------|
|
|
| `myapp.hole.sail` | `hole.sail` | `*.hole.sail` |
|
|
| `api.haha.wooo` | `haha.wooo` | `*.haha.wooo` |
|
|
| `i.love.hole.sail` | `love.hole.sail` | `*.love.hole.sail` |
|
|
| `a.b.c.my.internal` | `b.c.my.internal` | `*.b.c.my.internal` |
|
|
|
|
Certificates are generated on the first HTTPS connection to a new wildcard parent and cached on disk. No proxy restart is required.
|
|
|
|
### Lifetime
|
|
|
|
- Root CA: 10 years
|
|
- Wildcard domain certs: 1 year (auto-renewed on expiry)
|
|
|
|
### Fingerprint verification
|
|
|
|
`isRootCAInstalled` and `installRootCA` both verify the SHA-1 fingerprint of the CA cert in the keychain against the CA cert on disk. This detects stale entries — for example, after reinstalling Holesail Browser, the new CA will be installed and the old one removed automatically.
|
|
|
|
### Installation
|
|
|
|
| Platform | Method | Requires admin? |
|
|
|----------|--------|----------------|
|
|
| macOS | `security add-trusted-cert -r trustRoot -p ssl -k ~/Library/Keychains/login.keychain-db` | No |
|
|
| Linux | `cp` to `/usr/local/share/ca-certificates/` + `update-ca-certificates` | Yes (sudo) |
|
|
| Windows | `certutil -addstore -f ROOT` | Yes (admin) |
|
|
|
|
On macOS, the CA is installed to the user's login keychain with the SSL trust policy. Chrome evaluates TLS trust using the SSL policy against all user keychains, so this is sufficient without admin privileges.
|
|
|
|
---
|
|
|
|
## P2P encryption
|
|
|
|
All traffic between the native host and remote peers is encrypted using the **Noise protocol** (specifically the Noise_XX handshake pattern used by Holesail). This provides:
|
|
|
|
- **Mutual authentication** — both peers verify each other's identity using their public keys
|
|
- **Forward secrecy** — session keys are ephemeral; compromise of long-term keys does not expose past sessions
|
|
- **Integrity** — all messages are authenticated with a MAC
|
|
|
|
The `hs://` key is the remote peer's public key. Connecting to a peer requires knowing their key — there is no way to enumerate or discover peers without a key.
|
|
|
|
---
|
|
|
|
## Native host privilege model
|
|
|
|
The native host runs as your user account (not root). It:
|
|
|
|
- Binds only to `127.0.0.1` (loopback) — no network interfaces are exposed
|
|
- Reads and writes only to `~/.holesail-browser/` (state, certs, logs, backups)
|
|
- Spawns child processes: `ssh` (for SSH sessions), `tar` (for backups), `security`/`certutil`/`update-ca-certificates` (for CA installation)
|
|
- Does not require elevated privileges for normal operation
|
|
|
|
---
|
|
|
|
## Proxy security
|
|
|
|
### Loopback-only binding
|
|
|
|
Both the HTTPS proxy (8443) and CONNECT proxy (8442) bind to `127.0.0.1` only. They are not accessible from other machines on the network.
|
|
|
|
### SNI-based certificate selection
|
|
|
|
The HTTPS proxy reads the TLS ClientHello from each raw TCP connection to extract the SNI hostname before the TLS handshake begins. This allows it to present the correct wildcard certificate for each TLD without requiring a proxy restart. The SNI parsing is done entirely in JavaScript — no native TLS SNI callback is used.
|
|
|
|
### Unknown hostname handling
|
|
|
|
If the HTTPS proxy receives a request for a hostname with no registered tunnel, it returns a `502 Bad Gateway` response with a plain HTML error page. It does not attempt to forward the request to any external host.
|
|
|
|
### PAC script scope
|
|
|
|
The PAC script routes only configured TLD traffic through the proxy. For each active TLD (e.g. `.hole.sail`, `.haha.wooo`), a `dnsDomainIs` clause is added. All other traffic goes `DIRECT`. The extension monitors `proxy.settings.onChange` and re-applies the PAC script if it is overridden by another extension.
|
|
|
|
### Custom TLD validation
|
|
|
|
The dashboard enforces that virtual host hostnames:
|
|
|
|
- Have at least 3 labels (host + two-label TLD minimum)
|
|
- Use only letters, digits, and hyphens per label
|
|
- Do not use any real public TLD (`.com`, `.net`, `.co.uk`, and hundreds of others)
|
|
|
|
This prevents accidentally routing real internet traffic through the local proxy.
|
|
|
|
---
|
|
|
|
## Threats mitigated
|
|
|
|
| Threat | Mitigation |
|
|
|--------|-----------|
|
|
| Network eavesdropping on tunnel traffic | Noise protocol encryption (end-to-end) |
|
|
| Man-in-the-middle on browser↔proxy TLS | Locally-trusted per-TLD wildcard certs; CA key stored locally |
|
|
| Wrong cert served for custom TLD | JS-layer SNI: cert selected per-connection based on ClientHello hostname |
|
|
| Unauthorized access to remote peers | `hs://` key required to connect; Noise mutual auth |
|
|
| Proxy accessible from other machines | Loopback-only binding |
|
|
| Stale CA after reinstall causing cert errors | Fingerprint verification on every CA install check |
|
|
| Multiple native host instances with no tunnels | Process exits on EADDRINUSE; only one instance owns the ports |
|
|
| Custom TLD colliding with real internet domain | TLD blocklist validation in dashboard before saving |
|
|
|
|
## Threats not mitigated
|
|
|
|
| Threat | Notes |
|
|
|--------|-------|
|
|
| Malicious code with local user access | If an attacker has local user access, they can read the CA private key and the `hs://` keys in `state.json` |
|
|
| Compromised remote peer | Holesail authenticates the peer's public key but cannot verify what the peer does with your connection |
|
|
| Browser extension permissions | The extension has `proxy` and `nativeMessaging` permissions; a compromised extension could redirect all browser traffic |
|
|
|
|
---
|
|
|
|
## SSH security
|
|
|
|
SSH sessions use the system `ssh` binary. Authentication is tried in order: public key, then password.
|
|
|
|
- **Public key auth** — keys from `~/.ssh/` and `ssh-agent` are used automatically; no password prompt appears if a key is accepted
|
|
- **Password auth** — passwords are optionally saved as `passwordB64` (base64-encoded) in `state.json` and survive native host restarts; a saved password is delivered to SSH via `SSH_ASKPASS` and a named FIFO pipe (not via the process environment or command line); if no password is saved and key auth fails, an interactive password prompt appears in xterm.js and is delivered the same way
|
|
- **`StrictHostKeyChecking=no` / `UserKnownHostsFile=/dev/null`** — host key verification is disabled for Holesail tunnels because the remote host's identity is already established by the `hs://` public key (Noise mutual authentication at the tunnel layer); adding known-hosts entries for `127.0.0.1` would be misleading since the port changes per session
|
|
- The Holesail tunnel provides transport; `ssh`'s own encryption is layered on top, giving double encryption
|
|
|
|
---
|
|
|
|
## Tunnel state storage
|
|
|
|
`state.json` contains `hs://` keys for all configured tunnels. These keys are the public keys of remote peers — they are not secret. However, `state.json` also contains:
|
|
|
|
- **Server tunnel keypairs** — the `hs://` URL for each server tunnel is derived from a keypair that is preserved in state; anyone with this URL can connect to your exposed service
|
|
- **`passwordB64`** — SSH and RDP connection passwords are stored as base64-encoded strings under `sshConnections[].passwordB64` and `rdpConnections[].passwordB64`; server tunnel `label` fields are also stored but are not sensitive
|
|
|
|
Protect `~/.holesail-browser/` accordingly.
|
|
|
|
---
|
|
|
|
## Backup security
|
|
|
|
Backup archives contain `state.json` and all certificate files including the CA private key. Store backups securely. The archives are not encrypted.
|
|
|
|
---
|
|
|
|
## Recommendations
|
|
|
|
1. **Keep `~/.holesail-browser/` private** — it contains the CA private key, server tunnel keypairs, and any saved passwords
|
|
2. **Restart Chrome after CA installation** — Chrome caches trust store state; a restart is required for the new CA to take effect
|
|
3. **Use `readyTimeoutMs`** — setting a timeout (e.g. 30000ms) surfaces tunnel connection failures instead of hanging indefinitely
|
|
4. **Review `hs://` keys before adding them** — only add keys from sources you trust
|
|
5. **Use private TLDs only** — the dashboard enforces this, but be aware that any TLD you add will have a locally-trusted wildcard certificate issued for it
|
|
6. **Encrypt backups** if storing them in a shared location
|