Skip to main content

scll_core/
response.rs

1//! Card-response parsers — PDD §5.2 (discover) and §5.12 (`get_card_status`).
2//!
3//! Pure, fuzzable (§10.5 target #2): these consume bytes a malicious or buggy
4//! card controls, so every function is **total** — malformed input yields a
5//! typed [`TlvError`], never a panic (no `unwrap` on a bounded `heapless`
6//! push). They sit one layer above [`crate::tlv`]: each walks the BER-TLV
7//! template and decodes it into the public [`crate::model`] / [`crate::report`]
8//! types.
9//!
10//! Four templates, all decoded here:
11//!   * **Card Recognition Data** `'66'` → advertised SCP variants
12//!     (GPCS v2.3.1 §H.2 — the `'64'` OID's last two octets are `scp_id`/`i`).
13//!   * **Key Information Template** `'00E0'` → keysets grouped by KVN
14//!     (GPCS v2.3.1 §11.3.3.1).
15//!   * **Card Capability Information** `'67'` → [`CardCapabilities`]
16//!     (GPCS v2.3.1 §H.4).
17//!   * **`GET STATUS` GP Registry entry** `'E3'` → [`CardLifeCycle`]
18//!     (GPCS v2.3.1 §11.4, Table 11-36; life-cycle byte is `'9F70'`).
19//!
20//! **Absent vs malformed.** A *missing* optional element is not an error: it
21//! yields an empty list / `None` / the documented default, and the discovery
22//! workflow (§5.2) turns that into a [`crate::error::WarningKind`], not a hard
23//! failure. Only a structurally broken template (a real BER-TLV violation, or
24//! more objects than the bounded model can hold) is reported as [`TlvError`].
25//! Unknown *semantic* bytes (an unrecognised key type or life-cycle byte) are
26//! absorbed into the model's `Other` / `Unknown` variants, never errors.
27
28use heapless::Vec;
29
30use crate::limits::{
31    GETDATA_RAW_MAX, MAX_KEYSETS, MAX_KEYS_PER_SET, MAX_MODULES_PER_ELF, MAX_PRIVILEGE_BYTES,
32    MAX_REGISTRY_ENTRIES, MAX_SCP_VARIANTS,
33};
34use crate::model::{CardCapabilities, KeyInfo, KeyTemplateFormat, KeyType, Keyset, ScpVariant};
35use crate::report::CardLifeCycle;
36use crate::tlv::{self, Tlv, TlvError};
37
38// ---- Template / sub-tag constants (GPCS v2.3.1 §H / §11) -------------------
39
40/// Card Recognition Data template (GET DATA `'66'`; GPCS §H.2).
41const TAG_CRD: u32 = 0x66;
42/// BER-TLV discretionary data carrying the CRD OIDs, nested in `'66'` (§H.2).
43const TAG_CRD_BODY: u32 = 0x73;
44/// `'64'` = "Secure Channel Protocol of the ISD and its implementation
45/// options"; its `'06'` OID's final two octets are `(scp_id, i)` (§H.2).
46const TAG_SCP_ENTRY: u32 = 0x64;
47/// ASN.1 OBJECT IDENTIFIER tag.
48const TAG_OID: u32 = 0x06;
49
50/// Key Information Template (GET DATA `'00E0'`; GPCS §11.3.3.1).
51const TAG_KEY_TEMPLATE: u32 = 0xE0;
52/// One Key Information Data entry inside `'E0'`.
53const TAG_KEY_ENTRY: u32 = 0xC0;
54/// Extended-format marker following `KVN|KID` inside a `'C0'` (GPCS 2.3+).
55const KEY_EXTENDED_MARKER: u8 = 0xB9;
56
57/// Card Capability Information template (GET DATA `'67'`; GPCS §H.4).
58const TAG_CCI: u32 = 0x67;
59/// CCI sub-tag: number of logical channels (§H.4).
60const TAG_CCI_CHANNELS: u32 = 0xA0;
61/// CCI sub-tag: privileges supported (§H.4).
62const TAG_CCI_PRIVILEGES: u32 = 0xA3;
63
64/// `GlobalPlatform` Registry entry returned by `GET STATUS` (Table 11-36).
65const TAG_GP_REGISTRY: u32 = 0xE3;
66/// Life Cycle State, inside `'E3'`, length 1 (Table 11-36 / Table 11-6).
67const TAG_LIFE_CYCLE: u32 = 0x9F70;
68/// AID of the entry — ISD / Application / SD / ELF (`'4F'`, Table 11-36).
69const TAG_AID: u32 = 0x4F;
70/// Privileges of an Application / Security Domain (`'C5'`, 1 or 3 bytes;
71/// Table 11-36, §11.1.2 / Tables 11-7..11-9). Absent for ELF entries.
72const TAG_PRIVILEGES: u32 = 0xC5;
73/// Associated Security Domain AID (`'CC'`, Table 11-36): the SD an Application
74/// or ELF is associated with. (Not the ELF an Application loaded from.)
75const TAG_ASSOC_SD_AID: u32 = 0xCC;
76/// Application's Executable Load File AID (`'C4'`, Table 11-36): the ELF an
77/// Application instance was installed from.
78const TAG_ELF_AID: u32 = 0xC4;
79/// Executable Module AID (`'84'`, Table 11-37): a class inside an ELF, returned
80/// (possibly repeated) for the P1 `0x10` scope.
81const TAG_MODULE_AID: u32 = 0x84;
82
83// ---- GP key-type bytes (§5.2 step 4) --------------------------------------
84
85const KEY_TYPE_DES: u8 = 0x80;
86const KEY_TYPE_AES: u8 = 0x88;
87const KEY_TYPE_RSA_PUBLIC: u8 = 0xA1;
88const KEY_TYPE_RSA_PRIVATE_CRT: u8 = 0xA2;
89const KEY_TYPE_RSA_PRIVATE_EXP: u8 = 0xA3;
90const KEY_TYPE_ECC_PUBLIC: u8 = 0xB0;
91const KEY_TYPE_ECC_PRIVATE: u8 = 0xB1;
92const KEY_TYPE_ECC_PARAMS_REF: u8 = 0xB2;
93
94// ---- Card Recognition Data '66' -------------------------------------------
95
96/// Advertised secure-channel capability decoded from Card Recognition Data.
97/// `scp[0]` is the card's default (first `'64'` listed); the discovery workflow
98/// applies the §4.3 selection rule over the full list.
99#[derive(Debug, Clone, Default, PartialEq, Eq)]
100pub struct CardRecognition {
101    /// Every representable `(scp_id, i)` the card advertises, in CRD order.
102    /// SCP01 and any other non-SCP02/03 id is dropped (unrepresentable, and
103    /// §5.2 refuses SCP01); an empty list ⇒ no usable SCP advertised.
104    pub scp: Vec<ScpVariant, MAX_SCP_VARIANTS>,
105}
106
107/// Parse Card Recognition Data (the value of GET DATA `'66'`; GPCS §H.2).
108///
109/// Accepts the response either wrapped in the outer `'66'` tag or as a bare
110/// `'73'` body. Each `'64'` entry's `'06'` OID encodes the SCP as its final two
111/// octets `(scp_id, i)` (e.g. OID `2A864886FC6B 04 02 55` ⇒ SCP02, `i=0x55`).
112///
113/// # Errors
114/// Returns [`TlvError`] if the template (or a nested OID) is not well-formed
115/// BER-TLV, or [`TlvError::TooMany`] if more than [`MAX_SCP_VARIANTS`] usable
116/// variants are advertised.
117pub fn parse_card_recognition(data: &[u8]) -> Result<CardRecognition, TlvError> {
118    let mut out = CardRecognition::default();
119    if data.is_empty() {
120        return Ok(out);
121    }
122    let top = tlv::parse(data)?;
123    // CRD is normally wrapped in '66'; some cards return the '73' body directly.
124    let body = if let Some(v66) = find(&top, TAG_CRD) {
125        let wrapped = tlv::parse(v66)?;
126        find(&wrapped, TAG_CRD_BODY)
127    } else {
128        find(&top, TAG_CRD_BODY)
129    };
130    let Some(body) = body else {
131        return Ok(out); // no '73' template present
132    };
133    let entries = tlv::parse(body)?;
134    for entry in entries.iter().filter(|t| t.tag == TAG_SCP_ENTRY) {
135        let oid_tlvs = tlv::parse(entry.value)?;
136        let Some(oid) = find(&oid_tlvs, TAG_OID) else {
137            continue;
138        };
139        // Last two arcs of {globalPlatform 4 scp i} are (scp_id, i).
140        if let [.., scp_id, i] = oid {
141            if let Some(variant) = scp_variant(*scp_id, *i) {
142                out.scp.push(variant).map_err(|_| TlvError::TooMany)?;
143            }
144        }
145    }
146    Ok(out)
147}
148
149/// Map a `(scp_id, i)` pair to a representable [`ScpVariant`]. SCP01 (and any
150/// other id) is unrepresentable here and dropped — the workflow refuses SCP01
151/// (§5.2) and treats an empty list as `ScpProtocolUnsupported`.
152fn scp_variant(scp_id: u8, i_param: u8) -> Option<ScpVariant> {
153    match scp_id {
154        0x02 => Some(ScpVariant::Scp02 { i_param }),
155        0x03 => Some(ScpVariant::Scp03 { i_param }),
156        _ => None,
157    }
158}
159
160// ---- Key Information Template '00E0' --------------------------------------
161
162/// Decoded Key Information Template: keysets grouped by Key Version Number.
163/// `format` is [`KeyTemplateFormat::Extended`] if any entry used the `'B9'`
164/// sub-template, else [`KeyTemplateFormat::Basic`].
165pub struct KeyInformation {
166    pub format: KeyTemplateFormat,
167    pub keysets: Vec<Keyset, MAX_KEYSETS>,
168}
169
170/// Parse the Key Information Template (value of GET DATA `'00E0'`; §11.3.3.1).
171///
172/// Accepts the response either wrapped in the outer `'E0'` tag or as a bare
173/// sequence of `'C0'` Key Information Data entries. Each `'C0'` is
174/// `KVN | KID | (KeyType KeyLength)+` in basic format, or `KVN | KID | 'B9' …`
175/// in extended format. Entries are grouped into [`Keyset`]s by KVN.
176///
177/// # Errors
178/// Returns [`TlvError`] if the template is not well-formed BER-TLV, or
179/// [`TlvError::TooMany`] if the entries exceed [`MAX_KEYSETS`] /
180/// [`MAX_KEYS_PER_SET`].
181pub fn parse_key_information(data: &[u8]) -> Result<KeyInformation, TlvError> {
182    let mut format = KeyTemplateFormat::Basic;
183    let mut keysets: Vec<Keyset, MAX_KEYSETS> = Vec::new();
184    if data.is_empty() {
185        return Ok(KeyInformation { format, keysets });
186    }
187    let top = tlv::parse(data)?;
188    let body = find(&top, TAG_KEY_TEMPLATE).unwrap_or(data);
189    let entries = tlv::parse(body)?;
190    for entry in entries.iter().filter(|t| t.tag == TAG_KEY_ENTRY) {
191        let value = entry.value;
192        // Need at least KID + KVN; a shorter entry is degenerate — skip it.
193        // GPCS v2.3.1 §11.3.3.1, Table 11-70: the 'C0' Key Information Data
194        // carries the **Key Identifier first, then the Key Version Number**
195        // (confirmed against gppro's GPKeyInfo parsing and a live jcsim KIT
196        // `C0 04 01 30 88 10` whose INITIALIZE UPDATE reports KVN 0x30).
197        let (Some(&kid), Some(&kvn)) = (value.first(), value.get(1)) else {
198            continue;
199        };
200        let rest = &value[2..];
201        if rest.first() == Some(&KEY_EXTENDED_MARKER) {
202            // Extended format: the sub-template's internal layout is not
203            // pinned by the PDD; the slot is recorded with type `Other` and
204            // the template flagged Extended (full decode out of scope, v0.9k).
205            format = KeyTemplateFormat::Extended;
206            push_key(
207                &mut keysets,
208                kvn,
209                KeyInfo {
210                    kid,
211                    key_type: KeyType::Other(KEY_EXTENDED_MARKER),
212                    key_length: 0,
213                },
214            )?;
215        } else {
216            // Basic format: one or more (KeyType, KeyLength) pairs.
217            for pair in rest.chunks_exact(2) {
218                push_key(
219                    &mut keysets,
220                    kvn,
221                    KeyInfo {
222                        kid,
223                        key_type: decode_key_type(pair[0]),
224                        key_length: pair[1],
225                    },
226                )?;
227            }
228        }
229    }
230    Ok(KeyInformation { format, keysets })
231}
232
233/// Insert a [`KeyInfo`] under its KVN, creating the [`Keyset`] if new.
234fn push_key(keysets: &mut Vec<Keyset, MAX_KEYSETS>, kvn: u8, key: KeyInfo) -> Result<(), TlvError> {
235    if let Some(set) = keysets.iter_mut().find(|s| s.kvn == kvn) {
236        return set.keys.push(key).map_err(|_| TlvError::TooMany);
237    }
238    let mut keys: Vec<KeyInfo, MAX_KEYS_PER_SET> = Vec::new();
239    // Cannot fail: a fresh Vec has room for one element.
240    keys.push(key).map_err(|_| TlvError::TooMany)?;
241    keysets
242        .push(Keyset { kvn, keys })
243        .map_err(|_| TlvError::TooMany)
244}
245
246/// Decode the GP key-type byte (§5.2 step 4); unknown ⇒ [`KeyType::Other`].
247fn decode_key_type(byte: u8) -> KeyType {
248    match byte {
249        KEY_TYPE_DES => KeyType::Des,
250        KEY_TYPE_AES => KeyType::Aes,
251        KEY_TYPE_RSA_PUBLIC => KeyType::RsaPublic,
252        KEY_TYPE_RSA_PRIVATE_CRT => KeyType::RsaPrivateCrt,
253        KEY_TYPE_RSA_PRIVATE_EXP => KeyType::RsaPrivateExponent,
254        KEY_TYPE_ECC_PUBLIC => KeyType::EccPublic,
255        KEY_TYPE_ECC_PRIVATE => KeyType::EccPrivate,
256        KEY_TYPE_ECC_PARAMS_REF => KeyType::EccParametersRef,
257        other => KeyType::Other(other),
258    }
259}
260
261// ---- Card Capability Information '67' --------------------------------------
262
263/// Parse Card Capability Information (value of GET DATA `'67'`; GPCS §H.4).
264///
265/// Decodes the structurally unambiguous sub-tags — `'A0'` logical channels and
266/// `'A3'` privileges — and always preserves the raw `'67'` value in
267/// [`CardCapabilities::cci_raw`] for diagnostics. The `'A1'` cipher list,
268/// `'A2'` SCP options and `'A4'` memory sub-tags have value encodings that are
269/// not pinned by the PDD / public spec excerpts, so they are left undecoded
270/// (`ciphers_supported` empty, `memory_*` `None`) rather than guessed; their
271/// bytes remain available via `cci_raw` (see manifest / §5.2 step 5).
272///
273/// # Errors
274/// Returns [`TlvError`] if the template is not well-formed BER-TLV.
275pub fn parse_card_capabilities(data: &[u8]) -> Result<CardCapabilities, TlvError> {
276    let mut caps = CardCapabilities {
277        max_logical_channels: 1, // §5.2: default 1 when 'A0' absent
278        ciphers_supported: Vec::new(),
279        privileges_supported: Vec::new(),
280        memory_total_bytes: None,
281        memory_free_bytes: None,
282        cci_raw: Vec::new(),
283    };
284    let raw_len = data.len().min(GETDATA_RAW_MAX);
285    // Cannot fail: raw_len <= GETDATA_RAW_MAX, the buffer's capacity.
286    let _ = caps.cci_raw.extend_from_slice(&data[..raw_len]);
287    if data.is_empty() {
288        return Ok(caps);
289    }
290    let top = tlv::parse(data)?;
291    let body = find(&top, TAG_CCI).unwrap_or(data);
292    let subs = tlv::parse(body)?;
293    if let Some(channels) = find(&subs, TAG_CCI_CHANNELS) {
294        if let Some(&n) = channels.first() {
295            caps.max_logical_channels = n;
296        }
297    }
298    if let Some(privileges) = find(&subs, TAG_CCI_PRIVILEGES) {
299        let n = privileges.len().min(MAX_PRIVILEGE_BYTES);
300        // Cannot fail: n <= MAX_PRIVILEGE_BYTES, the buffer's capacity.
301        let _ = caps
302            .privileges_supported
303            .extend_from_slice(&privileges[..n]);
304    }
305    Ok(caps)
306}
307
308// ---- GET STATUS GP Registry entry 'E3' ------------------------------------
309
310/// Parse the single ISD `GlobalPlatform` Registry entry from a `GET STATUS`
311/// response and decode its life-cycle byte (`'E3'` → `'9F70'`; Table 11-36 /
312/// Table 11-6).
313///
314/// Returns `Ok(None)` when no `'E3'` / `'9F70'` is present (the §5.12 workflow
315/// maps that to `WarningKind::GetStatusParseFailed` + `CardLifeCycle::Unknown`).
316/// An unrecognised life-cycle byte decodes to [`CardLifeCycle::Unknown`], not an
317/// error.
318///
319/// # Errors
320/// Returns [`TlvError`] if the response is not well-formed BER-TLV.
321pub fn parse_status_e3(data: &[u8]) -> Result<Option<CardLifeCycle>, TlvError> {
322    if data.is_empty() {
323        return Ok(None);
324    }
325    let top = tlv::parse(data)?;
326    let Some(registry) = find(&top, TAG_GP_REGISTRY) else {
327        return Ok(None);
328    };
329    let fields = tlv::parse(registry)?;
330    let Some(life_cycle) = find(&fields, TAG_LIFE_CYCLE) else {
331        return Ok(None);
332    };
333    Ok(life_cycle.first().map(|&b| decode_life_cycle(b)))
334}
335
336/// Decode the raw `'9F70'` life-cycle byte (Table 11-6).
337fn decode_life_cycle(byte: u8) -> CardLifeCycle {
338    match byte {
339        0x01 => CardLifeCycle::OpReady,
340        0x07 => CardLifeCycle::Initialized,
341        0x0F => CardLifeCycle::Secured,
342        0x7F => CardLifeCycle::CardLocked,
343        0xFF => CardLifeCycle::Terminated,
344        other => CardLifeCycle::Unknown(other),
345    }
346}
347
348// ---- GET STATUS full registry (§5.12a) ------------------------------------
349
350/// One decoded `GlobalPlatform` Registry `'E3'` entry — the scope-agnostic
351/// shape the `get_card_inventory` workflow (§5.12a) maps into the typed
352/// [`crate::model`] entries (Security Domain / Application / ELF).
353///
354/// Borrows the parsed response (`'a`): the AIDs are zero-copy slices into the
355/// GET STATUS page. The workflow validates each into an owning
356/// [`crate::aid::Aid`] within the page's lifetime. `life_cycle` is the raw
357/// `'9F70'` byte (decode is the model's concern); `privileges` is the `'C5'`
358/// value padded/truncated to the model's fixed 3 bytes (a 1-byte legacy `'C5'`
359/// becomes `[b, 0, 0]` — the Security Domain bit stays in byte 0).
360#[derive(Debug, Clone, PartialEq, Eq)]
361pub struct RegistryEntry<'a> {
362    /// `'4F'` — the entry's AID (empty slice if the card omits it, which the
363    /// workflow then skips as malformed).
364    pub aid: &'a [u8],
365    /// `'9F70'` — raw life-cycle byte (0 if absent; see [`decode_life_cycle`]).
366    pub life_cycle: u8,
367    /// `'C5'` — privileges, padded/truncated to 3 bytes (all-zero if absent, as
368    /// for an ELF entry).
369    pub privileges: [u8; 3],
370    /// `'CC'` — associated Security Domain AID, when present.
371    pub associated_sd_aid: Option<&'a [u8]>,
372    /// `'C4'` — the Application's Executable Load File AID, when present.
373    pub elf_aid: Option<&'a [u8]>,
374    /// `'84'` — Executable Module AIDs inside an ELF (P1 `0x10` scope),
375    /// truncated at [`MAX_MODULES_PER_ELF`].
376    pub modules: Vec<&'a [u8], MAX_MODULES_PER_ELF>,
377}
378
379/// Parse **one GET STATUS page** into its `'E3'` registry entries
380/// (Table 11-36 / 11-37). Total and fuzzable (§10.5): malformed input yields a
381/// typed [`TlvError`], never a panic. Used by the `get_card_inventory` workflow
382/// (§5.12a), which calls it once per page (`63 10` continuation) per P1 scope
383/// and maps each [`RegistryEntry`] into the typed model.
384///
385/// Each `'E3'` is decoded leniently: a missing `'4F'` yields an empty AID
386/// (the workflow skips it), a missing `'9F70'` yields `0`, repeated `'84'`
387/// module tags are collected (capped at [`MAX_MODULES_PER_ELF`]). Capacity
388/// overflow — more than [`MAX_REGISTRY_ENTRIES`] entries in one page, or more
389/// than [`MAX_MODULES_PER_ELF`] modules in one ELF — **truncates** rather than
390/// erroring (the listing stays useful; the workflow surfaces
391/// `WarningKind::InventoryTruncated`). Only a structurally broken BER-TLV is a
392/// [`TlvError`].
393///
394/// # Errors
395/// Returns [`TlvError`] if the page (or a nested `'E3'`) is not well-formed
396/// BER-TLV.
397pub fn parse_status_registry(
398    data: &[u8],
399) -> Result<Vec<RegistryEntry<'_>, MAX_REGISTRY_ENTRIES>, TlvError> {
400    let mut out: Vec<RegistryEntry, MAX_REGISTRY_ENTRIES> = Vec::new();
401    if data.is_empty() {
402        return Ok(out);
403    }
404    let top = tlv::parse(data)?;
405    for e3 in top.iter().filter(|t| t.tag == TAG_GP_REGISTRY) {
406        let fields = tlv::parse(e3.value)?;
407        let aid = find(&fields, TAG_AID).unwrap_or(&[]);
408        let life_cycle = find(&fields, TAG_LIFE_CYCLE)
409            .and_then(|v| v.first().copied())
410            .unwrap_or(0);
411        let privileges = privileges_to_3(find(&fields, TAG_PRIVILEGES));
412        let associated_sd_aid = find(&fields, TAG_ASSOC_SD_AID);
413        let elf_aid = find(&fields, TAG_ELF_AID);
414        let mut modules: Vec<&[u8], MAX_MODULES_PER_ELF> = Vec::new();
415        for m in fields.iter().filter(|t| t.tag == TAG_MODULE_AID) {
416            if modules.push(m.value).is_err() {
417                break; // more modules than the model holds → truncate (no panic)
418            }
419        }
420        let entry = RegistryEntry {
421            aid,
422            life_cycle,
423            privileges,
424            associated_sd_aid,
425            elf_aid,
426            modules,
427        };
428        if out.push(entry).is_err() {
429            break; // more entries than one page can hold → truncate (no panic)
430        }
431    }
432    Ok(out)
433}
434
435/// Pad/truncate a `'C5'` privileges value to the model's fixed 3 bytes. A
436/// 1-byte legacy `'C5'` becomes `[b, 0, 0]`; absent ⇒ all-zero. (Tables
437/// 11-7..11-9: the Security Domain bit is byte 1 / b8, so it survives either
438/// encoding.)
439fn privileges_to_3(value: Option<&[u8]>) -> [u8; 3] {
440    let mut p = [0u8; 3];
441    if let Some(b) = value {
442        let n = b.len().min(3);
443        p[..n].copy_from_slice(&b[..n]);
444    }
445    p
446}
447
448// ---- shared --------------------------------------------------------------
449
450/// Return the value of the first TLV with `tag`. The returned slice borrows the
451/// original input (`'a`), not the `tlvs` list, so it outlives a local parse.
452fn find<'a>(tlvs: &[Tlv<'a>], tag: u32) -> Option<&'a [u8]> {
453    tlvs.iter().find(|t| t.tag == tag).map(|t| t.value)
454}
455
456#[cfg(test)]
457mod tests {
458    use super::*;
459
460    // ===== Card Recognition Data '66' =====================================
461
462    /// Real CRD dump (`JavaCard` OS forum / 0x9000 blog): SCP02, i=0x55.
463    const CRD_SCP02_55: &[u8] = &[
464        0x66, 0x4C, 0x73, 0x4A, 0x06, 0x07, 0x2A, 0x86, 0x48, 0x86, 0xFC, 0x6B, 0x01, 0x60, 0x0C,
465        0x06, 0x0A, 0x2A, 0x86, 0x48, 0x86, 0xFC, 0x6B, 0x02, 0x02, 0x01, 0x01, 0x63, 0x09, 0x06,
466        0x07, 0x2A, 0x86, 0x48, 0x86, 0xFC, 0x6B, 0x03, 0x64, 0x0B, 0x06, 0x09, 0x2A, 0x86, 0x48,
467        0x86, 0xFC, 0x6B, 0x04, 0x02, 0x55, 0x65, 0x0B, 0x06, 0x09, 0x2B, 0x85, 0x10, 0x86, 0x48,
468        0x64, 0x02, 0x01, 0x03, 0x66, 0x0C, 0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x2A, 0x02,
469        0x6E, 0x01, 0x02,
470    ];
471
472    #[test]
473    fn crd_decodes_scp02_i55() {
474        let crd = parse_card_recognition(CRD_SCP02_55).unwrap();
475        assert_eq!(crd.scp.len(), 1);
476        assert_eq!(crd.scp[0], ScpVariant::Scp02 { i_param: 0x55 });
477    }
478
479    #[test]
480    fn crd_decodes_scp03_i70() {
481        // Minimal '66'→'73'→'64'→'06' with OID tail 03 70 (SCP03, i=0x70).
482        let crd = [
483            0x66, 0x0E, 0x73, 0x0C, 0x64, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0x86, 0xFC, 0x6B,
484            0x03, 0x70,
485        ];
486        let r = parse_card_recognition(&crd).unwrap();
487        assert_eq!(r.scp[0], ScpVariant::Scp03 { i_param: 0x70 });
488    }
489
490    #[test]
491    fn crd_multiple_variants_preserve_order() {
492        // '73' with two '64' entries: SCP02/55 then SCP03/70.
493        let crd = [
494            0x73, 0x10, 0x64, 0x06, 0x06, 0x04, 0x00, 0x00, 0x02, 0x55, 0x64, 0x06, 0x06, 0x04,
495            0x00, 0x00, 0x03, 0x70,
496        ];
497        let r = parse_card_recognition(&crd).unwrap();
498        assert_eq!(r.scp.len(), 2);
499        assert_eq!(r.scp[0], ScpVariant::Scp02 { i_param: 0x55 });
500        assert_eq!(r.scp[1], ScpVariant::Scp03 { i_param: 0x70 });
501    }
502
503    #[test]
504    fn crd_drops_scp01() {
505        // '64' OID tail 01 05 ⇒ SCP01, which is unrepresentable and dropped.
506        let crd = [0x73, 0x08, 0x64, 0x06, 0x06, 0x04, 0x00, 0x00, 0x01, 0x05];
507        assert!(parse_card_recognition(&crd).unwrap().scp.is_empty());
508    }
509
510    #[test]
511    fn crd_empty_input_is_empty_not_error() {
512        assert!(parse_card_recognition(&[]).unwrap().scp.is_empty());
513    }
514
515    #[test]
516    fn crd_no_template_is_empty_not_error() {
517        // Well-formed TLV but no '73' body (just a stray '5C' tag list).
518        assert!(parse_card_recognition(&[0x5C, 0x01, 0x9F])
519            .unwrap()
520            .scp
521            .is_empty());
522    }
523
524    #[test]
525    fn crd_entry_without_oid_is_skipped() {
526        // '64' present but holds a non-'06' child ⇒ skipped, no error.
527        let crd = [0x73, 0x06, 0x64, 0x04, 0x80, 0x02, 0x00, 0x00];
528        assert!(parse_card_recognition(&crd).unwrap().scp.is_empty());
529    }
530
531    #[test]
532    fn crd_oid_shorter_than_two_octets_is_skipped() {
533        let crd = [0x73, 0x05, 0x64, 0x03, 0x06, 0x01, 0x2A];
534        assert!(parse_card_recognition(&crd).unwrap().scp.is_empty());
535    }
536
537    #[test]
538    fn crd_malformed_tlv_is_rejected() {
539        // '66' claims 5 bytes but only 1 follows.
540        assert_eq!(
541            parse_card_recognition(&[0x66, 0x05, 0x00]),
542            Err(TlvError::Truncated)
543        );
544    }
545
546    // ===== Key Information Template '00E0' ================================
547
548    #[test]
549    fn key_info_basic_single_keyset_three_kids() {
550        // 'E0' { 'C0'{01 01 88 10} 'C0'{02 01 88 10} 'C0'{03 01 88 10} } —
551        // KIDs 1/2/3 at KVN 1, AES, 16-byte (matches a modern SCP03 keyset;
552        // 'C0' carries KID first, then KVN — GPCS v2.3.1 Table 11-70).
553        let data = [
554            0xE0, 0x12, 0xC0, 0x04, 0x01, 0x01, 0x88, 0x10, 0xC0, 0x04, 0x02, 0x01, 0x88, 0x10,
555            0xC0, 0x04, 0x03, 0x01, 0x88, 0x10,
556        ];
557        let info = parse_key_information(&data).unwrap();
558        assert!(matches!(info.format, KeyTemplateFormat::Basic));
559        assert_eq!(info.keysets.len(), 1);
560        assert_eq!(info.keysets[0].kvn, 1);
561        assert_eq!(info.keysets[0].keys.len(), 3);
562        assert_eq!(info.keysets[0].keys[0].kid, 1);
563        assert!(matches!(info.keysets[0].keys[0].key_type, KeyType::Aes));
564        assert_eq!(info.keysets[0].keys[2].key_length, 0x10);
565    }
566
567    #[test]
568    fn key_info_groups_by_kvn() {
569        let data = [
570            0xE0, 0x0E, 0xC0, 0x04, 0x01, 0x01, 0x80, 0x10, 0xC0, 0x04, 0x01, 0x02, 0x88, 0x10,
571            0xC0, 0x00, // a degenerate empty 'C0' — skipped, no panic
572        ];
573        let info = parse_key_information(&data).unwrap();
574        assert_eq!(info.keysets.len(), 2);
575        assert!(matches!(info.keysets[0].keys[0].key_type, KeyType::Des));
576        assert!(matches!(info.keysets[1].keys[0].key_type, KeyType::Aes));
577    }
578
579    #[test]
580    fn key_info_multi_component_pairs() {
581        // One 'C0' with KID|KVN then two (type,len) pairs (e.g. RSA pub+priv).
582        let data = [0xE0, 0x08, 0xC0, 0x06, 0x10, 0x01, 0xA1, 0x80, 0xA2, 0x80];
583        let info = parse_key_information(&data).unwrap();
584        assert_eq!(info.keysets[0].keys.len(), 2);
585        assert!(matches!(
586            info.keysets[0].keys[0].key_type,
587            KeyType::RsaPublic
588        ));
589        assert!(matches!(
590            info.keysets[0].keys[1].key_type,
591            KeyType::RsaPrivateCrt
592        ));
593    }
594
595    #[test]
596    fn key_info_extended_format_sets_flag() {
597        // 'C0' { KID KVN 'B9' Lb .. } ⇒ Extended flag; sub-template not decoded.
598        let data = [0xE0, 0x06, 0xC0, 0x04, 0x01, 0x01, 0xB9, 0x00];
599        let info = parse_key_information(&data).unwrap();
600        assert!(matches!(info.format, KeyTemplateFormat::Extended));
601        assert_eq!(info.keysets[0].keys[0].kid, 1);
602        assert!(matches!(
603            info.keysets[0].keys[0].key_type,
604            KeyType::Other(0xB9)
605        ));
606    }
607
608    #[test]
609    fn key_info_unknown_type_is_other() {
610        // 'C0' { KID=05 KVN=09 KeyType=0x42 (unknown) KeyLength=08 }.
611        let data = [0xE0, 0x06, 0xC0, 0x04, 0x05, 0x09, 0x42, 0x08];
612        let info = parse_key_information(&data).unwrap();
613        assert!(matches!(
614            info.keysets[0].keys[0].key_type,
615            KeyType::Other(0x42)
616        ));
617    }
618
619    #[test]
620    fn key_info_bare_c0_without_e0_wrapper() {
621        let data = [0xC0, 0x04, 0x01, 0x01, 0x88, 0x10];
622        let info = parse_key_information(&data).unwrap();
623        assert_eq!(info.keysets[0].keys[0].kid, 1);
624    }
625
626    #[test]
627    fn key_info_empty_is_empty_not_error() {
628        let info = parse_key_information(&[]).unwrap();
629        assert!(info.keysets.is_empty());
630    }
631
632    #[test]
633    fn key_info_malformed_tlv_is_rejected() {
634        assert!(matches!(
635            parse_key_information(&[0xE0, 0x05, 0xC0]),
636            Err(TlvError::Truncated)
637        ));
638    }
639
640    // ===== Card Capability Information '67' ===============================
641
642    #[test]
643    fn cci_decodes_channels_and_privileges_and_keeps_raw() {
644        // '67' { 'A0'{04} 'A3'{80 00 00} } — 4 channels, ISD privilege byte.
645        let data = [0x67, 0x08, 0xA0, 0x01, 0x04, 0xA3, 0x03, 0x80, 0x00, 0x00];
646        let caps = parse_card_capabilities(&data).unwrap();
647        assert_eq!(caps.max_logical_channels, 4);
648        assert_eq!(caps.privileges_supported.len(), 3);
649        assert_eq!(caps.privileges_supported[0], 0x80);
650        assert_eq!(caps.cci_raw.len(), data.len());
651        // Undecoded by design (see fn docs): empty / None, raw retained.
652        assert!(caps.ciphers_supported.is_empty());
653        assert!(caps.memory_total_bytes.is_none());
654    }
655
656    #[test]
657    fn cci_defaults_to_one_channel_when_a0_absent() {
658        let data = [0x67, 0x05, 0xA3, 0x03, 0x80, 0x00, 0x00];
659        let caps = parse_card_capabilities(&data).unwrap();
660        assert_eq!(caps.max_logical_channels, 1);
661    }
662
663    #[test]
664    fn cci_empty_a0_keeps_default_channel() {
665        let data = [0x67, 0x02, 0xA0, 0x00];
666        let caps = parse_card_capabilities(&data).unwrap();
667        assert_eq!(caps.max_logical_channels, 1);
668    }
669
670    #[test]
671    fn cci_bare_subtags_without_67_wrapper() {
672        let data = [0xA0, 0x01, 0x02];
673        let caps = parse_card_capabilities(&data).unwrap();
674        assert_eq!(caps.max_logical_channels, 2);
675    }
676
677    #[test]
678    fn cci_empty_is_defaults_not_error() {
679        let caps = parse_card_capabilities(&[]).unwrap();
680        assert_eq!(caps.max_logical_channels, 1);
681        assert!(caps.cci_raw.is_empty());
682    }
683
684    #[test]
685    fn cci_malformed_tlv_is_rejected() {
686        assert!(matches!(
687            parse_card_capabilities(&[0x67, 0x05, 0xA0]),
688            Err(TlvError::Truncated)
689        ));
690    }
691
692    // ===== GET STATUS GP Registry entry 'E3' =============================
693
694    #[test]
695    fn e3_decodes_each_known_lifecycle_byte() {
696        for (raw, expect) in [
697            (0x01u8, CardLifeCycle::OpReady),
698            (0x07, CardLifeCycle::Initialized),
699            (0x0F, CardLifeCycle::Secured),
700            (0x7F, CardLifeCycle::CardLocked),
701            (0xFF, CardLifeCycle::Terminated),
702        ] {
703            // 'E3' { '4F'{A0000000030000} '9F70'{raw} }
704            let data = [
705                0xE3, 0x0D, 0x4F, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x9F, 0x70, 0x01,
706                raw,
707            ];
708            assert_eq!(parse_status_e3(&data).unwrap(), Some(expect));
709        }
710    }
711
712    #[test]
713    fn e3_unknown_byte_maps_to_unknown() {
714        let data = [0xE3, 0x04, 0x9F, 0x70, 0x01, 0x42];
715        assert_eq!(
716            parse_status_e3(&data).unwrap(),
717            Some(CardLifeCycle::Unknown(0x42))
718        );
719    }
720
721    #[test]
722    fn e3_absent_template_is_none() {
723        // Well-formed TLV, but no 'E3'.
724        assert_eq!(parse_status_e3(&[0x4F, 0x00]).unwrap(), None);
725    }
726
727    #[test]
728    fn e3_without_9f70_is_none() {
729        let data = [0xE3, 0x02, 0x4F, 0x00];
730        assert_eq!(parse_status_e3(&data).unwrap(), None);
731    }
732
733    #[test]
734    fn e3_empty_9f70_is_none() {
735        let data = [0xE3, 0x03, 0x9F, 0x70, 0x00];
736        assert_eq!(parse_status_e3(&data).unwrap(), None);
737    }
738
739    #[test]
740    fn e3_empty_input_is_none() {
741        assert_eq!(parse_status_e3(&[]).unwrap(), None);
742    }
743
744    #[test]
745    fn e3_malformed_tlv_is_rejected() {
746        assert_eq!(
747            parse_status_e3(&[0xE3, 0x05, 0x9F]),
748            Err(TlvError::Truncated)
749        );
750    }
751
752    // ===== GET STATUS full registry (§5.12a) ==============================
753
754    /// `E3{ 4F<aid> 9F70<lc> C5<priv> }` — the App/SD entry shape.
755    fn e3_app(aid: &[u8], lc: u8, privs: &[u8]) -> std::vec::Vec<u8> {
756        let mut inner = std::vec::Vec::new();
757        inner.push(0x4F);
758        inner.push(u8::try_from(aid.len()).unwrap());
759        inner.extend_from_slice(aid);
760        inner.extend_from_slice(&[0x9F, 0x70, 0x01, lc]);
761        inner.push(0xC5);
762        inner.push(u8::try_from(privs.len()).unwrap());
763        inner.extend_from_slice(privs);
764        let mut v = std::vec::Vec::new();
765        v.push(0xE3);
766        v.push(u8::try_from(inner.len()).unwrap());
767        v.extend_from_slice(&inner);
768        v
769    }
770
771    #[test]
772    fn registry_decodes_isd_with_full_3byte_privileges() {
773        // ISD entry: AID A0000000030000, OP_READY (0x01), all-3-byte privs.
774        let aid = [0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00];
775        let e = e3_app(&aid, 0x01, &[0x9E, 0xFE, 0x80]);
776        let r = parse_status_registry(&e).unwrap();
777        assert_eq!(r.len(), 1);
778        assert_eq!(r[0].aid, &aid);
779        assert_eq!(r[0].life_cycle, 0x01);
780        assert_eq!(r[0].privileges, [0x9E, 0xFE, 0x80]);
781        assert!(r[0].modules.is_empty());
782    }
783
784    #[test]
785    fn registry_pads_one_byte_privileges_into_byte_zero() {
786        // Legacy 1-byte C5 = 0x80 ⇒ [0x80, 0, 0]; SD bit survives in byte 0.
787        let e = e3_app(&[0xA0, 0x00, 0x00, 0x00, 0x18], 0x07, &[0x80]);
788        let r = parse_status_registry(&e).unwrap();
789        assert_eq!(r[0].privileges, [0x80, 0x00, 0x00]);
790    }
791
792    #[test]
793    fn registry_decodes_two_entries_in_one_page() {
794        let a = e3_app(&[0xA0, 0x00, 0x00, 0x00, 0x11], 0x07, &[0x00, 0x00, 0x00]);
795        let b = e3_app(&[0xA0, 0x00, 0x00, 0x00, 0x22], 0x0F, &[0x80, 0x00, 0x00]);
796        let mut page = a;
797        page.extend_from_slice(&b);
798        let r = parse_status_registry(&page).unwrap();
799        assert_eq!(r.len(), 2);
800        assert_eq!(r[0].life_cycle, 0x07);
801        assert_eq!(r[1].privileges, [0x80, 0x00, 0x00]);
802    }
803
804    #[test]
805    fn registry_decodes_elf_with_associated_sd_and_modules() {
806        // E3{ 4F<elf> 9F70<lc> CC<sd> 84<mod1> 84<mod2> } — P1=0x10 scope.
807        let elf = [0xA0, 0x00, 0x00, 0x00, 0x62, 0x01];
808        let sd = [0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00];
809        let m1 = [0xA0, 0x00, 0x00, 0x00, 0x62, 0x01, 0x01];
810        let m2 = [0xA0, 0x00, 0x00, 0x00, 0x62, 0x01, 0x02];
811        let mut inner = std::vec::Vec::new();
812        inner.push(0x4F);
813        inner.push(u8::try_from(elf.len()).unwrap());
814        inner.extend_from_slice(&elf);
815        inner.extend_from_slice(&[0x9F, 0x70, 0x01, 0x01]);
816        inner.push(0xCC);
817        inner.push(u8::try_from(sd.len()).unwrap());
818        inner.extend_from_slice(&sd);
819        for m in [&m1[..], &m2[..]] {
820            inner.push(0x84);
821            inner.push(u8::try_from(m.len()).unwrap());
822            inner.extend_from_slice(m);
823        }
824        let mut e = std::vec::Vec::new();
825        e.push(0xE3);
826        e.push(u8::try_from(inner.len()).unwrap());
827        e.extend_from_slice(&inner);
828
829        let r = parse_status_registry(&e).unwrap();
830        assert_eq!(r[0].aid, &elf);
831        assert_eq!(r[0].associated_sd_aid, Some(&sd[..]));
832        assert_eq!(r[0].modules.len(), 2);
833        assert_eq!(r[0].modules[0], &m1);
834        assert_eq!(r[0].modules[1], &m2);
835    }
836
837    #[test]
838    fn registry_decodes_application_elf_aid_tag_c4() {
839        // E3{ 4F<inst> 9F70 C5<priv> C4<elf> } — application's load file AID.
840        let inst = [0xA0, 0x00, 0x00, 0x00, 0x62, 0x03, 0x01, 0x0C];
841        let elf = [0xA0, 0x00, 0x00, 0x00, 0x62, 0x03];
842        let mut inner = std::vec::Vec::new();
843        inner.push(0x4F);
844        inner.push(u8::try_from(inst.len()).unwrap());
845        inner.extend_from_slice(&inst);
846        inner.extend_from_slice(&[0x9F, 0x70, 0x01, 0x07]);
847        inner.extend_from_slice(&[0xC5, 0x01, 0x00]);
848        inner.push(0xC4);
849        inner.push(u8::try_from(elf.len()).unwrap());
850        inner.extend_from_slice(&elf);
851        let mut e = std::vec::Vec::new();
852        e.push(0xE3);
853        e.push(u8::try_from(inner.len()).unwrap());
854        e.extend_from_slice(&inner);
855
856        let r = parse_status_registry(&e).unwrap();
857        assert_eq!(r[0].elf_aid, Some(&elf[..]));
858        assert_eq!(r[0].associated_sd_aid, None);
859    }
860
861    #[test]
862    fn registry_missing_4f_yields_empty_aid_not_error() {
863        // E3{ 9F70 01 0F } — no '4F'; workflow treats an empty AID as skip.
864        let e = [0xE3, 0x04, 0x9F, 0x70, 0x01, 0x0F];
865        let r = parse_status_registry(&e).unwrap();
866        assert_eq!(r.len(), 1);
867        assert!(r[0].aid.is_empty());
868        assert_eq!(r[0].life_cycle, 0x0F);
869    }
870
871    #[test]
872    fn registry_empty_page_is_empty_not_error() {
873        assert!(parse_status_registry(&[]).unwrap().is_empty());
874    }
875
876    #[test]
877    fn registry_no_e3_is_empty_not_error() {
878        // Well-formed TLV but no 'E3' (e.g. a card returning just '4F 00').
879        assert!(parse_status_registry(&[0x4F, 0x00]).unwrap().is_empty());
880    }
881
882    #[test]
883    fn registry_malformed_tlv_is_rejected() {
884        assert_eq!(
885            parse_status_registry(&[0xE3, 0x05, 0x4F]),
886            Err(TlvError::Truncated)
887        );
888    }
889
890    #[test]
891    fn registry_truncates_excess_modules_without_panic() {
892        // One ELF entry with MAX_MODULES_PER_ELF + 4 module tags ⇒ capped.
893        use crate::limits::MAX_MODULES_PER_ELF;
894        let elf = [0xA0, 0x00, 0x00, 0x00, 0x62, 0x09];
895        let mut inner = std::vec::Vec::new();
896        inner.push(0x4F);
897        inner.push(u8::try_from(elf.len()).unwrap());
898        inner.extend_from_slice(&elf);
899        inner.extend_from_slice(&[0x9F, 0x70, 0x01, 0x01]);
900        for i in 0..(MAX_MODULES_PER_ELF + 4) {
901            // 5-byte module AID, distinct last byte.
902            inner.extend_from_slice(&[
903                0x84,
904                0x05,
905                0xA0,
906                0x00,
907                0x00,
908                0x01,
909                u8::try_from(i).unwrap(),
910            ]);
911        }
912        let mut e = std::vec::Vec::new();
913        e.push(0xE3);
914        // Body exceeds 127 B (≥17 module TLVs), so use long-form length 81 LL.
915        if inner.len() < 0x80 {
916            e.push(u8::try_from(inner.len()).unwrap());
917        } else {
918            e.push(0x81);
919            e.push(u8::try_from(inner.len()).unwrap());
920        }
921        e.extend_from_slice(&inner);
922        let r = parse_status_registry(&e).unwrap();
923        assert_eq!(r[0].modules.len(), MAX_MODULES_PER_ELF);
924    }
925}