ADR-037 — SM Remote SM4 symmetric mode: ECB deprecation, IV transport, and vendor-contract versioning

This record was produced as an analysis + draft (BL-5 is an analysis task). It confirms the defect, quantifies the interop risk, lays out three options with tradeoffs, and records a recommended decision plus the open questions that a human crypto/product owner must answer before any code lands. RFC-vs-ADR note: because the change touches an external vendor wire contract, the maintainer may prefer to promote this to an docs/rfcs/ proposal for vendor consultation before accepting it as an ADR. Filed under decisions/ per the BL-5 task instruction; flip Status to Accepted (or move to rfcs/) at sign-off.

Context

The defect (confirmed against src/, 2026-07-12)

The SmRemote service (src/SmRemote/StellaOps.SmRemote.Service/Program.cs) supports SM4-ECB as its only symmetric mode. ECB is not semantically secure: identical 16-byte plaintext blocks map to identical ciphertext blocks, leaking plaintext structure. There is no IV and no integrity tag.

Verified locations (file:line):

Is this on a real data path or a key-wrap? (materiality)

The blast radius is narrow, which is what makes a fix affordable:

Bottom line: the SM4-ECB exposure is a real weak default on a published contract, but not currently protecting any live in-repo data. SM4 through SmRemote is best understood today as a GM/T vendor-bridge interop primitive, not a general-purpose confidentiality API.

Interop risk of changing it — does the remote SM peer dictate the mode?

The mode is effectively co-dictated and is negotiable in principle but only through the contract-version mechanism:

Options considered

Option 1 — Stay ECB, document the risk (status quo)

Option 2 — Hard cutover to CBC/GCM with an IV

Option 3 — Make the mode negotiable (RECOMMENDED, with a safe default)

Bump the contract to sm-remote-vendor-adapter.v2 that adds SM4-CBC (new default) and SM4-GCM, keeps SM4-ECB reachable only under an explicit interop opt-in (mirroring GOST’s ECB-Interop), and picks the strongest mutually advertised mode — failing closed rather than silently downgrading.

Decision (Accepted 2026-07-20 — owner selected Option 3)

Adopt Option 3, concretely:

  1. New default SM4-CBC; add SM4-GCM. TryGetSupportedSm4Algorithm (Program.cs:726-736) accepts SM4-CBC (mapping bare SM4SM4-CBC), SM4-GCM, and — only under opt-in — SM4-ECB.
  2. ECB behind an explicit opt-in, mirroring GOST. Introduce SMREMOTE_SM4_MODE with values CBC (default), GCM, ECB-Interop, exactly paralleling GostSymmetricModes / the GostOptions.SymmetricMode default of CBC and the loud startup warning on ECB (GostPlugin.cs:81-96,88-93,583-594; crypto architecture §4.4). No bare ECB token — the -Interop suffix makes the security waiver self-documenting.
  3. Contract bump …v1…v2. v2 adds IV/tag transport and the CBC/GCM algorithm ids; v1 remains valid and means ECB-only. Operators pin via SM_REMOTE_HSM_CONTRACT_VERSION.
  4. IV/tag on the wire (explicit fields, not prepend). Add IvBase64 to SmRemoteEncrypt{Request,Response} and SmRemoteDecryptRequest, and a GCM TagBase64 (or a documented ct‖tag convention). Prefer explicit fields over the SmPlugin/GostPlugin “IV‖ciphertext” envelope so the HSM can own IV generation and the wire stays self-describing. The in-process plugins already support a caller-owned IV (options.Iv), so this aligns the surfaces.
  5. Fail-closed mode negotiation. Extend the /status validation (today only asserts SM2, SmRemoteHttpClient.cs:410-414) to require the requested SM4 mode be present in supportedAlgorithms; otherwise throw algorithm_missing. Never downgrade to ECB implicitly. Update SupportsOperation (Program.cs:826-834) to match the negotiated mode’s key descriptor.
  6. Collapse the divergent soft path. Replace the inline ECB PaddedBufferedBlockCipher (Program.cs:379-385, :458-462) with CBC/GCM — ideally by delegating to the already-correct SmPlugin rather than hand-rolling a second SM4 implementation (also resolves the PKCS7-vs-zero-pad inconsistency noted above).
  7. Conformance vectors. Add frozen, deterministic GM/T 0002 SM4-CBC and SM4-GCM known-answer test vectors (offline, no network) covering encrypt→decrypt round-trips and cross-checking against SmPlugin; keep an explicit ECB-Interop vector so the legacy path stays verifiable. (BL-5’s completion criterion “if changed, conformance vectors pass” applies here.)

Until this ADR is Accepted and implemented, the status quo (Option 1) stands as the documented, tracked interim — the SmRemote dossier already carries the confidentiality warning and marks /encrypt /decrypt as vendor-bridge interop primitives, not a general-purpose API.

Consequences

Decision owed to the operator (crypto + product call)

These cannot be resolved from the codebase and must be answered before implementation:

  1. Do the operator’s contracted/deployed vendor SM-HSM bridges support SM4-CBC and/or SM4-GCM, or are they ECB-only? This decides whether a clean v2-only cutover (Option 2) is feasible or whether ECB-Interop must remain indefinitely (Option 3). (Recommendation: assume Option 3 unless the operator confirms zero ECB-only bridges.)
  2. Is SmRemote SM4 used by any real deployment today, or is all confidentiality handled by the in-process SmPlugin (credential-store DEK wrap)? If SmRemote SM4 has zero real traffic, Option 2 becomes viable with negligible breakage. (In-repo evidence: no consumer — see materiality.)
  3. Default mode: CBC or GCM? GCM (AEAD/integrity) is preferred where the vendor supports it; CBC as the interoperable floor. (Recommendation: negotiate GCM→CBC, fail-closed, ECB only on opt-in.)
  4. Is off-ECB a compliance gate (GM/T conformance) or a best-effort default? i.e. should ECB-Interop be permanently allowed or sunset on a date?
  5. ADR or RFC? Because this alters an external wire contract, promote to docs/rfcs/ for vendor consultation first, or accept directly as this ADR? (Maintainer’s call.)

Rejected / non-goals