README.md (608 lines): - Added 11 new feature sections: extract-then-strip EXIF, federation, timestamp anchoring, selective disclosure, evidence packages, cold archives, source drop box, key rotation/recovery, cover mode - Expanded steganography (transport-aware, carrier tracking), attestation (non-image files, investigation namespaces, derivation lineage), fieldkit (forensic scrub, webhook, self-uninstall) - Added Cross-Domain Applications section (human rights, research, elections, supply chain, art, whistleblowing, environment) - Updated CLI reference with chain anchor/disclose/export commands - Updated architecture with all new modules and data directory layout CLAUDE.md (155 lines): - Added metadata.py, evidence.py, archive.py, carrier_tracker.py, anchors.py, exchange.py, dropbox blueprint to architecture tree - Added 7 new design decisions (extract-then-strip, CSRF exemption, client-side hashing, ImageHashes generalization, lazy paths, two-way federation, chain record types) docs/deployment.md (1139 lines): - Added 5 new operational sections: source drop box setup, chain anchoring procedures, cross-org federation, evidence/archive workflows, cover/duress mode - Updated killswitch section with full 10-step destruction sequence - Updated config table with all new fields - Added 5 new troubleshooting entries Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1140 lines
36 KiB
Markdown
1140 lines
36 KiB
Markdown
# SooSeF Deployment Guide
|
|
|
|
Deploying SooSeF on a Raspberry Pi for airgapped LAN use.
|
|
|
|
This guide is for field deployers: IT staff at NGOs, technically competent journalists,
|
|
and anyone setting up a shared SooSeF instance on a local network with no internet access.
|
|
|
|
---
|
|
|
|
## 1. Hardware Requirements
|
|
|
|
**Minimum:**
|
|
|
|
- Raspberry Pi 4 Model B, 2 GB RAM (4 GB recommended)
|
|
- 32 GB microSD card (Class 10 / A2 recommended)
|
|
- USB-C power supply (5V 3A, official RPi PSU recommended)
|
|
- Ethernet cable or pre-configured Wi-Fi for LAN
|
|
|
|
**Optional:**
|
|
|
|
- USB GPS module (e.g., VK-162) for geofencing
|
|
- Momentary push button + 10k pull-down resistor on GPIO pin 17 for hardware killswitch
|
|
- USB drive for airgap package transfer and key backups
|
|
- Case with passive cooling (no fan noise in sensitive environments)
|
|
|
|
If you plan to use the hardware killswitch button, wire it between GPIO 17 and 3.3V with
|
|
a 10k pull-down to GND. The default requires a 5-second hold to trigger (configurable via
|
|
`gpio_killswitch_hold_seconds` in config).
|
|
|
|
---
|
|
|
|
## 2. OS Setup
|
|
|
|
Install **Raspberry Pi OS Lite (64-bit, Bookworm or later)**. The desktop environment is
|
|
unnecessary and wastes resources.
|
|
|
|
After first boot:
|
|
|
|
```bash
|
|
# Set hostname
|
|
sudo hostnamectl set-hostname soosef-node
|
|
|
|
# Create a dedicated service user
|
|
sudo useradd -m -s /bin/bash soosef
|
|
sudo passwd soosef
|
|
|
|
# Enable SSH (if not already)
|
|
sudo systemctl enable --now ssh
|
|
|
|
# Update system packages (do this before going airgapped)
|
|
sudo apt update && sudo apt upgrade -y
|
|
```
|
|
|
|
Set a strong password. If possible, use SSH key authentication and disable password login
|
|
in `/etc/ssh/sshd_config` (`PasswordAuthentication no`).
|
|
|
|
---
|
|
|
|
## 3. Security Hardening
|
|
|
|
### 3.1 Disable or encrypt swap
|
|
|
|
Python does not zero memory when objects are garbage-collected. Cryptographic keys,
|
|
passwords, and plaintext can persist in swap long after the process exits. On an SD card,
|
|
this data may survive even after the swap file is deleted (wear leveling).
|
|
|
|
**Option A: Disable swap entirely (recommended if you have 4 GB+ RAM)**
|
|
|
|
```bash
|
|
sudo dphys-swapfile swapoff
|
|
sudo systemctl disable dphys-swapfile
|
|
sudo rm /var/swap
|
|
```
|
|
|
|
**Option B: Encrypted swap (if you need swap on 2 GB models)**
|
|
|
|
```bash
|
|
sudo dphys-swapfile swapoff
|
|
sudo systemctl disable dphys-swapfile
|
|
|
|
# Use dm-crypt with a random key (regenerated each boot)
|
|
echo "swap /dev/mmcblk0p3 /dev/urandom swap,cipher=aes-xts-plain64,size=256" | sudo tee -a /etc/crypttab
|
|
echo "/dev/mapper/swap none swap sw 0 0" | sudo tee -a /etc/fstab
|
|
```
|
|
|
|
Adjust the device path to match your partition layout.
|
|
|
|
### 3.2 Disable core dumps
|
|
|
|
A core dump from the SooSeF process would contain key material in plaintext.
|
|
|
|
```bash
|
|
echo "* hard core 0" | sudo tee -a /etc/security/limits.conf
|
|
echo "kernel.core_pattern=/dev/null" | sudo tee -a /etc/sysctl.d/99-soosef.conf
|
|
sudo sysctl -p /etc/sysctl.d/99-soosef.conf
|
|
```
|
|
|
|
### 3.3 Firewall
|
|
|
|
```bash
|
|
sudo apt install -y ufw
|
|
sudo ufw default deny incoming
|
|
sudo ufw default allow outgoing
|
|
sudo ufw allow ssh
|
|
sudo ufw allow 5000/tcp # SooSeF web UI
|
|
sudo ufw enable
|
|
```
|
|
|
|
If running fully airgapped, also deny outgoing:
|
|
|
|
```bash
|
|
sudo ufw default deny outgoing
|
|
```
|
|
|
|
### 3.4 Disable unnecessary services
|
|
|
|
```bash
|
|
sudo systemctl disable bluetooth
|
|
sudo systemctl disable avahi-daemon
|
|
sudo systemctl disable triggerhappy
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Installation
|
|
|
|
### 4.1 System dependencies
|
|
|
|
```bash
|
|
sudo apt install -y \
|
|
python3 python3-pip python3-venv python3-dev \
|
|
libjpeg-dev libjpeg62-turbo-dev zlib1g-dev \
|
|
libffi-dev libssl-dev \
|
|
shred coreutils
|
|
```
|
|
|
|
`libjpeg-dev` is required for Pillow and jpeglib (DCT steganography). `libffi-dev` is
|
|
required for argon2-cffi.
|
|
|
|
### 4.2 Create virtual environment
|
|
|
|
```bash
|
|
sudo -u soosef -i # Switch to the soosef user
|
|
|
|
python3 -m venv ~/soosef-env
|
|
source ~/soosef-env/bin/activate
|
|
pip install --upgrade pip wheel
|
|
```
|
|
|
|
### 4.3 Install SooSeF
|
|
|
|
**If the Pi has internet access (pre-deployment):**
|
|
|
|
```bash
|
|
pip install "soosef[web,cli,fieldkit]"
|
|
```
|
|
|
|
For hardware killswitch support via GPIO:
|
|
|
|
```bash
|
|
pip install "soosef[rpi]"
|
|
```
|
|
|
|
The `rpi` extra includes `web`, `cli`, `fieldkit`, and `gpiozero`.
|
|
|
|
**For airgapped install (no internet on the Pi):**
|
|
|
|
On an internet-connected machine with the same architecture (aarch64 for RPi 4):
|
|
|
|
```bash
|
|
mkdir soosef-wheels
|
|
pip download "soosef[rpi]" -d soosef-wheels/
|
|
```
|
|
|
|
Copy the `soosef-wheels/` directory to a USB drive, then on the Pi:
|
|
|
|
```bash
|
|
pip install --no-index --find-links /media/usb/soosef-wheels/ "soosef[rpi]"
|
|
```
|
|
|
|
### 4.4 Verify installation
|
|
|
|
```bash
|
|
soosef --version
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Initial Setup
|
|
|
|
### 5.1 Initialize SooSeF
|
|
|
|
```bash
|
|
soosef init
|
|
```
|
|
|
|
This creates the `~/.soosef/` directory structure:
|
|
|
|
```
|
|
~/.soosef/
|
|
config.json Unified configuration
|
|
identity/ Ed25519 signing keypair (verisoo)
|
|
private.pem
|
|
public.pem
|
|
identity.meta.json
|
|
stegasoo/ Stegasoo state
|
|
channel.key AES-256-GCM channel key
|
|
attestations/ Verisoo attestation log and index
|
|
chain/ Hash chain data
|
|
anchors/ External timestamp anchors
|
|
auth/ Web UI user database (SQLite)
|
|
certs/ Self-signed TLS certificates
|
|
fieldkit/ Killswitch, deadman, tamper, USB, geofence state
|
|
temp/ Ephemeral upload/processing files
|
|
dropbox/ Source drop box submissions
|
|
instance/ Flask session data
|
|
audit.jsonl Append-only audit trail
|
|
```
|
|
|
|
The `identity/` and `auth/` directories are created with mode 0700.
|
|
|
|
`soosef init` generates:
|
|
|
|
- An Ed25519 identity keypair (for signing attestations)
|
|
- A channel key (for steganographic encoding)
|
|
- A default `config.json`
|
|
|
|
### 5.2 First user setup
|
|
|
|
Start the server and create the first admin user through the web UI:
|
|
|
|
```bash
|
|
soosef serve --host 0.0.0.0 --no-https
|
|
```
|
|
|
|
Navigate to `http://<pi-ip>:5000` from a device on the same LAN. The web UI will prompt
|
|
you to create the first user account.
|
|
|
|
After creating the admin account, stop the server (Ctrl+C) and restart with HTTPS
|
|
(see Section 6).
|
|
|
|
---
|
|
|
|
## 6. Running
|
|
|
|
### 6.1 Basic usage
|
|
|
|
```bash
|
|
# LAN-only, no HTTPS (acceptable if the network is physically isolated)
|
|
soosef serve --host 0.0.0.0 --no-https
|
|
|
|
# With self-signed HTTPS (recommended)
|
|
soosef serve --host 0.0.0.0
|
|
|
|
# Custom port
|
|
soosef serve --host 0.0.0.0 --port 8443
|
|
```
|
|
|
|
On first HTTPS start, SooSeF auto-generates a self-signed certificate at
|
|
`~/.soosef/certs/cert.pem`. Browsers will show a certificate warning -- this is expected
|
|
for self-signed certs. Instruct users to accept the warning or distribute the cert file
|
|
to client devices.
|
|
|
|
SooSeF uses Waitress (pure Python, no C dependencies) as its production server with 4
|
|
worker threads by default. Adjust with `--workers`.
|
|
|
|
### 6.2 systemd service
|
|
|
|
Create `/etc/systemd/system/soosef.service`:
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=SooSeF Security Fieldkit
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=soosef
|
|
Group=soosef
|
|
WorkingDirectory=/home/soosef
|
|
Environment="PATH=/home/soosef/soosef-env/bin:/usr/bin"
|
|
ExecStart=/home/soosef/soosef-env/bin/soosef serve --host 0.0.0.0 --workers 4
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
|
|
# Hardening
|
|
NoNewPrivileges=yes
|
|
ProtectSystem=strict
|
|
ProtectHome=read-only
|
|
ReadWritePaths=/home/soosef/.soosef
|
|
PrivateTmp=yes
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
Enable and start:
|
|
|
|
```bash
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable --now soosef
|
|
sudo journalctl -u soosef -f # Watch logs
|
|
```
|
|
|
|
Add `--no-https` to `ExecStart` if running on a physically isolated LAN where TLS is
|
|
unnecessary.
|
|
|
|
---
|
|
|
|
## 7. Configuration
|
|
|
|
Configuration lives at `~/.soosef/config.json`. Edit it directly or use the web admin
|
|
panel. All fields have sensible defaults -- you only need to set what you want to change.
|
|
|
|
| Field | Default | Description |
|
|
|---|---|---|
|
|
| `host` | `127.0.0.1` | Bind address. Set to `0.0.0.0` for LAN access. |
|
|
| `port` | `5000` | TCP port for the web UI. |
|
|
| `https_enabled` | `true` | Enable self-signed HTTPS. |
|
|
| `auth_enabled` | `true` | Require login. Do not disable this. |
|
|
| `max_upload_mb` | `50` | Maximum file upload size in MB. |
|
|
| `session_timeout_minutes` | `15` | Idle session expiry. Lower is safer. |
|
|
| `login_lockout_attempts` | `5` | Failed logins before lockout. |
|
|
| `login_lockout_minutes` | `15` | Lockout duration after exceeding failed login attempts. |
|
|
| `default_embed_mode` | `auto` | Default steganographic embedding mode for Stegasoo. |
|
|
| `killswitch_enabled` | `false` | Enable software killswitch. |
|
|
| `deadman_enabled` | `false` | Enable dead man's switch. |
|
|
| `deadman_interval_hours` | `24` | Hours between required check-ins. |
|
|
| `deadman_grace_hours` | `2` | Grace period after missed check-in before purge. |
|
|
| `deadman_warning_webhook` | `""` | URL to POST a JSON warning when check-in is overdue. Must be a public URL (SSRF protection blocks private IPs). |
|
|
| `usb_monitoring_enabled` | `false` | Monitor for unauthorized USB devices. |
|
|
| `tamper_monitoring_enabled` | `false` | File integrity monitoring. |
|
|
| `chain_enabled` | `true` | Wrap attestations in a hash chain. |
|
|
| `chain_auto_wrap` | `true` | Automatically chain verisoo attestations. |
|
|
| `backup_reminder_days` | `7` | Warn if no backup in this many days. `soosef status` reports overdue backups. |
|
|
| `cover_name` | `""` | If set, used as the CN in the self-signed SSL certificate instead of "localhost". See Section 14 (Cover/Duress Mode). |
|
|
| `gpio_killswitch_pin` | `17` | GPIO pin for hardware killswitch button. |
|
|
| `gpio_killswitch_hold_seconds` | `5.0` | Required hold time to trigger hardware killswitch. |
|
|
|
|
Example minimal config for a field deployment:
|
|
|
|
```json
|
|
{
|
|
"host": "0.0.0.0",
|
|
"port": 5000,
|
|
"https_enabled": true,
|
|
"session_timeout_minutes": 10,
|
|
"deadman_enabled": true,
|
|
"deadman_interval_hours": 12,
|
|
"deadman_grace_hours": 1,
|
|
"deadman_warning_webhook": "https://hooks.example.com/alert",
|
|
"killswitch_enabled": true,
|
|
"backup_reminder_days": 3,
|
|
"cover_name": "Local Inventory Manager"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Fieldkit Setup
|
|
|
|
### 8.1 Dead man's switch
|
|
|
|
The dead man's switch requires periodic check-ins. If you miss a check-in, SooSeF sends
|
|
a warning during the grace period. If the grace period expires without a check-in, the
|
|
killswitch fires automatically and destroys all key material and data.
|
|
|
|
Arm it:
|
|
|
|
```bash
|
|
soosef fieldkit deadman arm --interval 12 --grace 1
|
|
```
|
|
|
|
This requires a check-in every 12 hours, with a 1-hour grace period.
|
|
|
|
Check in:
|
|
|
|
```bash
|
|
soosef fieldkit checkin
|
|
```
|
|
|
|
You can also check in through the web UI at `/fieldkit`.
|
|
|
|
Check status:
|
|
|
|
```bash
|
|
soosef status
|
|
```
|
|
|
|
The dead man's switch enforcement loop runs as a background thread inside `soosef serve`,
|
|
checking every 60 seconds. It will send a webhook warning (if configured via
|
|
`deadman_warning_webhook`) during the grace period, then execute a full purge if the grace
|
|
period expires. The webhook must be a public URL -- SSRF protection blocks private/internal
|
|
IP ranges.
|
|
|
|
For cron-based enforcement outside the web server (e.g., on a headless node), use:
|
|
|
|
```bash
|
|
soosef fieldkit check-deadman
|
|
```
|
|
|
|
Exit codes: 0 = not armed or not overdue, 1 = unexpected error, 2 = killswitch fired.
|
|
|
|
Disarm:
|
|
|
|
```bash
|
|
soosef fieldkit deadman disarm
|
|
```
|
|
|
|
### 8.2 Geofence
|
|
|
|
If you have a USB GPS module, you can set a geographic boundary. SooSeF will trigger the
|
|
killswitch if the device moves outside the fence.
|
|
|
|
```bash
|
|
soosef fieldkit geofence set --lat 50.4501 --lon 30.5234 --radius 5000
|
|
```
|
|
|
|
Coordinates are in decimal degrees, radius in meters.
|
|
|
|
### 8.3 USB whitelist
|
|
|
|
Record currently connected USB devices as the trusted baseline:
|
|
|
|
```bash
|
|
soosef fieldkit usb snapshot
|
|
```
|
|
|
|
When monitoring is enabled, SooSeF will alert (or trigger killswitch, depending on config)
|
|
if an unknown USB device is connected.
|
|
|
|
### 8.4 Tamper baseline
|
|
|
|
Record file integrity baselines for critical files:
|
|
|
|
```bash
|
|
soosef fieldkit tamper baseline
|
|
```
|
|
|
|
SooSeF monitors for unexpected changes to tracked files when tamper monitoring is enabled.
|
|
|
|
### 8.5 Killswitch
|
|
|
|
The killswitch destroys all key material and data in a deliberate order designed to
|
|
maximize what is gone before any interruption. The destruction sequence is:
|
|
|
|
1. **Ed25519 identity keys** (most critical -- without these, signed attestations cannot
|
|
be forged and encrypted data is unrecoverable)
|
|
2. **AES-256-GCM channel key**
|
|
3. **Flask session secret**
|
|
4. **Auth database** (user accounts)
|
|
5. **Attestation log and chain data**
|
|
6. **Temp files and audit log**
|
|
7. **Configuration**
|
|
8. **System journal entries** for the soosef unit
|
|
9. **Deep forensic scrub** (see below)
|
|
10. **Self-uninstall** of the soosef pip package
|
|
|
|
Trigger manually:
|
|
|
|
```bash
|
|
soosef fieldkit purge --confirm CONFIRM-PURGE
|
|
```
|
|
|
|
**Deep forensic scrub.** When the killswitch fires with `ALL` scope, it performs a deep
|
|
forensic scrub that removes traces of SooSeF beyond the `~/.soosef/` directory:
|
|
|
|
- **Python bytecache**: removes all `__pycache__` directories and `.pyc` files for
|
|
soosef, stegasoo, and verisoo from site-packages
|
|
- **pip dist-info**: removes package metadata directories that would reveal what was
|
|
installed
|
|
- **pip download cache**: removes cached wheels and source distributions under
|
|
`~/.cache/pip/` matching soosef/stegasoo/verisoo
|
|
- **Shell history**: rewrites `~/.bash_history`, `~/.zsh_history`, and fish history to
|
|
remove all lines containing "soosef"
|
|
- **Self-uninstall**: runs `pip uninstall -y soosef` to remove the package from the
|
|
virtual environment
|
|
|
|
After a full purge, the system will show minimal evidence that SooSeF was ever installed.
|
|
Note that this is best-effort -- filesystem journal entries, inode metadata, and flash
|
|
wear-leveling remnants may still exist. For complete deniability, use full-disk encryption
|
|
(LUKS) and physically destroy the storage media.
|
|
|
|
---
|
|
|
|
## 9. Key Management
|
|
|
|
SooSeF manages two separate key domains:
|
|
|
|
- **Ed25519 identity key** (`~/.soosef/identity/`) -- used for signing attestations.
|
|
This is your provenance identity.
|
|
- **AES-256-GCM channel key** (`~/.soosef/stegasoo/channel.key`) -- used for
|
|
steganographic encoding/decoding. Shared with anyone who needs to read your
|
|
stego messages.
|
|
|
|
These are separate security concerns and are never merged.
|
|
|
|
### 9.1 Backup
|
|
|
|
Back up keys regularly. SooSeF warns if no backup has been taken within the
|
|
`backup_reminder_days` window (default: 7 days).
|
|
|
|
```bash
|
|
soosef keys export /media/usb/soosef-backup.enc
|
|
```
|
|
|
|
This creates an encrypted bundle. You will be prompted for a passphrase. Store the USB
|
|
drive physically separate from the Pi.
|
|
|
|
### 9.2 Restore
|
|
|
|
```bash
|
|
soosef keys import /media/usb/soosef-backup.enc
|
|
```
|
|
|
|
### 9.3 Key rotation
|
|
|
|
Rotate the identity keypair (old key is archived, not destroyed):
|
|
|
|
```bash
|
|
soosef keys rotate-identity
|
|
```
|
|
|
|
Rotate the channel key:
|
|
|
|
```bash
|
|
soosef keys rotate-channel
|
|
```
|
|
|
|
After rotating keys, take a fresh backup immediately. Notify all collaborators of the
|
|
new identity fingerprint so they can update their trusted-key lists.
|
|
|
|
### 9.4 Trusting collaborator keys
|
|
|
|
Import a collaborator's public key so you can verify their attestations:
|
|
|
|
```bash
|
|
soosef keys trust --import /media/usb/collaborator-pubkey.pem
|
|
```
|
|
|
|
Verify the fingerprint out-of-band (in person, over a secure channel) before trusting.
|
|
|
|
---
|
|
|
|
## 10. Source Drop Box
|
|
|
|
The source drop box provides a SecureDrop-like anonymous file intake that runs inside
|
|
SooSeF. Sources do not need a SooSeF account -- they receive a one-time upload URL and
|
|
submit files through their browser.
|
|
|
|
### 10.1 Creating tokens
|
|
|
|
An admin creates a time-limited upload token through the web UI at `/dropbox/admin` or
|
|
through the admin panel. Each token has:
|
|
|
|
- **Label**: a human-readable name for the source (stored server-side only)
|
|
- **Expiry**: how many hours the link is valid (default: 24)
|
|
- **Max files**: maximum number of uploads allowed on this link (default: 10)
|
|
|
|
Tokens are 32-byte cryptographically random URL-safe strings. Once created, the admin
|
|
receives a URL of the form `https://<host>:<port>/dropbox/upload/<token>`.
|
|
|
|
### 10.2 Sharing URLs securely
|
|
|
|
Share the upload URL with the source over an already-secure channel:
|
|
|
|
- In-person on paper (best for high-risk sources)
|
|
- Encrypted messaging (Signal, Wire)
|
|
- Verbal dictation over a secure voice call
|
|
|
|
Never send drop box URLs over unencrypted email or SMS.
|
|
|
|
### 10.3 What happens on upload
|
|
|
|
When a source uploads files:
|
|
|
|
1. The browser computes SHA-256 fingerprints client-side (via SubtleCrypto) before upload
|
|
so the source has a verifiable record of what they submitted
|
|
2. EXIF metadata is extracted for evidentiary fields (GPS, timestamp) and then stripped
|
|
from the stored copy to protect source device information
|
|
3. The original bytes are attested (signed) before stripping, so the attestation hash
|
|
matches what the source actually submitted
|
|
4. The source receives a receipt code (HMAC-derived from the file hash and token) for
|
|
each file
|
|
5. The token usage counter increments; once max files is reached, the link stops accepting
|
|
|
|
### 10.4 Receipt verification
|
|
|
|
Sources can verify their submission was received by posting their receipt code to
|
|
`/dropbox/verify-receipt`. This returns the filename, SHA-256, and reception timestamp if
|
|
the receipt is valid.
|
|
|
|
### 10.5 Operational security for the drop box
|
|
|
|
- **No SooSeF branding**: the upload page is a minimal HTML form with no identifying
|
|
marks, styled generically
|
|
- **No authentication required**: the source never creates an account or reveals
|
|
identity information
|
|
- **Token self-destruction**: tokens are deleted from the SQLite database after expiry;
|
|
expired tokens are cleaned up on every admin page load
|
|
- **Revocation**: admins can revoke tokens immediately from `/dropbox/admin`
|
|
- **Tor compatibility**: the upload page is a self-contained HTML page with inline
|
|
JavaScript (SubtleCrypto only) and no external resources. It works over Tor Browser
|
|
with JavaScript enabled. No CDN, no fonts, no analytics
|
|
- **No IP logging**: SooSeF does not log source IP addresses. Ensure your reverse proxy
|
|
(if any) also does not log access. If running behind Tor, the source's real IP is never
|
|
visible to the server
|
|
- **Receipt codes are deterministic**: the receipt is an HMAC of the file's SHA-256 keyed
|
|
by the token, so the source can independently verify it corresponds to their file
|
|
|
|
If operating in a high-risk environment, consider running SooSeF as a Tor hidden service
|
|
(`.onion` address). Configure a torrc hidden service pointing to `127.0.0.1:5000` and
|
|
share the `.onion` URL instead of a LAN address.
|
|
|
|
### 10.6 Drop box file storage
|
|
|
|
Uploaded files are stored in `~/.soosef/temp/dropbox/` with filenames derived from the
|
|
SHA-256 prefix. This directory has mode 0700. Token metadata and receipts are stored in a
|
|
SQLite database at `~/.soosef/auth/dropbox.db`.
|
|
|
|
---
|
|
|
|
## 11. Chain Anchoring
|
|
|
|
Chain anchoring externally proves that your attestation chain existed before a given time.
|
|
A single anchor for the chain head implicitly timestamps every record that preceded it,
|
|
because the chain is append-only with hash linkage.
|
|
|
|
### 11.1 When to anchor
|
|
|
|
Anchor your chain:
|
|
|
|
- Before sharing evidence with a third party (proves the chain existed before disclosure)
|
|
- At regular intervals (daily or weekly) to establish a timeline
|
|
- Before and after major investigations or events
|
|
- Before key rotation (locks the existing chain state)
|
|
|
|
### 11.2 Automated anchoring (RFC 3161 TSA)
|
|
|
|
If the device has internet access (even temporarily), submit the chain head to a
|
|
Timestamping Authority:
|
|
|
|
```bash
|
|
soosef chain anchor --tsa https://freetsa.org/tsr
|
|
```
|
|
|
|
This sends the chain head digest to the TSA, receives a signed timestamp token, and saves
|
|
both the anchor and the TSA response as a JSON file under `~/.soosef/chain/anchors/`.
|
|
|
|
The TSA token is a cryptographically signed proof from a third party that the hash existed
|
|
at the timestamp. This is legally stronger than a self-asserted timestamp.
|
|
|
|
### 11.3 Manual anchoring
|
|
|
|
Without `--tsa`, the command exports the anchor hash for manual external submission:
|
|
|
|
```bash
|
|
soosef chain anchor
|
|
```
|
|
|
|
This prints a compact text block containing the chain ID, head index, record count, and
|
|
digest hash. Publish this text to any external witness:
|
|
|
|
- Tweet or public social media post (timestamped by the platform)
|
|
- Email to a trusted third party (timestamped by the mail server)
|
|
- Newspaper classified advertisement
|
|
- Blockchain transaction (e.g., Bitcoin OP_RETURN)
|
|
- Notarized document
|
|
|
|
The anchor file is saved locally regardless of whether a TSA was used.
|
|
|
|
### 11.4 Airgapped anchoring procedure
|
|
|
|
For fully airgapped deployments:
|
|
|
|
1. Run `soosef chain anchor` on the airgapped device
|
|
2. Copy the printed anchor text to a USB drive (text file, photograph of screen, or
|
|
paper transcription)
|
|
3. On an internet-connected device, publish the anchor text to one or more external
|
|
witnesses
|
|
4. Document the publication (URL, screenshot, transaction ID) and store it alongside
|
|
the USB key backup
|
|
|
|
### 11.5 Verifying anchors
|
|
|
|
To verify that the current chain state matches a previously created anchor:
|
|
|
|
```bash
|
|
soosef chain verify
|
|
```
|
|
|
|
This checks all hash linkage and signatures in the chain. If the chain has been tampered
|
|
with since the anchor was created, verification will fail.
|
|
|
|
---
|
|
|
|
## 12. Cross-Organization Federation
|
|
|
|
Federation allows multiple SooSeF instances to exchange attestation records for
|
|
collaborative investigations. Bundles are self-authenticating: each record carries the
|
|
signer's public key, so the importer can verify signatures against their trust store.
|
|
|
|
### 12.1 Exchanging trust keys
|
|
|
|
Before two organizations can exchange attestation bundles, they must trust each other's
|
|
identity keys.
|
|
|
|
On Organization A:
|
|
|
|
```bash
|
|
# Export public key
|
|
cp ~/.soosef/identity/public.pem /media/usb/org-a-pubkey.pem
|
|
```
|
|
|
|
On Organization B:
|
|
|
|
```bash
|
|
# Import Org A's key and verify fingerprint
|
|
soosef keys trust --import /media/usb/org-a-pubkey.pem
|
|
```
|
|
|
|
Always verify fingerprints out-of-band (in person, over a known-secure voice channel).
|
|
Repeat in both directions so each organization trusts the other.
|
|
|
|
### 12.2 Exporting attestation bundles
|
|
|
|
Export a JSON bundle containing attestation records and chain data:
|
|
|
|
```bash
|
|
soosef chain export --output /media/usb/investigation-bundle.zip
|
|
```
|
|
|
|
To export only records from a specific index range:
|
|
|
|
```bash
|
|
soosef chain export --start 100 --end 200 --output /media/usb/partial-bundle.zip
|
|
```
|
|
|
|
The export includes:
|
|
|
|
- Attestation records with full signatures
|
|
- Chain records with hash linkage
|
|
- The signer's public key
|
|
- A standalone verification script (requires only Python + `cryptography`)
|
|
- A human-readable README
|
|
|
|
### 12.3 Importing attestation bundles
|
|
|
|
On the receiving organization's SooSeF instance:
|
|
|
|
- Records are imported into the local attestation log with a `federated_from` metadata tag
|
|
- Records signed by untrusted fingerprints are rejected (unless trust-on-first-use is used)
|
|
- Duplicate records (matching SHA-256) are skipped automatically
|
|
|
|
### 12.4 Delivery acknowledgments
|
|
|
|
When a bundle is imported and the receiving instance has a chain store and private key,
|
|
SooSeF automatically creates a delivery acknowledgment record in the local chain. This
|
|
records the bundle hash, sender fingerprint, and count of records received. The
|
|
acknowledgment provides a cryptographic receipt that the bundle was delivered and ingested.
|
|
|
|
### 12.5 Selective disclosure for legal proceedings
|
|
|
|
To produce evidence for a court order or legal discovery request without revealing the
|
|
entire chain:
|
|
|
|
```bash
|
|
soosef chain disclose --indices 42,43,44 --output disclosure.json
|
|
```
|
|
|
|
This exports a proof bundle where the selected records are shown in full and all other
|
|
records appear only as hashes. A third party can verify that the selected records are part
|
|
of an unbroken hash chain without seeing the contents of other records.
|
|
|
|
### 12.6 Federation on airgapped networks
|
|
|
|
All federation is designed for offline/sneakernet operation:
|
|
|
|
1. Export the bundle to a USB drive on the sending instance
|
|
2. Physically carry the USB to the receiving instance
|
|
3. Import the bundle
|
|
4. Export the delivery acknowledgment back on USB if needed
|
|
|
|
No network connectivity is required at any point.
|
|
|
|
---
|
|
|
|
## 13. Evidence Packages and Cold Archives
|
|
|
|
SooSeF provides two export formats for preserving evidence outside the running instance.
|
|
|
|
### 13.1 Evidence packages
|
|
|
|
An evidence package is a self-contained ZIP that bundles everything needed for independent
|
|
verification of specific attested images. Use evidence packages when you need to hand off
|
|
a subset of evidence to a third party (lawyer, court, partner organization).
|
|
|
|
Contents of an evidence package:
|
|
|
|
- `images/` -- original image files
|
|
- `manifest.json` -- attestation records and chain data for those images
|
|
- `public_key.pem` -- signer's Ed25519 public key
|
|
- `verify.py` -- standalone verification script (requires only Python 3.11+ and
|
|
`cryptography`)
|
|
- `README.txt` -- human-readable instructions
|
|
|
|
The package is self-contained. No SooSeF installation is required to verify the evidence.
|
|
The standalone `verify.py` script checks image SHA-256 hashes against attestation records
|
|
and verifies chain hash linkage.
|
|
|
|
**When to create evidence packages:**
|
|
|
|
- Before handing evidence to a legal team
|
|
- When sharing with a partner organization that does not run SooSeF
|
|
- For court submission (the self-contained verifier is the key feature)
|
|
- Before any action that might destroy the running instance (travel through hostile
|
|
checkpoints, anticipated raids)
|
|
|
|
### 13.2 Cold archives
|
|
|
|
A cold archive is a full snapshot of the entire SooSeF evidence store, designed for
|
|
long-term preservation. It follows OAIS (ISO 14721) alignment: the archive is
|
|
self-describing, includes its own verification code, and documents the cryptographic
|
|
algorithms used.
|
|
|
|
Contents of a cold archive:
|
|
|
|
- `chain/` -- raw append-only hash chain binary, state checkpoint, and anchor files
|
|
- `attestations/` -- full verisoo attestation log and LMDB index
|
|
- `keys/public.pem` -- signer's public key
|
|
- `keys/bundle.enc` -- encrypted key bundle (optional, password-protected)
|
|
- `keys/trusted/` -- trusted collaborator public keys
|
|
- `manifest.json` -- archive metadata and integrity hashes
|
|
- `verify.py` -- standalone verification script
|
|
- `ALGORITHMS.txt` -- documents all cryptographic algorithms and formats used (Ed25519,
|
|
SHA-256, AES-256-GCM, Argon2id, CBOR, etc.) so the archive remains verifiable even if
|
|
SooSeF no longer exists
|
|
- `README.txt` -- human-readable description
|
|
|
|
To restore a cold archive on a fresh SooSeF instance:
|
|
|
|
```bash
|
|
soosef archive import <archive.zip>
|
|
```
|
|
|
|
**When to create cold archives:**
|
|
|
|
- At regular intervals (weekly or monthly) as part of your backup strategy
|
|
- Before key rotation
|
|
- Before traveling with the device
|
|
- Before anticipated risk events
|
|
- When archiving a completed investigation
|
|
|
|
### 13.3 Legal discovery workflow
|
|
|
|
For legal discovery and court proceedings:
|
|
|
|
1. Use `soosef chain disclose` for selective disclosure (Section 12.5) when you must
|
|
respond to a specific request without revealing the full chain
|
|
2. Use evidence packages for handing specific images and their attestations to counsel
|
|
3. Use cold archives when full preservation is required
|
|
|
|
All three formats include standalone verification scripts so that the receiving party does
|
|
not need to install SooSeF.
|
|
|
|
### 13.4 Long-term archival best practices
|
|
|
|
- Store cold archives on at least two separate physical media (USB drives, optical discs)
|
|
- Keep one copy offsite (safe deposit box, trusted third party)
|
|
- Include the encrypted key bundle in the archive (set a strong passphrase and store it
|
|
separately from the archive media)
|
|
- Write the passphrase on paper and store it in a different physical location from the
|
|
archive media
|
|
- Periodically verify archive integrity: unzip and run `python verify.py`
|
|
- The `ALGORITHMS.txt` file documents everything needed to write a verifier from scratch,
|
|
even decades from now
|
|
|
|
---
|
|
|
|
## 14. Cover/Duress Mode
|
|
|
|
Cover mode disguises a SooSeF installation so that casual inspection of the device does
|
|
not immediately reveal it as a security toolkit.
|
|
|
|
### 14.1 Renaming the data directory
|
|
|
|
By default, SooSeF stores everything under `~/.soosef/`. To use an inconspicuous name,
|
|
set the `SOOSEF_DATA_DIR` environment variable:
|
|
|
|
```bash
|
|
export SOOSEF_DATA_DIR=~/.local/share/inventory
|
|
soosef init
|
|
```
|
|
|
|
All SooSeF commands respect this variable. Add it to the soosef user's shell profile or
|
|
the systemd service file:
|
|
|
|
```ini
|
|
Environment="SOOSEF_DATA_DIR=/home/soosef/.local/share/inventory"
|
|
```
|
|
|
|
You can also pass `--data-dir` to any command:
|
|
|
|
```bash
|
|
soosef --data-dir ~/.local/share/inventory serve --host 0.0.0.0
|
|
```
|
|
|
|
### 14.2 Cover name for SSL certificates
|
|
|
|
Set `cover_name` in config to change the Common Name (CN) in the self-signed SSL
|
|
certificate. Without this, the certificate CN defaults to "localhost". With a cover name,
|
|
a browser inspector sees a plausible-looking certificate:
|
|
|
|
```json
|
|
{
|
|
"cover_name": "Local Inventory Manager"
|
|
}
|
|
```
|
|
|
|
Delete `~/.soosef/certs/cert.pem` and restart the server to regenerate the certificate
|
|
with the new CN.
|
|
|
|
### 14.3 Portable USB operation
|
|
|
|
SooSeF can run entirely from a USB drive:
|
|
|
|
1. Install SooSeF into a virtualenv on the USB drive
|
|
2. Set `SOOSEF_DATA_DIR` to a directory on the USB
|
|
3. Run `soosef serve` from the USB
|
|
|
|
When you remove the USB, no trace of SooSeF remains on the host machine (assuming no
|
|
swap, no core dumps, and the host filesystem was not used for temp files).
|
|
|
|
Combine with the `cover_name` setting and a renamed data directory for maximum
|
|
deniability.
|
|
|
|
---
|
|
|
|
## 15. Operational Security Notes
|
|
|
|
SooSeF is a tool, not a shield. Understand what it cannot do.
|
|
|
|
### What SooSeF does not protect against
|
|
|
|
- **Physical coercion.** If someone forces you to unlock the device or reveal passwords,
|
|
no software can help. The killswitch is for situations where you can act before
|
|
interception, not during.
|
|
- **Social engineering.** SooSeF cannot prevent users from being tricked into revealing
|
|
credentials or disabling security features.
|
|
- **Leaving the browser open.** The session timeout helps, but if someone walks up to an
|
|
unlocked browser session, they have access. Train users to close the browser or lock the
|
|
screen.
|
|
- **Compromised client devices.** SooSeF secures the server. If a user's laptop has
|
|
malware, their browser session is compromised regardless of what the server does.
|
|
|
|
### Shred limitations on flash storage
|
|
|
|
The killswitch uses `shred` on Linux (3-pass overwrite + zero). On spinning disks, this
|
|
is effective. On SD cards and SSDs, **it is not reliable** because:
|
|
|
|
- Flash translation layers remap physical blocks. Overwritten data may persist on
|
|
remapped blocks.
|
|
- Wear leveling distributes writes across the flash, meaning the original block may be
|
|
preserved.
|
|
|
|
SooSeF's defense against this is **cryptographic erasure**: destroy the keys first, then
|
|
the data. Even if fragments of encrypted data survive on flash, they are useless without
|
|
the keys. The killswitch destroys keys before anything else, and keys are small enough to
|
|
fit in a single flash block.
|
|
|
|
This is why full-disk encryption matters -- see below.
|
|
|
|
### Full-disk encryption (LUKS)
|
|
|
|
For serious deployments, encrypt the entire SD card (or at least the data partition) with
|
|
LUKS. This way, even if the SD card is physically seized:
|
|
|
|
- All data at rest is encrypted
|
|
- Shred limitations are irrelevant because the underlying storage is encrypted
|
|
- Power-off = data inaccessible (assuming the LUKS passphrase is strong)
|
|
|
|
Setting up LUKS on Raspberry Pi OS is beyond the scope of this guide, but the short
|
|
version is: create an encrypted partition, mount it at `/home/soosef/.soosef`, and
|
|
configure auto-unlock via a keyfile on a separate USB (remove the USB after boot in
|
|
hostile environments).
|
|
|
|
### Memory considerations
|
|
|
|
Python does not securely zero memory. Key material, passwords, and plaintext may persist
|
|
in process memory and swap. The mitigations in Section 3 (disable swap, disable core
|
|
dumps) reduce the window, but a memory dump of the running process would expose secrets.
|
|
This is a fundamental limitation of Python-based security tools.
|
|
|
|
---
|
|
|
|
## 16. Troubleshooting
|
|
|
|
### Health check
|
|
|
|
SooSeF exposes a `/health` endpoint on the web UI. Hit it from the LAN to verify the
|
|
server is running:
|
|
|
|
```bash
|
|
curl -k https://<pi-ip>:5000/health
|
|
```
|
|
|
|
The `-k` flag skips certificate verification for self-signed certs.
|
|
|
|
### System status
|
|
|
|
```bash
|
|
soosef status
|
|
```
|
|
|
|
This checks identity key, channel key, trusted keys, dead man's switch state, geofence,
|
|
chain status, and backup status. Use `--json` for machine-readable output.
|
|
|
|
### Common issues
|
|
|
|
**scipy fails to build on Raspberry Pi**
|
|
|
|
scipy requires Fortran and BLAS libraries. On Raspberry Pi OS:
|
|
|
|
```bash
|
|
sudo apt install -y gfortran libopenblas-dev
|
|
```
|
|
|
|
If the build still fails (common on low-memory Pis), increase swap temporarily during
|
|
install, then disable it again:
|
|
|
|
```bash
|
|
sudo dphys-swapfile swapon
|
|
pip install scipy
|
|
sudo dphys-swapfile swapoff
|
|
```
|
|
|
|
Or use pre-built wheels:
|
|
|
|
```bash
|
|
pip install --prefer-binary scipy
|
|
```
|
|
|
|
**Argon2 OOM on low-memory Pi (2 GB)**
|
|
|
|
Argon2 password hashing is memory-intensive by design. If the server crashes during login
|
|
on a 2 GB Pi with other services running, either:
|
|
|
|
- Free memory by disabling unnecessary services
|
|
- Reduce Argon2 memory cost (not recommended -- weakens password hashing)
|
|
|
|
The default Argon2 parameters use ~64 MB per hash operation. With 2 GB total RAM and a
|
|
running server, this is tight but workable if nothing else is competing for memory.
|
|
|
|
**Web UI unreachable from LAN**
|
|
|
|
1. Check that `host` is set to `0.0.0.0` in config, not `127.0.0.1`
|
|
2. Check firewall: `sudo ufw status` -- port 5000 must be allowed
|
|
3. Check the service is running: `sudo systemctl status soosef`
|
|
4. Check the Pi's IP: `ip addr show`
|
|
|
|
**Certificate warnings in browser**
|
|
|
|
Expected with self-signed certificates. Users must click through the warning. To avoid
|
|
this, distribute `~/.soosef/certs/cert.pem` to client devices and install it as a
|
|
trusted certificate.
|
|
|
|
**Dead man's switch fires unexpectedly**
|
|
|
|
The switch checks every 60 seconds. If the Pi loses power or the service crashes and
|
|
does not restart within `interval_hours + grace_hours`, the switch will fire on the next
|
|
start. Make sure the systemd service is set to `Restart=on-failure` and the Pi has
|
|
reliable power.
|
|
|
|
If you need to perform maintenance, disarm the switch first:
|
|
|
|
```bash
|
|
soosef fieldkit deadman disarm
|
|
```
|
|
|
|
Re-arm when maintenance is complete.
|
|
|
|
**Permission errors on ~/.soosef/**
|
|
|
|
The `identity/`, `auth/`, and `certs/` directories are mode 0700. If running under a
|
|
different user than the one who ran `soosef init`, you will get permission denied errors.
|
|
Always run SooSeF as the same user.
|
|
|
|
```bash
|
|
ls -la ~/.soosef/
|
|
```
|
|
|
|
**Drop box tokens expire immediately**
|
|
|
|
Token expiry is checked against UTC. If the Pi's system clock is wrong, tokens may appear
|
|
expired as soon as they are created. Verify the clock:
|
|
|
|
```bash
|
|
date -u
|
|
```
|
|
|
|
On airgapped systems without NTP, set the clock manually before creating tokens:
|
|
|
|
```bash
|
|
sudo date -s "2026-04-01 12:00:00"
|
|
```
|
|
|
|
**Chain anchor TSA submission fails**
|
|
|
|
TSA submission requires network access. On an airgapped device, use manual anchoring
|
|
instead (`soosef chain anchor` without `--tsa`). If the TSA URL is unreachable, the anchor
|
|
is still saved locally -- only the external timestamp token is missing.
|
|
|
|
**SSL certificate shows wrong name**
|
|
|
|
If you set `cover_name` after the certificate was already generated, delete the old
|
|
certificate and restart:
|
|
|
|
```bash
|
|
rm ~/.soosef/certs/cert.pem ~/.soosef/certs/key.pem
|
|
sudo systemctl restart soosef
|
|
```
|
|
|
|
**Account lockout after repeated failed logins**
|
|
|
|
After `login_lockout_attempts` (default: 5) failed login attempts, the account is locked
|
|
for `login_lockout_minutes` (default: 15) minutes. Wait for the lockout to expire, or
|
|
restart the server to clear lockout state.
|
|
|
|
**Evidence package verify.py fails**
|
|
|
|
The standalone verification script requires Python 3.11+ and the `cryptography` package.
|
|
Install it in a fresh virtualenv on the verifying machine:
|
|
|
|
```bash
|
|
python3 -m venv verify-env
|
|
source verify-env/bin/activate
|
|
pip install cryptography
|
|
python verify.py
|
|
```
|