docs: security hardening plan v1.0

Addresses all 4 open CVEs with specific implementations:
- CVE-2: Per-device WiFi password derived from serial/MAC + salt
- CVE-11: SSH key-only auth, ADACAM_PUBKEY injection at liberation
- CVE-14: adacam-api bearer token derived from device serial
- CVE-7/16: Kill usb-updater, no OTA for single-owner device

Also covers: firewall rules, tunnel security assessment,
data-at-rest recommendations, priority order for implementation.
This commit is contained in:
Kayos 2026-03-14 11:27:17 -07:00
parent 86188ef54f
commit 9c4b0e26b9

272
security/HARDENING_PLAN.md Normal file
View file

@ -0,0 +1,272 @@
# AdaCam Security Hardening Plan
**Status:** v1.0 — 2026-03-14
**Based on:** 16 CVEs found in Hivemapper firmware
**12 already addressed by liberate.sh. 4 open items documented below.**
---
## Threat Model
### Tier 1: WiFi Proximity Attacker (PRIMARY THREAT)
Someone in range of the AP — parked next to you, neighbor, parking lot. Can see SSID, attempt to connect, sniff traffic. This is the realistic threat. WiFi password and AP-accessible services are the attack surface.
### Tier 2: Physical Attacker
Someone with hands on the device — theft, break-in, impound lot. Can access USB port, serial console, eMMC. Data at rest and the USB update path are the attack surface.
### Tier 3: Hivemapper Post-Liberation
mender-client killed. globalconfig blocked in /etc/hosts. They can't push updates or reach the device anymore. Attack surface: none if liberation was done correctly.
### Tier 4: Internet Attacker
Device has no inbound ports. Can't reach it directly. Would need to compromise Rackham first to abuse the reverse tunnel. Low likelihood.
**What we're NOT defending against:** Nation-state physical attacks (JTAG/chip-off), phone compromise while on AP, supply chain attacks on factory firmware.
**Bottom line:** WiFi proximity is the real threat. Everything else is handled or requires physical access you can't fully prevent on a dashcam.
---
## CVE Remediation
### CVE-2: WiFi Password — Per-Device Derivation
**Problem:** `adacam2026` is static across all liberated devices. One leak exposes everyone.
**Fix:** Derive per-device password from hardware serial at liberation time.
```bash
# In liberate.sh
SERIAL=$(cat /proc/device-tree/serial-number 2>/dev/null | tr -d '\0')
[ -z "$SERIAL" ] && SERIAL=$(ip link show wlp1s0f0 | grep link/ether | awk '{print $2}' | tr -d ':')
# 12-char derived password
WIFI_PASS=$(echo -n "adacam-${SERIAL}-2026" | sha256sum | cut -c1-12)
# Apply
sed -i "s/^wpa_passphrase=.*/wpa_passphrase=${WIFI_PASS}/" /var/hostapd-2g.conf
# Save for Varroa pairing
echo "$WIFI_PASS" > /data/adacam/wifi_password
chmod 600 /data/adacam/wifi_password
```
**Varroa gets the password:** liberate.sh prints it at the end. User notes it. Formula is public — deterministic from serial printed on device label.
---
### CVE-11: SSH — Key-Based Auth Only
**Problem:** root SSH with no password on an open AP is indefensible.
**Fix:** Disable password auth entirely. Key injection at liberation time.
```bash
# In liberate.sh
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/^#*PermitEmptyPasswords.*/PermitEmptyPasswords no/' /etc/ssh/sshd_config
mkdir -p /root/.ssh && chmod 700 /root/.ssh
# Inject user's key (pass as ADACAM_PUBKEY env var)
if [ -n "$ADACAM_PUBKEY" ]; then
echo "$ADACAM_PUBKEY" >> /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys
fi
# Device generates its own tunnel key (recovery path via Rackham)
ssh-keygen -t ed25519 -f /data/adacam/ssh/tunnel_key -N "" -C "adacam-tunnel"
cat /data/adacam/ssh/tunnel_key.pub >> /root/.ssh/authorized_keys
systemctl restart sshd
```
**Usage:**
```bash
ADACAM_PUBKEY="ssh-ed25519 AAAA... cobb@phone" bash liberate.sh
```
**Recovery:** If personal key is lost, SSH in via Rackham tunnel (which uses the device's own tunnel key).
---
### CVE-14: adacam-api Config Endpoint — Bearer Token Auth
**Problem:** Config endpoint unauthenticated — anyone on AP can reconfigure device.
**Fix:** Bearer token derived from device serial. Same formula on device and Varroa app.
```python
import hashlib
def get_api_token():
serial = open('/data/adacam/device_serial').read().strip()
return hashlib.sha256(f"adacam-api-{serial}-token".encode()).hexdigest()[:32]
def require_auth(f):
from functools import wraps
from flask import request, jsonify
@wraps(f)
def decorated(*args, **kwargs):
token = request.headers.get('Authorization', '').replace('Bearer ', '')
if token != get_api_token():
return jsonify({'error': 'unauthorized'}), 401
return f(*args, **kwargs)
return decorated
```
**Endpoints requiring auth:** `POST /config`, `POST /capture`, `GET /frames`
**No auth needed:** `GET /status`, `GET /deviceinfo`, `GET /pair`
**Varroa pairing flow:**
1. Connect to AdaCam WiFi
2. `GET /pair` → returns `{"serial": "ABC123"}`
3. App derives token: `sha256("adacam-api-ABC123-token")[:32]`
4. App uses token for all subsequent requests — no storage needed, deterministic
---
### CVE-7/16: Firmware Updates — Kill It
**Problem:** Mender has no signing key. usb-updater installs anything without verification.
**Decision: Kill both. No OTA for adacam.**
```bash
# In liberate.sh
systemctl mask usb-updater 2>/dev/null || true
rm -f /usr/bin/usb-updater
# mender-client already masked
# Belt and suspenders — also block update endpoints
echo "0.0.0.0 s3.amazonaws.com" >> /etc/hosts # blocks firmware bucket
```
**Rationale:** Single owner, SSH access available, no need for OTA infrastructure. Updates = SSH in and redeploy services. If this becomes a multi-user product, revisit with proper signing.
**Update procedure:**
```bash
ssh root@10.77.0.1 "cd /opt/adacam && git pull && systemctl restart adacam-api adacam-capture"
```
---
## Firewall Rules
Factory firmware has zero iptables rules. Add to liberate.sh:
```bash
cat > /data/persist/firewall.sh << 'FIREWALL'
#!/bin/bash
iptables -F && iptables -X
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Loopback
iptables -A INPUT -i lo -j ACCEPT
# Established
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# AP interface only — SSH and API
iptables -A INPUT -i wlp1s0f0 -p tcp --dport 22 -j ACCEPT # SSH
iptables -A INPUT -i wlp1s0f0 -p tcp --dport 5000 -j ACCEPT # adacam-api
iptables -A INPUT -i wlp1s0f0 -p udp --dport 67 -j ACCEPT # DHCP
iptables -A INPUT -i wlp1s0f0 -p udp --dport 53 -j ACCEPT # DNS
# Block everything on LTE/WAN
iptables -A INPUT -i wwan0 -j DROP
iptables -A INPUT -i ppp+ -j DROP
# ICMP for diagnostics
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
FIREWALL
chmod +x /data/persist/firewall.sh
/data/persist/firewall.sh # Apply immediately
```
Add `/data/persist/firewall.sh` call to `/data/persist/install.sh` so it runs on every boot.
---
## adacam-api Auth for Varroa
**Decision:** Bearer token derived from device serial. No complex pairing state.
| Option | Decision |
|--------|----------|
| No auth (trust AP) | ❌ WiFi password could leak |
| mTLS | ❌ Android cert management is pain |
| Bearer token from serial | ✅ Simple, stateless, deterministic |
| OAuth/JWT | ❌ Who's the auth server? |
| Pairing flow with state | ❌ Phone reset = broken pairing |
Token is never transmitted — both sides derive it from serial. Attacker needs serial + WiFi password to have any chance. Good enough for a private AP dashcam.
---
## Tunnel Security
**Current:** Device generates ed25519 keypair at liberation. Reverse tunnel to `cobb@142.44.213.229:2222`.
**Assessment:**
- ✅ Per-device key (not shared across devices)
- ✅ Outbound only — device initiates, nothing inbound
- ✅ autossh keeps it alive
- ⚠️ If Rackham is compromised, attacker has tunnel access to device
- ⚠️ Tunnel key is stored on device at `/data/adacam/ssh/tunnel_key` — if device is stolen, key is exposed
**Mitigations:**
- Rackham firewall: restrict port 2222 to localhost only (already the case with reverse tunnels)
- Rotate tunnel key if device is stolen: `ssh root@device "ssh-keygen -f /data/adacam/ssh/tunnel_key -N ''"` and update Rackham authorized_keys
**For multi-user deployments:** Each user runs their own tunnel to their own server. Document this clearly.
---
## Data at Rest
**What's in /data:**
- `/data/recording/` — GPS tracks, IMU data, frame metadata in SQLite
- `/data/adacam/` — config, device identity, API token material
- `/data/persist/` — liberation artifacts
**Privacy considerations:**
- GPS tracks = complete driving history. Sensitive.
- Frames from camera = faces, plates, private property
- No encryption at rest currently
**Recommendation for now:** Don't encrypt. Complexity vs. threat. If device is stolen:
1. GPS tracks in /data — attacker gets your driving history. Accept this risk.
2. Frames in /tmp/adacam/pics — tmpfs, lost on reboot. Not a concern.
3. Config/keys — device is compromised, rotate everything from Rackham.
**Future:** If this becomes a product, encrypt /data with a key derived from device-specific hardware (TPM would be ideal but Keem Bay TEE is available via tee-supplicant).
---
## liberate.sh Changes Required
1. **WiFi password**: Replace static `adacam2026` with per-device derived password from serial/MAC
2. **SSH hardening**: Add sshd_config changes, disable password auth, inject `$ADACAM_PUBKEY`
3. **USB updater**: `systemctl mask usb-updater && rm -f /usr/bin/usb-updater`
4. **Firewall**: Add firewall.sh to /data/persist/ and call it, add to install.sh
5. **Device serial**: Write serial to `/data/adacam/device_serial` for token derivation
6. **Print summary**: End of script prints WiFi password, device serial, SSH connection string
---
## Priority Order
| Priority | Item | Effort | Risk if Skipped |
|----------|------|--------|-----------------|
| 🔴 P0 | SSH key auth (CVE-11) | 30 min | Anyone on AP = root |
| 🔴 P0 | Firewall rules | 30 min | Services exposed unnecessarily |
| 🟡 P1 | Per-device WiFi password (CVE-2) | 1 hr | Shared password across devices |
| 🟡 P1 | adacam-api bearer token (CVE-14) | 2 hrs | Config writable from AP |
| 🟢 P2 | Kill usb-updater (CVE-7/16) | 10 min | Physical USB flash possible |
| 🟢 P2 | Data at rest encryption | weeks | Low threat, high effort |
**Do P0 items before the device leaves the house.**