A
2026-07-01 02:03:48 -04:00
|
|
|
# Changelog
|
|
|
|
|
|
|
|
|
|
All notable changes to this project are documented here.
|
|
|
|
|
|
|
|
|
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
|
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
|
|
|
|
|
|
|
## [Unreleased]
|
|
|
|
|
|
A
2026-07-01 02:11:14 -04:00
|
|
|
### Security
|
A
2026-07-01 18:01:59 -04:00
|
|
|
- `/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.
|
A
2026-07-01 02:11:14 -04:00
|
|
|
- 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.
|
A
2026-07-01 02:13:26 -04:00
|
|
|
- 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"`.
|
A
2026-07-01 02:16:25 -04:00
|
|
|
- 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.
|
A
2026-07-01 17:59:27 -04:00
|
|
|
- `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).
|
A
2026-07-01 02:11:14 -04:00
|
|
|
- Added `test_app.py` (API-level: provision, rate-limit, token download,
|
A
2026-07-01 02:16:25 -04:00
|
|
|
404 on unknown/enumerated ids, collision retry/give-up), `test_utils.py`
|
|
|
|
|
(MAC/IMSI/op_key/config/ensure_parent_dir), and adapter APN coverage.
|
A
2026-07-01 02:11:14 -04:00
|
|
|
|
A
2026-07-01 02:03:48 -04:00
|
|
|
### 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.
|