OTA SIM Management — over-the-air RFM/RAM
The OTA module manages SIMs over the air: it reads and edits files on a remote card by sending secured packets (ETSI TS 102 225 / 3GPP TS 31.115 — "03.48" / SCP80) that carry Remote File Management (RFM) and Remote Application Management (RAM) commands. The card verifies and decrypts each packet, executes the commands, and returns a signed Proof-of-Receipt (POR).
In practice it gives you the same file-level editing as the offline SIM Filesystem Explorer — browse the file tree, decode an EF, edit the fields, write it back — except the card is reached remotely: through a PC/SC reader on the central host, a reader at a remote agent, or over SMPP to a SIM in the field.
It builds on the same per-ICCID key store as the SIM Key Store: a profile says which keyset and algorithms, the key store holds the actual KIC/KID for that card, and the engine never logs or returns the keys — only the resulting ciphertext and the POR.

What a secured packet is
A secured packet protects the command APDUs end-to-end, independent of the (plain) bearer. The engine has been validated against physical ISIM cards.
Command Packet = CPL | CHL | SPI | KIc | KID | TAR(3) | CIPHERED
CIPHERED = 3DES-CBC( CNTR(5) | PCNTR(1) | CC(8) | APDUs+padding ) IV=0
CC = last8( 3DES-CBC( KID, zeropad(CPL+hdr+CNTR+PCNTR+APDUs) ) )
- TAR (Toolkit Application Reference, 3 bytes) routes the packet to a service on the card — a specific RFM application or the RAM (card manager).
- SPI flags select ciphering, the cryptographic checksum (CC), the counter policy, and what the POR must contain.
- KIc / KID index which transport (ciphering) and authentication keyset the card uses; the values come from the key store per ICCID.
The APDUs inside are an ordinary C-APDU script — SELECT + READ BINARY for an
RFM read, SELECT + UPDATE BINARY for a write, GlobalPlatform DELETE /
INSTALL for RAM.
On the wire (an SMS-PP) the packet rides in the TP-User-Data with a UDH Command
Packet Identifier (IEI 0x70), PID 0x7F (SIM data download), DCS 0xF6
(8-bit, message class 2). In-shell it is delivered as a CAT
ENVELOPE(SMS-PP-Download) APDU and the POR comes back synchronously.
Profiles, contexts, and auto-routing
An OTA profile is a reusable security preset — it holds no key material:

| Field | Meaning |
|---|---|
tar | 3-byte Toolkit Application Reference (the target service). |
kic_index / kid_index | Which keyset (1/2/3) → KIC{n}/KID{n} from the card's key-store row. |
cipher_algo / cc_algo | Ciphering and checksum algorithm (triple_des_cbc2, aes_cbc, …). |
ciphering, rc_cc_ds, counter_mode | SPI: encrypt? CC vs RC vs none? replay-counter policy. |
por, por_ciphered, por_rc_cc_ds | POR required? ciphered? signed? |
Two profiles are seeded, matching the values validated on sysmoISIM:
- RFM-USIM-3DES-keyset3 — TAR
B00011, keyset 3, 3DES, ciphering + CC, POR required + ciphered + CC. Operates inside ADF.USIM. - RFM-SIM-3DES-keyset2 — TAR
B00010, keyset 2. Operates on the MF-rooted 2G tree (DF.GSM / DF.TELECOM).
Different applications need different services, so the explorer routes the profile and the SELECT context automatically per file, from the file's application:
| File application | Context | Profile (by TAR) | Pre-SELECT before the ENVELOPE |
|---|---|---|---|
ADF.USIM | usim | TAR B00011 | SELECT ADF.USIM (by AID) |
DF.GSM, DF.TELECOM | sim | TAR B00010 | none (MF-rooted) |
ADF.ISIM | isim | TAR B00013 | SELECT ADF.ISIM (by AID) |
Each context maps to one of your profiles (defaulted by matching TAR, overridable in the explorer). Pick a card and click any file — USIM, GSM or TELECOM — and the right profile, keyset and SELECT are applied without switching anything. ISIM works as soon as you create a profile for it (its TAR varies by card).
Transports
A transport delivers the secured packet and, where the bearer allows, collects the POR. The pluggable adapters share one interface:
| Kind | Reaches | POR | Notes |
|---|---|---|---|
local_pcsc | a card in a PC/SC reader on the central host | synchronous | the directly-attached validation path. |
agent_reader | a card in a reader at a remote agent | synchronous | rides the same agent WebSocket as Test Devices / SIM Bank (session_apdu relay). |
smpp | a field SIM over SMPP submit_sm | asynchronous | MT SMS to an SMSC; the POR returns later as an MO SMS. |
The Explorer uses only the in-shell transports (it needs a synchronous POR); Campaigns can use any, including SMPP.
Explorer
src/pages/ota/OtaExplorer.tsx — the remote SIM file editor.
- Pick a target ICCID (from the SIM Key Store) and a transport.
- Browse the file catalog — every known EF from the offline file model (ADF.USIM, ADF.ISIM, DF.GSM, DF.TELECOM), grouped by application and searchable by code / name / description.
- Select a file → Read over OTA. The secured RFM read is delivered, the POR decoded, and the EF rendered: POR status, last SW, raw hex + ASCII, and a typed field form (decoded to named fields).
- Edit the fields (re-encoded automatically) or the raw hex, then Write
over OTA. The secured RFM
UPDATEis sent and the file is read back automatically so you see the on-card state after the write.
Reads/writes are gated by the ota control permission; everything is recorded as
a job.
Campaigns
src/pages/ota/OtaCampaigns.tsx — a saved operation fanned out across a set of
SIMs: target query → operation → profile → transport.

