496 lines
14 KiB
Markdown
496 lines
14 KiB
Markdown
|
A
|
# Closed-Loop eSIM Provisioning System for Emergency/NTN 5G Deployment
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
Self-contained eSIM provisioning system for ad-hoc 5G network deployment in emergency or NTN scenarios. Users connect to WiFi AP, automatically receive eSIM profile, then connect to 5G network.
|
||
|
|
|
||
|
|
## System Architecture
|
||
|
|
|
||
|
|
```
|
||
|
|
┌─────────────────────────────────────────┐
|
||
|
|
│ Server Host (srsRAN gNB) │
|
||
|
|
│ - srsRAN gNB │
|
||
|
|
│ - Open5GS Core (or free5gc) │
|
||
|
|
│ - Subscriber database │
|
||
|
|
└─────────────┬───────────────────────────┘
|
||
|
|
│ 5G NR
|
||
|
|
│
|
||
|
|
▼
|
||
|
|
┌─────────────────────────────────────────┐
|
||
|
|
│ UE WiFi AP Drop │
|
||
|
|
│ (Android device or Raspberry Pi) │
|
||
|
|
│ │
|
||
|
|
│ ┌───────────────────────────────────┐ │
|
||
|
|
│ │ 5G Modem (pre-provisioned SIM) │ │
|
||
|
|
│ │ Connects to gNB for backhaul │ │
|
||
|
|
│ └───────────────────────────────────┘ │
|
||
|
|
│ │
|
||
|
|
│ ┌───────────────────────────────────┐ │
|
||
|
|
│ │ WiFi AP (open network) │ │
|
||
|
|
│ └───────────────────────────────────┘ │
|
||
|
|
│ │
|
||
|
|
│ ┌───────────────────────────────────┐ │
|
||
|
|
│ │ eSIM Provisioning Server │ │
|
||
|
|
│ │ - Profile generator │ │
|
||
|
|
│ │ - Captive portal │ │
|
||
|
|
│ │ - Credential database │ │
|
||
|
|
│ └───────────────────────────────────┘ │
|
||
|
|
└─────────────────────────────────────────┘
|
||
|
|
│ WiFi
|
||
|
|
▼
|
||
|
|
End User Devices
|
||
|
|
```
|
||
|
|
|
||
|
|
## User Flow
|
||
|
|
|
||
|
|
1. User device detects open WiFi network
|
||
|
|
2. Connects to WiFi → Auto-redirected to captive portal
|
||
|
|
3. Captive portal displays "Activate eSIM" button
|
||
|
|
4. Click → Server generates unique IMSI/Ki/OPc credentials
|
||
|
|
5. Server generates SGP.22 eSIM profile with credentials
|
||
|
|
6. Profile delivered to device via LPA protocol
|
||
|
|
7. Device installs eSIM profile
|
||
|
|
8. User disconnects from WiFi
|
||
|
|
9. Device connects to 5G network using new eSIM
|
||
|
|
10. Open5GS/free5gc authenticates user
|
||
|
|
|
||
|
|
## Components
|
||
|
|
|
||
|
|
### 1. UE WiFi AP Drop Device
|
||
|
|
|
||
|
|
**Hardware Options:**
|
||
|
|
- **Primary**: Android phone (cheapest if app is simple)
|
||
|
|
- **Fallback**: Raspberry Pi 4 + Quectel RM500Q 5G modem
|
||
|
|
|
||
|
|
**Requirements:**
|
||
|
|
- Pre-provisioned "admin" SIM to connect to gNB
|
||
|
|
- WiFi hotspot capability
|
||
|
|
- Runs provisioning server stack
|
||
|
|
|
||
|
|
**Software Stack:**
|
||
|
|
- Cellular connection to gNB (uses pre-provisioned SIM)
|
||
|
|
- WiFi AP (hostapd or Android hotspot)
|
||
|
|
- Captive portal (dnsmasq + nginx)
|
||
|
|
- eSIM provisioning server (Python)
|
||
|
|
|
||
|
|
### 2. eSIM Provisioning Server
|
||
|
|
|
||
|
|
**Technology:** Python + Flask
|
||
|
|
|
||
|
|
**Core Functions:**
|
||
|
|
- Generate IMSI/Ki/OPc credentials on-demand
|
||
|
|
- Create SGP.22-compliant eSIM profiles using pySim
|
||
|
|
- Serve profiles via LPA protocol
|
||
|
|
- Store generated credentials in SQLite
|
||
|
|
- Rate limiting (1 profile per MAC address per hour)
|
||
|
|
- Export credentials for core sync
|
||
|
|
|
||
|
|
**Dependencies:**
|
||
|
|
- `pySim` - SIM/eSIM profile generation
|
||
|
|
- `Flask` - Web server
|
||
|
|
- `sqlite3` - Local credential storage
|
||
|
|
- `cryptography` - Key generation
|
||
|
|
|
||
|
|
**Endpoints:**
|
||
|
|
```
|
||
|
|
GET / - Captive portal landing page
|
||
|
|
POST /activate - Trigger eSIM generation
|
||
|
|
GET /profile/:id - Download eSIM profile (LPA)
|
||
|
|
GET /export - Export credentials for core sync
|
||
|
|
```
|
||
|
|
|
||
|
|
**Profile Generation Logic:**
|
||
|
|
```python
|
||
|
|
# Generate credentials
|
||
|
|
IMSI = generate_imsi(mcc=302, mnc=720) # Adjust for your network
|
||
|
|
Ki = random_bytes(16)
|
||
|
|
OPc = derive_opc(Ki, OP)
|
||
|
|
|
||
|
|
# Create eSIM profile
|
||
|
|
profile = create_esim_profile(
|
||
|
|
iccid=generate_iccid(),
|
||
|
|
imsi=IMSI,
|
||
|
|
ki=Ki,
|
||
|
|
opc=OPc,
|
||
|
|
profile_name="Emergency 5G"
|
||
|
|
)
|
||
|
|
|
||
|
|
# Store in database
|
||
|
|
store_subscriber(imsi, ki, opc, timestamp, mac_address)
|
||
|
|
|
||
|
|
# Return profile for LPA download
|
||
|
|
return profile
|
||
|
|
```
|
||
|
|
|
||
|
|
### 3. Captive Portal
|
||
|
|
|
||
|
|
**Technology:** HTML + JavaScript + nginx
|
||
|
|
|
||
|
|
**Features:**
|
||
|
|
- Auto-redirect any HTTP request to portal
|
||
|
|
- Single-button interface: "Activate eSIM"
|
||
|
|
- Instructions for eSIM installation
|
||
|
|
- Minimal UI, works on any device
|
||
|
|
|
||
|
|
**DNS Hijacking:**
|
||
|
|
```
|
||
|
|
# dnsmasq configuration
|
||
|
|
address=/#/192.168.4.1
|
||
|
|
```
|
||
|
|
|
||
|
|
**nginx Configuration:**
|
||
|
|
```nginx
|
||
|
|
server {
|
||
|
|
listen 80 default_server;
|
||
|
|
server_name _;
|
||
|
|
|
||
|
|
location / {
|
||
|
|
return 302 http://192.168.4.1/activate;
|
||
|
|
}
|
||
|
|
|
||
|
|
location /activate {
|
||
|
|
root /var/www/captive;
|
||
|
|
index index.html;
|
||
|
|
}
|
||
|
|
|
||
|
|
location /api {
|
||
|
|
proxy_pass http://127.0.0.1:5000;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. Core Integration Module
|
||
|
|
|
||
|
|
**Design:** Adapter pattern for portability
|
||
|
|
|
||
|
|
**Supported Cores (initially):**
|
||
|
|
- Open5GS
|
||
|
|
- free5gc
|
||
|
|
|
||
|
|
**Interface:**
|
||
|
|
```python
|
||
|
|
class CoreAdapter:
|
||
|
|
def add_subscriber(self, imsi, ki, opc):
|
||
|
|
"""Add subscriber to core network"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
def remove_subscriber(self, imsi):
|
||
|
|
"""Remove subscriber from core network"""
|
||
|
|
pass
|
||
|
|
|
||
|
|
def list_subscribers(self):
|
||
|
|
"""List all subscribers"""
|
||
|
|
pass
|
||
|
|
```
|
||
|
|
|
||
|
|
**Open5GS Implementation:**
|
||
|
|
```python
|
||
|
|
class Open5GSAdapter(CoreAdapter):
|
||
|
|
def add_subscriber(self, imsi, ki, opc):
|
||
|
|
# Direct MongoDB insertion
|
||
|
|
db.subscribers.insert_one({
|
||
|
|
"imsi": imsi,
|
||
|
|
"security": {
|
||
|
|
"k": ki,
|
||
|
|
"opc": opc,
|
||
|
|
"amf": "8000",
|
||
|
|
"sqn": 0
|
||
|
|
},
|
||
|
|
"ambr": {
|
||
|
|
"downlink": {"value": 1, "unit": 3}, # 1 Gbps
|
||
|
|
"uplink": {"value": 1, "unit": 3}
|
||
|
|
},
|
||
|
|
"slice": [{
|
||
|
|
"sst": 1,
|
||
|
|
"default_indicator": True,
|
||
|
|
"session": [{
|
||
|
|
"name": "internet",
|
||
|
|
"type": 3, # IPv4v6
|
||
|
|
"ambr": {
|
||
|
|
"downlink": {"value": 1, "unit": 3},
|
||
|
|
"uplink": {"value": 1, "unit": 3}
|
||
|
|
}
|
||
|
|
}]
|
||
|
|
}]
|
||
|
|
})
|
||
|
|
```
|
||
|
|
|
||
|
|
**Sync Options:**
|
||
|
|
1. **Push**: Provisioning server calls core adapter immediately
|
||
|
|
2. **Pull**: Core polls provisioning server API periodically
|
||
|
|
3. **Manual**: Export JSON, manually import to core
|
||
|
|
|
||
|
|
**Initial Implementation:** Export JSON format
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"subscribers": [
|
||
|
|
{
|
||
|
|
"imsi": "302720123456789",
|
||
|
|
"ki": "00112233445566778899aabbccddeeff",
|
||
|
|
"opc": "63bfa50ee6523365ff14c1f45f88737d",
|
||
|
|
"timestamp": "2025-02-11T10:30:00Z",
|
||
|
|
"mac_address": "aa:bb:cc:dd:ee:ff"
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 5. eSIM Profile Format
|
||
|
|
|
||
|
|
**Standard:** SGP.22 compliant eSIM profile
|
||
|
|
|
||
|
|
**Profile Contents:**
|
||
|
|
- ICCID (SIM card identifier)
|
||
|
|
- IMSI (subscriber identity)
|
||
|
|
- Ki (authentication key)
|
||
|
|
- OPc (derived operator key)
|
||
|
|
- Profile metadata (name, description)
|
||
|
|
- APN configuration (if needed)
|
||
|
|
|
||
|
|
**Generation Method:**
|
||
|
|
Use pySim's eUICC profile generator:
|
||
|
|
```python
|
||
|
|
from pySim.esim import EsimProfile
|
||
|
|
|
||
|
|
profile = EsimProfile(
|
||
|
|
iccid=iccid,
|
||
|
|
imsi=imsi,
|
||
|
|
ki=ki,
|
||
|
|
opc=opc,
|
||
|
|
profile_name="Emergency 5G",
|
||
|
|
carrier_name="Ad-Hoc Network"
|
||
|
|
)
|
||
|
|
|
||
|
|
profile_bytes = profile.encode()
|
||
|
|
```
|
||
|
|
|
||
|
|
**Fallback:** If SGP.22 generation fails, serve simplified profile (may have compatibility issues with some devices)
|
||
|
|
|
||
|
|
## Implementation Phases
|
||
|
|
|
||
|
|
### Phase 1: Core Server Components
|
||
|
|
- [ ] eSIM provisioning server (Python/Flask)
|
||
|
|
- [ ] Credential generation and storage
|
||
|
|
- [ ] Profile generation using pySim
|
||
|
|
- [ ] LPA protocol implementation
|
||
|
|
- [ ] SQLite database schema
|
||
|
|
|
||
|
|
### Phase 2: Captive Portal
|
||
|
|
- [ ] HTML/JS frontend
|
||
|
|
- [ ] nginx configuration
|
||
|
|
- [ ] DNS hijacking setup
|
||
|
|
- [ ] Auto-redirect logic
|
||
|
|
|
||
|
|
### Phase 3: Core Integration
|
||
|
|
- [ ] Open5GS adapter
|
||
|
|
- [ ] JSON export format
|
||
|
|
- [ ] Sync script
|
||
|
|
|
||
|
|
### Phase 4: UE WiFi AP Drop
|
||
|
|
- [ ] Android app (if feasible)
|
||
|
|
- [ ] OR Raspberry Pi setup scripts
|
||
|
|
- [ ] Auto-connection to gNB
|
||
|
|
- [ ] WiFi AP configuration
|
||
|
|
- [ ] Service orchestration
|
||
|
|
|
||
|
|
### Phase 5: Testing & Hardening
|
||
|
|
- [ ] End-to-end flow testing
|
||
|
|
- [ ] Rate limiting verification
|
||
|
|
- [ ] Profile compatibility testing
|
||
|
|
- [ ] Network isolation verification
|
||
|
|
|
||
|
|
## Deployment Instructions
|
||
|
|
|
||
|
|
### Prerequisites
|
||
|
|
- srsRAN gNB running and configured
|
||
|
|
- Open5GS core running (or free5gc)
|
||
|
|
- One pre-provisioned "admin" SIM for backhaul UE
|
||
|
|
- UE device (Android phone or Raspberry Pi)
|
||
|
|
|
||
|
|
### UE WiFi AP Drop Setup
|
||
|
|
|
||
|
|
**If Android:**
|
||
|
|
1. Install Termux
|
||
|
|
2. Install required packages (Python, hostapd, dnsmasq, nginx)
|
||
|
|
3. Deploy provisioning server
|
||
|
|
4. Configure WiFi hotspot
|
||
|
|
5. Start services
|
||
|
|
|
||
|
|
**If Raspberry Pi:**
|
||
|
|
1. Install Raspberry Pi OS Lite
|
||
|
|
2. Install Quectel RM500Q modem drivers
|
||
|
|
3. Configure modem for gNB connection
|
||
|
|
4. Install provisioning server stack
|
||
|
|
5. Configure hostapd + dnsmasq
|
||
|
|
6. Start services
|
||
|
|
|
||
|
|
### Provisioning Server Setup
|
||
|
|
```bash
|
||
|
|
# Install dependencies
|
||
|
|
pip install flask pySim cryptography
|
||
|
|
|
||
|
|
# Create directory structure
|
||
|
|
mkdir -p /opt/esim-provisioning/{server,portal,data}
|
||
|
|
|
||
|
|
# Deploy server code
|
||
|
|
cp provisioning_server.py /opt/esim-provisioning/server/
|
||
|
|
cp -r templates/ /opt/esim-provisioning/server/
|
||
|
|
|
||
|
|
# Deploy captive portal
|
||
|
|
cp -r portal/* /opt/esim-provisioning/portal/
|
||
|
|
|
||
|
|
# Initialize database
|
||
|
|
python init_db.py
|
||
|
|
|
||
|
|
# Start server
|
||
|
|
systemctl start esim-provisioning
|
||
|
|
```
|
||
|
|
|
||
|
|
### Core Integration
|
||
|
|
```bash
|
||
|
|
# Export subscribers from provisioning server
|
||
|
|
curl http://192.168.4.1:5000/export > subscribers.json
|
||
|
|
|
||
|
|
# Import to Open5GS
|
||
|
|
python open5gs_import.py subscribers.json
|
||
|
|
|
||
|
|
# OR for free5gc
|
||
|
|
python free5gc_import.py subscribers.json
|
||
|
|
```
|
||
|
|
|
||
|
|
## Configuration
|
||
|
|
|
||
|
|
### Network Parameters
|
||
|
|
```python
|
||
|
|
# config.py
|
||
|
|
MCC = "302" # Canada (adjust for your region)
|
||
|
|
MNC = "720" # Adjust for your network
|
||
|
|
PLMN = f"{MCC}{MNC}"
|
||
|
|
|
||
|
|
# IMSI format: MCC + MNC + MSIN
|
||
|
|
# Example: 302720123456789
|
||
|
|
IMSI_PREFIX = PLMN
|
||
|
|
MSIN_LENGTH = 10
|
||
|
|
|
||
|
|
# Operator key (OP) - used to derive OPc
|
||
|
|
OP = bytes.fromhex("63bfa50ee6523365ff14c1f45f88737d")
|
||
|
|
|
||
|
|
# WiFi AP configuration
|
||
|
|
WIFI_SSID = "Emergency-5G"
|
||
|
|
WIFI_CHANNEL = 6
|
||
|
|
WIFI_IP = "192.168.4.1"
|
||
|
|
WIFI_SUBNET = "192.168.4.0/24"
|
||
|
|
WIFI_DHCP_RANGE = "192.168.4.10,192.168.4.250"
|
||
|
|
|
||
|
|
# Rate limiting
|
||
|
|
MAX_PROFILES_PER_MAC = 1
|
||
|
|
RATE_LIMIT_HOURS = 1
|
||
|
|
```
|
||
|
|
|
||
|
|
### Open5GS Connection
|
||
|
|
```python
|
||
|
|
# open5gs_config.py
|
||
|
|
MONGODB_URI = "mongodb://localhost:27017"
|
||
|
|
DATABASE_NAME = "open5gs"
|
||
|
|
COLLECTION_NAME = "subscribers"
|
||
|
|
```
|
||
|
|
|
||
|
|
## Security Considerations
|
||
|
|
|
||
|
|
### Current Implementation (Phase 1)
|
||
|
|
- Open WiFi network (no authentication)
|
||
|
|
- Rate limiting by MAC address
|
||
|
|
- No encryption on provisioning server traffic (local network only)
|
||
|
|
|
||
|
|
### Future Enhancements
|
||
|
|
- Optional WiFi password (distribute via physical signage)
|
||
|
|
- Pre-qualified activation codes
|
||
|
|
- HTTPS for provisioning server
|
||
|
|
- Profile encryption
|
||
|
|
- Audit logging
|
||
|
|
|
||
|
|
## Scalability
|
||
|
|
|
||
|
|
### On-Demand Generation
|
||
|
|
- Generates credentials only when requested
|
||
|
|
- No pre-generation overhead
|
||
|
|
- Limited only by:
|
||
|
|
- IMSI space (10^10 possible MSINs with 10-digit MSIN)
|
||
|
|
- Storage (SQLite can handle millions of records)
|
||
|
|
- Processing power (generates ~10 profiles/second on modest hardware)
|
||
|
|
|
||
|
|
### Multi-Core Session Handover
|
||
|
|
**Design Consideration:** Profile format supports core mobility
|
||
|
|
|
||
|
|
Current design doesn't preclude future session handover between cores:
|
||
|
|
- IMSI/Ki/OPc are standardized, portable between cores
|
||
|
|
- Export format is core-agnostic JSON
|
||
|
|
- Core adapters can be extended to support handover signaling
|
||
|
|
|
||
|
|
**Future Implementation:**
|
||
|
|
- Add `home_core_id` field to subscriber record
|
||
|
|
- Export includes core affinity metadata
|
||
|
|
- Handover protocol exchanges subscriber credentials between cores
|
||
|
|
|
||
|
|
## Troubleshooting
|
||
|
|
|
||
|
|
### Profile Generation Fails
|
||
|
|
- Check pySim installation: `python -c "import pySim"`
|
||
|
|
- Verify OP key is 16 bytes
|
||
|
|
- Check IMSI format (15 digits total)
|
||
|
|
|
||
|
|
### Device Won't Download Profile
|
||
|
|
- Verify device supports eSIM (eUICC)
|
||
|
|
- Check LPA protocol implementation
|
||
|
|
- Verify profile format is SGP.22 compliant
|
||
|
|
- Try simplified profile format
|
||
|
|
|
||
|
|
### Can't Connect to 5G After eSIM Install
|
||
|
|
- Verify subscriber added to Open5GS/free5gc
|
||
|
|
- Check IMSI/Ki/OPc match between profile and core
|
||
|
|
- Verify PLMN configuration matches gNB
|
||
|
|
- Check AMF/UDM logs for authentication failures
|
||
|
|
|
||
|
|
### WiFi Captive Portal Not Triggering
|
||
|
|
- Verify DNS hijacking: `nslookup google.com 192.168.4.1`
|
||
|
|
- Check nginx is running: `systemctl status nginx`
|
||
|
|
- Verify iptables rules for captive portal
|
||
|
|
|
||
|
|
## File Structure
|
||
|
|
```
|
||
|
|
/opt/esim-provisioning/
|
||
|
|
├── server/
|
||
|
|
│ ├── provisioning_server.py # Main Flask application
|
||
|
|
│ ├── profile_generator.py # eSIM profile generation
|
||
|
|
│ ├── database.py # SQLite interface
|
||
|
|
│ ├── core_adapter.py # Core integration adapters
|
||
|
|
│ └── config.py # Configuration
|
||
|
|
├── portal/
|
||
|
|
│ ├── index.html # Captive portal page
|
||
|
|
│ ├── style.css
|
||
|
|
│ └── script.js
|
||
|
|
├── data/
|
||
|
|
│ ├── subscribers.db # SQLite database
|
||
|
|
│ └── profiles/ # Generated profiles (temp)
|
||
|
|
├── scripts/
|
||
|
|
│ ├── sync_open5gs.py # Open5GS sync script
|
||
|
|
│ ├── sync_free5gc.py # free5gc sync script
|
||
|
|
│ └── export_subscribers.py # Export to JSON
|
||
|
|
└── systemd/
|
||
|
|
├── esim-provisioning.service # Systemd unit file
|
||
|
|
└── captive-portal.service # Captive portal service
|
||
|
|
```
|
||
|
|
|
||
|
|
## Next Steps
|
||
|
|
|
||
|
|
1. Implement provisioning server (Python/Flask)
|
||
|
|
2. Implement profile generator using pySim
|
||
|
|
3. Create captive portal frontend
|
||
|
|
4. Build Open5GS adapter
|
||
|
|
5. Test end-to-end flow
|
||
|
|
6. Deploy to UE WiFi AP drop device
|
||
|
|
|
||
|
|
## References
|
||
|
|
- SGP.22 eSIM Specification: https://www.gsma.com/esim/
|
||
|
|
- pySim Documentation: https://github.com/osmocom/pysim
|
||
|
|
- Open5GS Documentation: https://open5gs.org/
|
||
|
|
- srsRAN Documentation: https://docs.srsran.com/
|