From 9c4b0e26b93fc78f5fe45706f8c43df21d46d292 Mon Sep 17 00:00:00 2001 From: Kayos Date: Sat, 14 Mar 2026 11:27:17 -0700 Subject: [PATCH] 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. --- security/HARDENING_PLAN.md | 272 +++++++++++++++++++++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 security/HARDENING_PLAN.md diff --git a/security/HARDENING_PLAN.md b/security/HARDENING_PLAN.md new file mode 100644 index 0000000..761b1e1 --- /dev/null +++ b/security/HARDENING_PLAN.md @@ -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.**