Push Tracker
ria-ran--sim-drop/server/profile_generator.py

93 lines
2.7 KiB
Python
Raw Normal View History

import json
import secrets
import logging
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
logger = logging.getLogger(__name__)
def generate_imsi(mcc: str, mnc: str) -> str:
"""Generate a random 15-digit IMSI: MCC + MNC + MSIN.
IMSI is always 15 digits. MCC is 3 digits; MNC is 2 or 3 digits, so the
MSIN fills the remaining 9 or 10 digits.
"""
msin_len = 15 - len(mcc) - len(mnc)
if msin_len <= 0:
raise ValueError(f"MCC/MNC too long for a 15-digit IMSI: {mcc!r}/{mnc!r}")
msin = str(secrets.randbelow(10**msin_len)).zfill(msin_len)
imsi = mcc + mnc + msin
assert len(imsi) == 15, f"IMSI length error: {imsi!r}"
return imsi
def generate_ki() -> bytes:
"""Generate a 16-byte cryptographically random authentication key."""
return secrets.token_bytes(16)
def luhn_checksum(number: str) -> int:
"""Compute the Luhn check digit for a string of digits."""
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
def generate_iccid() -> str:
"""Generate a 19-digit ICCID with a valid Luhn check digit.
Format: 8910 (telecom prefix) + 10 random digits + 5 more + Luhn digit = 19 total.
"""
prefix = "8910"
body = prefix + str(secrets.randbelow(10**14)).zfill(14) # 18 digits
check = luhn_checksum(body)
return body + str(check)
def derive_opc(ki: bytes, op: bytes) -> bytes:
"""Derive OPc from Ki and OP using the Milenage algorithm.
OPc = AES_Ki(OP) XOR OP
"""
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))
def create_esim_profile(
imsi: str,
ki: bytes,
opc: bytes,
iccid: str,
profile_name: str,
carrier_name: str,
) -> bytes:
"""Create an eSIM profile.
Phase 1: returns a JSON-encoded mock profile suitable for end-to-end
testing. Phase 2 (Unit 06 upgrade): replace with real pySim SGP.22
profile generation.
"""
ki_hex = ki.hex() if isinstance(ki, bytes) else ki
opc_hex = opc.hex() if isinstance(opc, bytes) else opc
profile = {
"version": "1.0",
"type": "mock",
"iccid": iccid,
"imsi": imsi,
"ki": ki_hex,
"opc": opc_hex,
"profile_name": profile_name,
"carrier_name": carrier_name,
}
logger.debug(f"Created mock eSIM profile for IMSI {imsi}")
return json.dumps(profile, indent=2).encode("utf-8")