Push Tracker
ria-ran--sim-drop/claude.md
A ashkan@beigi.net 8672b0de6b Add Phase-1 eSIM provisioning implementation
Full working closed-loop eSIM provisioning stack (spec in claude.md / plan/):
Flask API, SQLite subscriber store, credential/profile generator, Open5GS
and free5gc core adapters, captive portal, operational scripts, systemd
units, network configs, and a 44-test suite.

Profile generation currently returns a mock JSON profile; real SGP.22
generation via pySim is the planned Phase-2 upgrade.

Fixes two bugs found while getting the suite green:
- generate_imsi hardcoded a 10-digit MSIN, producing 16-digit IMSIs for a
  3-digit MNC; MSIN length is now 15 - len(mcc) - len(mnc).
- check_rate_limit compared timestamps as strings across mismatched formats
  (SQLite CURRENT_TIMESTAMP vs Python isoformat); both sides now normalized
  via SQLite datetime() so the window check is chronologically correct.

Add .gitignore for venv, caches, runtime config.yaml, and databases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 05:56:31 +00:00

418 lines
11 KiB
Markdown

# eSIM Provisioning System - Project Setup Instructions
## Project Overview
Build a closed-loop eSIM provisioning system for emergency/NTN 5G deployments. System runs on a UE device (Android or Raspberry Pi) that connects to a 5G gNB and provides WiFi AP with automatic eSIM provisioning.
## Repository Structure
Create the following directory structure:
```
esim-provisioning/
├── README.md
├── requirements.txt
├── config.yaml.example
├── docker-compose.yml (optional)
├── server/
│ ├── __init__.py
│ ├── app.py # Main Flask application
│ ├── profile_generator.py # eSIM profile generation using pySim
│ ├── database.py # SQLite database interface
│ ├── core_adapters/
│ │ ├── __init__.py
│ │ ├── base.py # Base adapter class
│ │ ├── open5gs.py # Open5GS adapter
│ │ └── free5gc.py # free5gc adapter
│ ├── models.py # Database models
│ └── utils.py # Helper functions
├── portal/
│ ├── index.html # Captive portal landing page
│ ├── css/
│ │ └── style.css
│ ├── js/
│ │ └── script.js
│ └── assets/
│ └── (images if needed)
├── scripts/
│ ├── init_db.py # Initialize database
│ ├── sync_subscribers.py # Sync to core network
│ ├── export_subscribers.py # Export subscriber data
│ └── setup_wifi_ap.sh # WiFi AP setup (for Pi)
├── systemd/
│ ├── esim-provisioning.service
│ └── captive-portal.service
├── configs/
│ ├── nginx.conf # nginx captive portal config
│ ├── dnsmasq.conf # DNS/DHCP config
│ └── hostapd.conf # WiFi AP config (for Pi)
├── android/
│ ├── README.md # Android setup instructions
│ └── termux_setup.sh # Termux installation script
└── tests/
├── test_profile_generator.py
├── test_database.py
└── test_core_adapters.py
```
## Step 1: Initialize Project
Create the repository structure and initialize Python virtual environment:
```bash
# Create project directory
mkdir esim-provisioning
cd esim-provisioning
# Initialize git
git init
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Create all directories
mkdir -p server/core_adapters portal/css portal/js portal/assets scripts systemd configs android tests
```
## Step 2: Create Requirements File
Create `requirements.txt` with:
```
Flask==3.0.0
pySim==1.0.0
cryptography==41.0.7
pymongo==4.6.0
requests==2.31.0
pyyaml==6.0.1
```
## Step 3: Create Configuration Template
Create `config.yaml.example`:
```yaml
network:
mcc: "302"
mnc: "720"
plmn: "302720"
op_key: "63bfa50ee6523365ff14c1f45f88737d" # Hex string
wifi:
ssid: "Emergency-5G"
channel: 6
ip: "192.168.4.1"
subnet: "192.168.4.0/24"
dhcp_range: "192.168.4.10,192.168.4.250"
server:
host: "0.0.0.0"
port: 5000
debug: false
database:
path: "/opt/esim-provisioning/data/subscribers.db"
rate_limiting:
max_profiles_per_mac: 1
window_hours: 1
core:
type: "open5gs" # or "free5gc"
mongodb_uri: "mongodb://localhost:27017"
database_name: "open5gs"
profile:
carrier_name: "Ad-Hoc Network"
profile_name: "Emergency 5G"
apn: "internet"
```
## Step 4: Core Server Components
### database.py
Create database interface with:
- SQLite connection management
- Schema creation (subscribers table)
- CRUD operations for subscribers
- Rate limiting checks by MAC address
- Export functionality
Schema:
```sql
CREATE TABLE subscribers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
imsi TEXT UNIQUE NOT NULL,
iccid TEXT UNIQUE NOT NULL,
ki TEXT NOT NULL,
opc TEXT NOT NULL,
mac_address TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
synced_to_core BOOLEAN DEFAULT 0
);
CREATE INDEX idx_imsi ON subscribers(imsi);
CREATE INDEX idx_mac_address ON subscribers(mac_address);
```
### profile_generator.py
Implement eSIM profile generation:
- Generate random IMSI (15 digits: MCC + MNC + MSIN)
- Generate random Ki (16 bytes)
- Derive OPc from Ki and OP using Milenage algorithm
- Generate ICCID (19-20 digits)
- Create SGP.22 eSIM profile using pySim
- Return profile bytes and credentials
Key functions:
```python
def generate_imsi(mcc, mnc) -> str
def generate_iccid() -> str
def generate_ki() -> bytes
def derive_opc(ki: bytes, op: bytes) -> bytes
def create_esim_profile(imsi, ki, opc, iccid, profile_name, carrier_name) -> bytes
```
### core_adapters/base.py
Define abstract base class:
```python
class CoreAdapter(ABC):
@abstractmethod
def add_subscriber(self, imsi, ki, opc):
pass
@abstractmethod
def remove_subscriber(self, imsi):
pass
@abstractmethod
def list_subscribers(self):
pass
```
### core_adapters/open5gs.py
Implement Open5GS adapter using MongoDB:
- Connect to MongoDB
- Insert subscriber with proper schema
- Configure slice, APN, AMBR settings
- Handle errors gracefully
### app.py
Main Flask application with endpoints:
```python
GET / # Captive portal redirect
GET /activate # Serve captive portal HTML
POST /api/provision # Generate and provision eSIM
GET /api/profile/:id # Download eSIM profile (LPA)
GET /api/export # Export subscribers JSON
GET /api/status # System status
```
Implement:
- Rate limiting middleware (check MAC address)
- Profile generation workflow
- LPA protocol handling
- Error handling and logging
## Step 5: Captive Portal
### portal/index.html
Simple single-page interface:
- "Activate eSIM" button
- Instructions for installation
- Progress indicator
- Success/error messages
- Mobile-friendly responsive design
### portal/js/script.js
Client-side logic:
- POST to `/api/provision` with MAC address
- Poll for profile generation status
- Trigger eSIM download via LPA URL scheme
- Handle errors
## Step 6: System Scripts
### scripts/init_db.py
Initialize SQLite database with schema
### scripts/sync_subscribers.py
Sync subscribers to core:
```python
# Read from SQLite
# For each unsynced subscriber:
# - Call core adapter
# - Mark as synced
# - Log result
```
### scripts/export_subscribers.py
Export to JSON format:
```json
{
"exported_at": "2025-02-11T10:30:00Z",
"count": 123,
"subscribers": [
{
"imsi": "302720123456789",
"ki": "00112233445566778899aabbccddeeff",
"opc": "63bfa50ee6523365ff14c1f45f88737d",
"iccid": "8910302720123456789",
"created_at": "2025-02-11T09:15:00Z"
}
]
}
```
### scripts/setup_wifi_ap.sh
Bash script for Raspberry Pi WiFi AP setup:
- Install hostapd, dnsmasq, nginx
- Configure network interfaces
- Set up iptables for captive portal
- Enable IP forwarding
- Configure services
## Step 7: Configuration Files
### configs/nginx.conf
Configure captive portal:
- Listen on port 80
- Catch-all server block
- Redirect to /activate
- Proxy /api/* to Flask backend
### configs/dnsmasq.conf
DNS/DHCP configuration:
- Interface binding
- DHCP range
- DNS hijacking (return WiFi AP IP for all domains)
- Lease time
### configs/hostapd.conf
WiFi AP configuration:
- SSID
- Channel
- Interface
- Open network (no password)
## Step 8: Systemd Services
### systemd/esim-provisioning.service
Flask server service:
- Run as dedicated user
- Auto-restart on failure
- Log to journal
- Load config from /etc/esim-provisioning/config.yaml
### systemd/captive-portal.service
Nginx + dnsmasq orchestration
## Step 9: Testing
Create unit tests for:
- Profile generation (valid IMSI, Ki, OPc formats)
- Database operations (CRUD, rate limiting)
- Core adapters (mock MongoDB)
- API endpoints (Flask test client)
## Step 10: Documentation
### README.md
Include:
- Project overview
- System requirements
- Installation instructions (Pi and Android)
- Configuration guide
- Usage instructions
- Troubleshooting
- Architecture diagram
- License
## Implementation Priority
1. **First:** Database layer + models
2. **Second:** Profile generator (without pySim, use mock for testing)
3. **Third:** Flask API with all endpoints
4. **Fourth:** Core adapters (Open5GS first)
5. **Fifth:** Captive portal frontend
6. **Sixth:** Integration with pySim for real profile generation
7. **Seventh:** System scripts and configs
8. **Eighth:** Testing and documentation
## Key Implementation Notes
- Use environment variables or config.yaml for all configuration (never hardcode)
- Log everything (use Python logging module)
- Handle all errors gracefully (no stack traces to user)
- Validate all inputs (IMSI format, MAC address format)
- Use secrets module for cryptographic randomness
- Make core adapters truly modular (easy to add new cores)
- Keep captive portal extremely simple (mobile-first)
- Rate limiting must be robust (prevent abuse)
- Database operations should be atomic
- Profile generation should be fast (<1 second)
## Testing Checklist
- [ ] Can generate valid IMSI
- [ ] Can generate valid Ki/OPc
- [ ] Can create eSIM profile
- [ ] Can store subscriber in database
- [ ] Rate limiting works (same MAC can't request multiple times)
- [ ] Can export subscribers to JSON
- [ ] Open5GS adapter can add subscriber
- [ ] API endpoints return correct responses
- [ ] Captive portal loads on mobile
- [ ] Profile downloads correctly
- [ ] End-to-end: WiFi provision 5G connection
## Deployment Targets
**Raspberry Pi:**
- Raspberry Pi 4 (4GB+ RAM)
- Quectel RM500Q 5G modem
- Raspberry Pi OS Lite 64-bit
- Python 3.11+
**Android:**
- Android 10+ with root/Termux
- 5G modem built-in
- Termux + proot environment
- Python 3.11+
## Security Considerations (Future)
Currently implementing with no security for simplicity, but design should allow easy addition of:
- WiFi password authentication
- HTTPS for API endpoints
- Activation code requirements
- Subscriber audit logging
- Profile encryption at rest
## Success Criteria
System is complete when:
1. User connects to WiFi
2. Captive portal loads automatically
3. Click "Activate eSIM" button
4. eSIM profile downloads to device
5. Device installs profile
6. Device connects to 5G using new eSIM
7. Open5GS authenticates successfully
Total user time: <60 seconds
---
## Starting Point
Begin by creating the project structure, then implement in this order:
1. `database.py` - Get data persistence working
2. `profile_generator.py` - Mock implementation first
3. `app.py` - Flask API with all endpoints
4. `portal/index.html` - Simple UI
5. `core_adapters/open5gs.py` - Core integration
6. Integration testing
7. Real pySim integration
8. Deployment scripts
Focus on getting a working end-to-end flow before optimizing or adding features.