Skip to content

SE050Sim: UserID and AESKey sessions, attested reads; SDK test in all session modes - #25

Open
mcamposv wants to merge 6 commits into
wolfSSL:mainfrom
shosoin:userid-aeskey-attested
Open

mcamposv wants to merge 6 commits into
wolfSSL:mainfrom
shosoin:userid-aeskey-attested

Conversation

@mcamposv

@mcamposv mcamposv commented Nov 1, 2026

Copy link
Copy Markdown

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:

  • UserID sessions (VerifySessionUserID; plain session afterwards; attempts counted but not reported, AN12543
    3.2.4.4). WriteUserID creates UserID Authentication Objects (4-16 bytes, at most 255 attempts, not updatable).
  • AESKey sessions: SCP03 handshake with the object's AES-128 key as static ENC/MAC/DEK, key version 0; AES-128
    Authentication Objects.
  • Attested reads (INS_READ | INS_ATTEST, applet 7.x layout as read by Se05x_API_ReadObject_W_Attst_V2), signed
    by the SE's attestation key pair 0xF0000012. Plug & Trust's ECKey session setup verifies the SE's ECKey session
    public key this way, so it was needed for SE05X_AUTH=ECKey.
  • SE050_SIM_DEMO_AUTH=1 provisions the demo Authentication Objects of Plug & Trust's examples (0x7DA00001..3,
    ex_sss_auth.h); sdk-test/run_test.sh enables it for the session variants and loads the Platform SCP03 keys for
    every *_PlatfSCP03 variant.

Results with Dockerfile.sdk-test (32 tests): all pass with SE05X_AUTH = UserID, AESKey, ECKey,
UserID_PlatfSCP03, AESKey_PlatfSCP03 and ECKey_PlatfSCP03, as with None and PlatfSCP03 (the 33 0x6985
warnings in the log are the same negative cases as in None mode). Unit tests for the UserID and AESKey sessions.

🤖 Generated with Claude Code

mcamposv and others added 3 commits November 1, 2026 16:02
…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>
Copilot AI balanced review requested due to automatic review settings November 1, 2026 15:32

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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
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;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6149ca8 (same fix as #24): sessions are bound to an instance tag of their Authentication Object and are refused before dispatch once it is deleted or recreated.

self.sessions.remove(&sid);
return ApduResponse::error(SW_CONDITIONS_NOT_SATISFIED);
}
let resp = pending.external_authenticate(&raw);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +119 to +128
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);
}
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2dd21eb (same fix as #23): snapshots are coalesced per destination path.

Comment on lines +132 to +133
if let Ok(tx) = tx.lock() {
let _ = tx.send((path.to_path_buf(), json));

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2dd21eb (same fix as #23): per-path flush barrier when a store file is loaded and when a persistent store is dropped; tests/scp03.rs reload test passes again.

Comment on lines +285 to +289
self.objects.insert(id, SecureObject::ECKeyPair {
curve: crate::object_store::types::ECCurve::NistP256,
private_key: sk.to_bytes().to_vec(),
public_key: pk,
});

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +169 to +170
/// Applet sessions (CreateSession / ProcessSessionCmd), reset by SELECT.
applet_sessions: crate::applet_session::AppletSessions,

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6149ca8 (same fix as #24): both S-block resets clear applet sessions.

if cmd.base_ins() == INS_READ {
store.ensure_eckey_session_key();
}
let r = crate::dispatch::dispatch(cmd, store, scp_active);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread SE050Sim/se050-sim/src/handlers/aes.rs Outdated
Comment on lines +255 to +257
// 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) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread SE050Sim/se050-sim/src/handlers/ec.rs Outdated
Comment on lines +150 to +152
// INS_AUTH_OBJECT: an ECKey Authentication Object, with its maximum
// number of failed attempts (0 = unlimited, at most 0x7FFF).
if apdu.ins & 0x40 != 0 {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

mcamposv and others added 3 commits November 2, 2026 07:10
…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>
@mcamposv

mcamposv commented Nov 2, 2026

Copy link
Copy Markdown
Author

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:

  • a pending AESKey handshake cannot bypass or clear a block
  • AES one-shot and CipherInit check ALLOW_ENC / ALLOW_DEC
  • the attestation key is reserved like the session key
  • attested reads work inside sessions
  • AESKey Authentication Objects stay AES-128 on update
  • one validated TAG_MAX_ATTEMPTS parser for all three types

Verified locally: cargo test (unit, integration and SCP03 suites), the NXP Plug & Trust SDK test 32/32 in all seven session modes (None, UserID, AESKey, ECKey, and the three with Platform SCP03), and the wolfCrypt test, which gives the same result as on main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants