175ca934d8
- scp81.py: PSK TLS listener (stdlib ssl PSK callbacks) speaking the GP HTTP administration dialog; configurable framing (chunked/Content-Length, TLS record split, Apache-style/compact headers, Connection header, keep-alive, Next-URI template with %d, TLS version/cipher, answer delay, keylog for capture decryption) - server.py: script responder + Response Scripting parsing (AF/AB, 80/23 TLVs), memory decoder, SCP81 start options, terminal-side timer management, background-mode BIP events, permissive OPEN CHANNEL - BIP fix: the RECEIVE DATA channel-data TLV length is BER long form (36 81 <len>) above 127 bytes; a raw length byte is mis-parsed on the card, so the large TLS records never reached its stack (a live card fetched the script response and silently never processed it - endless resume). The card now executes scripts and returns R-APDUs: memory (13 applets, 50646 B NV free, 2402 B volatile), ISD, stored HTTP OTA parameters, ELF and application registries - frontend: SCP81 tab (listener, script selection, HTTP OTA log), phone event forms, i18n; service worker v141 - docs: api.md, scp81-findings.md (attempt matrix + root cause analysis); tools/scp81_decrypt.py decrypts listener captures via the keylog - tests: 187 python + 337 frontend
542 lines
18 KiB
Markdown
542 lines
18 KiB
Markdown
# pysim-otaman-server — HTTP API reference
|
||
|
||
The server exposes a JSON HTTP API under `/api/*`. All responses carry
|
||
`Access-Control-Allow-Origin: *` (plus `Access-Control-Allow-Private-Network: true`
|
||
on the preflight), so the API is reachable from a separately-hosted PWA.
|
||
|
||
## Version compatibility
|
||
|
||
| Server | PWA (OTAMan) | Status |
|
||
|--------|-------------|--------|
|
||
| 1.x.x | 1.x.x | ✅ Compatible |
|
||
| 0.x.x | 1.x.x | ❌ Outdated — update server |
|
||
| 2.x.x+ | 1.x.x | ⚠️ Server newer — update PWA |
|
||
|
||
The server reports its version via `GET /api/version`. The PWA checks this on
|
||
connect and warns if versions are incompatible.
|
||
|
||
## Endpoints
|
||
|
||
| Endpoint | Method | Purpose |
|
||
|----------|--------|---------|
|
||
| `/api/version` | GET | Server version string |
|
||
| `/api/status` | GET | Card reader + card info + current selection |
|
||
| `/api/command` | POST | pySim command (equip, status, tree, etc.) |
|
||
| `/api/commands` | GET | List available pySim commands |
|
||
| `/api/tree` | POST | File tree browser for given FID/name |
|
||
| `/api/select` | POST | Select a file by name or FID |
|
||
| `/api/read` | POST | Read file content |
|
||
| `/api/write` | POST | Write raw hex data to a file |
|
||
| `/api/apdu` | POST | Raw APDU send |
|
||
| `/api/help` | POST | pySim help for a given command |
|
||
| `/api/send-ota` | POST | SCP80 OTA secured packet delivery |
|
||
| `/api/ram-install` | POST | Install a Java Card `.cap` file via SCP80 (INSTALL[for load] → LOAD ×N → INSTALL[for install]) |
|
||
| `/api/sp-verify` | POST | Verify secured packet against pySim reference |
|
||
| `/api/menu` | GET | Current STK menu (title + items + active) |
|
||
| `/api/menu-select` | POST | ENVELOPE(Menu Selection) with item_id |
|
||
| `/api/menu-respond` | POST | TERMINAL RESPONSE for paused STK command |
|
||
| `/api/stk-status` | GET | STK session state (active/pending/type) |
|
||
| `/api/events` | GET | Event list from SET UP EVENT LIST |
|
||
| `/api/event-send` | POST | Send ENVELOPE(Event Download) |
|
||
| `/api/proactive-log` | GET | Last 50 proactive commands |
|
||
| `/api/status-poll` | POST | Manual STATUS poll + FETCH if 91XX |
|
||
| `/api/rescue` | POST | Re-send TERMINAL PROFILE to recover CAT session |
|
||
| `/api/poll-status` | GET | Background STATUS polling state |
|
||
| `/api/poll-toggle` | POST | Enable/disable background polling |
|
||
| `/api/pli-qualifiers` | GET | List of qualifier codes with descriptions |
|
||
| `/api/pli-dict` | GET | Current dictionary (hex values per qualifier) |
|
||
| `/api/pli-dict` | POST | Update dictionary entries |
|
||
| `/api/scp81/bip` | POST | Start/stop the HTTP OTA listener (dump capture or PSK TLS server) |
|
||
| `/api/scp81/status` | GET | BIP terminal + listener state (channels, PSK identity seen) |
|
||
| `/api/scp81/log` | GET | HTTP OTA event log (`?after=<seq>`) |
|
||
| `/api/scp81/log-clear` | POST | Clear the HTTP OTA event log |
|
||
|
||
## Endpoint details
|
||
|
||
### `GET /api/version`
|
||
|
||
Returns server version for compatibility checking.
|
||
|
||
**Example response:**
|
||
```json
|
||
{"version": "2.1.2"}
|
||
```
|
||
|
||
### `GET /api/status`
|
||
|
||
Card reader, card type, current selection, and card state.
|
||
|
||
### `GET /api/commands`
|
||
|
||
List all available shell commands for the current card profile.
|
||
|
||
### `POST /api/command`
|
||
|
||
Execute any pysim-shell command.
|
||
|
||
```json
|
||
{"cmd": "select MF"}
|
||
```
|
||
|
||
Returns:
|
||
|
||
```json
|
||
{"output": "..."}
|
||
```
|
||
|
||
### `POST /api/apdu`
|
||
|
||
Send a raw APDU to the card.
|
||
|
||
```json
|
||
{"apdu": "00A4040000..."}
|
||
```
|
||
|
||
Returns:
|
||
|
||
```json
|
||
{"response": "...", "sw": "9000"}
|
||
```
|
||
|
||
### `POST /api/help`
|
||
|
||
Get structured help for a shell command.
|
||
|
||
```json
|
||
{"cmd": "apdu"}
|
||
```
|
||
|
||
Returns:
|
||
```json
|
||
{"usage": "apdu [-h] [--expect-sw EXPECT_SW] [--raw] APDU", "description": "...", "args": [{"name": "APDU", "type": "positional", "help": "..."}]}
|
||
```
|
||
|
||
### `POST /api/send-ota`
|
||
|
||
Send an OTA command (SCP80) to the card via SMS-PP-DOWNLOAD ENVELOPE.
|
||
The secured packet is delivered in an SMS-DELIVER TPDU wrapped in an ENVELOPE command.
|
||
|
||
**Request body:**
|
||
```json
|
||
{
|
||
"sp": "00201516011515b00000...",
|
||
"spi1": "16",
|
||
"spi2": "01",
|
||
"kic": "15",
|
||
"kid": "15",
|
||
"tar": "b00000",
|
||
"cntr": "0000000001",
|
||
"kicKey": "D6FCC023...",
|
||
"kidKey": "1B07E7E0..."
|
||
}
|
||
```
|
||
|
||
**Response (delivery PoR):**
|
||
```json
|
||
{"success": true, "sw": "9000", "response_data": "027100000e0a...",
|
||
"por": {"response_status": "por_ok", "tar": "B00000", "pcntr": 0,
|
||
"decoded": {"number_of_commands": 1, "last_status_word": "6e00",
|
||
"last_response_data": ""}}}
|
||
```
|
||
|
||
**Response (submit PoR):** PoR is extracted from the SMS-SUBMIT TPDU
|
||
fetched via a proactive command (FETCH). The response contains the
|
||
same `por` structure if decoding succeeds.
|
||
|
||
The SPI2 `por_in_submit` bit (0x20) selects submit-mode PoR.
|
||
|
||
### `POST /api/ram-install`
|
||
|
||
Install a Java Card `.cap` file on the card via GlobalPlatform commands (INSTALL[for load] → LOAD ×N → INSTALL[for install (+ make selectable)]) wrapped in SCP80 secured packets. Each step is sent via ENVELOPE and its PoR is checked; the sequence aborts on the first PoR error. The `.cap` archive (a ZIP of nested components) is parsed server-side in `_cap_parse`; no external tooling is required.
|
||
|
||
**Request body:**
|
||
```json
|
||
{
|
||
"cap_hex": "DECAFFED...",
|
||
"sd_aid": "A000000003000000",
|
||
"install_params": "C90000",
|
||
"stk_params": "",
|
||
"nv_quota": 0,
|
||
"volatile_quota": 0,
|
||
"make_selectable": true,
|
||
"spi1": "0E", "spi2": "01",
|
||
"kic": "15", "kid": "15",
|
||
"tar": "000000",
|
||
"cntr": "0000000001",
|
||
"kicKey": "D6FCC023...",
|
||
"kidKey": "1B07E7E0..."
|
||
}
|
||
```
|
||
|
||
| Field | Req | Description |
|
||
|---|---|---|
|
||
| `cap_hex` | yes | Even-length hex of the `.cap` file (zipped Java Card CAP), max 48 kB (98304 hex chars) |
|
||
| `sd_aid` | no | Security Domain AID for INSTALL[for load]; empty → default ISD `A000000003000000` |
|
||
| `install_params` | no | Hex C9 TLV install parameters; if empty, `gen_install_parameters()` is used with the quota/stk params |
|
||
| `stk_params` | no | Hex CA TLV (TS 102 226 §8.2.1.3.2.1) for SIM toolkit app-specific params |
|
||
| `nv_quota` / `volatile_quota` | no | Integer memory quotas (bytes) for `gen_install_parameters()` |
|
||
| `make_selectable` | no | If true (default), final INSTALL uses P1=`0C` (install + make selectable) |
|
||
|
||
**Response (success):**
|
||
```json
|
||
{"success": true, "failed_step": null,
|
||
"steps": [{"name": "install_for_load", "apdu": "80E60200...", "por_status": "por_ok", "sw": "9000"},
|
||
{"name": "load_0", "apdu": "80E80000...", "por_status": "por_ok", "sw": "9000"},
|
||
{"name": "install_for_install", "apdu": "80E60C00...", "por_status": "por_ok", "sw": "9000"}],
|
||
"final_cntr": "0000000004",
|
||
"load_file_aid": "A000000003000000",
|
||
"module_aid": "A000000003000000",
|
||
"application_aid": "A000000003000000"}
|
||
```
|
||
|
||
**Response (failure):**
|
||
```json
|
||
{"success": false, "failed_step": "load_1",
|
||
"steps": [{"name": "install_for_load", "por_status": "por_ok", "sw": "9000"},
|
||
{"name": "load_1", "por_status": "rc_error", "sw": null}],
|
||
"error": "..."}
|
||
```
|
||
|
||
The `steps` array contains one entry per GP command. `final_cntr` is the counter value after all successful steps (use it to update the card preset). The response is not streamed — all steps run server-side before the JSON is returned.
|
||
|
||
### `POST /api/sp-verify`
|
||
|
||
Cross-check a secured packet against pySim's `OtaDialectSms.encode_cmd`
|
||
reference. Returns the JS-generated packet, pySim reference, a match flag,
|
||
and the decoded SPI fields.
|
||
|
||
```json
|
||
{"spi1": "16", "spi2": "01", "kic": "15", "kid": "15", "tar": "b00000",
|
||
"cntr": "0000000001", "apdu": "00a40000023f00",
|
||
"kicKey": "D6FCC023...", "kidKey": "1B07E7E0..."}
|
||
```
|
||
|
||
**Response:**
|
||
```json
|
||
{"js_sp": "...", "py_sp": "...", "match": true,
|
||
"diffs": [], "spi": {"counter": "counter_must_be_higher", ...}}
|
||
```
|
||
|
||
### `GET /api/menu`
|
||
|
||
Returns the SIM Toolkit SETUP MENU captured from the card's TERMINAL PROFILE
|
||
response at startup. Empty `{"items": []}` if the card didn't send a menu.
|
||
|
||
**Response:**
|
||
```json
|
||
{"command_number": 1, "items": [{"id": 128, "text": "Настройки/Settings"}],
|
||
"title": "Alfa Mobile", "active": false}
|
||
```
|
||
|
||
### `POST /api/menu-select`
|
||
|
||
Sends an `ENVELOPE(MENU SELECTION)` with the selected item ID, then handles
|
||
the card's proactive response (DISPLAY TEXT or SELECT ITEM).
|
||
|
||
```json
|
||
{"item_id": 128}
|
||
```
|
||
|
||
**Response:**
|
||
```json
|
||
{"type": "display_text", "text": "Hello", "sw": "9122"}
|
||
```
|
||
or
|
||
```json
|
||
{"type": "select_item", "items": [{"id": 1, "text": "Sub-menu"}], "sw": "9122"}
|
||
```
|
||
|
||
### `POST /api/menu-respond`
|
||
|
||
Sends `TERMINAL RESPONSE` to the current proactive command with the given result
|
||
code. Continues the proactive chain if the card responds with `91XX`. If no
|
||
response arrives within `--menu-timeout` seconds (default 60, `0` disables), the
|
||
server watchdog sends the `timeout` result itself.
|
||
|
||
```json
|
||
{"result": "ok", "item_id": 1}
|
||
```
|
||
|
||
| `result` | TERMINAL RESPONSE code | Meaning |
|
||
|---|---|---|
|
||
| `ok` | `0x00` | Command performed successfully |
|
||
| `cancel` | `0x10` | Proactive session terminated by the user |
|
||
| `back` | `0x11` | Backward move in the proactive session requested by the user |
|
||
| `timeout` | `0x12` | No response from the user |
|
||
|
||
### `GET /api/stk-status`
|
||
|
||
Returns the current STK session state.
|
||
```json
|
||
{"active": true, "pending": true, "pending_type": "select_item"}
|
||
```
|
||
|
||
### `POST /api/read`
|
||
|
||
Read file content. Auto-detects transparent vs record files.
|
||
|
||
```json
|
||
{"name": "EF.ICCID", "fid": "2FE2", "parent_path": ["MF"], "mode": "raw"}
|
||
```
|
||
|
||
Returns transparent data:
|
||
```json
|
||
{"success": true, "sw": "9000", "file_type": "transparent", "data": "...",
|
||
"apdu_times": [{"type": "select", "ms": 12}, {"type": "read_binary", "ms": 9}]}
|
||
```
|
||
|
||
Returns records:
|
||
```json
|
||
{"success": true, "sw": "9000", "file_type": "linear_fixed",
|
||
"records": [{"num": 1, "data": "..."}, {"num": 2, "data": "..."}],
|
||
"apdu_times": [{"type": "select", "ms": 12},
|
||
{"type": "read_record", "ms": 11}, {"type": "read_record", "ms": 13}]}
|
||
```
|
||
|
||
`apdu_times` reports each command's duration (command sent to response
|
||
received) classified as `select`, `read_binary` or `read_record`; the PWA uses
|
||
it for snapshot timing statistics. Other commands are not reported.
|
||
|
||
### `POST /api/write`
|
||
|
||
Write raw hex data to a file.
|
||
|
||
```json
|
||
{"name": "EF.ICCID", "fid": "2FE2", "data": "A0A1A2...", "parent_path": ["MF"]}
|
||
```
|
||
|
||
For record files:
|
||
```json
|
||
{"name": "EF.ADN", "fid": "6F3A", "data": "A0A1...", "record_nr": 1, "parent_path": ["MF", "7F10"]}
|
||
```
|
||
|
||
Returns:
|
||
```json
|
||
{"success": true, "sw": "9000"}
|
||
```
|
||
|
||
### `POST /api/select`
|
||
|
||
Select a file by name or FID, with optional parent selection.
|
||
|
||
```json
|
||
{"name": "EF.ICCID", "fid": "2FE2", "parent_path": ["MF"]}
|
||
```
|
||
|
||
`parent_path` lists the path segments from MF to the parent (ADF names or
|
||
FIDs); the legacy single-segment `parent_sel` is still accepted but is only
|
||
unambiguous for ADFs. Resolution is strictly parent-scoped: model-known files
|
||
are selected through the requested parent only (pySim `select_file()`), never
|
||
via pySim's global selectables or its `probe_file()` model injection, so a
|
||
same-FID file under another parent is never picked and the filesystem model
|
||
is not modified. `allow_probe: true` (PWA custom files) additionally allows a
|
||
model-unknown 4-hex FID to be selected directly; any temporary model object
|
||
created for it is detached again before the response is sent.
|
||
|
||
Returns:
|
||
```json
|
||
{"name": "EF.ICCID", "fid": "2FE2", "file_type": "transparent",
|
||
"file_size": 10, "record_len": null, "num_of_rec": null,
|
||
"fci_hex": "621082024021...",
|
||
"apdu_times": [{"type": "select", "ms": 12}], "exists": true}
|
||
```
|
||
|
||
`fci_hex` is the raw FCP template (`'62'`) from the SELECT response, used by
|
||
the PWA's Exact FCI checks; `file_size` / `record_len` / `num_of_rec` drive
|
||
the profiler's size and record checks. When the file does not exist the
|
||
endpoint responds `404` with `{"error": "...", "exists": false}`.
|
||
|
||
### `POST /api/tree`
|
||
|
||
Get directory listing with typed children.
|
||
|
||
```json
|
||
{"name": "MF", "fid": "3F00"}
|
||
```
|
||
|
||
Use `parent_path` (or the legacy `parent_sel`) to list a subdirectory, e.g.
|
||
`{"name": "DF.GSM-ACCESS", "fid": "5F3B", "parent_path": ["MF", "ADF.USIM"]}`.
|
||
|
||
Returns:
|
||
```json
|
||
{"exists": true, "name": "MF", "fid": "3F00", "file_type": "df", "children": [{"name": "EF.ICCID", "fid": "2fe2", "isDir": false}]}
|
||
```
|
||
|
||
### `GET /api/events`
|
||
|
||
Returns the event list captured from the card's SET UP EVENT LIST (an array of
|
||
event byte values, or `[]` when none was received).
|
||
|
||
### `POST /api/event-send`
|
||
|
||
Sends an `ENVELOPE(Event Download)` for a subscribed event.
|
||
|
||
```json
|
||
{"event_type": 4, "event_data": "01A0"}
|
||
```
|
||
|
||
`event_type` is required (the SET UP EVENT LIST event byte); `event_data` is
|
||
optional hex for events that carry data. Returns the SW and any response data:
|
||
|
||
```json
|
||
{"sw": "9000", "data": "..."}
|
||
```
|
||
|
||
Channel status (event `0x0A`, TS 102 223 §8.56) carries the Channel status TLV
|
||
`B8 02 <status> <info>`, where the status byte is the channel id (1–7) OR-ed
|
||
with the state bits (0x00 link not established / 0x40 TCP LISTEN / 0x80 link
|
||
established) and the info byte is `00` (no further info) or `05` (link
|
||
dropped). The server also sends this event automatically when a BIP link drops
|
||
outside a proactive command and the card subscribed to `0x0A`.
|
||
|
||
### `GET /api/proactive-log`
|
||
|
||
Returns the last 50 proactive commands fetched during CAT sessions, newest
|
||
first:
|
||
|
||
```json
|
||
[{"type_hex": "25", "type_name": "SET UP MENU", "elapsed": 3.2, "bytes": 97}]
|
||
```
|
||
|
||
### `POST /api/status-poll`
|
||
|
||
Manually sends `STATUS` (F2) and, if the card answers `91XX`, runs the
|
||
proactive chain (FETCH → TERMINAL RESPONSE) until it settles. Returns:
|
||
|
||
```json
|
||
{"sw": "9000", "proactive": true}
|
||
```
|
||
|
||
### `POST /api/rescue`
|
||
|
||
Recovers a stuck CAT session by clearing the pending state and re-sending the
|
||
TERMINAL PROFILE. Returns whether a menu and event list were captured again:
|
||
|
||
```json
|
||
{"menu": true, "events": [4, 5]}
|
||
```
|
||
|
||
### `GET /api/poll-status`
|
||
|
||
Background STATUS polling state.
|
||
|
||
```json
|
||
{"enabled": true, "interval": 300}
|
||
```
|
||
|
||
### `POST /api/poll-toggle`
|
||
|
||
Turns background STATUS polling on or off.
|
||
|
||
```json
|
||
{"enabled": true}
|
||
```
|
||
|
||
Returns the new state (`{"enabled": ..., "interval": ...}`).
|
||
|
||
### `GET /api/pli-qualifiers`
|
||
|
||
Lists the PROVIDE LOCAL INFORMATION qualifier codes with their names.
|
||
|
||
```json
|
||
[{"code": "00", "name": "Location Information"}, {"code": "0A", "name": "Battery Charge Level"}]
|
||
```
|
||
|
||
### `GET /api/pli-dict`
|
||
|
||
Returns the current PLI data dictionary as a qualifier-code map.
|
||
|
||
```json
|
||
{"00": "0291...", "0A": "64"}
|
||
```
|
||
|
||
### `POST /api/pli-dict`
|
||
|
||
Updates dictionary entries. Body is a map of qualifier code to hex value; keys
|
||
must be known qualifiers and values valid hex, otherwise they are ignored.
|
||
Returns the updated dictionary.
|
||
|
||
### `POST /api/scp81/bip`
|
||
|
||
Starts or stops the local target the card's BIP channel is redirected to.
|
||
|
||
Dump mode (default) captures whatever the card sends (e.g. its TLS
|
||
ClientHello) without answering:
|
||
|
||
```json
|
||
{"action": "start", "mode": "dump", "host": "127.0.0.1", "port": 8443}
|
||
```
|
||
|
||
TLS mode runs the Phase B PSK TLS server (GPC v2.2 Amendment B): the PSK key
|
||
and optional identity are applied to the TLS handshake, and the GP HTTP
|
||
administration dialog (`X-Admin-*` headers, 200 with a command string or 204
|
||
No Content) is served. `psk_hex` is required (the previous key is reused when
|
||
omitted); `psk_identity` restricts the accepted identity. The key is never
|
||
stored or logged.
|
||
|
||
```json
|
||
{"action": "start", "mode": "tls", "host": "127.0.0.1", "port": 8443,
|
||
"psk_hex": "00112233445566778899aabbccddeeff",
|
||
"psk_identity": "89012345678901234567"}
|
||
```
|
||
|
||
Stop either mode with `{"action": "stop"}` (also disables the BIP terminal).
|
||
|
||
### `GET /api/scp81/status`
|
||
|
||
```json
|
||
{"bip": {"enabled": true, "target": "127.0.0.1:8443", "channels": [], "seq": 12},
|
||
"listener": {"mode": "tls", "host": "127.0.0.1", "port": 8443,
|
||
"psk_identity": null, "identity_seen": "89012345678901234567"}}
|
||
```
|
||
|
||
### `GET /api/scp81/log`
|
||
|
||
Returns the BIP/TLS event log (open/close, SEND/RECEIVE DATA hex, TLS
|
||
handshake and HTTP request/response records). `?after=<seq>` returns only
|
||
newer entries; `seq` echoes the latest sequence number.
|
||
|
||
### `GET /api/scp81/script`
|
||
|
||
Returns the active command script and the R-APDUs collected so far:
|
||
|
||
```json
|
||
{"script": ["80CAFF2100", "80F28002024F0000"], "sent": 1,
|
||
"results": [{"index": 1, "sw": "9000", "rapdu": "FF210C810102..."}]}
|
||
```
|
||
|
||
The script is selected when starting the TLS listener with the `script`
|
||
parameter: `explore` (default — the reference administration server's command
|
||
sequence: GET DATA FF21 extended resources / free memory, GET STATUS P1=80
|
||
Issuer Security Domain, GET DATA 0085 HTTP administration parameters, GET
|
||
STATUS P1=40 executable load files and P1=10 applications), `none` (answer
|
||
every POST with 204), or an explicit list of APDU hex strings. Each APDU is
|
||
delivered in an `AE 80 22 <len> <apdu> 00 00` Command Scripting template
|
||
(TS 102 226 §5.2.1) with `X-Admin-Next-URI`; the card returns its R-APDUs in
|
||
the next POST's Response Scripting template, which is parsed and logged
|
||
(`script-rapdu`, `script-memory`).
|
||
|
||
TLS mode also accepts `chunked` (**default `true`** — the reference server's
|
||
chunked framing; the card rejects a chunked response that also carries a
|
||
Content-Length) and `chunk_size` (default `0` — the whole response in one TLS
|
||
record, as in the decrypted reference session; a positive value writes the
|
||
head and each body piece as its own record). Both are echoed by
|
||
`GET /api/scp81/status`.
|
||
|
||
`keep_alive` (default `true`, matching the reference session: the card sends
|
||
all its POSTs on one connection until the 204) ends the TLS connection after
|
||
each response
|
||
(after the card drained the BIP buffer, with `close_notify`, so the card
|
||
processes the script and opens a new connection for its next POST);
|
||
`compact_headers` (default `false`) drops the space after each header colon,
|
||
`apache_headers` (default `true`) adds Date/Server/X-Powered-By like the
|
||
reference servers and puts Transfer-Encoding before Content-Type,
|
||
`conn_header` (default `'none'` = omit the header, like the reference)
|
||
declares the connection fate, `tls_version` pins `1.1`/`1.0` for cards that
|
||
only speak the older record layer, `cipher` pins one suite, `next_uri`
|
||
overrides the per-command `X-Admin-Next-URI` (`%d` = command id; empty string
|
||
omits the header), `link_events` (default `true`) controls the automatic
|
||
Channel status events, `answer_delay` waits before answering a request. `keylog` writes the TLS traffic secrets to
|
||
the given file (SSLKEYLOGFILE format) for debugging captures — it contains key
|
||
material, use a temporary path.
|