102 lines
3.6 KiB
Python
102 lines
3.6 KiB
Python
|
A
|
#!/usr/bin/env python3
|
||
|
|
"""Generate or inspect an eSIM profile — a dev/ops utility.
|
||
|
|
|
||
|
|
Generates one profile (v2 JSON or real SGP.22 SAIP DER) using the network and
|
||
|
|
profile settings from config.yaml, with random or supplied credentials, and
|
||
|
|
writes it to a file or stdout. Can also decode an existing SAIP profile file.
|
||
|
|
|
||
|
|
Examples:
|
||
|
|
python scripts/make_profile.py --format saip --out /tmp/p.der
|
||
|
|
python scripts/make_profile.py --inspect /tmp/p.der
|
||
|
|
"""
|
||
|
|
import argparse
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
|
||
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||
|
|
|
||
|
|
from server import saip_profile
|
||
|
|
from server.profile_generator import (
|
||
|
|
create_esim_profile,
|
||
|
|
derive_opc,
|
||
|
|
generate_iccid,
|
||
|
|
generate_imsi,
|
||
|
|
generate_ki,
|
||
|
|
)
|
||
|
|
from server.utils import hex_to_bytes, load_config
|
||
|
|
|
||
|
|
|
||
|
|
def build_profile(cfg: dict, fmt: str = "v2", imsi=None, iccid=None):
|
||
|
|
"""Return (imsi, iccid, ki, opc, profile_bytes) for a freshly built profile."""
|
||
|
|
net = cfg["network"]
|
||
|
|
prof = cfg.get("profile", {})
|
||
|
|
imsi = imsi or generate_imsi(net["mcc"], net["mnc"])
|
||
|
|
iccid = iccid or generate_iccid()
|
||
|
|
ki = generate_ki()
|
||
|
|
opc = derive_opc(ki, hex_to_bytes(net["op_key"]))
|
||
|
|
|
||
|
|
if fmt == "saip":
|
||
|
|
if not saip_profile.saip_available():
|
||
|
|
raise SystemExit("SAIP unavailable: install requirements-saip.txt "
|
||
|
|
"(asn1tools + SAIP ASN.1 schema)")
|
||
|
|
data = saip_profile.create_saip_profile(
|
||
|
|
imsi, ki, opc, iccid,
|
||
|
|
prof.get("profile_name", ""), prof.get("carrier_name", ""),
|
||
|
|
)
|
||
|
|
else:
|
||
|
|
data = create_esim_profile(
|
||
|
|
imsi, ki, opc, iccid,
|
||
|
|
prof.get("profile_name", ""), prof.get("carrier_name", ""),
|
||
|
|
smdp_address=prof.get("smdp_address", "esim.local"),
|
||
|
|
apn=prof.get("apn", "internet"),
|
||
|
|
)
|
||
|
|
return imsi, iccid, ki, opc, data
|
||
|
|
|
||
|
|
|
||
|
|
def summarize_saip(data: bytes) -> str:
|
||
|
|
"""Return a human-readable summary of a SAIP profile's ProfileElements."""
|
||
|
|
pes = saip_profile.decode_saip_profile(data)
|
||
|
|
lines = [f"{len(data)} bytes, {len(pes)} ProfileElement(s):"]
|
||
|
|
for choice, value in pes:
|
||
|
|
detail = ""
|
||
|
|
if choice == "header":
|
||
|
|
detail = f" iccid={value.get('iccid', b'').hex()}"
|
||
|
|
elif choice in ("mf", "usim") and isinstance(value, dict):
|
||
|
|
detail = f" templateID={value.get('templateID')}"
|
||
|
|
lines.append(f" - {choice}{detail}")
|
||
|
|
return "\n".join(lines)
|
||
|
|
|
||
|
|
|
||
|
|
def main(argv=None):
|
||
|
|
ap = argparse.ArgumentParser(description=__doc__,
|
||
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||
|
|
ap.add_argument("--config", default=os.environ.get("CONFIG_PATH", "config.yaml"))
|
||
|
|
ap.add_argument("--format", choices=["v2", "saip"], default="v2")
|
||
|
|
ap.add_argument("--imsi", help="use this IMSI instead of a random one")
|
||
|
|
ap.add_argument("--iccid", help="use this ICCID instead of a random one")
|
||
|
|
ap.add_argument("--out", help="output file (default: stdout)")
|
||
|
|
ap.add_argument("--inspect", metavar="FILE",
|
||
|
|
help="decode a SAIP profile file and exit")
|
||
|
|
args = ap.parse_args(argv)
|
||
|
|
|
||
|
|
if args.inspect:
|
||
|
|
with open(args.inspect, "rb") as f:
|
||
|
|
print(summarize_saip(f.read()))
|
||
|
|
return 0
|
||
|
|
|
||
|
|
cfg = load_config(args.config)
|
||
|
|
imsi, iccid, _ki, _opc, data = build_profile(cfg, args.format, args.imsi, args.iccid)
|
||
|
|
|
||
|
|
if args.out:
|
||
|
|
with open(args.out, "wb") as f:
|
||
|
|
f.write(data)
|
||
|
|
print(f"Wrote {len(data)} bytes to {args.out} "
|
||
|
|
f"(format={args.format}, IMSI {imsi}, ICCID {iccid})", file=sys.stderr)
|
||
|
|
else:
|
||
|
|
sys.stdout.buffer.write(data)
|
||
|
|
return 0
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
sys.exit(main())
|