Skip to main content

keyroost_piv/
lib.rs

1//! PIV (Personal Identity Verification — NIST SP 800-73-4 / FIPS 201) byte layer.
2//!
3//! A pure, I/O-free APDU builder + parser layer for the PIV smartcard
4//! application, the same shape as [`keyroost_oath`] and [`keyroost_openpgp`]: it
5//! turns intentions into APDU byte vectors and response bytes into typed values,
6//! and performs **no card I/O** (that lives in `keyroost-transport`'s
7//! `PivSession`). PIV is a CCID/APDU applet on YubiKeys (and other PIV cards),
8//! reachable over the same PC/SC layer keyroost already uses.
9//!
10//! # Scope
11//!
12//! Covers the full management surface: SELECT, GET DATA, the Yubico
13//! version/serial/metadata extensions, PIN-retry querying (the read path), plus
14//! GENERAL AUTHENTICATE (management-key mutual auth and key-slot signing),
15//! GENERATE ASYMMETRIC KEY PAIR, PUT DATA (certificate import), CHANGE
16//! REFERENCE DATA / RESET RETRY COUNTER (PIN/PUK), and the Yubico SET MANAGEMENT
17//! KEY / SET PIN RETRIES / RESET extensions. The block-cipher math for the
18//! management-key challenge/response lives in `keyroost-transport` (where the
19//! cipher dependency is); this layer stays pure and I/O-free.
20
21#![forbid(unsafe_code)]
22
23use keyroost_proto::apdu::{build_apdu, build_apdu_get};
24
25pub mod spki;
26pub mod x509;
27pub mod x509_parse;
28
29/// PIV card-application AID (the 5-byte RID/PIX prefix; the card matches on it).
30/// Full PIV AID is `A0 00 00 03 08 00 00 10 00 01 00`; selecting by the prefix
31/// is what `yubikey-piv-tool` / `ykman` do and the card resolves it.
32pub const AID: [u8; 5] = [0xA0, 0x00, 0x00, 0x03, 0x08];
33
34/// Status word: success.
35pub const SW_OK: u16 = 0x9000;
36/// First byte of a `61xx` "more data available" status word.
37pub const SW_MORE_DATA: u8 = 0x61;
38/// File/application or object not found (e.g. an empty certificate slot).
39pub const SW_NOT_FOUND: u16 = 0x6A82;
40
41/// Security status not satisfied (a write needed an auth/PIN that wasn't done).
42pub const SW_SECURITY_NOT_SATISFIED: u16 = 0x6982;
43/// Authentication method blocked (PIN/PUK exhausted, or RESET preconditions
44/// unmet).
45pub const SW_AUTH_BLOCKED: u16 = 0x6983;
46/// Reference data (key/PIN) not found.
47pub const SW_REFERENCE_NOT_FOUND: u16 = 0x6A88;
48
49/// PIN reference (P2) for the PIV application PIN.
50pub const PIN_REF_APPLICATION: u8 = 0x80;
51/// PIN reference (P2) for the PUK.
52pub const PIN_REF_PUK: u8 = 0x81;
53/// Key reference (P2) for the card-management (9B) key.
54pub const KEY_REF_MANAGEMENT: u8 = 0x9B;
55
56/// PIV / Yubico-PIV instruction bytes.
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58#[repr(u8)]
59pub enum Instruction {
60    /// SELECT (ISO 7816) — activate the PIV application by AID.
61    Select = 0xA4,
62    /// VERIFY — present the PIN (or query its retry counter with an empty body).
63    Verify = 0x20,
64    /// GET DATA — read a PIV data object (certificate, CHUID, …).
65    GetData = 0xCB,
66    /// GET RESPONSE — pull the next chunk of a `61xx`-chained reply.
67    GetResponse = 0xC0,
68    /// GENERAL AUTHENTICATE — management-key mutual auth and key-slot signing.
69    GeneralAuthenticate = 0x87,
70    /// GENERATE ASYMMETRIC KEY PAIR — create a key in a slot, return its public key.
71    GenerateKeyPair = 0x47,
72    /// PUT DATA — write a data object (e.g. a slot's certificate).
73    PutData = 0xDB,
74    /// CHANGE REFERENCE DATA — change the PIN or PUK.
75    ChangeReference = 0x24,
76    /// RESET RETRY COUNTER — unblock the PIN using the PUK.
77    ResetRetryCounter = 0x2C,
78    /// Yubico extension: GET VERSION (applet/firmware version, 3 bytes).
79    GetVersion = 0xFD,
80    /// Yubico extension: GET SERIAL (4-byte device serial; firmware 5+).
81    GetSerial = 0xF8,
82    /// Yubico extension: GET METADATA (key/PIN algorithm, policy, retries; fw 5.3+).
83    GetMetadata = 0xF7,
84    /// Yubico extension: SET MANAGEMENT KEY (9B).
85    SetManagementKey = 0xFF,
86    /// Yubico extension: SET PIN RETRIES (PIN + PUK try counts).
87    SetPinRetries = 0xFA,
88    /// Yubico extension: RESET the PIV application (only when PIN and PUK blocked).
89    Reset = 0xFB,
90}
91
92impl Instruction {
93    #[must_use]
94    pub const fn code(self) -> u8 {
95        self as u8
96    }
97}
98
99const INS_SELECT_P1_BY_AID: u8 = 0x04;
100/// P1-P2 addressing the data-object namespace for GET DATA.
101const GET_DATA_P1: u8 = 0x3F;
102const GET_DATA_P2: u8 = 0xFF;
103/// BER tag introducing a GET DATA object selector.
104const TAG_OBJECT_SELECTOR: u8 = 0x5C;
105/// BER tag wrapping a GET DATA response payload.
106const TAG_DATA_TEMPLATE: u8 = 0x53;
107/// BER tag for the GENERAL AUTHENTICATE dynamic-authentication template.
108const TAG_DYN_AUTH: u8 = 0x7C;
109
110/// The four PIV asymmetric key slots, identified by their key reference and the
111/// certificate data object that holds the slot's X.509 certificate.
112#[derive(Clone, Copy, Debug, PartialEq, Eq)]
113pub enum Slot {
114    /// `9A` — PIV Authentication.
115    Authentication,
116    /// `9C` — Digital Signature.
117    Signature,
118    /// `9D` — Key Management (decryption).
119    KeyManagement,
120    /// `9E` — Card Authentication.
121    CardAuthentication,
122}
123
124impl Slot {
125    /// The key-reference byte (`9A`/`9C`/`9D`/`9E`).
126    #[must_use]
127    pub const fn key_ref(self) -> u8 {
128        match self {
129            Slot::Authentication => 0x9A,
130            Slot::Signature => 0x9C,
131            Slot::KeyManagement => 0x9D,
132            Slot::CardAuthentication => 0x9E,
133        }
134    }
135
136    /// The 3-byte certificate data-object tag for this slot (`5F C1 0x`).
137    #[must_use]
138    pub const fn cert_object_tag(self) -> [u8; 3] {
139        match self {
140            Slot::Authentication => [0x5F, 0xC1, 0x05],
141            Slot::Signature => [0x5F, 0xC1, 0x0A],
142            Slot::KeyManagement => [0x5F, 0xC1, 0x0B],
143            Slot::CardAuthentication => [0x5F, 0xC1, 0x01],
144        }
145    }
146
147    /// Short human label.
148    #[must_use]
149    pub const fn label(self) -> &'static str {
150        match self {
151            Slot::Authentication => "authentication (9A)",
152            Slot::Signature => "signature (9C)",
153            Slot::KeyManagement => "key management (9D)",
154            Slot::CardAuthentication => "card authentication (9E)",
155        }
156    }
157
158    /// All four slots, in canonical order.
159    #[must_use]
160    pub const fn all() -> [Slot; 4] {
161        [
162            Slot::Authentication,
163            Slot::Signature,
164            Slot::KeyManagement,
165            Slot::CardAuthentication,
166        ]
167    }
168}
169
170/// CHUID (Card Holder Unique Identifier) data-object tag.
171pub const OBJECT_CHUID: [u8; 3] = [0x5F, 0xC1, 0x02];
172
173/// Management-key (9B) cipher algorithm. The card stores one of these; auth
174/// uses a witness/challenge round whose block size this dictates.
175#[derive(Clone, Copy, Debug, PartialEq, Eq)]
176pub enum MgmtAlg {
177    /// 3DES (TDEA) — pre-5.7 YubiKey default; 24-byte key, 8-byte block.
178    TripleDes,
179    /// AES-128 — 16-byte key, 16-byte block.
180    Aes128,
181    /// AES-192 — 24-byte key, 16-byte block; the YubiKey 5.7+ default.
182    Aes192,
183    /// AES-256 — 32-byte key, 16-byte block.
184    Aes256,
185}
186
187impl MgmtAlg {
188    /// PIV algorithm identifier byte.
189    #[must_use]
190    pub const fn id(self) -> u8 {
191        match self {
192            MgmtAlg::TripleDes => 0x03,
193            MgmtAlg::Aes128 => 0x08,
194            MgmtAlg::Aes192 => 0x0A,
195            MgmtAlg::Aes256 => 0x0C,
196        }
197    }
198
199    /// Resolve a PIV algorithm identifier (e.g. from GET METADATA tag 0x01).
200    #[must_use]
201    pub const fn from_id(id: u8) -> Option<Self> {
202        match id {
203            0x03 => Some(MgmtAlg::TripleDes),
204            0x08 => Some(MgmtAlg::Aes128),
205            0x0A => Some(MgmtAlg::Aes192),
206            0x0C => Some(MgmtAlg::Aes256),
207            _ => None,
208        }
209    }
210
211    /// Cipher block size (= witness/challenge length): 8 for 3DES, 16 for AES.
212    #[must_use]
213    pub const fn block_size(self) -> usize {
214        match self {
215            MgmtAlg::TripleDes => 8,
216            _ => 16,
217        }
218    }
219
220    /// Expected key length in bytes.
221    #[must_use]
222    pub const fn key_len(self) -> usize {
223        match self {
224            MgmtAlg::TripleDes | MgmtAlg::Aes192 => 24,
225            MgmtAlg::Aes128 => 16,
226            MgmtAlg::Aes256 => 32,
227        }
228    }
229
230    /// Short human label.
231    #[must_use]
232    pub const fn label(self) -> &'static str {
233        match self {
234            MgmtAlg::TripleDes => "3DES",
235            MgmtAlg::Aes128 => "AES-128",
236            MgmtAlg::Aes192 => "AES-192",
237            MgmtAlg::Aes256 => "AES-256",
238        }
239    }
240}
241
242/// Asymmetric key algorithm for GENERATE ASYMMETRIC KEY PAIR.
243#[derive(Clone, Copy, Debug, PartialEq, Eq)]
244pub enum KeyAlg {
245    Rsa1024,
246    Rsa2048,
247    Rsa3072,
248    Rsa4096,
249    EccP256,
250    EccP384,
251    Ed25519,
252    X25519,
253}
254
255impl KeyAlg {
256    /// PIV algorithm identifier byte.
257    #[must_use]
258    pub const fn id(self) -> u8 {
259        match self {
260            KeyAlg::Rsa1024 => 0x06,
261            KeyAlg::Rsa2048 => 0x07,
262            KeyAlg::Rsa3072 => 0x05,
263            KeyAlg::Rsa4096 => 0x16,
264            KeyAlg::EccP256 => 0x11,
265            KeyAlg::EccP384 => 0x14,
266            KeyAlg::Ed25519 => 0xE0,
267            KeyAlg::X25519 => 0xE1,
268        }
269    }
270
271    /// Resolve a PIV algorithm identifier.
272    #[must_use]
273    pub const fn from_id(id: u8) -> Option<Self> {
274        match id {
275            0x06 => Some(KeyAlg::Rsa1024),
276            0x07 => Some(KeyAlg::Rsa2048),
277            0x05 => Some(KeyAlg::Rsa3072),
278            0x16 => Some(KeyAlg::Rsa4096),
279            0x11 => Some(KeyAlg::EccP256),
280            0x14 => Some(KeyAlg::EccP384),
281            0xE0 => Some(KeyAlg::Ed25519),
282            0xE1 => Some(KeyAlg::X25519),
283            _ => None,
284        }
285    }
286
287    /// Short human label.
288    #[must_use]
289    pub const fn label(self) -> &'static str {
290        match self {
291            KeyAlg::Rsa1024 => "RSA-1024",
292            KeyAlg::Rsa2048 => "RSA-2048",
293            KeyAlg::Rsa3072 => "RSA-3072",
294            KeyAlg::Rsa4096 => "RSA-4096",
295            KeyAlg::EccP256 => "ECC P-256",
296            KeyAlg::EccP384 => "ECC P-384",
297            KeyAlg::Ed25519 => "Ed25519",
298            KeyAlg::X25519 => "X25519",
299        }
300    }
301}
302
303/// PIN policy for a generated key (when the slot's private key may be used).
304#[derive(Clone, Copy, Debug, PartialEq, Eq)]
305pub enum PinPolicy {
306    Default,
307    Never,
308    Once,
309    Always,
310}
311
312impl PinPolicy {
313    #[must_use]
314    pub const fn id(self) -> u8 {
315        match self {
316            PinPolicy::Default => 0x00,
317            PinPolicy::Never => 0x01,
318            PinPolicy::Once => 0x02,
319            PinPolicy::Always => 0x03,
320        }
321    }
322}
323
324/// Touch policy for a generated key (whether the key requires a physical touch).
325#[derive(Clone, Copy, Debug, PartialEq, Eq)]
326pub enum TouchPolicy {
327    Default,
328    Never,
329    Always,
330    Cached,
331}
332
333impl TouchPolicy {
334    #[must_use]
335    pub const fn id(self) -> u8 {
336        match self {
337            TouchPolicy::Default => 0x00,
338            TouchPolicy::Never => 0x01,
339            TouchPolicy::Always => 0x02,
340            TouchPolicy::Cached => 0x03,
341        }
342    }
343}
344
345/// A public key returned by GENERATE ASYMMETRIC KEY PAIR.
346#[derive(Debug, Clone, PartialEq, Eq)]
347pub enum PublicKey {
348    /// RSA modulus (`n`) and public exponent (`e`).
349    Rsa { modulus: Vec<u8>, exponent: Vec<u8> },
350    /// Elliptic-curve / EdDSA public point (uncompressed `04 || X || Y` for the
351    /// NIST curves, or the raw 32-byte point for Ed25519/X25519).
352    Ecc { point: Vec<u8> },
353}
354
355/// Parsed GET METADATA response for a key/PIN reference.
356#[derive(Debug, Clone, Default, PartialEq, Eq)]
357pub struct Metadata {
358    /// Algorithm identifier (tag 0x01), if reported.
359    pub algorithm: Option<u8>,
360    /// Whether the credential still holds its factory-default value (tag 0x05).
361    pub is_default: Option<bool>,
362    /// `(remaining, total)` retry counts (tag 0x06), for PIN/PUK references.
363    pub retries: Option<(u8, u8)>,
364    /// `(pin_policy, touch_policy)` bytes (tag 0x02), for key references.
365    pub policy: Option<(u8, u8)>,
366    /// Key origin (tag 0x03): 1 = generated on-card, 2 = imported.
367    pub origin: Option<u8>,
368    /// The slot's public key (tag 0x04), as the same inner TLVs a GENERATE
369    /// response carries (`81`/`82` for RSA, `86` for EC) — feed to
370    /// [`parse_public_key`] after wrapping, or match the tags directly.
371    pub public_key: Option<Vec<u8>>,
372}
373
374/// Errors from parsing PIV responses.
375#[derive(Debug, PartialEq, Eq)]
376pub enum ParseError {
377    /// A length field ran past the end of the buffer.
378    Truncated,
379    /// Expected the `0x53` data template wrapper and didn't find it.
380    NotDataObject,
381    /// A version/serial response was the wrong size.
382    BadResponse(&'static str),
383    /// A `0x7C` GENERAL AUTHENTICATE template was missing or malformed.
384    NotAuthTemplate,
385    /// A `0x7F49` generated-public-key template was missing or malformed.
386    NotPublicKey,
387}
388
389impl core::fmt::Display for ParseError {
390    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
391        match self {
392            ParseError::Truncated => write!(f, "PIV response truncated"),
393            ParseError::NotDataObject => write!(f, "PIV response is not a 0x53 data object"),
394            ParseError::BadResponse(w) => write!(f, "malformed PIV response: {w}"),
395            ParseError::NotAuthTemplate => {
396                write!(f, "PIV response is not a 0x7C dynamic-auth template")
397            }
398            ParseError::NotPublicKey => {
399                write!(f, "PIV response is not a 0x7F49 public-key template")
400            }
401        }
402    }
403}
404
405impl std::error::Error for ParseError {}
406
407// ---------------------------------------------------------------------------
408// APDU builders
409// ---------------------------------------------------------------------------
410
411/// SELECT the PIV application by AID (case 4: a trailing `Le` requests the
412/// application property template the card returns on success).
413#[must_use]
414pub fn select() -> Vec<u8> {
415    let mut apdu = build_apdu(
416        0x00,
417        Instruction::Select.code(),
418        INS_SELECT_P1_BY_AID,
419        0x00,
420        &AID,
421    );
422    apdu.push(0x00); // case-4 Le
423    apdu
424}
425
426/// GET DATA for the 3-byte object `tag` (e.g. a slot's [`Slot::cert_object_tag`]
427/// or [`OBJECT_CHUID`]). Case 4 — a certificate response is large and arrives via
428/// the `61xx` / GET RESPONSE loop.
429#[must_use]
430pub fn get_data(tag: &[u8]) -> Vec<u8> {
431    assert!(tag.len() <= 0x7F, "GET DATA object tag too long");
432    let mut selector = Vec::with_capacity(2 + tag.len());
433    selector.push(TAG_OBJECT_SELECTOR);
434    selector.push(tag.len() as u8);
435    selector.extend_from_slice(tag);
436    let mut apdu = build_apdu(
437        0x00,
438        Instruction::GetData.code(),
439        GET_DATA_P1,
440        GET_DATA_P2,
441        &selector,
442    );
443    apdu.push(0x00); // case-4 Le
444    apdu
445}
446
447/// VERIFY the application PIN. The PIN is padded to 8 bytes with `0xFF` per
448/// SP 800-73. The PIN bytes come from the caller and are never logged.
449#[must_use]
450pub fn verify_pin(pin: &[u8]) -> Vec<u8> {
451    build_apdu(
452        0x00,
453        Instruction::Verify.code(),
454        0x00,
455        PIN_REF_APPLICATION,
456        &pad_pin(pin),
457    )
458}
459
460/// VERIFY with an empty body — queries the PIN retry counter without consuming a
461/// try. The card answers `63Cx` (x tries left), `9000` (already verified), or
462/// `6983` (blocked). Case 1 (no `Lc`, no `Le`).
463#[must_use]
464pub fn verify_pin_status() -> Vec<u8> {
465    vec![0x00, Instruction::Verify.code(), 0x00, PIN_REF_APPLICATION]
466}
467
468/// Yubico GET VERSION (case 2): 3-byte `major.minor.patch`.
469#[must_use]
470pub fn get_version() -> Vec<u8> {
471    build_apdu_get(0x00, Instruction::GetVersion.code(), 0x00, 0x00, 0x00)
472}
473
474/// Yubico GET SERIAL (case 2): 4-byte big-endian serial (firmware 5+).
475#[must_use]
476pub fn get_serial() -> Vec<u8> {
477    build_apdu_get(0x00, Instruction::GetSerial.code(), 0x00, 0x00, 0x00)
478}
479
480/// GET RESPONSE for the `61xx` continuation loop.
481#[must_use]
482pub fn get_response() -> Vec<u8> {
483    build_apdu_get(0x00, Instruction::GetResponse.code(), 0x00, 0x00, 0x00)
484}
485
486// ---------------------------------------------------------------------------
487// TLV + extended-APDU helpers (write path)
488// ---------------------------------------------------------------------------
489
490/// Encode a BER-TLV definite length: short form below 0x80, else `0x81`/`0x82`
491/// long form. PIV write objects (certs, RSA moduli) exceed 255 bytes, so the
492/// 2-byte form is required. Values are host-built and never legitimately exceed
493/// the 2-byte form, so anything larger is a caller bug — assert rather than
494/// silently truncate the length field.
495fn push_ber_len(out: &mut Vec<u8>, len: usize) {
496    assert!(len <= 0xFFFF, "BER-TLV value too large");
497    if len < 0x80 {
498        out.push(len as u8);
499    } else if len <= 0xFF {
500        out.push(0x81);
501        out.push(len as u8);
502    } else {
503        out.push(0x82);
504        out.push((len >> 8) as u8);
505        out.push(len as u8);
506    }
507}
508
509/// Append a TLV: `tag || ber_len(value) || value`.
510fn push_tlv(out: &mut Vec<u8>, tag: &[u8], value: &[u8]) {
511    out.extend_from_slice(tag);
512    push_ber_len(out, value.len());
513    out.extend_from_slice(value);
514}
515
516/// Build a case-3/case-4 APDU, choosing short or extended-length encoding by
517/// body size. `le` requests a response (`Some(0)` = "up to 65536" in extended
518/// form, 256 in short form). YubiKey accepts extended-length APDUs over CCID;
519/// bodies over 255 bytes (cert import, RSA signing input) require them.
520fn build_apdu_ext(cla: u8, ins: u8, p1: u8, p2: u8, data: &[u8], le: Option<u16>) -> Vec<u8> {
521    assert!(data.len() <= 0xFFFF, "extended APDU body too large");
522    if data.len() <= 255 && le.map_or(true, |v| v <= 256) {
523        // Short form. Le==256 is encoded as the single byte 0x00.
524        let mut out = Vec::with_capacity(6 + data.len());
525        out.extend_from_slice(&[cla, ins, p1, p2]);
526        if !data.is_empty() {
527            out.push(data.len() as u8);
528            out.extend_from_slice(data);
529        }
530        if let Some(le) = le {
531            out.push(if le == 256 { 0x00 } else { le as u8 });
532        }
533        return out;
534    }
535    // Extended form: a leading 0x00 marker, then 2-byte Lc and/or 2-byte Le.
536    let mut out = Vec::with_capacity(9 + data.len());
537    out.extend_from_slice(&[cla, ins, p1, p2, 0x00]);
538    if !data.is_empty() {
539        out.push((data.len() >> 8) as u8);
540        out.push(data.len() as u8);
541        out.extend_from_slice(data);
542    }
543    if let Some(le) = le {
544        // 0 → 0x0000 meaning 65536.
545        out.push((le >> 8) as u8);
546        out.push(le as u8);
547    }
548    out
549}
550
551// ---------------------------------------------------------------------------
552// Write / auth APDU builders
553// ---------------------------------------------------------------------------
554
555/// GENERAL AUTHENTICATE step 1: request a witness from the management key. The
556/// card replies with `7C L 80 <bs> <ciphertext>` — the witness encrypted under
557/// the stored key.
558#[must_use]
559pub fn general_auth_request_witness(alg: MgmtAlg, key_ref: u8) -> Vec<u8> {
560    // 7C 02 80 00  — dynamic-auth template requesting tag 0x80 (witness).
561    let data = [TAG_DYN_AUTH, 0x02, 0x80, 0x00];
562    build_apdu_ext(
563        0x00,
564        Instruction::GeneralAuthenticate.code(),
565        alg.id(),
566        key_ref,
567        &data,
568        Some(256),
569    )
570}
571
572/// GENERAL AUTHENTICATE step 2: return the decrypted witness and present our own
573/// challenge. The card replies with `7C L 82 <bs> <enc(challenge)>`, which the
574/// host verifies to complete mutual authentication.
575#[must_use]
576pub fn general_auth_mutual(
577    alg: MgmtAlg,
578    key_ref: u8,
579    decrypted_witness: &[u8],
580    challenge: &[u8],
581) -> Vec<u8> {
582    let mut inner = Vec::with_capacity(decrypted_witness.len() + challenge.len() + 6);
583    push_tlv(&mut inner, &[0x80], decrypted_witness); // witness (decrypted)
584    push_tlv(&mut inner, &[0x81], challenge); // our challenge
585    let mut data = Vec::with_capacity(inner.len() + 4);
586    push_tlv(&mut data, &[TAG_DYN_AUTH], &inner);
587    build_apdu_ext(
588        0x00,
589        Instruction::GeneralAuthenticate.code(),
590        alg.id(),
591        key_ref,
592        &data,
593        Some(256),
594    )
595}
596
597/// GENERAL AUTHENTICATE in signing mode: ask a key slot to sign/decrypt
598/// `payload` (a PKCS#1 block for RSA, or a raw hash for ECC). The card replies
599/// with `7C L 82 <l> <result>`. `key_alg` is the slot's algorithm (P1),
600/// `key_ref` its slot (P2).
601#[must_use]
602pub fn general_auth_sign(key_alg: KeyAlg, key_ref: u8, payload: &[u8]) -> Vec<u8> {
603    let mut inner = Vec::with_capacity(payload.len() + 6);
604    inner.extend_from_slice(&[0x82, 0x00]); // response tag, empty: "give me the answer"
605    push_tlv(&mut inner, &[0x81], payload); // challenge / data to sign
606    let mut data = Vec::with_capacity(inner.len() + 4);
607    push_tlv(&mut data, &[TAG_DYN_AUTH], &inner);
608    build_apdu_ext(
609        0x00,
610        Instruction::GeneralAuthenticate.code(),
611        key_alg.id(),
612        key_ref,
613        &data,
614        Some(0), // large RSA result: request the lot
615    )
616}
617
618/// GENERATE ASYMMETRIC KEY PAIR in `slot`. The card creates a fresh private key
619/// and returns its public key (`7F49` template). Requires prior management-key
620/// authentication.
621#[must_use]
622pub fn generate_key(
623    slot: Slot,
624    alg: KeyAlg,
625    pin_policy: PinPolicy,
626    touch_policy: TouchPolicy,
627) -> Vec<u8> {
628    let mut control = Vec::with_capacity(9);
629    push_tlv(&mut control, &[0x80], &[alg.id()]); // algorithm
630    if pin_policy != PinPolicy::Default {
631        push_tlv(&mut control, &[0xAA], &[pin_policy.id()]);
632    }
633    if touch_policy != TouchPolicy::Default {
634        push_tlv(&mut control, &[0xAB], &[touch_policy.id()]);
635    }
636    let mut data = Vec::with_capacity(control.len() + 3);
637    push_tlv(&mut data, &[0xAC], &control); // control reference template
638    build_apdu_ext(
639        0x00,
640        Instruction::GenerateKeyPair.code(),
641        0x00,
642        slot.key_ref(),
643        &data,
644        Some(0),
645    )
646}
647
648/// PUT DATA for the 3-byte object `tag`, writing `value` wrapped in the `0x53`
649/// template. Used to import a slot certificate (see [`encode_certificate`]).
650/// Requires management-key authentication.
651#[must_use]
652pub fn put_data(tag: &[u8], value: &[u8]) -> Vec<u8> {
653    let mut data = Vec::with_capacity(tag.len() + value.len() + 8);
654    push_tlv(&mut data, &[TAG_OBJECT_SELECTOR], tag); // 5C <tag>
655    push_tlv(&mut data, &[TAG_DATA_TEMPLATE], value); // 53 <value>
656    build_apdu_ext(
657        0x00,
658        Instruction::PutData.code(),
659        GET_DATA_P1,
660        GET_DATA_P2,
661        &data,
662        None,
663    )
664}
665
666/// Wrap a DER X.509 certificate in the PIV cert data-object value: `70 <der>
667/// 71 01 <certinfo> FE 00`. `certinfo` is 0 for an uncompressed cert.
668#[must_use]
669pub fn encode_certificate(der: &[u8]) -> Vec<u8> {
670    let mut out = Vec::with_capacity(der.len() + 8);
671    push_tlv(&mut out, &[0x70], der); // the certificate itself
672    push_tlv(&mut out, &[0x71], &[0x00]); // CertInfo: 0 = uncompressed
673    push_tlv(&mut out, &[0xFE], &[]); // LRC (empty)
674    out
675}
676
677/// CHANGE REFERENCE DATA: change the PIN (`PIN_REF_APPLICATION`) or PUK
678/// (`PIN_REF_PUK`) from `old` to `new`. Both are padded to 8 bytes.
679#[must_use]
680pub fn change_reference(reference: u8, old: &[u8], new: &[u8]) -> Vec<u8> {
681    let mut body = pad_pin(old);
682    body.extend_from_slice(&pad_pin(new));
683    build_apdu(
684        0x00,
685        Instruction::ChangeReference.code(),
686        0x00,
687        reference,
688        &body,
689    )
690}
691
692/// RESET RETRY COUNTER: unblock the PIN using the PUK, setting a new PIN. Both
693/// `puk` and `new_pin` are padded to 8 bytes.
694#[must_use]
695pub fn unblock_pin(puk: &[u8], new_pin: &[u8]) -> Vec<u8> {
696    let mut body = pad_pin(puk);
697    body.extend_from_slice(&pad_pin(new_pin));
698    build_apdu(
699        0x00,
700        Instruction::ResetRetryCounter.code(),
701        0x00,
702        PIN_REF_APPLICATION,
703        &body,
704    )
705}
706
707/// Yubico SET MANAGEMENT KEY: replace the 9B card-management key. `require_touch`
708/// gates every future management-key auth on a physical touch. Requires prior
709/// management-key authentication.
710#[must_use]
711pub fn set_management_key(alg: MgmtAlg, key: &[u8], require_touch: bool) -> Vec<u8> {
712    assert!(key.len() <= 255, "management key too long");
713    // Body: <alg> 9B <keylen> <key>.
714    let mut body = Vec::with_capacity(3 + key.len());
715    body.push(alg.id());
716    body.push(KEY_REF_MANAGEMENT);
717    body.push(key.len() as u8);
718    body.extend_from_slice(key);
719    build_apdu(
720        0x00,
721        Instruction::SetManagementKey.code(),
722        0xFF,
723        // P2: 0xFF = no touch required, 0xFE = touch required (Yubico).
724        if require_touch { 0xFE } else { 0xFF },
725        &body,
726    )
727}
728
729/// Yubico SET PIN RETRIES: set the PIN and PUK retry counts. Resets both to
730/// their defaults. Requires management-key auth **and** a verified PIN.
731#[must_use]
732pub fn set_pin_retries(pin_tries: u8, puk_tries: u8) -> Vec<u8> {
733    vec![
734        0x00,
735        Instruction::SetPinRetries.code(),
736        pin_tries,
737        puk_tries,
738    ]
739}
740
741/// Yubico GET METADATA for a key/PIN reference (`0x9B`, `0x80`, `0x81`, or a
742/// slot key ref). Requires firmware 5.3+.
743#[must_use]
744pub fn get_metadata(key_ref: u8) -> Vec<u8> {
745    vec![0x00, Instruction::GetMetadata.code(), 0x00, key_ref]
746}
747
748/// Yubico RESET: wipe the PIV application back to factory defaults. Only
749/// succeeds when **both** the PIN and PUK are blocked.
750#[must_use]
751pub fn reset() -> Vec<u8> {
752    vec![0x00, Instruction::Reset.code(), 0x00, 0x00]
753}
754
755/// Pad a PIN to the fixed 8-byte PIV field with trailing `0xFF`. A PIN already
756/// 8 bytes or longer is returned truncated to 8 (PIV PINs are 6–8 bytes).
757fn pad_pin(pin: &[u8]) -> Vec<u8> {
758    let mut out = [0xFFu8; 8].to_vec();
759    let n = pin.len().min(8);
760    out[..n].copy_from_slice(&pin[..n]);
761    out
762}
763
764// ---------------------------------------------------------------------------
765// Response parsers
766// ---------------------------------------------------------------------------
767
768/// Unwrap a GET DATA response: strip the outer `0x53` template and return the
769/// inner value bytes (for a certificate object, the `70`/`71`/`FE` cert TLVs).
770pub fn unwrap_data_object(buf: &[u8]) -> Result<&[u8], ParseError> {
771    if buf.first() != Some(&TAG_DATA_TEMPLATE) {
772        return Err(ParseError::NotDataObject);
773    }
774    let (len, header) = read_ber_len(&buf[1..])?;
775    let start = 1 + header;
776    let end = start.checked_add(len).ok_or(ParseError::Truncated)?;
777    buf.get(start..end).ok_or(ParseError::Truncated)
778}
779
780/// Parse a Yubico GET VERSION reply (exactly 3 bytes) into `(major, minor, patch)`.
781pub fn parse_version(buf: &[u8]) -> Result<(u8, u8, u8), ParseError> {
782    match buf {
783        [a, b, c] => Ok((*a, *b, *c)),
784        _ => Err(ParseError::BadResponse("version is not 3 bytes")),
785    }
786}
787
788/// Parse a Yubico GET SERIAL reply (4-byte big-endian).
789pub fn parse_serial(buf: &[u8]) -> Result<u32, ParseError> {
790    match buf {
791        [a, b, c, d] => Ok(u32::from_be_bytes([*a, *b, *c, *d])),
792        _ => Err(ParseError::BadResponse("serial is not 4 bytes")),
793    }
794}
795
796/// Extract one inner TLV value (`inner_tag`) from a `0x7C` GENERAL AUTHENTICATE
797/// response template — the witness (`0x80`) from step 1, or the encrypted
798/// challenge / signature (`0x82`) from step 2 / signing.
799pub fn parse_general_auth(buf: &[u8], inner_tag: u8) -> Result<&[u8], ParseError> {
800    if buf.first() != Some(&TAG_DYN_AUTH) {
801        return Err(ParseError::NotAuthTemplate);
802    }
803    let (len, header) = read_ber_len(&buf[1..])?;
804    let start = 1 + header;
805    let end = start.checked_add(len).ok_or(ParseError::Truncated)?;
806    let inner = buf.get(start..end).ok_or(ParseError::Truncated)?;
807    find_tlv(inner, inner_tag).ok_or(ParseError::NotAuthTemplate)
808}
809
810/// Parse a `0x7F49` generated-public-key template into a [`PublicKey`]. RSA
811/// carries `81` (modulus) and `82` (exponent); EC/EdDSA carry `86` (point).
812pub fn parse_public_key(buf: &[u8]) -> Result<PublicKey, ParseError> {
813    // The template tag 0x7F49 is two bytes.
814    if buf.get(..2) != Some(&[0x7F, 0x49][..]) {
815        return Err(ParseError::NotPublicKey);
816    }
817    let (len, header) = read_ber_len(&buf[2..])?;
818    let start = 2 + header;
819    let end = start.checked_add(len).ok_or(ParseError::Truncated)?;
820    let inner = buf.get(start..end).ok_or(ParseError::Truncated)?;
821    if let Some(point) = find_tlv(inner, 0x86) {
822        return Ok(PublicKey::Ecc {
823            point: point.to_vec(),
824        });
825    }
826    let modulus = find_tlv(inner, 0x81).ok_or(ParseError::NotPublicKey)?;
827    let exponent = find_tlv(inner, 0x82).ok_or(ParseError::NotPublicKey)?;
828    Ok(PublicKey::Rsa {
829        modulus: modulus.to_vec(),
830        exponent: exponent.to_vec(),
831    })
832}
833
834/// Parse a Yubico GET METADATA response (a flat list of `tag len value` TLVs).
835pub fn parse_metadata(buf: &[u8]) -> Result<Metadata, ParseError> {
836    let mut md = Metadata::default();
837    let mut i = 0;
838    while i < buf.len() {
839        let tag = buf[i];
840        let (len, header) = read_ber_len(&buf[i + 1..])?;
841        let vstart = i + 1 + header;
842        let vend = vstart.checked_add(len).ok_or(ParseError::Truncated)?;
843        let value = buf.get(vstart..vend).ok_or(ParseError::Truncated)?;
844        match tag {
845            0x01 => md.algorithm = value.first().copied(),
846            0x02 if value.len() >= 2 => md.policy = Some((value[0], value[1])),
847            0x03 => md.origin = value.first().copied(),
848            0x04 => md.public_key = Some(value.to_vec()),
849            0x05 => md.is_default = value.first().map(|&b| b != 0),
850            0x06 if value.len() >= 2 => md.retries = Some((value[0], value[1])),
851            _ => {}
852        }
853        i = vend;
854    }
855    Ok(md)
856}
857
858/// Find the value of the first top-level TLV with single-byte `tag` in `buf`.
859/// Public so the transport layer can reuse it instead of growing its own
860/// BER-TLV walker.
861#[must_use]
862pub fn find_tlv(buf: &[u8], tag: u8) -> Option<&[u8]> {
863    let mut i = 0;
864    while i < buf.len() {
865        let t = buf[i];
866        let (len, header) = read_ber_len(buf.get(i + 1..)?).ok()?;
867        let vstart = i + 1 + header;
868        let vend = vstart.checked_add(len)?;
869        let value = buf.get(vstart..vend)?;
870        if t == tag {
871            return Some(value);
872        }
873        i = vend;
874    }
875    None
876}
877
878/// Read a BER-TLV length field, returning `(length, header_byte_count)`.
879/// Handles the short form and the `0x81`/`0x82` long forms (a PIV cert easily
880/// exceeds 255 bytes, so the 2-byte form is required). Indefinite (`0x80`) and
881/// longer forms are deliberately rejected — no PIV object needs them.
882pub fn read_ber_len(buf: &[u8]) -> Result<(usize, usize), ParseError> {
883    let first = *buf.first().ok_or(ParseError::Truncated)?;
884    if first < 0x80 {
885        return Ok((first as usize, 1));
886    }
887    let n = (first & 0x7F) as usize;
888    if n == 0 || n > 2 {
889        return Err(ParseError::BadResponse("unsupported BER length form"));
890    }
891    let bytes = buf.get(1..1 + n).ok_or(ParseError::Truncated)?;
892    let len = bytes.iter().fold(0usize, |acc, &b| (acc << 8) | b as usize);
893    Ok((len, 1 + n))
894}
895
896#[cfg(test)]
897mod tests {
898    use super::*;
899
900    #[test]
901    fn select_bytes() {
902        // 00 A4 04 00 05 A0 00 00 03 08 00
903        assert_eq!(
904            select(),
905            vec![0x00, 0xA4, 0x04, 0x00, 0x05, 0xA0, 0x00, 0x00, 0x03, 0x08, 0x00]
906        );
907    }
908
909    #[test]
910    fn get_data_auth_cert_bytes() {
911        // 00 CB 3F FF 05 5C 03 5F C1 05 00
912        assert_eq!(
913            get_data(&Slot::Authentication.cert_object_tag()),
914            vec![0x00, 0xCB, 0x3F, 0xFF, 0x05, 0x5C, 0x03, 0x5F, 0xC1, 0x05, 0x00]
915        );
916    }
917
918    #[test]
919    fn slot_key_refs_and_tags() {
920        assert_eq!(Slot::Authentication.key_ref(), 0x9A);
921        assert_eq!(Slot::Signature.key_ref(), 0x9C);
922        assert_eq!(Slot::KeyManagement.key_ref(), 0x9D);
923        assert_eq!(Slot::CardAuthentication.key_ref(), 0x9E);
924        assert_eq!(Slot::Signature.cert_object_tag(), [0x5F, 0xC1, 0x0A]);
925        assert_eq!(
926            Slot::CardAuthentication.cert_object_tag(),
927            [0x5F, 0xC1, 0x01]
928        );
929    }
930
931    #[test]
932    fn verify_pin_pads_to_eight() {
933        // 00 20 00 80 08 31 32 33 34 35 36 FF FF   ("123456" + FF FF)
934        assert_eq!(
935            verify_pin(b"123456"),
936            vec![0x00, 0x20, 0x00, 0x80, 0x08, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0xFF, 0xFF]
937        );
938    }
939
940    #[test]
941    fn verify_status_is_case1() {
942        assert_eq!(verify_pin_status(), vec![0x00, 0x20, 0x00, 0x80]);
943    }
944
945    #[test]
946    fn version_and_serial_apdus() {
947        assert_eq!(get_version(), vec![0x00, 0xFD, 0x00, 0x00, 0x00]);
948        assert_eq!(get_serial(), vec![0x00, 0xF8, 0x00, 0x00, 0x00]);
949    }
950
951    #[test]
952    fn unwrap_short_data_object() {
953        // 53 03 AA BB CC -> AA BB CC
954        assert_eq!(
955            unwrap_data_object(&[0x53, 0x03, 0xAA, 0xBB, 0xCC]).unwrap(),
956            &[0xAA, 0xBB, 0xCC]
957        );
958    }
959
960    #[test]
961    fn unwrap_long_form_data_object() {
962        // 53 81 80 <128 bytes>
963        let mut buf = vec![0x53, 0x81, 0x80];
964        buf.extend(std::iter::repeat(0x11).take(128));
965        let inner = unwrap_data_object(&buf).unwrap();
966        assert_eq!(inner.len(), 128);
967        assert!(inner.iter().all(|&b| b == 0x11));
968    }
969
970    #[test]
971    fn unwrap_rejects_non_template_and_truncation() {
972        assert_eq!(
973            unwrap_data_object(&[0x70, 0x01, 0x00]),
974            Err(ParseError::NotDataObject)
975        );
976        assert_eq!(
977            unwrap_data_object(&[0x53, 0x05, 0x00]),
978            Err(ParseError::Truncated)
979        );
980    }
981
982    #[test]
983    fn parse_version_and_serial_values() {
984        assert_eq!(parse_version(&[5, 7, 1]).unwrap(), (5, 7, 1));
985        assert!(parse_version(&[5, 7]).is_err());
986        assert_eq!(parse_serial(&[0x02, 0x40, 0x8A, 0x1B]).unwrap(), 0x02408A1B);
987        assert!(parse_serial(&[0x00, 0x01]).is_err());
988    }
989
990    #[test]
991    fn mgmt_alg_round_trips_and_sizes() {
992        for a in [
993            MgmtAlg::TripleDes,
994            MgmtAlg::Aes128,
995            MgmtAlg::Aes192,
996            MgmtAlg::Aes256,
997        ] {
998            assert_eq!(MgmtAlg::from_id(a.id()), Some(a));
999        }
1000        assert_eq!(MgmtAlg::Aes192.id(), 0x0A);
1001        assert_eq!(MgmtAlg::Aes192.block_size(), 16);
1002        assert_eq!(MgmtAlg::Aes192.key_len(), 24);
1003        assert_eq!(MgmtAlg::TripleDes.block_size(), 8);
1004        assert_eq!(MgmtAlg::Aes256.key_len(), 32);
1005        assert_eq!(MgmtAlg::from_id(0x99), None);
1006    }
1007
1008    #[test]
1009    fn key_alg_round_trips() {
1010        for a in [
1011            KeyAlg::Rsa1024,
1012            KeyAlg::Rsa2048,
1013            KeyAlg::Rsa3072,
1014            KeyAlg::Rsa4096,
1015            KeyAlg::EccP256,
1016            KeyAlg::EccP384,
1017            KeyAlg::Ed25519,
1018            KeyAlg::X25519,
1019        ] {
1020            assert_eq!(KeyAlg::from_id(a.id()), Some(a));
1021        }
1022        assert_eq!(KeyAlg::Rsa2048.id(), 0x07);
1023        assert_eq!(KeyAlg::EccP256.id(), 0x11);
1024    }
1025
1026    #[test]
1027    fn witness_request_bytes() {
1028        // 00 87 0A 9B 04 7C 02 80 00 00  (P1=AES-192 alg, P2=9B, Le=00)
1029        assert_eq!(
1030            general_auth_request_witness(MgmtAlg::Aes192, KEY_REF_MANAGEMENT),
1031            vec![0x00, 0x87, 0x0A, 0x9B, 0x04, 0x7C, 0x02, 0x80, 0x00, 0x00]
1032        );
1033    }
1034
1035    #[test]
1036    fn mutual_auth_bytes_aes() {
1037        // 16-byte witness + 16-byte challenge → inner 7C 24 80 10 .. 81 10 ..
1038        let w = [0xAAu8; 16];
1039        let c = [0xBBu8; 16];
1040        let apdu = general_auth_mutual(MgmtAlg::Aes192, KEY_REF_MANAGEMENT, &w, &c);
1041        assert_eq!(&apdu[..5], &[0x00, 0x87, 0x0A, 0x9B, 0x26]); // Lc = 0x26 = 38
1042        assert_eq!(&apdu[5..9], &[0x7C, 0x24, 0x80, 0x10]);
1043        assert_eq!(&apdu[9..25], &w);
1044        assert_eq!(&apdu[25..27], &[0x81, 0x10]);
1045        assert_eq!(&apdu[27..43], &c);
1046        assert_eq!(apdu[43], 0x00); // Le
1047    }
1048
1049    #[test]
1050    fn generate_key_bytes_default_policy() {
1051        // 00 47 00 9A 05 AC 03 80 01 11 00  (ECC P-256 in 9A, default policies)
1052        assert_eq!(
1053            generate_key(
1054                Slot::Authentication,
1055                KeyAlg::EccP256,
1056                PinPolicy::Default,
1057                TouchPolicy::Default
1058            ),
1059            vec![0x00, 0x47, 0x00, 0x9A, 0x05, 0xAC, 0x03, 0x80, 0x01, 0x11, 0x00]
1060        );
1061    }
1062
1063    #[test]
1064    fn generate_key_bytes_with_policies() {
1065        // control: 80 01 07, AA 01 02 (pin once), AB 01 02 (touch always)
1066        assert_eq!(
1067            generate_key(
1068                Slot::Signature,
1069                KeyAlg::Rsa2048,
1070                PinPolicy::Once,
1071                TouchPolicy::Always
1072            ),
1073            vec![
1074                0x00, 0x47, 0x00, 0x9C, 0x0B, 0xAC, 0x09, 0x80, 0x01, 0x07, 0xAA, 0x01, 0x02, 0xAB,
1075                0x01, 0x02, 0x00
1076            ]
1077        );
1078    }
1079
1080    #[test]
1081    fn change_pin_bytes() {
1082        // 00 24 00 80 10 <old pad8> <new pad8>
1083        let apdu = change_reference(PIN_REF_APPLICATION, b"123456", b"654321");
1084        assert_eq!(&apdu[..5], &[0x00, 0x24, 0x00, 0x80, 0x10]);
1085        assert_eq!(
1086            &apdu[5..],
1087            &[
1088                0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0xFF, 0xFF, // 123456 + FF FF
1089                0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0xFF, 0xFF, // 654321 + FF FF
1090            ]
1091        );
1092    }
1093
1094    #[test]
1095    fn unblock_pin_bytes() {
1096        let apdu = unblock_pin(b"12345678", b"000000");
1097        assert_eq!(&apdu[..5], &[0x00, 0x2C, 0x00, 0x80, 0x10]);
1098        assert_eq!(&apdu[5..13], b"12345678");
1099        assert_eq!(
1100            &apdu[13..],
1101            &[0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0xFF, 0xFF]
1102        );
1103    }
1104
1105    #[test]
1106    fn set_management_key_bytes() {
1107        let key = [0x42u8; 24];
1108        // 00 FF FF 00 1B 0A 9B 18 <24 key bytes>
1109        let apdu = set_management_key(MgmtAlg::Aes192, &key, false);
1110        assert_eq!(&apdu[..5], &[0x00, 0xFF, 0xFF, 0xFF, 0x1B]);
1111        assert_eq!(&apdu[5..8], &[0x0A, 0x9B, 0x18]);
1112        assert_eq!(&apdu[8..], &key);
1113        // touch flag flips P2 to 0xFE
1114        assert_eq!(set_management_key(MgmtAlg::Aes192, &key, true)[3], 0xFE);
1115    }
1116
1117    #[test]
1118    fn set_pin_retries_and_reset_and_metadata_bytes() {
1119        assert_eq!(set_pin_retries(5, 3), vec![0x00, 0xFA, 0x05, 0x03]);
1120        assert_eq!(reset(), vec![0x00, 0xFB, 0x00, 0x00]);
1121        assert_eq!(get_metadata(0x9B), vec![0x00, 0xF7, 0x00, 0x9B]);
1122    }
1123
1124    #[test]
1125    fn put_data_short_object() {
1126        // small value uses short-form Lc
1127        let apdu = put_data(&OBJECT_CHUID, &[0xDE, 0xAD]);
1128        // 00 DB 3F FF <Lc> 5C 03 5F C1 02 53 02 DE AD
1129        assert_eq!(&apdu[..4], &[0x00, 0xDB, 0x3F, 0xFF]);
1130        assert_eq!(apdu[4], 0x09); // Lc = 9 (5-byte selector + 4-byte template)
1131        assert_eq!(
1132            &apdu[5..],
1133            &[0x5C, 0x03, 0x5F, 0xC1, 0x02, 0x53, 0x02, 0xDE, 0xAD]
1134        );
1135    }
1136
1137    #[test]
1138    fn put_data_large_object_uses_extended_apdu() {
1139        // A 1 KB cert forces extended-length encoding (leading 00, 2-byte Lc).
1140        let der = vec![0x11u8; 1024];
1141        let value = encode_certificate(&der);
1142        let apdu = put_data(&Slot::Signature.cert_object_tag(), &value);
1143        assert_eq!(&apdu[..5], &[0x00, 0xDB, 0x3F, 0xFF, 0x00]); // extended marker
1144        let lc = ((apdu[5] as usize) << 8) | apdu[6] as usize;
1145        assert_eq!(lc, apdu.len() - 7); // body length matches 2-byte Lc
1146    }
1147
1148    #[test]
1149    fn encode_certificate_wraps_der() {
1150        let der = [0xAB, 0xCD, 0xEF];
1151        // 70 03 AB CD EF 71 01 00 FE 00
1152        assert_eq!(
1153            encode_certificate(&der),
1154            vec![0x70, 0x03, 0xAB, 0xCD, 0xEF, 0x71, 0x01, 0x00, 0xFE, 0x00]
1155        );
1156    }
1157
1158    #[test]
1159    fn parse_general_auth_extracts_witness() {
1160        // 7C 0A 80 08 <8-byte witness>
1161        let mut buf = vec![0x7C, 0x0A, 0x80, 0x08];
1162        buf.extend_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]);
1163        assert_eq!(
1164            parse_general_auth(&buf, 0x80).unwrap(),
1165            &[1, 2, 3, 4, 5, 6, 7, 8]
1166        );
1167        // wrong outer tag
1168        assert_eq!(
1169            parse_general_auth(&[0x70, 0x02, 0x80, 0x00], 0x80),
1170            Err(ParseError::NotAuthTemplate)
1171        );
1172    }
1173
1174    #[test]
1175    fn parse_public_key_rsa_and_ecc() {
1176        // RSA: 7F49 <len> 81 04 <mod> 82 03 01 00 01
1177        let mut rsa = vec![
1178            0x7F, 0x49, 0x0B, 0x81, 0x04, 0xAA, 0xBB, 0xCC, 0xDD, 0x82, 0x03,
1179        ];
1180        rsa.extend_from_slice(&[0x01, 0x00, 0x01]);
1181        match parse_public_key(&rsa).unwrap() {
1182            PublicKey::Rsa { modulus, exponent } => {
1183                assert_eq!(modulus, vec![0xAA, 0xBB, 0xCC, 0xDD]);
1184                assert_eq!(exponent, vec![0x01, 0x00, 0x01]);
1185            }
1186            _ => panic!("expected RSA"),
1187        }
1188        // ECC: 7F49 <len> 86 04 <point>
1189        let ecc = vec![0x7F, 0x49, 0x06, 0x86, 0x04, 0x04, 0x11, 0x22, 0x33];
1190        match parse_public_key(&ecc).unwrap() {
1191            PublicKey::Ecc { point } => assert_eq!(point, vec![0x04, 0x11, 0x22, 0x33]),
1192            _ => panic!("expected ECC"),
1193        }
1194    }
1195
1196    #[test]
1197    fn parse_metadata_mgmt_and_pin() {
1198        // mgmt 9B: 01 01 0A  02 02 00 01  05 01 01   (alg AES-192, default)
1199        let md =
1200            parse_metadata(&[0x01, 0x01, 0x0A, 0x02, 0x02, 0x00, 0x01, 0x05, 0x01, 0x01]).unwrap();
1201        assert_eq!(md.algorithm, Some(0x0A));
1202        assert_eq!(md.is_default, Some(true));
1203        assert_eq!(md.policy, Some((0x00, 0x01)));
1204        // PIN 80: 06 02 03 03 (3 of 3 retries), 05 01 00 (not default)
1205        let pin = parse_metadata(&[0x06, 0x02, 0x03, 0x03, 0x05, 0x01, 0x00]).unwrap();
1206        assert_eq!(pin.retries, Some((3, 3)));
1207        assert_eq!(pin.is_default, Some(false));
1208    }
1209
1210    #[test]
1211    fn parse_metadata_origin_and_public_key() {
1212        // slot 9A: 01 01 11 (ECC P-256), 03 01 01 (generated), 04 04 86 02 AA BB
1213        let md = parse_metadata(&[
1214            0x01, 0x01, 0x11, 0x03, 0x01, 0x01, 0x04, 0x04, 0x86, 0x02, 0xAA, 0xBB,
1215        ])
1216        .unwrap();
1217        assert_eq!(md.origin, Some(1));
1218        assert_eq!(md.public_key, Some(vec![0x86, 0x02, 0xAA, 0xBB]));
1219    }
1220
1221    #[test]
1222    fn parse_metadata_rejects_garbage() {
1223        // tag with no length byte
1224        assert_eq!(parse_metadata(&[0x06]), Err(ParseError::Truncated));
1225        // length runs past the buffer
1226        assert_eq!(
1227            parse_metadata(&[0x01, 0x05, 0xAA]),
1228            Err(ParseError::Truncated)
1229        );
1230        // indefinite-length form is rejected, not misread
1231        assert!(matches!(
1232            parse_metadata(&[0x01, 0x80, 0x00]),
1233            Err(ParseError::BadResponse(_))
1234        ));
1235    }
1236
1237    #[test]
1238    fn general_auth_sign_short_and_extended() {
1239        // Small ECC payload stays in a short APDU:
1240        // 00 87 11 9A 0A  7C 08 82 00 81 04 <payload>  00
1241        let apdu = general_auth_sign(KeyAlg::EccP256, 0x9A, &[0xAA, 0xBB, 0xCC, 0xDD]);
1242        assert_eq!(
1243            apdu,
1244            vec![
1245                0x00, 0x87, 0x11, 0x9A, 0x0A, 0x7C, 0x08, 0x82, 0x00, 0x81, 0x04, 0xAA, 0xBB, 0xCC,
1246                0xDD, 0x00,
1247            ]
1248        );
1249        // A 256-byte RSA-2048 block forces the extended form: marker 0x00,
1250        // 2-byte Lc, body, 2-byte Le 0x0000 ("up to 65536").
1251        let apdu = general_auth_sign(KeyAlg::Rsa2048, 0x9A, &[0x55; 256]);
1252        // data: 7C 82 01 06 ( 82 00  81 82 01 00 <256> )
1253        assert_eq!(&apdu[..5], &[0x00, 0x87, 0x07, 0x9A, 0x00]);
1254        let lc = ((apdu[5] as usize) << 8) | apdu[6] as usize;
1255        assert_eq!(lc, 4 + 2 + 4 + 256); // 7C len hdr + 82 00 + 81 len hdr + payload
1256        assert_eq!(&apdu[7..11], &[0x7C, 0x82, 0x01, 0x06]);
1257        assert_eq!(&apdu[apdu.len() - 2..], &[0x00, 0x00]);
1258        assert_eq!(apdu.len(), 7 + lc + 2);
1259    }
1260
1261    #[test]
1262    fn get_response_bytes() {
1263        assert_eq!(get_response(), vec![0x00, 0xC0, 0x00, 0x00, 0x00]);
1264    }
1265
1266    #[test]
1267    fn pad_pin_truncates_and_pads() {
1268        // Documented behavior: longer-than-8 input is truncated (callers must
1269        // validate 6–8 first); shorter input is 0xFF-padded.
1270        let apdu = verify_pin(b"1234567890");
1271        assert_eq!(&apdu[5..], b"12345678");
1272        let apdu = verify_pin(b"123456");
1273        assert_eq!(
1274            &apdu[5..],
1275            &[0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0xFF, 0xFF]
1276        );
1277    }
1278
1279    #[test]
1280    fn read_ber_len_forms() {
1281        // two-byte long form (a typical certificate length)
1282        assert_eq!(read_ber_len(&[0x82, 0x01, 0x30]).unwrap(), (0x130, 3));
1283        assert_eq!(read_ber_len(&[0x81, 0xC8]).unwrap(), (0xC8, 2));
1284        // indefinite and >2-byte forms are unsupported
1285        assert!(matches!(
1286            read_ber_len(&[0x80]),
1287            Err(ParseError::BadResponse(_))
1288        ));
1289        assert!(matches!(
1290            read_ber_len(&[0x83, 0x01, 0x00, 0x00]),
1291            Err(ParseError::BadResponse(_))
1292        ));
1293        // truncated long form
1294        assert_eq!(read_ber_len(&[0x82, 0x01]), Err(ParseError::Truncated));
1295        assert_eq!(read_ber_len(&[]), Err(ParseError::Truncated));
1296    }
1297}