Push Tracker
ria-ran--sim-drop/README.md
A ashkan@beigi.net b88fe4eed1 Add real SGP.22 SAIP profile generation (optional)
New server/saip_profile.py encodes a DER ProfileElement sequence (ProfileHeader
with ICCID + Milenage AKA parameters carrying Ki/OPc) using asn1tools against
the official PE_Definitions SAIP ASN.1 schema. It locates the schema from an
installed osmocom pySim (or SAIP_ASN_PATH) via find_spec WITHOUT importing
pySim's Python package, so it avoids the pyscard/smpp card-reader stack — the
only runtime dep is asn1tools.

Selectable via profile.format ("v2" default | "saip"); the app falls back to
profile-package-v2 with a warning when SAIP prerequisites are absent. IMSI and
other EFs (USIM-template PEs) are the documented next layer.

Add requirements-saip.txt, pytest.ini (silence asn1tools warnings), README and
config updates. Tests: +6 (encode/round-trip/credentials, app emit + fallback);
92 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 02:35:54 +00:00

196 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ria-ran--sim-drop
**Closed-loop eSIM provisioning for emergency / NTN 5G deployments.**
A self-contained appliance (Raspberry Pi or Android) that connects to a 5G gNB
for backhaul, broadcasts an open WiFi AP, and hands out eSIM credentials to
connecting devices through a captive portal. Users go from "no signal" to
"connected to the ad-hoc 5G network" in under a minute, with no operator in the
loop.
> Deep design rationale lives in [`plan/design.md`](plan/design.md). This README
> is the operational guide. Notable changes are tracked in
> [`CHANGELOG.md`](CHANGELOG.md).
## Architecture
```
┌─────────────────────────────────────────┐
│ Server Host (srsRAN gNB) │
│ - srsRAN gNB │
│ - Open5GS Core (or free5gc) │
│ - Subscriber database │
└─────────────┬───────────────────────────┘
│ 5G NR
┌─────────────────────────────────────────┐
│ UE WiFi AP Drop │
│ (Android device or Raspberry Pi) │
│ - WiFi AP (open network) │
│ - eSIM Provisioning Server (Flask) │
│ - Captive Portal (nginx + dnsmasq) │
└─────────────┬───────────────────────────┘
│ WiFi
End User Devices
```
## User flow
1. User connects to the open WiFi network `Emergency-5G`.
2. The captive portal auto-redirects to the activation page.
3. User taps **Activate eSIM**.
4. The server generates a unique IMSI / Ki / OPc / ICCID.
5. An eSIM profile is generated and offered as a one-time download.
6. User installs the profile via device Settings.
7. The device connects to the 5G network automatically.
## Quick start (local dev)
```bash
git clone https://riahub.ai/qoherent/ria-ran--sim-drop.git
cd ria-ran--sim-drop
python3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
cp config.yaml.example config.yaml # edit MCC/MNC/op_key as needed
python scripts/init_db.py
python -m server.app # → http://localhost:5000
```
Open <http://localhost:5000/activate> and click **Activate eSIM**.
## Configuration
All configuration is in `config.yaml` (start from `config.yaml.example`).
| Field | Description | Example |
|-------|-------------|---------|
| `network.mcc` | Mobile Country Code (3 digits) | `"302"` |
| `network.mnc` | Mobile Network Code (23 digits) | `"720"` |
| `network.op_key` | Operator key, 16-byte hex (32 chars) | `"63bfa50e…"` |
| `wifi.ip` | WiFi AP IP address | `"192.168.4.1"` |
| `server.host` / `server.port` | Flask bind address | `"0.0.0.0"` / `5000` |
| `server.admin_token` | Token guarding `/api/export`; empty = export disabled | `""` |
| `database.path` | SQLite database path | `"/opt/…/subscribers.db"` |
| `rate_limiting.window_hours` | One profile per MAC per window | `1` |
| `profile.apn` | APN / DNN used in the profile and core session | `"internet"` |
| `profile.smdp_address` | SM-DP+ address embedded in the LPA activation code | `"esim.local"` |
| `core.type` | Core adapter: `open5gs` / `free5gc` / `none` | `"open5gs"` |
| `core.mongodb_uri` | MongoDB connection for the core | `"mongodb://localhost:27017"` |
`op_key` is validated at startup (must be 16-byte hex) and the config file is
rejected if it is empty or missing a required section.
## API reference
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/activate` | GET | Captive portal page |
| `/api/provision` | POST | Generate credentials + profile; returns a one-time `profile_url` |
| `/api/profile/<token>` | GET | Download the profile via its unguessable capability token |
| `/api/export` | GET | Export all subscribers (JSON). Requires `X-Admin-Token`; disabled unless `server.admin_token` is set |
| `/api/status` | GET | Health check (subscriber count, uptime, core adapter) |
`POST /api/provision` accepts `{"mac_address": "aa:bb:cc:dd:ee:ff"}`; if omitted,
the server resolves the client MAC from the ARP table. Rate limiting is per MAC.
## Profile format
Two formats are selectable via `profile.format`:
- **`v2`** (default, always available) — a JSON `profile-package-v2` bundle with
the raw credentials plus **real 3GPP field encodings** (`EF_ICCID`, `EF_IMSI`,
nibble-swapped BCD per 3GPP TS 31.102) and an SGP.22 **LPA activation code**
(`LPA:1$<smdp>$<matching-id>`).
- **`saip`** — a real DER-encoded **SGP.22 SAIP `ProfileElement` sequence**
(ProfileHeader with ICCID + Milenage AKA parameters carrying Ki/OPc), built
with `asn1tools` against the official `PE_Definitions` ASN.1 schema. Enable by
installing `requirements-saip.txt` (see that file for the schema source) and
setting `profile.format: saip`. If prerequisites are missing the server logs a
warning and falls back to `v2`.
The SAIP package currently carries ICCID and the AKA credentials; emitting IMSI
and other EFs (USIM-template / GenericFileManagement PEs with File Control
Parameters) is the next layer. Note that consumer over-the-air install still
requires an SM-DP+ / LPA; these packages target programmable SIMs and
core-network sync. See [`CHANGELOG.md`](CHANGELOG.md).
## Deployment
### Raspberry Pi
Requirements: Pi 4 (4GB+), a compatible 5G modem (e.g. Quectel RM500Q), Raspberry
Pi OS Lite 64-bit, Python 3.11+, and a pre-provisioned SIM for gNB backhaul.
```bash
sudo bash scripts/setup_wifi_ap.sh
```
This installs hostapd/dnsmasq/nginx, configures the AP and captive-portal
redirect, deploys the server under `/opt/esim-provisioning`, creates the `esim`
service user, restricts `config.yaml` to `600`, initialises the DB, and enables
the systemd services. Then edit `/etc/esim-provisioning/config.yaml` and
`systemctl restart esim-provisioning`.
### Android / Termux
```bash
bash android/termux_setup.sh
~/esim-provisioning/start.sh
```
See [`android/README.md`](android/README.md) for details and limitations.
## Core network sync
With `core.type: none`, credentials are stored locally only. To push them to a
core network:
```bash
python scripts/sync_subscribers.py # sync unsynced subscribers to Open5GS/free5gc
python scripts/export_subscribers.py # dump all subscribers as JSON (reads the DB directly)
```
Adding a new core is a single class in `server/core_adapters/` implementing the
`CoreAdapter` interface.
## Security notes
- Profiles are served at an unguessable one-time token URL, never an enumerable
id — the profile body contains Ki/OPc.
- `/api/export` returns all key material and is **disabled by default**; set
`server.admin_token` and send it as `X-Admin-Token` to enable.
- The deployed `config.yaml` (holds `op_key`) is `chmod 600`, owned by the
service user; the systemd unit runs with `NoNewPrivileges`, `PrivateTmp`, and
`ProtectSystem=strict`.
- The WiFi network is intentionally open (emergency access); there is no
transport encryption on the local link.
## Testing
```bash
pip install -r requirements.txt
python -m pytest tests/ -q
```
The suite covers credential generation and 3GPP encodings, the database and rate
limiting, the core adapters, config/utility validation, and the Flask API
(provisioning, token download, rate-limit and export auth).
## Project layout
```
server/ Flask app, profile generator, database, core adapters
portal/ Captive-portal frontend (HTML/CSS/JS)
scripts/ init_db, sync/export subscribers, Pi AP setup
configs/ nginx / dnsmasq / hostapd reference configs
systemd/ Service units
android/ Termux deployment
tests/ Test suite
plan/ Design doc and per-unit implementation notes
```
## License
MIT (or as appropriate for the deployment context).