Repository navigation
Conversation
…licies - CreateSession / ProcessSessionCmd / CloseSession (AN12543 3.6.2, 4.5.1) with ECKey authentication (ECKeySessionInternalAuthenticate, master key from ECDH with the SE's ECKey session key pair 0x7FFF0201) and the session's SCP03 channel, also nested inside Platform SCP03. - Authentication Objects (INS_AUTH_OBJECT) with a persistent failed-attempt counter and a hard block at the maximum; ReadObjectAttributes reports it. - Policies evaluated for the session owner (rules are not inherited across Authentication Object IDs), read-only default for Authentication Objects, ALLOW_SIGN / ALLOW_VERIFY checked for MAC operations. - TLV lengths in the 0x81 nn form, as the NXP host encodes them. - SE050_SIM_TRACE=1 logs every command after SCP03 unwrapping. Unit test of the whole flow (counting, blocking, policy scoping, close). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the command path - An R-block with an error indication (the host did not get the last block, e.g. it gave up waiting) now gets the last single-frame response again. Before it got no answer, so the host, and any bridge relaying frames, waited forever. - With persistence enabled, the store was rewritten synchronously on every change, several times per command; on a busy disk that took up to a second, longer than NXP's T=1 host waits before asking for a retransmission. A background thread now writes the latest snapshot (temporary file, then rename). Unit tests keep the synchronous write. Unit test for the retransmission. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… all session modes - UserID sessions (AN12543 3.6.3.1, 4.5.2.1): VerifySessionUserID, plain session afterwards; attempts counted but not reported. WriteUserID creates UserID Authentication Objects (4-16 bytes, at most 255 attempts, not updatable). - AESKey sessions (3.6.3.2, 4.5.3): SCP03 handshake with the object's AES-128 key as static ENC/MAC/DEK and KVN 0; AES-128 Authentication Objects. - Attested reads (INS_READ | INS_ATTEST, applet 7.x layout) signed by the SE's attestation key pair 0xF0000012, as NXP's ECKey session setup verifies the SE's ECKey session public key that way. - SE050_SIM_DEMO_AUTH=1 provisions the demo Authentication Objects that Plug & Trust's examples use (0x7DA00001..3), and sdk-test/run_test.sh enables it for the session variants (and loads the Platform SCP03 keys for every *_PlatfSCP03 variant). With this, Dockerfile.sdk-test passes 32/32 with SE05X_AUTH = UserID, AESKey, ECKey, UserID_PlatfSCP03, AESKey_PlatfSCP03 and ECKey_PlatfSCP03, as with None and PlatfSCP03. Unit tests for the UserID and AESKey sessions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical flaws expose reserved keys, bypass authentication and authorization safeguards, preserve sessions across resets, and risk losing acknowledged persistent state.
10 open issues
Reject commands for sessions whose Authentication Object was deleted · New Recheck authentication blocking before completing pending handshakes · New Coalesce queued writes separately for each destination path · New Flush queued writes before persistent stores are dropped or reopened · New Protect reserved keys from generic private-key operations · New Enforce authorization in all AES and EC crypto paths · New Clear applet sessions on SCP reset and resynchronization · New Route authenticated attested reads through the attestation handler · New Validate AES-128 key length when updating Authentication Objects · New Reject unsupported authentication curves before key import or generation · New
What changed in this PR
Adds UserID and AESKey authentication sessions and attested reads to SE050Sim, building on the ECKey-session and transport improvements from #23 and #24.
Changes:
- Supports all three authentication-object types, attempt limits, and owner-scoped policies.
- Adds attested EC reads and demo provisioning for Plug & Trust session modes.
- Includes T=1 retransmission handling, background persistence, and session tests.
| File | Description |
|---|---|
SE050Sim/se050-sim/src/tlv.rs |
Accepts one-byte extended TLV lengths. |
SE050Sim/se050-sim/src/t1.rs |
Integrates applet sessions, tracing, and retransmission. |
SE050Sim/se050-sim/src/scp03/mod.rs |
Adds session extraction and key-based construction. |
SE050Sim/se050-sim/src/policy.rs |
Adds owner-specific access-rule lookup. |
SE050Sim/se050-sim/src/object_store/types.rs |
Defines persistent authentication-attempt metadata. |
SE050Sim/se050-sim/src/object_store/mod.rs |
Adds authentication state, reserved keys, and background writes. |
SE050Sim/se050-sim/src/lib.rs |
Exposes the applet-session module. |
SE050Sim/se050-sim/src/handlers/object_mgmt.rs |
Implements UserID creation and authentication attributes. |
SE050Sim/se050-sim/src/handlers/mac.rs |
Checks signing and verification permissions. |
SE050Sim/se050-sim/src/handlers/ec.rs |
Records EC authentication-object metadata. |
SE050Sim/se050-sim/src/handlers/aes.rs |
Adds AES-128 authentication-object creation. |
SE050Sim/se050-sim/src/applet_session.rs |
Implements session flows, attested reads, and tests. |
SE050Sim/sdk-test/run_test.sh |
Provisions demo objects and nested platform-session keys. |
Review effort: Balanced
| return ApduResponse::error(SW_WRONG_DATA); | ||
| } | ||
|
|
||
| let auth_id = session.auth_id; |
| self.sessions.remove(&sid); | ||
| return ApduResponse::error(SW_CONDITIONS_NOT_SATISFIED); | ||
| } | ||
| let resp = pending.external_authenticate(&raw); |
There was a problem hiding this comment.
Fixed in 922a22d: a pending AESKey handshake rechecks the block at EXTERNAL AUTHENTICATE and is refused without touching the counter, so it can neither pass nor clear a block. Covered by pending_aes_handshake_neither_passes_nor_clears_a_block.
| while let Ok((mut p, mut j)) = rx.recv() { | ||
| while let Ok((p2, j2)) = rx.try_recv() { | ||
| p = p2; | ||
| j = j2; | ||
| } | ||
| let tmp = p.with_extension("json.tmp"); | ||
| if std::fs::write(&tmp, &j).is_ok() { | ||
| let _ = std::fs::rename(&tmp, &p); | ||
| } | ||
| } |
| if let Ok(tx) = tx.lock() { | ||
| let _ = tx.send((path.to_path_buf(), json)); |
| self.objects.insert(id, SecureObject::ECKeyPair { | ||
| curve: crate::object_store::types::ECCurve::NistP256, | ||
| private_key: sk.to_bytes().to_vec(), | ||
| public_key: pk, | ||
| }); |
There was a problem hiding this comment.
Fixed in 6149ca8 and 922a22d: both 0x7FFF0201 and 0xF0000012 are applet-reserved, read only at dispatch (public key, attributes and existence still readable; create, replace, delete, sign and ECDH refused, also before they are provisioned). The attested-read and session flows use them internally. Covered by reserved_session_key_is_read_only and attested_read_inside_a_session_and_reserved_attestation_key.
| Some(raw) => crate::policy::ar_header_for(raw, &self.session_owner) | ||
| .map(|header| header & permission != 0) | ||
| .unwrap_or(false), | ||
| None if meta.and_then(|m| m.auth).is_some() => permission == crate::policy::POLICY_OBJ_ALLOW_READ, |
There was a problem hiding this comment.
Fixed: EC sign (ALLOW_SIGN) and ECDH (ALLOW_KA) in 6149ca8; AES one-shot and CipherInit (ALLOW_ENC / ALLOW_DEC) in 922a22d. Update and final use the key bound at init, so the check there covers multi-part. Covered by aes_authentication_object_policies_and_key_length and key_pair_authentication_object_does_not_sign_for_others.
| /// Applet sessions (CreateSession / ProcessSessionCmd), reset by SELECT. | ||
| applet_sessions: crate::applet_session::AppletSessions, |
| if cmd.base_ins() == INS_READ { | ||
| store.ensure_eckey_session_key(); | ||
| } | ||
| let r = crate::dispatch::dispatch(cmd, store, scp_active); |
There was a problem hiding this comment.
Fixed in 922a22d: an attested ReadObject inside an authenticated session now goes through the attestation handler, with the session's Authentication Object as the policy owner. Covered by attested_read_inside_a_session_and_reserved_attestation_key.
| // INS_AUTH_OBJECT: an AESKey Authentication Object (AES-128 only). | ||
| if apdu.ins & 0x40 != 0 { | ||
| if !matches!(store.get(&obj_id), Some(SecureObject::AESKey { key }) if key.len() == 16) { |
There was a problem hiding this comment.
Fixed in 922a22d: the key length is computed before anything is stored, and an Authentication Object, new or existing, only takes a 16-byte key. Otherwise 0x6A80 and the old key stays. Covered by aes_authentication_object_policies_and_key_length.
| // INS_AUTH_OBJECT: an ECKey Authentication Object, with its maximum | ||
| // number of failed attempts (0 = unlimited, at most 0x7FFF). | ||
| if apdu.ins & 0x40 != 0 { |
There was a problem hiding this comment.
Fixed in 6149ca8: ECKey Authentication Objects must be NIST P-256, checked before key import or generation, and an existing one cannot be rewritten on another curve. Covered by authentication_object_parameters_are_validated.
…sion lifetime Fixes from review feedback on the ECKey applet sessions. The SE's ECKey session key pair (0x7FFF0201) was an ordinary object: a plain caller could run ECDH with it against a host's ephemeral key and derive that session's keys, or sign, replace or delete it. Applet-reserved objects are now read only at dispatch: reads, attributes and existence checks work (hosts read the public key during session setup), any other command naming one of them gets 0x6985. Only the applet's own session flow uses the private key. EC signing and ECDH ignored object policies, so a key-pair Authentication Object could sign the authentication challenge for a caller without the private key. They now require POLICY_OBJ_ALLOW_SIGN and POLICY_OBJ_ALLOW_KA for the current session owner, as MAC generation already did. Objects without a policy keep the applet default (everything for ordinary objects, read only for Authentication Objects). A session only noticed that its Authentication Object was gone after running its next command, and a new object created under the same ID kept old sessions alive. Authentication Objects now carry a random instance tag; a session is tied to it and is refused (and dropped) before authentication or dispatch once the object is deleted or replaced, by whichever session. A malformed TAG_MAX_ATTEMPTS (wrong length, or above 0x7FFF) silently became "unlimited". It is now refused with 0x6A80 before anything is stored; an absent tag still means unlimited. ECKey Authentication Objects are also restricted to NIST P-256, the only curve sessions accept, so a P-384 one is refused instead of being created unusable. Applet sessions are now cleared by T=1 interface soft reset and resync, not only by SELECT, as a chip reset ends them. Tests: reserved key (ECDH, ECDH into it, sign, delete, replace; public read still works; also inside a session), key-pair Authentication Object refused for sign and ECDH, sessions ending on delete and recreate of their object, Authentication Object parameter validation, and a T=1 transport test for both resets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1 reset Fixes from review feedback on the T=1 retransmission and background store writes. The background writer kept only the last queued snapshot, whatever its destination, so two persistent stores in one process could lose each other's updates. Snapshots are now coalesced per path. Writes returned before reaching the disk and nothing waited for them, so a store dropped and reopened on the same file could load stale state: tests/scp03.rs set_platform_scp_request_flow failed on the reload of scp_required. There is now a flush barrier per path: loading a store file waits for its pending write, and dropping a persistent store waits for its own. APDU handling still never waits for the disk. The replacement file was created with the umask, so a store chmodded to 0600 became world-readable on its next update, and the temporary file was readable while being written. The store holds private keys and SCP03 keys: the temporary file is now created with mode 0600 and takes the mode of the file it replaces, if any. The cached response for an error R-block survived InterfaceSoftReset and resync, so a host could get a response from the previous link replayed. Both resets now clear it. Tests: per-path coalescing with two stores, reopening a store while the first is still open, file modes (new file 0600, existing mode kept), and an error R-block after each reset. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ttested reads in sessions Fixes from review feedback specific to the UserID / AESKey sessions (the ones shared with the ECKey sessions and the store writer are in the two previous commits, also applied to wolfSSL#23 and wolfSSL#24). The attempt limit was checked at INITIALIZE UPDATE only. With a maximum of one, a host could start two AESKey handshakes, fail the first to block the object, then complete the second, which also reset the counter. A pending handshake now rechecks the block at EXTERNAL AUTHENTICATE and is refused without touching the counter. AES encryption and decryption ignored object policies, so a new AESKey Authentication Object without a policy (read only by default) still encrypted and decrypted in the default session. One-shot and CipherInit now require POLICY_OBJ_ALLOW_ENC / POLICY_OBJ_ALLOW_DEC for the current session owner; update and final use the key bound at init. The SE's attestation key pair (0xF0000012) is now applet-reserved like the ECKey session key: readable, but not usable for generic signing, nor replaceable or deletable. An attested ReadObject inside an authenticated session went to plain dispatch, which masks INS_ATTEST, and came back without attestation fields. It now goes through the attestation handler, with the session's Authentication Object as the policy owner. Updating an existing AESKey Authentication Object with a 24 or 32 byte key succeeded and left it unusable for sessions. Authentication Objects stay AES-128: the length is checked before anything is stored, on creation and on update. TAG_MAX_ATTEMPTS is parsed by one helper for all three types: a malformed or out-of-range value is refused with 0x6A80 instead of meaning "unlimited" (UserID: at most 255). Tests: two pending AESKey handshakes against a one-attempt object, cipher policies (one-shot and init, both directions), AES-128 on update, malformed maximum for AESKey and UserID, attested read in a UserID session, and the reserved attestation key. The NXP Plug & Trust SDK test passes 32/32 in all seven session modes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the review. This PR builds on #23 and #24, so their fixes are applied here too (6149ca8, 2dd21eb). The points specific to this PR are in 922a22d:
Verified locally: |


Depends on #23 (T=1 retransmission) and #24 (ECKey sessions) (this branch is on top of both, so their commits show here too; only the last commit is new).
Completes the three kinds of Authentication Object and lets the Plug & Trust test run in every session mode:
VerifySessionUserID; plain session afterwards; attempts counted but not reported, AN125433.2.4.4).
WriteUserIDcreates UserID Authentication Objects (4-16 bytes, at most 255 attempts, not updatable).Authentication Objects.
INS_READ | INS_ATTEST, applet 7.x layout as read bySe05x_API_ReadObject_W_Attst_V2), signedby the SE's attestation key pair
0xF0000012. Plug & Trust's ECKey session setup verifies the SE's ECKey sessionpublic key this way, so it was needed for
SE05X_AUTH=ECKey.SE050_SIM_DEMO_AUTH=1provisions the demo Authentication Objects of Plug & Trust's examples (0x7DA00001..3,ex_sss_auth.h);sdk-test/run_test.shenables it for the session variants and loads the Platform SCP03 keys forevery
*_PlatfSCP03variant.Results with
Dockerfile.sdk-test(32 tests): all pass withSE05X_AUTH=UserID,AESKey,ECKey,UserID_PlatfSCP03,AESKey_PlatfSCP03andECKey_PlatfSCP03, as withNoneandPlatfSCP03(the 330x6985warnings in the log are the same negative cases as in
Nonemode). Unit tests for the UserID and AESKey sessions.🤖 Generated with Claude Code