276 lines
7.7 KiB
Markdown
276 lines
7.7 KiB
Markdown
# Security
|
||
|
||
goget implements multiple security features to protect both the client and server during file transfers. This document covers TLS configuration, certificate handling, checksum verification, PGP support, HSTS, and SSRF protection.
|
||
|
||
## TLS / HTTPS
|
||
|
||
### Default Security
|
||
|
||
- **TLS 1.2 minimum** — TLS 1.0 and 1.1 are disabled
|
||
- **Certificate verification** — Enabled by default against system CA bundle
|
||
- **HTTP/2 support** — Automatic ALPN negotiation
|
||
|
||
### Certificate Pinning
|
||
|
||
Pin a specific certificate by its SHA-256 hash. This works across every
|
||
TLS-bearing protocol goget supports: HTTPS, FTPS, Gemini, and WebDAVS.
|
||
|
||
```bash
|
||
goget --pinned-cert "a1b2c3d4e5f6..." --url https://example.com
|
||
goget --pinned-cert "a1b2c3d4e5f6..." --url ftps://ftp.example.com/file
|
||
goget --pinned-cert "a1b2c3d4e5f6..." --url gemini://geminiprotocol.net
|
||
goget --pinned-cert "a1b2c3d4e5f6..." --url webdavs://dav.example.com/file
|
||
```
|
||
|
||
If the leaf certificate's SHA-256 doesn't match, the connection is aborted.
|
||
|
||
### SSH Host-Key Pinning (SFTP)
|
||
|
||
Pin the SSH host key for SFTP by its OpenSSH fingerprint (the same value
|
||
printed by `ssh-keygen -lf ~/.ssh/known_hosts`). Pinning takes precedence
|
||
over `--ssh-insecure` and the `known_hosts` file.
|
||
|
||
```bash
|
||
goget --pinned-host-key "SHA256:AAAAC3NzaC1lZDI1NTE5AAAAIE+HHi0MtRj4VPl8mdP8gniGZDRb0SZTdI2TPxRyCm1H" \
|
||
--url sftp://example.com/file
|
||
```
|
||
|
||
The raw lowercase-hex SHA-256 of the key wire format is also accepted.
|
||
|
||
### Client Certificates (mTLS)
|
||
|
||
Authenticate the client to the server with mutual TLS:
|
||
|
||
```bash
|
||
goget --cert client.pem --key client.key --url https://mtls.example.com
|
||
```
|
||
|
||
### Custom CA Bundle
|
||
|
||
```bash
|
||
goget --cacert custom-ca.pem --url https://internal.example.com
|
||
```
|
||
|
||
### Insecure Mode
|
||
|
||
Disable all TLS verification (⚠ **for testing only**):
|
||
|
||
```bash
|
||
goget --insecure --url https://self-signed.local
|
||
```
|
||
|
||
### SSLKEYLOGFILE
|
||
|
||
Log TLS session keys for Wireshark debugging:
|
||
|
||
```bash
|
||
goget --ssl-key-log tls.keys --url https://example.com
|
||
# Then: Wireshark → Preferences → TLS → (Pre-)Master-Secret log filename → tls.keys
|
||
```
|
||
|
||
## HSTS (HTTP Strict Transport Security)
|
||
|
||
Per RFC 6797, goget maintains an HSTS cache that automatically upgrades HTTP connections to HTTPS for known hosts.
|
||
|
||
### How It Works
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Client as goget
|
||
participant Server as Server
|
||
|
||
Client->>Server: GET http://example.com
|
||
Server-->>Client: 200 OK + Strict-Transport-Security: max-age=31536000
|
||
Client->>Client: Store: example.com → 365 days
|
||
Client->>Server: GET http://example.com/page (later)
|
||
Client->>Client: HSTS cache hit → upgrade to HTTPS
|
||
Client->>Server: GET https://example.com/page
|
||
```
|
||
|
||
### Cache Location
|
||
|
||
```
|
||
~/.config/goget/hsts
|
||
```
|
||
|
||
Can be overridden with `GOGET_HSTS` environment variable or `--hsts-file`.
|
||
|
||
### Cache Format
|
||
|
||
```json
|
||
[
|
||
{
|
||
"host": "example.com",
|
||
"include_subdomains": true,
|
||
"expires": "2027-06-01T00:00:00Z"
|
||
}
|
||
]
|
||
```
|
||
|
||
Expired entries are automatically pruned on load.
|
||
|
||
## Checksum Verification
|
||
|
||
Verify file integrity after download:
|
||
|
||
### Direct Hash
|
||
|
||
```bash
|
||
goget --checksum "a1b2c3d4e5f6..." --checksum-algo sha256 --url https://example.com/file.zip
|
||
```
|
||
|
||
Supported algorithms:
|
||
|
||
| Algorithm | Flag Value | Hash Length |
|
||
|---|---|---|
|
||
| SHA-256 | `sha256` | 64 hex chars |
|
||
| SHA-512 | `sha512` | 128 hex chars |
|
||
| SHA3-256 | `sha3-256` | 64 hex chars |
|
||
| SHA3-512 | `sha3-512` | 128 hex chars |
|
||
| BLAKE2b | `blake2b` | 128 hex chars |
|
||
| MD5 | `md5` | 32 hex chars |
|
||
|
||
### Checksum File
|
||
|
||
Supports standard `SHA256SUMS` format:
|
||
|
||
```bash
|
||
# SHA256SUMS file:
|
||
# a1b2c3d4... file.zip
|
||
# e5f6a7b8... *file.tar.gz
|
||
|
||
goget --checksum-file SHA256SUMS --url https://example.com/file.zip
|
||
```
|
||
|
||
Both plain (`file.zip`) and binary-mode (`*file.tar.gz`) formats are supported.
|
||
|
||
### Error Handling
|
||
|
||
On mismatch:
|
||
```
|
||
Checksum mismatch
|
||
Expected: a1b2c3d4...
|
||
Actual: e5f6a7b8...
|
||
```
|
||
|
||
The error is a typed `core.GogetError` with `Type: "CHECKSUM"` and `Details["expected"]` / `Details["actual"]`.
|
||
|
||
## PGP / GPG
|
||
|
||
### Signature Verification
|
||
|
||
Verify PGP detached signatures:
|
||
|
||
```bash
|
||
# With explicit signature file
|
||
goget --pgp-verify --pgp-sig file.sig --pgp-key public.key --url https://example.com/file
|
||
|
||
# Auto-detect signature file (tries: file.asc, file.sig, file.gpg)
|
||
goget --pgp-verify --pgp-key public.key --url https://example.com/file
|
||
```
|
||
|
||
### Decryption
|
||
|
||
Decrypt PGP-encrypted files:
|
||
|
||
```bash
|
||
# Decrypt with key and passphrase
|
||
goget --pgp-decrypt --pgp-key private.key --pgp-passphrase "secret" --url https://example.com/file.gpg
|
||
|
||
# Read passphrase from file (safer)
|
||
goget --pgp-decrypt --pgp-key private.key --pgp-passphrase-file pass.txt --url https://example.com/file.gpg
|
||
```
|
||
|
||
The decrypted file replaces the encrypted one:
|
||
- `file.gpg` → `file`
|
||
- `file.pgp` → `file`
|
||
- `file.asc` → `file`
|
||
- Other → `filename.decrypted`
|
||
|
||
> **Note:** PGP functionality uses `golang.org/x/crypto/openpgp`, which is frozen. A replacement will be provided in a future release.
|
||
|
||
## SSRF Protection
|
||
|
||
By default, goget blocks connections to private and internal IP ranges:
|
||
|
||
| Range | CIDR | Description |
|
||
|---|---|---|
|
||
| `10.0.0.0/8` | Private | RFC 1918 |
|
||
| `172.16.0.0/12` | Private | RFC 1918 |
|
||
| `127.0.0.0/8` | Loopback | Internal |
|
||
| `169.254.0.0/16` | Link-local | Auto-configuration |
|
||
| `::1/128` | IPv6 loopback | Internal |
|
||
|
||
### IPv4-Mapped IPv6 Normalization
|
||
|
||
DNS-controlled IPv4-mapped IPv6 addresses (e.g. `::ffff:10.0.0.1`) are now normalized to plain IPv4 before the private-IP check. This prevents an attacker from reaching internal hosts through DNS responses that embed IPv4 addresses in IPv6 form. Covered by a 17-case unit test in the transport layer.
|
||
|
||
Override with `--allow-private`:
|
||
|
||
```bash
|
||
goget --allow-private --url https://internal.example.com
|
||
```
|
||
|
||
## DNS Security
|
||
|
||
### Custom Resolvers
|
||
|
||
Bypass system DNS for specific queries:
|
||
|
||
```bash
|
||
goget --dns-servers 1.1.1.1,8.8.8.8 --url https://example.com
|
||
```
|
||
|
||
### IPv6-First
|
||
|
||
The transport dialer prefers IPv6 and only falls back to IPv4 when no IPv6 address is available. This reduces NAT-related attack surface:
|
||
|
||
```bash
|
||
# IPv6 only (abort if no IPv6)
|
||
goget --no-ipv4 --url https://example.com
|
||
```
|
||
|
||
## Credential Safety
|
||
|
||
### Safe Error Logging
|
||
|
||
When a URL contains user credentials (`https://user:pass@example.com`), `core.SafeURL()` strips them from error messages and logs:
|
||
|
||
```go
|
||
// Original: https://admin:secret@api.example.com/data
|
||
// Logged: https://api.example.com/data
|
||
```
|
||
|
||
### OAuth Error Sanitization
|
||
|
||
Non-2xx OAuth responses are sanitized to extract only the structured `error` / `error_description` fields (RFC 6749 §5.2). Providers that echo `client_secret` or other secrets in their error response bodies can no longer leak them into goget logs or error displays.
|
||
|
||
### Config Sanitization
|
||
|
||
`config.Save()` strips sensitive fields before writing to disk:
|
||
- `auth.password`
|
||
- `auth.oauth.client_secret`
|
||
- `auth.oauth.access_token`
|
||
- `auth.oauth.refresh_token`
|
||
|
||
### File Permission Hardening
|
||
|
||
Configuration files (`config.toml`), resume metadata sidecars (`.goget.meta`), and atomic temp files are written with mode `0600` (owner-only), so OAuth tokens, source URLs, and downloaded payloads are never world-readable on multi-user systems.
|
||
|
||
### Environment Variables
|
||
|
||
- `GOGET_CONFIG` — alternative config file path
|
||
- `GOGET_PASSWORD` — password override (no file storage)
|
||
- `GOGET_HSTS` — alternative HSTS cache path
|
||
- `NO_COLOR` — disable color output
|
||
|
||
## Timeouts & Limits
|
||
|
||
| Feature | Default | Description |
|
||
|---|---|---|
|
||
| Connection timeout | 30s (auto) | Smart calculation based on file size |
|
||
| Max total time | — (`--max-time`) | Abort if exceeded |
|
||
| Max file size | — (`--max-filesize`) | Abort if server response exceeds limit |
|
||
| Max retries | 3 | Exponential backoff with 30s cap |
|
||
| Retry delay | 1s initial, 2× multiplier | Configurable via `--retry-delay` |
|