- Target query is evaluated against the SIM Bank inventory at run time: ICCID list / prefix, IMSI prefix or range, agent, bank. An empty query deliberately matches nothing (no accidental fan-out).
- Operation is the RFM/RAM command (read/update binary or record, GP delete / get-status / install).
- Run creates one job per matched card. SMPP campaigns are fire-and-forget (the POR arrives later as an MO SMS).
A run is bounded (CAMPAIGN_MAX_TARGETS) so a fat-finger query can't blast
thousands of cards in one request.
Jobs
src/pages/ota/OtaJobs.tsx — every OTA delivery (explorer reads/writes and
campaign runs) with its status and POR. The detail drawer shows the secured
request packet (ciphertext), the Proof-of-Receipt, and the decoded result
(commands executed, last SW, response data). Card secrets are never stored on
a job — only the ciphertext and POR.

REST API (/api/ota, JWT, RBAC element type ota)
| Method | Route | Purpose |
|---|---|---|
| GET | /profiles · POST · /profiles/<id> PUT/DELETE | OTA security profiles. |
| GET | /campaigns · POST · /campaigns/<id> GET/PUT/DELETE | Campaigns. |
| GET | /campaigns/<id>/targets | Preview which SIMs the query currently matches. |
| POST | /campaigns/<id>/run | Execute — one job per matched card. |
| GET | /jobs, /jobs/<id> | Job history (filter by campaign_id / iccid). |
| GET | /files/catalog | The browsable EF catalog (offline file model). |
| POST | /explore/read | Read one EF over OTA; returns the job + decoded EF. |
| POST | /execute | Run one operation (RFM update, RAM, raw) against one card. |
| POST | /preview | Build the secured packet without sending (inspect the ciphertext + SMS framings). |
| POST | /decode · /encode | Decode EF bytes ↔ typed fields (offline). |
RBAC mirrors simbank/ir38: admins always pass; otherwise a JWT permission
with element_type == "ota" grants view, and POST in its methods grants
control. Reads are treated as control (they transmit a packet to the card).
Routes are auto-documented in the API Reference.
Database schema
| Table | Row | Key columns |
|---|---|---|
ota_profiles | one security preset | name, tar, kic_index/kid_index, algorithms, SPI flags. |
ota_campaigns | one saved fan-out | name, profile_id, target_query (JSON), operation (JSON), transport (JSON), status. |
ota_jobs | one delivery to one card | iccid, operation, request_hex (ciphertext), response_hex (POR), por_status, result (JSON), status. |
Keys live in the existing simbank_keys table (the SIM Key Store),
referenced per ICCID by the profile's keyset index — not duplicated here.
How it works
The OTA engine handles the crypto, secured-packet structure, CAT/SMS TLVs and the offline file model needed to build and decode 03.48/SCP80 RFM/RAM packets. It runs in the agent-role backend service and shares the agent connection for the in-shell transport. Delivery is pluggable across the local-reader, remote-agent-reader, and SMPP transports.
Security notes
- Card secrets (KIC/KID) are read from the key store to build packets but are never written to the audit log or returned in any response — only the ciphertext request and the POR are stored.
- Writing a file is destructive; the explorer confirms before each write and reads back the result.
- The secured packet is genuine 03.48: tamper a byte and the card rejects it (the CC fails); with ciphering on, the plaintext APDUs never appear on the wire.
- SCP80 only — this is not SCP81 (no PSK-TLS / HTTPS Admin Agent).