Push Tracker
ria-ran--sim-drop/plan/02-profile-generator.md

107 lines
3.1 KiB
Markdown
Raw Permalink Normal View History

# Unit 02 — Profile Generator
## Goal
Cryptographic credential generation (IMSI, Ki, OPc, ICCID) and Phase 1 mock eSIM profile. Real pySim integration is deferred to Unit 06.
## File
- `server/profile_generator.py`
---
## Functions
### `generate_imsi(mcc: str, mnc: str) -> str`
- Concatenate MCC + MNC + 10-digit random MSIN
- MSIN generated with `secrets.randbelow(10**10)` zero-padded to 10 digits
- Result must be exactly 15 digits
- Validates format before returning
### `generate_ki() -> bytes`
- `secrets.token_bytes(16)` — 16 cryptographically random bytes
### `generate_iccid() -> str`
- Industry prefix: `8910` (Telecom, country code 10)
- Append MCC+MNC from config
- Fill remaining digits randomly
- Total 18 digits + 1 Luhn check digit = 19 digits
- Luhn algorithm:
```python
def luhn_checksum(number: str) -> int:
digits = [int(d) for d in number]
odd_digits = digits[-1::-2]
even_digits = digits[-2::-2]
total = sum(odd_digits)
for d in even_digits:
total += sum(divmod(d * 2, 10))
return (10 - (total % 10)) % 10
```
### `derive_opc(ki: bytes, op: bytes) -> bytes`
Milenage OPc derivation — XOR of OP with AES_Ki(OP):
```python
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
def derive_opc(ki: bytes, op: bytes) -> bytes:
cipher = Cipher(algorithms.AES(ki), modes.ECB(), backend=default_backend())
enc = cipher.encryptor()
encrypted_op = enc.update(op) + enc.finalize()
return bytes(a ^ b for a, b in zip(encrypted_op, op))
```
### `create_esim_profile(imsi, ki, opc, iccid, profile_name, carrier_name) -> bytes`
**Phase 1 (mock):** Returns JSON-encoded bytes with all credential fields:
```python
import json
def create_esim_profile(imsi, ki, opc, iccid, profile_name, carrier_name):
profile = {
"version": "1.0",
"type": "mock",
"iccid": iccid,
"imsi": imsi,
"ki": ki.hex() if isinstance(ki, bytes) else ki,
"opc": opc.hex() if isinstance(opc, bytes) else opc,
"profile_name": profile_name,
"carrier_name": carrier_name,
}
return json.dumps(profile).encode("utf-8")
```
**Phase 2 (Unit 06):** Replace with real pySim SGP.22 profile generation.
---
## Known Test Vectors (for Unit 10 tests)
From 3GPP TS 35.208:
- Ki = `000102030405060708090a0b0c0d0e0f`
- OP = `63bfa50ee6523365ff14c1f45f88737d`
- OPc = compute at test time: `AES_Ki(OP) XOR OP`
---
## Verification
```bash
python -c "
from server.profile_generator import generate_imsi, generate_ki, generate_iccid, derive_opc, create_esim_profile
import binascii
imsi = generate_imsi('302', '720')
assert len(imsi) == 15 and imsi.startswith('302720'), f'bad IMSI: {imsi}'
ki = generate_ki()
assert len(ki) == 16
iccid = generate_iccid()
assert len(iccid) == 19, f'bad ICCID length: {len(iccid)}'
op = bytes.fromhex('63bfa50ee6523365ff14c1f45f88737d')
opc = derive_opc(ki, op)
assert len(opc) == 16
profile = create_esim_profile(imsi, ki, opc, iccid, 'Emergency 5G', 'Ad-Hoc Network')
assert len(profile) > 0
print('All checks passed')
"
```