Push Tracker
ria-ran--sim-drop/CHANGELOG.md
A ashkan@beigi.net b88fe4eed1 Add real SGP.22 SAIP profile generation (optional)
New server/saip_profile.py encodes a DER ProfileElement sequence (ProfileHeader
with ICCID + Milenage AKA parameters carrying Ki/OPc) using asn1tools against
the official PE_Definitions SAIP ASN.1 schema. It locates the schema from an
installed osmocom pySim (or SAIP_ASN_PATH) via find_spec WITHOUT importing
pySim's Python package, so it avoids the pyscard/smpp card-reader stack — the
only runtime dep is asn1tools.

Selectable via profile.format ("v2" default | "saip"); the app falls back to
profile-package-v2 with a warning when SAIP prerequisites are absent. IMSI and
other EFs (USIM-template PEs) are the documented next layer.

Add requirements-saip.txt, pytest.ini (silence asn1tools warnings), README and
config updates. Tests: +6 (encode/round-trip/credentials, app emit + fallback);
92 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 02:35:54 +00:00

5.0 KiB

Changelog

All notable changes to this project are documented here.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[Unreleased]

Added

  • Real SGP.22 SAIP profile generation (profile.format: saip). server/saip_profile.py encodes a DER ProfileElement sequence (ProfileHeader with ICCID + Milenage AKA parameters carrying Ki/OPc) with asn1tools against the official PE_Definitions ASN.1 schema — no pySim Python imports (the schema is located from an installed pySim, or via SAIP_ASN_PATH). Self-contained and optional: saip_available() gates it and the app falls back to profile-v2 when prerequisites are missing. Optional deps in requirements-saip.txt. IMSI/other EFs (USIM-template PEs) are the documented next layer.
  • pytest.ini to silence asn1tools/pyparsing deprecation warnings.

Security

  • /api/export (dumps every subscriber's Ki/OPc) now requires an admin token (server.admin_token, sent as X-Admin-Token, constant-time compared) and is disabled by default. Previously any WiFi client could pull all credentials unauthenticated.
  • Profile download URLs now use an unguessable capability token (secrets.token_urlsafe) instead of the sequential subscriber id. Profiles contain Ki/OPc, so the old /api/profile/<int> scheme let anyone on the WiFi enumerate and harvest every subscriber's keys. Unknown tokens return a plain 404 (indistinguishable from evicted) so tokens cannot be probed.

Hardened

  • Core adapters set short MongoDB timeouts (serverSelectionTimeoutMS etc.) so an unreachable core fails provisioning fast instead of hanging ~30s.
  • In-memory profile cache is now a size-capped LRU (_MAX_CACHED_PROFILES) and can no longer grow without bound.
  • load_config rejects an empty/non-mapping YAML file and validates network.op_key is a 16-byte hex string, with clear error messages.
  • Fixed FileNotFoundError when database.path is a bare filename with no directory component: app.py and scripts/init_db.py now use a shared ensure_parent_dir helper that no-ops on an empty parent.
  • Open5GS/free5gc adapters now use the configured profile.apn for the core session/DNN name instead of a hardcoded "internet".
  • Connections set PRAGMA busy_timeout (5s) so concurrent writers under a threaded server wait for the lock instead of raising "database is locked".
  • Provisioning retries on the (astronomically rare) IMSI/ICCID collision instead of returning a 500.
  • The in-memory profile cache is guarded by a lock (thread-safe under a threaded server), and the captive portal disables the activate button while a request is in flight to prevent double provisioning.
  • setup_wifi_ap.sh: the deployed config.yaml (holds the OP key) is now chmod 600 and owned by the service user instead of world-readable, and data/ is chowned after init_db so the root-created SQLite file is writable by the esim service (previously it was root-owned and the service could not write to it).
  • Added test_app.py (API-level: provision, rate-limit, token download, 404 on unknown/enumerated ids, collision retry/give-up), test_utils.py (MAC/IMSI/op_key/config/ensure_parent_dir), and adapter APN coverage.

Added

  • Phase-2 profile generation. create_esim_profile now emits a structured profile package with real 3GPP encodings instead of a loose JSON mock:
    • enc_iccid — EF_ICCID encoding (nibble-swapped BCD, 10-byte, 0xF padded) per 3GPP TS 31.102 §4.2.1.
    • enc_imsi — EF_IMSI encoding (length octet + parity nibble + nibble-swapped BCD) per 3GPP TS 31.102 §4.2.2.
    • build_lpa_activation_code — SGP.22 LPA activation string (LPA:1$<smdp>$<matching-id>) so the profile carries a device-consumable activation reference.
  • CHANGELOG.md.

Changed

  • Profile package type is now profile-package-v2; it embeds the raw credentials plus the encoded EF byte values and the LPA activation code.

Fixed

  • generate_imsi hardcoded a 10-digit MSIN, producing invalid 16-digit IMSIs for a 3-digit MNC (e.g. MCC 302 / MNC 720). 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), making the rate-limit window unreliable. Both sides are now normalized via SQLite datetime().

Notes

  • Full SGP.22 SIMalliance Interoperable Profile (SAIP / ASN.1 UPP) generation via osmocom pySim remains the follow-up step; it needs an SM-DP+ / LPA to be installed onto consumer devices over the air. The current package targets programmable SIMs and core-network sync.

[0.1.0] — 2026-07-01

Added

  • Initial Phase-1 closed-loop eSIM provisioning stack: Flask API, SQLite subscriber store, credential generator (IMSI/Ki/OPc/ICCID), Open5GS and free5gc core adapters, captive portal, operational scripts, systemd units, network configs, and a test suite.