Skip to content

Fix PACE access control failures on eIDs#269

Open
gbrickley wants to merge 1 commit intoAndyQ:mainfrom
gbrickley:fix-pace-configuration
Open

Fix PACE access control failures on eIDs#269
gbrickley wants to merge 1 commit intoAndyQ:mainfrom
gbrickley:fix-pace-configuration

Conversation

@gbrickley
Copy link
Copy Markdown

🧾 Summary

I've been struggling with PACE failures for months (specifically German and Polish eIDs) and I finally figured out the issue.

This PR fixes the issue by including the Parameter ID (tag 0x84) in the MSE SET AT command when multiple PACE configurations are supported by the card.

This resolves NFC session failures during PACE Step 2 caused by a mismatch between the card’s selected domain parameters and the ones used by the reader (PCD).


🐛 Problem

Some eIDs (I've personally seen on German / Polish) advertise multiple PACE configurations via the CardAccess file. Each configuration is represented as a PACEInfo entry with a distinct parameter ID.

Per spec, this field is technically optional — but in practice, required when multiple parameter sets exist. Omitting causes the reading to fail.

Without it:

  • The card defaults to the first available parameter set (typically P-256)
  • The existing implementation proceeded using a different set (e.g. P-384)
  • This caused a mismatch in expected key sizes during PACE Step 2

Result:

  • Card expected: 65-byte public key (P-256)
  • We send: 97-byte public key (P-384)
  • Card failed to parse → dropped NFC connection
  • Observed error: Tag response error / no response (no SW returned)

✅ Solution

Include the parameter ID (tag 0x84) in the MSE SET AT command using the value parsed from CardAccess.

Before

80 ...   ← OID (tag 0x80)
83 01 01 ← key reference (MRZ)

After

80 ...   ← OID (tag 0x80)
83 01 01 ← key reference (MRZ)
84 01 10 ← parameter ID (e.g. 16 = brainpoolP384r1)

🔍 Implementation Details

  • parameterId is parsed from the ASN.1 PACEInfo structure in CardAccess
  • Stored in PACEInfo.parameterId
  • Retrieved via paceInfo.getParameterId() when building the MSE SET AT APDU
// SecurityInfo.swift
if let optionalData = optionalData {
    parameterId = Int(optionalData.value, radix: 16)
}
return PACEInfo(oid: oid, version: version, parameterId: parameterId)

🧠 Background

  • CardAccess contains a list of SecurityInfo entries, including one or more PACEInfo records
  • Each PACEInfo defines:
    • Algorithm (via OID)
    • Version
    • Optional parameter ID (domain parameters)

Example:

PACEInfo #1: ECDH-GM / AES-128 / brainpoolP256r1  (ID = 13)
PACEInfo #2: ECDH-GM / AES-256 / brainpoolP384r1  (ID = 16)

The parameter ID maps to standardized curves defined in BSI TR-03110.


⚠️ Spec vs Reality

  • Spec: parameter ID (0x84) is optional
  • Reality: mandatory when multiple parameter sets are present

Cards supporting multiple curves cannot infer which one the PCD intends to use — omission leads to undefined behavior (typically fallback to the weakest/default curve).


🧪 Result

  • Correct parameter set is explicitly selected
  • Key sizes align between card and PCD
  • PACE Step 2 succeeds
  • NFC session remains stable (no more silent disconnects)

@gbrickley gbrickley marked this pull request as ready for review April 1, 2026 18:56
@gbrickley gbrickley force-pushed the fix-pace-configuration branch from fca563f to 73aa6e5 Compare April 1, 2026 18:59
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.

1 participant