Skip to main content

keyroost_openpgp/
lib.rs

1//! OpenPGP Card (v3.4) APDU command/response layer.
2//!
3//! Phase 4 of extending keyroost toward ykman parity. The OpenPGP applet is a
4//! CCID/APDU smartcard applet on YubiKeys and Trussed devices (Solo 2 / Nitrokey
5//! 3, via `opcard`), reachable over the existing PC/SC transport — no second
6//! transport stack. This crate is the pure-Rust command/response layer (APDU
7//! builders + the application-related-data TLV parser); the actual card exchange
8//! lives in `keyroost-transport`.
9//!
10//! Reference: OpenPGP Card spec v3.4, and `Nitrokey/opcard-rs`.
11//!
12//! # What is and isn't here
13//!
14//! This is the *byte layer*: it turns intentions into APDU byte vectors and
15//! turns response byte slices into typed structures. It performs **no I/O**.
16//! Card transmit, the `61xx` / `GET RESPONSE` reassembly loop, PIN entry, and
17//! the higher-level key-management operations are deliberately left for the
18//! transport phase; see the `TODO(transport)` notes on [`Instruction`] and the
19//! builders that are intentionally absent.
20//!
21//! Unlike the OATH applet (Yubico's SIMPLE-TLV, short-form lengths only), the
22//! OpenPGP applet uses ISO 7816-4 **BER-TLV**: two-byte ("high") tags and
23//! long-form lengths. The parser here handles both forms; see [`parse_tlvs`].
24
25use keyroost_proto::apdu::{build_apdu, build_apdu_get};
26
27// ---------------------------------------------------------------------------
28// Application identifier
29// ---------------------------------------------------------------------------
30
31/// OpenPGP application AID *prefix* used to `SELECT` the applet by DF name:
32/// RID `D2 76 00 01 24` (PGP) + application `01` (OpenPGP). The full 16-byte
33/// AID on the card additionally carries the spec version, manufacturer, and a
34/// serial number — but [`select`] addresses the applet with this 6-byte prefix.
35pub const AID_PREFIX: [u8; 6] = [0xD2, 0x76, 0x00, 0x01, 0x24, 0x01];
36
37// ---------------------------------------------------------------------------
38// Instruction bytes
39// ---------------------------------------------------------------------------
40
41/// ISO 7816 `SELECT` instruction (used to activate the applet).
42pub const INS_SELECT: u8 = 0xA4;
43/// `SELECT` P1: select by DF name (AID).
44pub const P1_SELECT_BY_NAME: u8 = 0x04;
45
46/// OpenPGP Card instruction bytes (OpenPGP Card spec v3.4, §7.2).
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48#[repr(u8)]
49pub enum Instruction {
50    /// `SELECT` (ISO 7816) — activate the OpenPGP applet.
51    Select = 0xA4,
52    /// `GET DATA` — read a data object by its (1- or 2-byte) tag in P1-P2.
53    GetData = 0xCA,
54    /// `PUT DATA` — write a data object (see [`put_data`], [`put_cardholder_name`],
55    /// [`put_url`], and the key-import builders).
56    PutData = 0xDA,
57    /// `VERIFY` — present a PIN (PW1/PW3) referenced by P2 (see [`verify`]).
58    Verify = 0x20,
59    /// `CHANGE REFERENCE DATA` — change a PIN (see [`change_reference_data`]).
60    ChangeReferenceData = 0x24,
61    /// `RESET RETRY COUNTER` — unblock PW1 using PW3 (see [`reset_retry_counter`])
62    /// or the resetting code (not modelled).
63    ResetRetryCounter = 0x2C,
64    /// `PERFORM SECURITY OPERATION` — compute signature (P1P2 `9E9A`, see
65    /// [`pso_compute_signature`]) or decipher (P1P2 `8086`, see [`pso_decipher`]).
66    PerformSecurityOperation = 0x2A,
67    /// `INTERNAL AUTHENTICATE` — client/SSH authentication signature.
68    ///
69    /// TODO(transport): builder not provided yet.
70    InternalAuthenticate = 0x88,
71    /// `GENERATE ASYMMETRIC KEY PAIR` — P1 `80` generate, `81` read public key
72    /// (see [`generate_key`], [`read_public_key`]).
73    GenerateAsymmetricKeyPair = 0x47,
74    /// `GET RESPONSE` — continue reading a response the card split across `61xx`.
75    GetResponse = 0xC0,
76    /// `ACTIVATE FILE` — paired with [`Instruction::TerminateDf`] for factory
77    /// reset (see [`activate_file`]).
78    ActivateFile = 0x44,
79    /// `TERMINATE DF` — paired with [`Instruction::ActivateFile`] for factory
80    /// reset (see [`terminate_df`]).
81    TerminateDf = 0xE6,
82}
83
84impl Instruction {
85    /// The raw instruction byte.
86    #[must_use]
87    pub const fn code(self) -> u8 {
88        self as u8
89    }
90}
91
92// ---------------------------------------------------------------------------
93// VERIFY password-reference (P2) constants
94// ---------------------------------------------------------------------------
95
96/// `VERIFY` P2: PW1 in the signing context (valid for PSO:CDS).
97pub const PW1_SIGN: u8 = 0x81;
98/// `VERIFY` P2: PW1 in the "other" context (decipher, internal authenticate).
99pub const PW1_OTHER: u8 = 0x82;
100/// `VERIFY` P2: PW3 (admin) PIN.
101pub const PW3_ADMIN: u8 = 0x83;
102
103// ---------------------------------------------------------------------------
104// PSO / GENERATE parameter bytes (exposed for the transport phase)
105// ---------------------------------------------------------------------------
106
107/// `PSO` P1-P2 selecting *compute digital signature* (`9E 9A`).
108pub const PSO_COMPUTE_SIGNATURE: u16 = 0x9E9A;
109/// `PSO` P1-P2 selecting *decipher* (`80 86`).
110pub const PSO_DECIPHER: u16 = 0x8086;
111/// `GENERATE ASYMMETRIC KEY PAIR` P1: generate a fresh key pair.
112pub const GENERATE_KEY: u8 = 0x80;
113/// `GENERATE ASYMMETRIC KEY PAIR` P1: read an existing public key.
114pub const READ_PUBLIC_KEY: u8 = 0x81;
115
116// ---------------------------------------------------------------------------
117// Data-object tags (BER-TLV; 1- or 2-byte)
118// ---------------------------------------------------------------------------
119
120/// Application Identifier (full 16-byte AID).
121pub const TAG_AID: u16 = 0x004F;
122/// Login data.
123pub const TAG_LOGIN_DATA: u16 = 0x005E;
124/// URL of the public key.
125pub const TAG_URL: u16 = 0x5F50;
126/// Historical bytes.
127pub const TAG_HISTORICAL_BYTES: u16 = 0x5F52;
128/// Cardholder Related Data (constructed: contains `5B`, `5F2D`, `5F35`).
129pub const TAG_CARDHOLDER_RELATED_DATA: u16 = 0x0065;
130/// Cardholder Name (inside [`TAG_CARDHOLDER_RELATED_DATA`]).
131pub const TAG_NAME: u16 = 0x005B;
132/// Language preference (inside [`TAG_CARDHOLDER_RELATED_DATA`]).
133pub const TAG_LANGUAGE: u16 = 0x5F2D;
134/// Sex (inside [`TAG_CARDHOLDER_RELATED_DATA`]).
135pub const TAG_SEX: u16 = 0x5F35;
136/// Application Related Data (constructed; the big aggregate object).
137pub const TAG_APPLICATION_RELATED_DATA: u16 = 0x006E;
138/// Discretionary data objects (constructed; inside [`TAG_APPLICATION_RELATED_DATA`]).
139pub const TAG_DISCRETIONARY: u16 = 0x0073;
140/// Extended capabilities (inside [`TAG_DISCRETIONARY`]).
141pub const TAG_EXTENDED_CAPABILITIES: u16 = 0x00C0;
142/// Algorithm attributes — signature key (inside [`TAG_DISCRETIONARY`]).
143pub const TAG_ALGO_ATTR_SIG: u16 = 0x00C1;
144/// Algorithm attributes — decryption key.
145pub const TAG_ALGO_ATTR_DEC: u16 = 0x00C2;
146/// Algorithm attributes — authentication key.
147pub const TAG_ALGO_ATTR_AUT: u16 = 0x00C3;
148/// PW status bytes (inside [`TAG_DISCRETIONARY`], also a standalone GET DATA).
149pub const TAG_PW_STATUS: u16 = 0x00C4;
150/// Fingerprints — 60 bytes = 3×20 (Sig, Dec, Aut).
151pub const TAG_FINGERPRINTS: u16 = 0x00C5;
152/// CA fingerprints — 60 bytes = 3×20.
153pub const TAG_CA_FINGERPRINTS: u16 = 0x00C6;
154/// Key generation timestamps.
155pub const TAG_GENERATION_TIMES: u16 = 0x00CD;
156
157/// Fingerprint — signature key (`C7`, 20 bytes); a standalone PUT DATA target.
158pub const TAG_FPR_SIGN: u16 = 0x00C7;
159/// Fingerprint — decryption key (`C8`, 20 bytes); a standalone PUT DATA target.
160pub const TAG_FPR_DEC: u16 = 0x00C8;
161/// Fingerprint — authentication key (`C9`, 20 bytes); a standalone PUT DATA target.
162pub const TAG_FPR_AUTH: u16 = 0x00C9;
163/// Generation timestamp — signature key (`CE`, 4-byte big-endian Unix time).
164pub const TAG_TIME_SIGN: u16 = 0x00CE;
165/// Generation timestamp — decryption key (`CF`, 4-byte big-endian Unix time).
166pub const TAG_TIME_DEC: u16 = 0x00CF;
167/// Generation timestamp — authentication key (`D0`, 4-byte big-endian Unix time).
168pub const TAG_TIME_AUTH: u16 = 0x00D0;
169/// Security support template (constructed; contains [`TAG_DS_COUNTER`]).
170pub const TAG_SECURITY_SUPPORT: u16 = 0x007A;
171/// Digital signature counter (3-byte big-endian; inside [`TAG_SECURITY_SUPPORT`]).
172pub const TAG_DS_COUNTER: u16 = 0x0093;
173
174/// Public-key data object (constructed) returned by GENERATE / READ PUBLIC KEY.
175pub const TAG_PUBLIC_KEY: u16 = 0x7F49;
176/// RSA modulus *n* (inside [`TAG_PUBLIC_KEY`]).
177pub const TAG_RSA_MODULUS: u16 = 0x0081;
178/// RSA public exponent *e* (inside [`TAG_PUBLIC_KEY`]).
179pub const TAG_RSA_EXPONENT: u16 = 0x0082;
180/// EC public point (inside [`TAG_PUBLIC_KEY`]) for ECDSA/ECDH/EdDSA keys.
181pub const TAG_EC_PUBLIC_POINT: u16 = 0x0086;
182
183/// Status word: success.
184pub const SW_OK: u16 = 0x9000;
185/// High byte of the "more data available" status (`61xx`).
186pub const SW_MORE_DATA: u8 = 0x61;
187
188// ---------------------------------------------------------------------------
189// APDU builders
190// ---------------------------------------------------------------------------
191
192/// `SELECT` the OpenPGP applet by DF name:
193/// `00 A4 04 00 06 D2 76 00 01 24 01`.
194#[must_use]
195pub fn select() -> Vec<u8> {
196    build_apdu(0x00, INS_SELECT, P1_SELECT_BY_NAME, 0x00, &AID_PREFIX)
197}
198
199/// `GET DATA` for the data object identified by the 2-byte `tag` (placed in
200/// P1-P2). Case-2 APDU with `Le = 0` ("up to 256 bytes"); larger objects are
201/// continued with [`get_response`] in the transport phase.
202#[must_use]
203pub fn get_data(tag: u16) -> Vec<u8> {
204    let p1 = (tag >> 8) as u8;
205    let p2 = (tag & 0xFF) as u8;
206    build_apdu_get(0x00, Instruction::GetData.code(), p1, p2, 0x00)
207}
208
209/// `GET DATA 006E` — the Application Related Data aggregate:
210/// `00 CA 00 6E 00`.
211#[must_use]
212pub fn get_application_related_data() -> Vec<u8> {
213    get_data(TAG_APPLICATION_RELATED_DATA)
214}
215
216/// `GET DATA 00C4` — the standalone PW status bytes: `00 CA 00 C4 00`.
217#[must_use]
218pub fn get_pw_status() -> Vec<u8> {
219    get_data(TAG_PW_STATUS)
220}
221
222/// `VERIFY` — present `pin` against the password reference `pw_ref` (one of
223/// [`PW1_SIGN`], [`PW1_OTHER`], [`PW3_ADMIN`]).
224///
225/// Builds a case-3 APDU `00 20 00 <pw_ref> <Lc> <pin...>`. The PIN bytes come
226/// from the caller; this layer neither sources nor stores them (see the privacy
227/// posture in `CLAUDE.md`).
228#[must_use]
229pub fn verify(pw_ref: u8, pin: &[u8]) -> Vec<u8> {
230    build_apdu(0x00, Instruction::Verify.code(), 0x00, pw_ref, pin)
231}
232
233/// `GET RESPONSE` (case-2): retrieve the next chunk after a `61xx` status word.
234///
235/// TODO(transport): the reassembly loop (transmit, inspect `SW`, repeat) belongs
236/// in `keyroost-transport`; this builder only emits the request APDU.
237#[must_use]
238pub fn get_response() -> Vec<u8> {
239    build_apdu_get(0x00, Instruction::GetResponse.code(), 0x00, 0x00, 0x00)
240}
241
242/// `TERMINATE DF` (`00 E6 00 00`) — the first half of an OpenPGP applet factory
243/// reset. Case-1 APDU (no body, no Le). When the applet is unblocked this needs
244/// PW3, but once PW1 *and* PW3 are blocked (both retry counters at 0) the card
245/// accepts it unconditionally — that's how a forgotten-PIN card is reset. After
246/// this the applet is in the "terminated" state and only [`activate_file`] (or
247/// re-SELECT) is accepted.
248#[must_use]
249pub fn terminate_df() -> Vec<u8> {
250    vec![0x00, Instruction::TerminateDf.code(), 0x00, 0x00]
251}
252
253/// `ACTIVATE FILE` (`00 44 00 00`) — the second half of the reset: re-initialize
254/// the terminated applet to factory defaults (PW1 `123456`, PW3 `12345678`, all
255/// key slots empty). Case-1 APDU.
256#[must_use]
257pub fn activate_file() -> Vec<u8> {
258    vec![0x00, Instruction::ActivateFile.code(), 0x00, 0x00]
259}
260
261// ---------------------------------------------------------------------------
262// Control Reference Templates (CRT) for GENERATE ASYMMETRIC KEY PAIR
263// ---------------------------------------------------------------------------
264
265/// CRT tag selecting the *signature* key (OpenPGP Card v3.4, §7.2.14, table).
266pub const CRT_TAG_SIGN: u8 = 0xB6;
267/// CRT tag selecting the *decryption* (confidentiality) key.
268pub const CRT_TAG_DECRYPT: u8 = 0xB8;
269/// CRT tag selecting the *authentication* key.
270pub const CRT_TAG_AUTH: u8 = 0xA4;
271
272/// Selects which on-card key slot a GENERATE / READ PUBLIC KEY operation refers
273/// to. The wire form is a 2-byte Control Reference Template — the slot's CRT tag
274/// followed by an empty value (`B6 00`, `B8 00`, or `A4 00`).
275#[derive(Debug, Clone, Copy, PartialEq, Eq)]
276pub enum KeyCrt {
277    /// Digital-signature key (CRT tag `B6`).
278    Sign,
279    /// Decryption / confidentiality key (CRT tag `B8`).
280    Decrypt,
281    /// Authentication key (CRT tag `A4`).
282    Auth,
283}
284
285impl KeyCrt {
286    /// The CRT tag byte for this key slot.
287    #[must_use]
288    pub const fn tag(self) -> u8 {
289        match self {
290            KeyCrt::Sign => CRT_TAG_SIGN,
291            KeyCrt::Decrypt => CRT_TAG_DECRYPT,
292            KeyCrt::Auth => CRT_TAG_AUTH,
293        }
294    }
295
296    /// The 2-byte Control Reference Template (`<tag> 00`) naming this key slot.
297    #[must_use]
298    pub const fn crt(self) -> [u8; 2] {
299        [self.tag(), 0x00]
300    }
301
302    /// The data-object tag of this slot's 20-byte fingerprint (`C7`/`C8`/`C9`),
303    /// the target for a [`put_fingerprint`] PUT DATA. The card *also* mirrors
304    /// these into the 60-byte [`TAG_FINGERPRINTS`] (`C5`) aggregate, but writes
305    /// address the per-slot object.
306    #[must_use]
307    pub const fn fpr_tag(self) -> u16 {
308        match self {
309            KeyCrt::Sign => TAG_FPR_SIGN,
310            KeyCrt::Decrypt => TAG_FPR_DEC,
311            KeyCrt::Auth => TAG_FPR_AUTH,
312        }
313    }
314
315    /// The data-object tag of this slot's 4-byte generation timestamp
316    /// (`CE`/`CF`/`D0`), the target for a [`put_generation_time`] PUT DATA.
317    #[must_use]
318    pub const fn time_tag(self) -> u16 {
319        match self {
320            KeyCrt::Sign => TAG_TIME_SIGN,
321            KeyCrt::Decrypt => TAG_TIME_DEC,
322            KeyCrt::Auth => TAG_TIME_AUTH,
323        }
324    }
325}
326
327// ---------------------------------------------------------------------------
328// Operation / write APDU builders
329// ---------------------------------------------------------------------------
330
331/// `GENERATE ASYMMETRIC KEY PAIR` with P1 = `80` (generate a fresh key pair).
332///
333/// Builds a case-4 APDU `00 47 80 00 02 <CRT> 00`, where `<CRT>` is the 2-byte
334/// Control Reference Template selecting the key slot (see [`KeyCrt::crt`]). The
335/// card answers with a `7F49` public-key object (parse it with
336/// [`parse_generated_public_key`]).
337///
338/// **This is destructive**: generating overwrites any existing key in the slot.
339///
340/// Case-4 note: [`build_apdu`] only emits case-3 (`CLA INS P1 P2 Lc data`); a
341/// GENERATE additionally needs an `Le` so the card returns the public key. We
342/// append a trailing `0x00` `Le` byte ("up to 256/extended") by hand.
343#[must_use]
344pub fn generate_key(crt: KeyCrt) -> Vec<u8> {
345    let mut apdu = build_apdu(
346        0x00,
347        Instruction::GenerateAsymmetricKeyPair.code(),
348        GENERATE_KEY,
349        0x00,
350        &crt.crt(),
351    );
352    apdu.push(0x00); // case-4 Le
353    apdu
354}
355
356/// `GENERATE ASYMMETRIC KEY PAIR` with P1 = `81` (read the *existing* public
357/// key, no generation).
358///
359/// Builds a case-4 APDU `00 47 81 00 02 <CRT> 00`. Same CRT data and same
360/// trailing-`Le` handling as [`generate_key`] (see its case-4 note); read-only.
361#[must_use]
362pub fn read_public_key(crt: KeyCrt) -> Vec<u8> {
363    let mut apdu = build_apdu(
364        0x00,
365        Instruction::GenerateAsymmetricKeyPair.code(),
366        READ_PUBLIC_KEY,
367        0x00,
368        &crt.crt(),
369    );
370    apdu.push(0x00); // case-4 Le
371    apdu
372}
373
374/// `PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE` (P1-P2 = `9E 9A`).
375///
376/// `data` is the caller-supplied input the card signs — for RSA the `DigestInfo`
377/// (a DER `AlgorithmIdentifier` + the already-computed hash), for EdDSA the raw
378/// message-hash. This layer never hashes; it just frames the bytes. Requires
379/// PW1 verified in the signing context ([`PW1_SIGN`]).
380///
381/// Builds a case-4 APDU `00 2A 9E 9A <Lc> <data> 00`; the trailing `0x00` `Le`
382/// is appended by hand (see the case-4 note on [`generate_key`]).
383#[must_use]
384pub fn pso_compute_signature(data: &[u8]) -> Vec<u8> {
385    let mut apdu = build_apdu(
386        0x00,
387        Instruction::PerformSecurityOperation.code(),
388        (PSO_COMPUTE_SIGNATURE >> 8) as u8,
389        (PSO_COMPUTE_SIGNATURE & 0xFF) as u8,
390        data,
391    );
392    apdu.push(0x00); // case-4 Le
393    apdu
394}
395
396/// `PERFORM SECURITY OPERATION: DECIPHER` (P1-P2 = `80 86`).
397///
398/// `data` is the cipher Data Object the card deciphers (its exact framing — a
399/// padding-indicator byte plus the RSA cryptogram, or an ECDH `A6` template —
400/// is the caller's; this layer only frames the bytes). Requires PW1 verified in
401/// the "other" context ([`PW1_OTHER`]).
402///
403/// For a short body (`<= 255` bytes) builds a case-4 short APDU
404/// `00 2A 80 86 <Lc> <data> 00`. For a longer body — an RSA-2048 cryptogram is
405/// 256 bytes, so with the padding-indicator the DO is 257 — it builds a case-4
406/// *extended* APDU `00 2A 80 86 00 <Lc-hi> <Lc-lo> <data> 00 00` (3-byte
407/// extended `Lc`, 2-byte extended `Le`). Readers that won't pass an extended
408/// `Lc` are handled by the command-chaining variant
409/// ([`pso_decipher_chained`]); the transport layer falls back to it on `6700` /
410/// `6883`, mirroring the key-import path.
411#[must_use]
412pub fn pso_decipher(data: &[u8]) -> Vec<u8> {
413    let ins = Instruction::PerformSecurityOperation.code();
414    let p1 = (PSO_DECIPHER >> 8) as u8;
415    let p2 = (PSO_DECIPHER & 0xFF) as u8;
416    if data.len() <= 255 {
417        let mut apdu = build_apdu(0x00, ins, p1, p2, data);
418        apdu.push(0x00); // case-4 short Le
419        apdu
420    } else {
421        // Extended case 4: extended Lc from build_apdu_extended, then a 2-byte
422        // extended Le (`00 00` = up to 65536 bytes returned).
423        let mut apdu = build_apdu_extended(0x00, ins, p1, p2, data);
424        apdu.extend_from_slice(&[0x00, 0x00]);
425        apdu
426    }
427}
428
429/// Build PSO:DECIPHER as an ISO 7816 **command-chaining** sequence — the
430/// fallback for readers/cards that won't accept a single extended-`Lc` APDU.
431///
432/// `data` is the full RSA cipher DO (the `0x00` padding-indicator byte followed
433/// by the cryptogram). It is split into chunks of at most `max_chunk` bytes;
434/// every chunk but the last carries the chaining class bit (CLA `0x10`), and the
435/// final chunk uses CLA `0x00` with a trailing case-4 `Le` (`0x00`) so the card
436/// returns the recovered plaintext. The card reassembles the chunks into one
437/// logical command, so the concatenated bodies equal the extended-length APDU's
438/// data field. GnuPG chains PSO operations in 254-byte links; pass
439/// `max_chunk = 254` to match.
440///
441/// # Panics
442/// Panics if `max_chunk` is 0 or greater than 255.
443#[must_use]
444pub fn pso_decipher_chained(data: &[u8], max_chunk: usize) -> Vec<Vec<u8>> {
445    assert!(
446        (1..=255).contains(&max_chunk),
447        "command-chaining chunk size must be 1..=255"
448    );
449    let ins = Instruction::PerformSecurityOperation.code();
450    let p1 = (PSO_DECIPHER >> 8) as u8;
451    let p2 = (PSO_DECIPHER & 0xFF) as u8;
452    if data.is_empty() {
453        return vec![vec![0x00, ins, p1, p2, 0x00, 0x00]];
454    }
455    let chunks: Vec<&[u8]> = data.chunks(max_chunk).collect();
456    let last = chunks.len() - 1;
457    chunks
458        .into_iter()
459        .enumerate()
460        .map(|(i, chunk)| {
461            let cla = if i < last { 0x10 } else { 0x00 };
462            let mut apdu = Vec::with_capacity(6 + chunk.len());
463            apdu.extend_from_slice(&[cla, ins, p1, p2, chunk.len() as u8]);
464            apdu.extend_from_slice(chunk);
465            if i == last {
466                apdu.push(0x00); // case-4 Le on the final link
467            }
468            apdu
469        })
470        .collect()
471}
472
473/// `CHANGE REFERENCE DATA` (INS `24`) — change a PIN from `old` to `new`.
474///
475/// `pw_ref` is the password reference in P2: [`PW1_SIGN`] (`0x81`) changes PW1,
476/// [`PW3_ADMIN`] (`0x83`) changes PW3. The data field is the old PIN bytes
477/// concatenated with the new PIN bytes; the card splits them by the stored PIN
478/// length. Builds a case-3 APDU `00 24 00 <pw_ref> <Lc> <old || new>`.
479///
480/// PIN material is the caller's (see the privacy posture in `CLAUDE.md`); this
481/// builder only frames the bytes — it never sources, stores, or logs them.
482#[must_use]
483pub fn change_reference_data(pw_ref: u8, old: &[u8], new: &[u8]) -> Vec<u8> {
484    let mut data = Vec::with_capacity(old.len() + new.len());
485    data.extend_from_slice(old);
486    data.extend_from_slice(new);
487    build_apdu(
488        0x00,
489        Instruction::ChangeReferenceData.code(),
490        0x00,
491        pw_ref,
492        &data,
493    )
494}
495
496/// `RESET RETRY COUNTER` P1: reset PW1 after PW3 (admin) has been verified; the
497/// body carries the new PW1 only. (The resetting-code variant, P1 `0x00`, which
498/// carries `resetting_code || new_pw1`, is not modelled — the admin path is what
499/// the transport layer uses.)
500pub const RESET_RC_BY_ADMIN: u8 = 0x02;
501
502/// `RESET RETRY COUNTER` (`00 2C 02 81`) — unblock the user PIN (PW1) and set it
503/// to `new_pw1`, after PW3 (admin) has been verified in the same session. This
504/// is how a card whose user PIN is blocked (retry counter at 0) is recovered
505/// without a factory reset. Builds a case-3 APDU `00 2C 02 81 <Lc> <new_pw1>`.
506///
507/// PIN material is the caller's (see the privacy posture in `CLAUDE.md`); this
508/// builder only frames the bytes — it never sources, stores, or logs them.
509#[must_use]
510pub fn reset_retry_counter(new_pw1: &[u8]) -> Vec<u8> {
511    build_apdu(
512        0x00,
513        Instruction::ResetRetryCounter.code(),
514        RESET_RC_BY_ADMIN,
515        0x81, // P2 = PW1
516        new_pw1,
517    )
518}
519
520// ---------------------------------------------------------------------------
521// PUT DATA builders
522// ---------------------------------------------------------------------------
523
524/// `PUT DATA` for the data object identified by the 2-byte `tag` (placed in
525/// P1-P2), carrying `value` as the body.
526///
527/// Builds a case-3 APDU `00 DA <p1> <p2> <Lc> <value...>` (no `Le`). The
528/// inverse of [`get_data`]: where GET DATA reads a data object, PUT DATA writes
529/// one. Requires the appropriate PIN already verified (most writable objects
530/// need PW3; see the OpenPGP Card spec v3.4, §7.2.8). This layer only frames
531/// the bytes — it does not present a PIN.
532#[must_use]
533pub fn put_data(tag: u16, value: &[u8]) -> Vec<u8> {
534    let p1 = (tag >> 8) as u8;
535    let p2 = (tag & 0xFF) as u8;
536    build_apdu(0x00, Instruction::PutData.code(), p1, p2, value)
537}
538
539/// `PUT DATA 005B` — write the cardholder name ([`TAG_NAME`]).
540///
541/// The OpenPGP Card stores the name as the value of the standalone `5B` object
542/// (not wrapped in the `65` Cardholder Related Data template on write). The
543/// spec recommends the OpenPGP "Name" convention (`Surname<<Given Names`), but
544/// the encoding is the caller's; this builder only frames the bytes.
545#[must_use]
546pub fn put_cardholder_name(name: &[u8]) -> Vec<u8> {
547    put_data(TAG_NAME, name)
548}
549
550/// `PUT DATA 5F50` — write the URL of the public key ([`TAG_URL`]).
551#[must_use]
552pub fn put_url(url: &[u8]) -> Vec<u8> {
553    put_data(TAG_URL, url)
554}
555
556/// `PUT DATA C7`/`C8`/`C9` — write the 20-byte v4 fingerprint of the key in
557/// `crt`'s slot (see [`KeyCrt::fpr_tag`]).
558///
559/// After an on-card GENERATE the applet knows the key material but not the
560/// OpenPGP v4 fingerprint (which folds in the host-chosen creation timestamp);
561/// the host computes it with [`rsa_v4_fingerprint`] and registers it here so
562/// that `gpg` and the card agree on the key's identity.
563#[must_use]
564pub fn put_fingerprint(crt: KeyCrt, fpr: &[u8; 20]) -> Vec<u8> {
565    put_data(crt.fpr_tag(), fpr)
566}
567
568/// `PUT DATA CE`/`CF`/`D0` — write the 4-byte big-endian Unix generation
569/// timestamp of the key in `crt`'s slot (see [`KeyCrt::time_tag`]).
570///
571/// This timestamp *must* match the `creation_time` fed to
572/// [`rsa_v4_fingerprint`]: the v4 fingerprint hashes the creation time, so a
573/// mismatch yields a fingerprint the card and `gpg` disagree on.
574#[must_use]
575pub fn put_generation_time(crt: KeyCrt, unix_time: u32) -> Vec<u8> {
576    put_data(crt.time_tag(), &unix_time.to_be_bytes())
577}
578
579// ---------------------------------------------------------------------------
580// OpenPGP v4 key fingerprint (RFC 4880 §12.2)
581// ---------------------------------------------------------------------------
582
583/// OpenPGP MPI (Multiprecision Integer, RFC 4880 §3.2) encoding of the
584/// big-endian integer `bytes`.
585///
586/// An MPI is a 2-byte big-endian *bit length* followed by the minimal
587/// big-endian value bytes. Leading zero *bytes* are stripped first; the bit
588/// length then counts from the highest set bit of the first remaining byte
589/// (so the encoding never has a leading zero byte). An all-zero or empty
590/// integer encodes as bit length 0 with no value bytes.
591fn mpi(bytes: &[u8]) -> Vec<u8> {
592    // Strip leading zero bytes to reach the minimal big-endian form.
593    let start = bytes.iter().position(|&b| b != 0).unwrap_or(bytes.len());
594    let value = &bytes[start..];
595    if value.is_empty() {
596        return vec![0x00, 0x00];
597    }
598    // Bit length: significant bits in the (non-zero) top byte + 8 per
599    // remaining byte. `u8::leading_zeros` is 8 minus the bit position of the
600    // highest set bit, so `8 - leading_zeros` is the count we want.
601    let top_bits = 8 - value[0].leading_zeros() as usize;
602    let bit_len = top_bits + 8 * (value.len() - 1);
603    let mut out = Vec::with_capacity(2 + value.len());
604    out.push((bit_len >> 8) as u8);
605    out.push((bit_len & 0xFF) as u8);
606    out.extend_from_slice(value);
607    out
608}
609
610/// Compute the OpenPGP **v4 fingerprint** (RFC 4880 §12.2) of an RSA public
611/// key from its big-endian `modulus`, `exponent`, and the key's `creation_time`
612/// (Unix seconds).
613///
614/// The fingerprint is `SHA1(0x99 || len16 || body)`, where `body` is the
615/// public-key packet body: `04` (version) || `creation_time` (4 big-endian
616/// bytes) || `01` (RSA algorithm id) || [MPI](modulus) || [MPI](exponent), and
617/// `len16` is the 2-byte big-endian length of `body`. The `creation_time` must
618/// equal the value later written with [`put_generation_time`], or the card and
619/// `gpg` will compute different fingerprints for the same key.
620#[must_use]
621pub fn rsa_v4_fingerprint(modulus: &[u8], exponent: &[u8], creation_time: u32) -> [u8; 20] {
622    let m = mpi(modulus);
623    let e = mpi(exponent);
624    let ct = creation_time.to_be_bytes();
625    let mut body = Vec::with_capacity(1 + 4 + 1 + m.len() + e.len());
626    body.push(0x04); // packet version
627    body.extend_from_slice(&ct); // key creation time
628    body.push(0x01); // public-key algorithm: RSA
629    body.extend_from_slice(&m);
630    body.extend_from_slice(&e);
631
632    let len = body.len() as u16;
633    let mut hashed = Vec::with_capacity(3 + body.len());
634    hashed.push(0x99); // old-format CTB: public-key packet, two-octet length
635    hashed.push((len >> 8) as u8);
636    hashed.push((len & 0xFF) as u8);
637    hashed.extend_from_slice(&body);
638
639    keyroost_proto::sha1::sha1(&hashed)
640}
641
642/// Convenience wrapper around [`rsa_v4_fingerprint`] taking a parsed
643/// [`PublicKey`] (e.g. straight from [`parse_generated_public_key`]).
644#[must_use]
645pub fn rsa_v4_fingerprint_from(key: &PublicKey, creation_time: u32) -> [u8; 20] {
646    rsa_v4_fingerprint(&key.modulus, &key.exponent, creation_time)
647}
648
649// ---------------------------------------------------------------------------
650// Key import (PUT DATA, Extended Header List)
651// ---------------------------------------------------------------------------
652
653/// RSA private key material needed to import a key into a card slot.
654///
655/// All fields are big-endian, unsigned, with leading zero padding already
656/// stripped by the caller. ([`extended_header_list`] also strips a single
657/// defensive leading `0x00` from each field, as DER `INTEGER` encodings of
658/// positive values often carry one.)
659///
660/// The full Chinese-Remainder-Theorem set is carried so the builder can satisfy
661/// whichever import format the card declares (see [`RsaImportFormat`]). Real
662/// YubiKeys (5.7, verified) declare the **CRT** format and reject the bare
663/// `e`/`p`/`q` triple with `SW=6A80` — they want the precomputed `u`, `dp`, `dq`
664/// too. GnuPG's `do_writekey` sends exactly these; this mirrors it. Cards that
665/// declare the *standard* format simply ignore the CRT components (and the
666/// modulus). The mapping to the OpenPGP `7F48` tags is: `91`=e, `92`=p, `93`=q,
667/// `94`=u, `95`=dp, `96`=dq, `97`=n (OpenPGP Card spec v3.4, §4.4.3.12).
668#[derive(Debug, Clone, Copy, PartialEq, Eq)]
669pub struct RsaPrivateKeyParts<'a> {
670    /// Public exponent `e`, big-endian (commonly `01 00 01`).
671    pub e: &'a [u8],
672    /// Prime `p`, big-endian.
673    pub p: &'a [u8],
674    /// Prime `q`, big-endian.
675    pub q: &'a [u8],
676    /// CRT coefficient `u = q⁻¹ mod p` (OpenPGP tag `94`, "PQ").
677    pub u: &'a [u8],
678    /// `dp = d mod (p−1)` (OpenPGP tag `95`, "DP1").
679    pub dp: &'a [u8],
680    /// `dq = d mod (q−1)` (OpenPGP tag `96`, "DQ1").
681    pub dq: &'a [u8],
682    /// Modulus `n` (OpenPGP tag `97`); emitted only for the `*WithModulus`
683    /// import formats, ignored otherwise.
684    pub n: &'a [u8],
685}
686
687/// The RSA private-key import format a card accepts, taken from byte 5 of its
688/// algorithm-attributes object (`C1`/`C2`/`C3`; OpenPGP Card spec v3.4 §4.4.3.10).
689///
690/// The card *dictates* this — the host must send the matching component set or
691/// the card rejects the import. (GnuPG reads the same byte and branches on it.)
692#[derive(Debug, Clone, Copy, PartialEq, Eq)]
693pub enum RsaImportFormat {
694    /// `0x00` — standard: `e`, `p`, `q` only (card recomputes the rest).
695    Standard,
696    /// `0x01` — standard plus the modulus `n`.
697    StandardWithModulus,
698    /// `0x02` — CRT: `e`, `p`, `q`, `u`, `dp`, `dq`.
699    Crt,
700    /// `0x03` — CRT plus the modulus `n`.
701    CrtWithModulus,
702}
703
704impl RsaImportFormat {
705    /// Decode the import-format byte (byte 5 of the RSA algorithm attributes).
706    #[must_use]
707    pub const fn from_attr_byte(b: u8) -> Option<Self> {
708        match b {
709            0x00 => Some(Self::Standard),
710            0x01 => Some(Self::StandardWithModulus),
711            0x02 => Some(Self::Crt),
712            0x03 => Some(Self::CrtWithModulus),
713            _ => None,
714        }
715    }
716
717    /// Whether this format carries the CRT components `u`, `dp`, `dq`.
718    #[must_use]
719    pub const fn includes_crt(self) -> bool {
720        matches!(self, Self::Crt | Self::CrtWithModulus)
721    }
722
723    /// Whether this format carries the modulus `n` (tag `97`).
724    #[must_use]
725    pub const fn includes_modulus(self) -> bool {
726        matches!(self, Self::StandardWithModulus | Self::CrtWithModulus)
727    }
728}
729
730/// RSA key attributes parsed from an algorithm-attributes object (`C1`/`C2`/`C3`).
731#[derive(Debug, Clone, Copy, PartialEq, Eq)]
732pub struct RsaAttributes {
733    /// Modulus size in bits (e.g. 2048).
734    pub n_bits: u16,
735    /// Public-exponent field size in bits — fixes the byte length the card
736    /// expects for `e` on import (`(e_bits + 7) / 8`, right-justified).
737    pub e_bits: u16,
738    /// The import format the card accepts.
739    pub format: RsaImportFormat,
740}
741
742/// Parse the RSA algorithm attributes (`01 | n_bits | e_bits | [format]`).
743///
744/// The leading byte must be `0x01` (RSA). The format byte is optional: a 5-byte
745/// attribute (no format byte) is treated as [`RsaImportFormat::Standard`], per
746/// the spec and GnuPG. Returns [`ParseError::UnsupportedAlgorithm`] for a
747/// non-RSA object, [`ParseError::UnexpectedLength`] for a short/garbled one.
748pub fn parse_rsa_algorithm_attributes(attr: &[u8]) -> Result<RsaAttributes, ParseError> {
749    if attr.first() != Some(&0x01) {
750        return Err(ParseError::UnsupportedAlgorithm);
751    }
752    if attr.len() < 5 {
753        return Err(ParseError::UnexpectedLength);
754    }
755    let n_bits = u16::from_be_bytes([attr[1], attr[2]]);
756    let e_bits = u16::from_be_bytes([attr[3], attr[4]]);
757    let format = match attr.get(5) {
758        Some(&b) => RsaImportFormat::from_attr_byte(b).ok_or(ParseError::UnexpectedLength)?,
759        None => RsaImportFormat::Standard,
760    };
761    Ok(RsaAttributes {
762        n_bits,
763        e_bits,
764        format,
765    })
766}
767
768/// Encode `n` as a minimal big-endian byte sequence (no leading zeros).
769///
770/// `0` encodes as a single `0x00` byte. Used to build BER length octets.
771fn minimal_be(n: usize) -> Vec<u8> {
772    if n == 0 {
773        return vec![0x00];
774    }
775    let bytes = n.to_be_bytes();
776    let first = bytes
777        .iter()
778        .position(|&b| b != 0)
779        .unwrap_or(bytes.len() - 1);
780    bytes[first..].to_vec()
781}
782
783/// Encode a BER-TLV length field for a value of `len` bytes.
784///
785/// Short form (`< 0x80`) is a single byte. Long form emits `0x81` / `0x82`
786/// (the `0x80` flag OR'd with the count of following octets) followed by the
787/// minimal big-endian length. A realistic 4096-bit RSA `5F48` value (~518
788/// bytes) is the largest object here and still fits in the `0x82` form.
789fn ber_len(len: usize) -> Vec<u8> {
790    if len < 0x80 {
791        return vec![len as u8];
792    }
793    let value = minimal_be(len);
794    let mut out = Vec::with_capacity(1 + value.len());
795    out.push(0x80 | value.len() as u8);
796    out.extend_from_slice(&value);
797    out
798}
799
800/// Emit a full BER-TLV element: tag (1 or 2 bytes) + length + value.
801///
802/// A `tag` whose high byte is zero is emitted as a single byte; otherwise the
803/// two bytes are emitted big-endian (e.g. `0x7F48` -> `7F 48`).
804fn ber_tlv(tag: u16, value: &[u8]) -> Vec<u8> {
805    let mut out = Vec::new();
806    if tag > 0xFF {
807        out.push((tag >> 8) as u8);
808        out.push((tag & 0xFF) as u8);
809    } else {
810        out.push(tag as u8);
811    }
812    out.extend_from_slice(&ber_len(value.len()));
813    out.extend_from_slice(value);
814    out
815}
816
817/// Encode a single `7F48` Cardholder Private Key Template entry: the `tag` byte
818/// followed by the field's byte length as a **BER length** — *with no value*.
819///
820/// In the `7F48` template each tag's BER *length* directly carries the byte
821/// length of the corresponding field in `5F48`; there is no value payload (it
822/// lives in `5F48`). So a 3-byte exponent under tag `0x91` becomes `91 03`, and
823/// a 128-byte prime under `0x92` becomes `92 81 80` (long-form BER length).
824/// This matches GnuPG's `add_tlv(tp, tag, len)` and ykman's
825/// `Tlv(tag, value)[:-len]`; the earlier `91 01 03` form (a TLV whose value was
826/// the length) was malformed and the card rejected the import with `SW=6A80`.
827fn key_template_entry(tag: u8, field_len: usize) -> Vec<u8> {
828    let mut out = Vec::with_capacity(1 + 3);
829    out.push(tag);
830    out.extend_from_slice(&ber_len(field_len));
831    out
832}
833
834/// Strip a single leading `0x00` byte if present (defensive; positive DER
835/// `INTEGER`s carry one). Never strips the value down to empty.
836fn strip_leading_zero(field: &[u8]) -> &[u8] {
837    if field.len() > 1 && field[0] == 0x00 {
838        &field[1..]
839    } else {
840        field
841    }
842}
843
844/// Right-justify the public exponent into a fixed `reqlen`-byte field,
845/// zero-padding on the left.
846///
847/// The card declares the exponent size it expects via `e_bits` in its
848/// algorithm attributes; the import must present exactly `(e_bits + 7) / 8`
849/// bytes (GnuPG does the same — see `build_privkey_template`). For the usual
850/// `e = 65537` and a card declaring `e_bits = 32` this turns `01 00 01` into
851/// `00 01 00 01`. If the (minimal) exponent is already at least `reqlen` bytes
852/// its low `reqlen` bytes are used unchanged.
853fn pad_exponent(e: &[u8], reqlen: usize) -> Vec<u8> {
854    let e = strip_leading_zero(e);
855    // An exponent wider than the card's declared field can't be imported
856    // faithfully — truncating it would import a *different* key. Host-supplied
857    // input (e is from the caller's key, reqlen from the card), so assert
858    // rather than silently corrupt.
859    assert!(
860        e.len() <= reqlen,
861        "RSA exponent is wider than the card's declared exponent field"
862    );
863    let mut out = vec![0u8; reqlen];
864    out[reqlen - e.len()..].copy_from_slice(e);
865    out
866}
867
868/// Build just the `0x4D` Extended Header List object for an RSA key import
869/// (OpenPGP Card spec v3.4, §4.4.3.12). Returned *without* the APDU wrapper so
870/// it can be unit-tested directly; see [`import_rsa_key`] for the full command.
871///
872/// Layout:
873///
874/// ```text
875/// 4D <len>
876///    <CRT>                          empty control reference template: B6 00 / B8 00 / A4 00
877///    7F48 <len> 91 .. 92 .. 93 ..   Cardholder Private Key Template: lengths of e, p, q
878///    5F48 <len> <e><p><q>           Cardholder Private Key: the field bytes, same order
879/// ```
880///
881/// Unlike GENERATE (which uses the same `B6`/`B8`/`A4` tag bytes), the CRT
882/// here is *empty* (length `0x00`). Which components are emitted depends on
883/// `format`: the standard triple `e`, `p`, `q`, optionally the CRT components
884/// `u`, `dp`, `dq` and/or the modulus `n` (see [`RsaImportFormat`]). `e` is
885/// right-justified to `(e_bits + 7) / 8` bytes (see [`pad_exponent`]); the
886/// other fields keep their minimal big-endian length, and `7F48` declares each
887/// length so the card can split `5F48` correctly.
888#[must_use]
889pub fn extended_header_list(
890    crt: KeyCrt,
891    key: &RsaPrivateKeyParts,
892    format: RsaImportFormat,
893    e_bits: u16,
894) -> Vec<u8> {
895    let e = pad_exponent(key.e, e_bits.div_ceil(8) as usize);
896    let p = strip_leading_zero(key.p);
897    let q = strip_leading_zero(key.q);
898    let u = strip_leading_zero(key.u);
899    let dp = strip_leading_zero(key.dp);
900    let dq = strip_leading_zero(key.dq);
901    let n = strip_leading_zero(key.n);
902
903    // Empty CRT selecting the slot: e.g. B6 00.
904    let crt_body = [crt.tag(), 0x00];
905
906    // Cardholder Private Key Template (7F48): the byte length of each field, in
907    // the spec's tag order — 91 e, 92 p, 93 q, [94 u, 95 dp, 96 dq], [97 n].
908    let mut template = Vec::new();
909    template.extend_from_slice(&key_template_entry(0x91, e.len()));
910    template.extend_from_slice(&key_template_entry(0x92, p.len()));
911    template.extend_from_slice(&key_template_entry(0x93, q.len()));
912    if format.includes_crt() {
913        template.extend_from_slice(&key_template_entry(0x94, u.len()));
914        template.extend_from_slice(&key_template_entry(0x95, dp.len()));
915        template.extend_from_slice(&key_template_entry(0x96, dq.len()));
916    }
917    if format.includes_modulus() {
918        template.extend_from_slice(&key_template_entry(0x97, n.len()));
919    }
920
921    // Cardholder Private Key (5F48): the concatenated field bytes, same order.
922    let mut key_data = Vec::new();
923    key_data.extend_from_slice(&e);
924    key_data.extend_from_slice(p);
925    key_data.extend_from_slice(q);
926    if format.includes_crt() {
927        key_data.extend_from_slice(u);
928        key_data.extend_from_slice(dp);
929        key_data.extend_from_slice(dq);
930    }
931    if format.includes_modulus() {
932        key_data.extend_from_slice(n);
933    }
934
935    // Assemble the 4D value, then wrap the whole thing in the 4D tag/length.
936    let mut body = Vec::new();
937    body.extend_from_slice(&crt_body);
938    body.extend_from_slice(&ber_tlv(0x7F48, &template));
939    body.extend_from_slice(&ber_tlv(0x5F48, &key_data));
940
941    ber_tlv(0x4D, &body)
942}
943
944/// Build an *extended-length* command APDU: `CLA INS P1 P2 00 Lc-hi Lc-lo DATA`.
945///
946/// The standard short-form [`build_apdu`] caps `Lc` at one byte (255 bytes of
947/// data). A 2048-bit RSA import exceeds that, so we emit the 3-byte extended
948/// `Lc` (a `0x00` marker plus the 2-byte big-endian length) and no `Le`.
949///
950/// The alternative is ISO command chaining (`CLA` bit `0x10`), which YubiKeys
951/// also support; extended length is simpler and YubiKeys advertise the
952/// capability in their historical bytes, so we use it here.
953///
954/// # Panics
955/// Panics if `data.len()` exceeds `0xFFFF` (no OpenPGP import object is that
956/// large).
957fn build_apdu_extended(cla: u8, ins: u8, p1: u8, p2: u8, data: &[u8]) -> Vec<u8> {
958    assert!(data.len() <= 0xFFFF, "APDU data too long for extended Lc");
959    let lc = data.len() as u16;
960    let mut out = Vec::with_capacity(7 + data.len());
961    out.extend_from_slice(&[cla, ins, p1, p2, 0x00, (lc >> 8) as u8, (lc & 0xFF) as u8]);
962    out.extend_from_slice(data);
963    out
964}
965
966/// Build the full PUT DATA APDU that imports an RSA private key into `crt`'s
967/// slot: `00 DB 3F FF <extended Lc> <4D extended header list>` (odd PUT DATA;
968/// see [`INS_PUT_DATA_ODD`]).
969///
970/// P1-P2 = `0x3FFF` selects the Extended Header List data object (OpenPGP Card
971/// spec v3.4, §4.4.3.12). The body is always emitted as an extended-length
972/// APDU (see [`build_apdu_extended`]) — even small synthetic keys use the
973/// extended framing, matching the realistic 2048/4096-bit case.
974///
975/// The caller must have a verified PW3 (admin) session for the card to accept
976/// the import; this layer only frames the bytes.
977/// ISO 7816 "odd" PUT DATA instruction (`0xDB`). Key import addresses the
978/// Extended Header List DO (`3FFF`) via this *odd* instruction, not the normal
979/// PUT DATA (`0xDA`) — GnuPG's `do_writekey` uses `iso7816_put_data_odd` for the
980/// same reason. Using `0xDA` here makes the card reject with `SW=6B00`.
981pub const INS_PUT_DATA_ODD: u8 = 0xDB;
982
983#[must_use]
984pub fn import_rsa_key(
985    crt: KeyCrt,
986    key: &RsaPrivateKeyParts,
987    format: RsaImportFormat,
988    e_bits: u16,
989) -> Vec<u8> {
990    let header_list = extended_header_list(crt, key, format, e_bits);
991    build_apdu_extended(0x00, INS_PUT_DATA_ODD, 0x3F, 0xFF, &header_list)
992}
993
994/// Build an "odd" PUT DATA (`0xDB`) as an **ISO 7816 command-chaining**
995/// sequence: split `data` into chunks of at most `max_chunk` bytes, every chunk
996/// but the last carrying the chaining class bit (CLA `0x10`) and the final one
997/// CLA `0x00`. Each chunk is a case-3 APDU `CLA DB <p1> <p2> <Lc> <chunk>`.
998///
999/// This is the fallback to the single extended-length APDU ([`import_rsa_key`])
1000/// for cards/readers that don't accept an extended `Lc`. GnuPG uses 254-byte
1001/// chunks here (`exmode = -254`); pass `max_chunk = 254` to match. The card
1002/// reassembles the chunks into one logical command, so the reassembled bodies
1003/// are byte-identical to the extended-length APDU's data field.
1004///
1005/// # Panics
1006/// Panics if `max_chunk` is 0 or greater than 255 (a single-byte `Lc` can't
1007/// exceed 255).
1008#[must_use]
1009pub fn put_data_odd_chained(p1: u8, p2: u8, data: &[u8], max_chunk: usize) -> Vec<Vec<u8>> {
1010    assert!(
1011        (1..=255).contains(&max_chunk),
1012        "command-chaining chunk size must be 1..=255"
1013    );
1014    if data.is_empty() {
1015        return vec![vec![0x00, INS_PUT_DATA_ODD, p1, p2, 0x00]];
1016    }
1017    let chunks: Vec<&[u8]> = data.chunks(max_chunk).collect();
1018    let last = chunks.len() - 1;
1019    chunks
1020        .into_iter()
1021        .enumerate()
1022        .map(|(i, chunk)| {
1023            let cla = if i < last { 0x10 } else { 0x00 };
1024            let mut apdu = Vec::with_capacity(5 + chunk.len());
1025            apdu.extend_from_slice(&[cla, INS_PUT_DATA_ODD, p1, p2, chunk.len() as u8]);
1026            apdu.extend_from_slice(chunk);
1027            apdu
1028        })
1029        .collect()
1030}
1031
1032/// Command-chaining form of [`import_rsa_key`]: the same `4D` Extended Header
1033/// List, but emitted as a sequence of chained `0xDB` PUT DATA APDUs (see
1034/// [`put_data_odd_chained`]) instead of one extended-length APDU.
1035#[must_use]
1036pub fn import_rsa_key_chained(
1037    crt: KeyCrt,
1038    key: &RsaPrivateKeyParts,
1039    format: RsaImportFormat,
1040    e_bits: u16,
1041    max_chunk: usize,
1042) -> Vec<Vec<u8>> {
1043    let header_list = extended_header_list(crt, key, format, e_bits);
1044    put_data_odd_chained(0x3F, 0xFF, &header_list, max_chunk)
1045}
1046
1047// ---------------------------------------------------------------------------
1048// BER-TLV parsing
1049// ---------------------------------------------------------------------------
1050
1051/// Error returned by the response parsers.
1052#[derive(Debug, Clone, PartialEq, Eq)]
1053pub enum ParseError {
1054    /// A TLV claimed more bytes than the buffer contained (or a tag/length ran
1055    /// off the end while being read).
1056    Truncated,
1057    /// A tag byte sequence was malformed (e.g. a high-tag-number first byte with
1058    /// no following byte, or a 3+ byte tag we do not model).
1059    BadTag,
1060    /// A length field was malformed or wider than we support (we accept short
1061    /// form and long form `81`/`82`; `83`+ is rejected).
1062    UnexpectedLength,
1063    /// A required tag was absent from the response.
1064    MissingTag(u16),
1065    /// An algorithm-attributes object named an algorithm we don't handle here
1066    /// (e.g. a non-RSA key where RSA was required for import).
1067    UnsupportedAlgorithm,
1068}
1069
1070impl core::fmt::Display for ParseError {
1071    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1072        match self {
1073            ParseError::Truncated => write!(f, "BER-TLV truncated: length exceeds buffer"),
1074            ParseError::BadTag => write!(f, "malformed BER-TLV tag"),
1075            ParseError::UnexpectedLength => write!(f, "malformed or unsupported BER-TLV length"),
1076            ParseError::MissingTag(t) => write!(f, "expected TLV tag {t:#06x} not present"),
1077            ParseError::UnsupportedAlgorithm => write!(f, "unsupported key algorithm"),
1078        }
1079    }
1080}
1081
1082impl std::error::Error for ParseError {}
1083
1084/// A single parsed BER-TLV borrowed from the response buffer.
1085///
1086/// `tag` is normalised to a `u16`: a 1-byte tag occupies the low byte (e.g.
1087/// `0x004F`), a 2-byte high tag fills both (e.g. `0x5F52`).
1088#[derive(Debug, Clone, PartialEq, Eq)]
1089pub struct Tlv<'a> {
1090    /// Tag, normalised to `u16` (see type docs).
1091    pub tag: u16,
1092    /// Whether the tag is *constructed* (bit 6 / `0x20` of the first tag byte
1093    /// set) and so carries nested TLVs, vs. primitive (a raw value). Used by
1094    /// [`find_nested`] to decide where to descend.
1095    pub constructed: bool,
1096    /// Value bytes (length already validated against the buffer).
1097    pub value: &'a [u8],
1098}
1099
1100/// Read a BER-TLV tag at `buf[i..]`, returning `(tag_u16, bytes_consumed)`.
1101///
1102/// Supports the single-byte form and the high-tag-number form where the low 5
1103/// bits of the first byte are all set (`& 0x1F == 0x1F`), in which case a second
1104/// byte completes the tag (e.g. `5F 52`). Tags wider than two bytes (a second
1105/// byte with bit 7 set, indicating still more bytes) are rejected as
1106/// [`ParseError::BadTag`]; no OpenPGP Card object uses one.
1107fn read_tag(buf: &[u8], i: usize) -> Result<(u16, usize), ParseError> {
1108    let first = *buf.get(i).ok_or(ParseError::Truncated)?;
1109    if first & 0x1F != 0x1F {
1110        return Ok((u16::from(first), 1));
1111    }
1112    let second = *buf.get(i + 1).ok_or(ParseError::Truncated)?;
1113    // A set high bit on the second byte would mean a third tag byte follows.
1114    if second & 0x80 != 0 {
1115        return Err(ParseError::BadTag);
1116    }
1117    Ok(((u16::from(first) << 8) | u16::from(second), 2))
1118}
1119
1120/// Read a BER-TLV length at `buf[i..]`, returning `(length, bytes_consumed)`.
1121///
1122/// Short form: a single byte `0x00..=0x7F` is the length. Long form: `0x81`
1123/// means the next byte is the length; `0x82` means the next two bytes are a
1124/// big-endian length. `0x80` (indefinite) and `0x83`+ are rejected as
1125/// [`ParseError::UnexpectedLength`].
1126fn read_len(buf: &[u8], i: usize) -> Result<(usize, usize), ParseError> {
1127    let first = *buf.get(i).ok_or(ParseError::Truncated)?;
1128    if first & 0x80 == 0 {
1129        return Ok((usize::from(first), 1));
1130    }
1131    match first {
1132        0x81 => {
1133            let n = *buf.get(i + 1).ok_or(ParseError::Truncated)?;
1134            Ok((usize::from(n), 2))
1135        }
1136        0x82 => {
1137            let hi = *buf.get(i + 1).ok_or(ParseError::Truncated)?;
1138            let lo = *buf.get(i + 2).ok_or(ParseError::Truncated)?;
1139            Ok(((usize::from(hi) << 8) | usize::from(lo), 3))
1140        }
1141        _ => Err(ParseError::UnexpectedLength),
1142    }
1143}
1144
1145/// Parse a flat sequence of BER-TLVs from `buf`.
1146///
1147/// Handles two-byte high tags and long-form lengths (`81`/`82`). Constructed
1148/// objects are returned with their full (still-encoded) value; descend into them
1149/// by parsing the value again or with [`find_nested`]. Returns
1150/// [`ParseError::Truncated`] if any TLV runs off the end of the buffer.
1151pub fn parse_tlvs(buf: &[u8]) -> Result<Vec<Tlv<'_>>, ParseError> {
1152    let mut out = Vec::new();
1153    let mut i = 0;
1154    while i < buf.len() {
1155        // Bit 6 (0x20) of the first tag byte marks a constructed object.
1156        let constructed = buf[i] & 0x20 != 0;
1157        let (tag, tag_len) = read_tag(buf, i)?;
1158        let (len, len_len) = read_len(buf, i + tag_len)?;
1159        let start = i
1160            .checked_add(tag_len)
1161            .and_then(|v| v.checked_add(len_len))
1162            .ok_or(ParseError::Truncated)?;
1163        let end = start.checked_add(len).ok_or(ParseError::Truncated)?;
1164        if end > buf.len() {
1165            return Err(ParseError::Truncated);
1166        }
1167        out.push(Tlv {
1168            tag,
1169            constructed,
1170            value: &buf[start..end],
1171        });
1172        i = end;
1173    }
1174    Ok(out)
1175}
1176
1177/// Find the value of the first TLV with `tag` in a flat bag.
1178#[must_use]
1179pub fn find_tag<'a>(tlvs: &[Tlv<'a>], tag: u16) -> Option<&'a [u8]> {
1180    tlvs.iter().find(|t| t.tag == tag).map(|t| t.value)
1181}
1182
1183/// Recursively locate the value of the first TLV with `tag`, descending into
1184/// constructed objects.
1185///
1186/// Recursion is deterministic: it descends *only* into TLVs flagged
1187/// [`Tlv::constructed`] (BER bit 6 set), so a primitive value whose bytes happen
1188/// to look like TLVs is never misread. Used to reach, e.g., `C5` fingerprints
1189/// nested inside `73` inside `6E`.
1190#[must_use]
1191pub fn find_nested<'a>(tlvs: &[Tlv<'a>], tag: u16) -> Option<&'a [u8]> {
1192    find_nested_at(tlvs, tag, 0)
1193}
1194
1195/// Real OpenPGP data objects nest two or three levels (`6E` → `73` → leaf);
1196/// anything deeper is garbage. Without a cap, a card answering with
1197/// `73 <len> 73 <len> …` recurses once per ~2 input bytes and can overflow a
1198/// worker thread's stack (the CBOR decoder caps at 16 for the same reason).
1199const NEST_DEPTH_LIMIT: usize = 16;
1200
1201fn find_nested_at<'a>(tlvs: &[Tlv<'a>], tag: u16, depth: usize) -> Option<&'a [u8]> {
1202    if depth >= NEST_DEPTH_LIMIT {
1203        return None;
1204    }
1205    for tlv in tlvs {
1206        if tlv.tag == tag {
1207            return Some(tlv.value);
1208        }
1209        if tlv.constructed {
1210            if let Ok(children) = parse_tlvs(tlv.value) {
1211                if let Some(found) = find_nested_at(&children, tag, depth + 1) {
1212                    return Some(found);
1213                }
1214            }
1215        }
1216    }
1217    None
1218}
1219
1220// ---------------------------------------------------------------------------
1221// Typed parsers
1222// ---------------------------------------------------------------------------
1223
1224/// PW status bytes (`C4`), parsed from the 7-byte form.
1225///
1226/// The 4-byte legacy form omits the max-length triplet; this parser requires the
1227/// 7-byte form (OpenPGP Card v3.x), returning [`ParseError::UnexpectedLength`]
1228/// otherwise.
1229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1230pub struct PwStatus {
1231    /// Byte 0: whether PW1 stays valid for multiple PSO:CDS commands (`01`) or
1232    /// only one (`00`).
1233    pub pw1_valid_multiple: bool,
1234    /// Byte 1: maximum length / format of PW1.
1235    pub max_pw1: u8,
1236    /// Byte 2: maximum length of the resetting code (RC).
1237    pub max_rc: u8,
1238    /// Byte 3: maximum length of PW3.
1239    pub max_pw3: u8,
1240    /// Byte 4: remaining retry counter for PW1.
1241    pub tries_pw1: u8,
1242    /// Byte 5: remaining retry counter for the resetting code.
1243    pub tries_rc: u8,
1244    /// Byte 6: remaining retry counter for PW3.
1245    pub tries_pw3: u8,
1246}
1247
1248/// Parse the 7-byte PW status (`C4`) value.
1249pub fn parse_pw_status(buf: &[u8]) -> Result<PwStatus, ParseError> {
1250    if buf.len() != 7 {
1251        return Err(ParseError::UnexpectedLength);
1252    }
1253    Ok(PwStatus {
1254        pw1_valid_multiple: buf[0] == 0x01,
1255        max_pw1: buf[1],
1256        max_rc: buf[2],
1257        max_pw3: buf[3],
1258        tries_pw1: buf[4],
1259        tries_rc: buf[5],
1260        tries_pw3: buf[6],
1261    })
1262}
1263
1264/// A 20-byte (SHA-1-sized) OpenPGP key fingerprint.
1265pub type Fingerprint = [u8; 20];
1266
1267/// Selected fields pulled out of the Application Related Data (`6E`) object.
1268///
1269/// Algorithm-attribute blobs (`C1`/`C2`/`C3`) are kept raw; the first byte (the
1270/// algorithm id, e.g. `01` = RSA, `12` = ECDH, `13` = ECDSA, `16` = EdDSA) is
1271/// exposed via [`AppRelatedData::sig_algo_id`] and friends.
1272#[derive(Debug, Clone, PartialEq, Eq)]
1273pub struct AppRelatedData {
1274    /// Full AID bytes from `4F` (typically 16 bytes).
1275    pub aid: Vec<u8>,
1276    /// Raw algorithm attributes for the signature key (`C1`).
1277    pub algo_attr_sig: Vec<u8>,
1278    /// Raw algorithm attributes for the decryption key (`C2`).
1279    pub algo_attr_dec: Vec<u8>,
1280    /// Raw algorithm attributes for the authentication key (`C3`).
1281    pub algo_attr_aut: Vec<u8>,
1282    /// PW status (`C4`), parsed.
1283    pub pw_status: PwStatus,
1284    /// Signature-key fingerprint (`C5`, bytes 0..20).
1285    pub fingerprint_sig: Fingerprint,
1286    /// Decryption-key fingerprint (`C5`, bytes 20..40).
1287    pub fingerprint_dec: Fingerprint,
1288    /// Authentication-key fingerprint (`C5`, bytes 40..60).
1289    pub fingerprint_aut: Fingerprint,
1290}
1291
1292impl AppRelatedData {
1293    /// Algorithm id (first byte) of the signature-key attributes, if any.
1294    #[must_use]
1295    pub fn sig_algo_id(&self) -> Option<u8> {
1296        self.algo_attr_sig.first().copied()
1297    }
1298    /// Algorithm id (first byte) of the decryption-key attributes, if any.
1299    #[must_use]
1300    pub fn dec_algo_id(&self) -> Option<u8> {
1301        self.algo_attr_dec.first().copied()
1302    }
1303    /// Algorithm id (first byte) of the authentication-key attributes, if any.
1304    #[must_use]
1305    pub fn aut_algo_id(&self) -> Option<u8> {
1306        self.algo_attr_aut.first().copied()
1307    }
1308}
1309
1310/// Parse the Application Related Data (`6E`) blob.
1311///
1312/// `buf` may be either the raw value of the `6E` object *or* the full `6E`
1313/// envelope; both are accepted. Required sub-objects are `4F` (AID), `C1`/`C2`/
1314/// `C3` (algorithm attributes), `C4` (PW status), and `C5` (60-byte
1315/// fingerprints); a missing one yields [`ParseError::MissingTag`].
1316pub fn parse_application_related_data(buf: &[u8]) -> Result<AppRelatedData, ParseError> {
1317    let top = parse_tlvs(buf)?;
1318    // Accept either the bare value or the wrapping 6E envelope.
1319    let inner: &[u8] = match find_tag(&top, TAG_APPLICATION_RELATED_DATA) {
1320        Some(v) => v,
1321        None => buf,
1322    };
1323    let tlvs = parse_tlvs(inner)?;
1324
1325    let aid = find_nested(&tlvs, TAG_AID)
1326        .ok_or(ParseError::MissingTag(TAG_AID))?
1327        .to_vec();
1328    let algo_attr_sig = find_nested(&tlvs, TAG_ALGO_ATTR_SIG)
1329        .ok_or(ParseError::MissingTag(TAG_ALGO_ATTR_SIG))?
1330        .to_vec();
1331    let algo_attr_dec = find_nested(&tlvs, TAG_ALGO_ATTR_DEC)
1332        .ok_or(ParseError::MissingTag(TAG_ALGO_ATTR_DEC))?
1333        .to_vec();
1334    let algo_attr_aut = find_nested(&tlvs, TAG_ALGO_ATTR_AUT)
1335        .ok_or(ParseError::MissingTag(TAG_ALGO_ATTR_AUT))?
1336        .to_vec();
1337
1338    let pw_raw = find_nested(&tlvs, TAG_PW_STATUS).ok_or(ParseError::MissingTag(TAG_PW_STATUS))?;
1339    let pw_status = parse_pw_status(pw_raw)?;
1340
1341    // The spec defines C5 as 60 bytes (3×20: Sig, Dec, Aut), but real cards
1342    // report more — a YubiKey 5.7 returns 80 bytes (a fourth, "attestation" key
1343    // slot). Require at least the three standard fingerprints and read those;
1344    // ignore any trailing slots we don't model.
1345    let fpr =
1346        find_nested(&tlvs, TAG_FINGERPRINTS).ok_or(ParseError::MissingTag(TAG_FINGERPRINTS))?;
1347    if fpr.len() < 60 {
1348        return Err(ParseError::UnexpectedLength);
1349    }
1350    let mut fingerprint_sig = [0u8; 20];
1351    let mut fingerprint_dec = [0u8; 20];
1352    let mut fingerprint_aut = [0u8; 20];
1353    fingerprint_sig.copy_from_slice(&fpr[0..20]);
1354    fingerprint_dec.copy_from_slice(&fpr[20..40]);
1355    fingerprint_aut.copy_from_slice(&fpr[40..60]);
1356
1357    Ok(AppRelatedData {
1358        aid,
1359        algo_attr_sig,
1360        algo_attr_dec,
1361        algo_attr_aut,
1362        pw_status,
1363        fingerprint_sig,
1364        fingerprint_dec,
1365        fingerprint_aut,
1366    })
1367}
1368
1369/// Parse the digital-signature counter from a Security Support Template (`7A`).
1370///
1371/// The counter is a 3-byte big-endian value carried in the `93` object nested
1372/// inside `7A`. `buf` may be the raw value of `7A` or the full `7A` envelope.
1373pub fn parse_signature_counter(buf: &[u8]) -> Result<u32, ParseError> {
1374    let top = parse_tlvs(buf)?;
1375    let inner: &[u8] = find_tag(&top, TAG_SECURITY_SUPPORT).unwrap_or(buf);
1376    let tlvs = parse_tlvs(inner)?;
1377    let v = find_nested(&tlvs, TAG_DS_COUNTER).ok_or(ParseError::MissingTag(TAG_DS_COUNTER))?;
1378    if v.len() != 3 {
1379        return Err(ParseError::UnexpectedLength);
1380    }
1381    Ok((u32::from(v[0]) << 16) | (u32::from(v[1]) << 8) | u32::from(v[2]))
1382}
1383
1384/// The RSA public key parsed from a GENERATE / READ PUBLIC KEY response.
1385///
1386/// The card returns a `7F49` constructed object; for an RSA key it carries the
1387/// modulus *n* (tag `81`) and the public exponent *e* (tag `82`). Both are kept
1388/// as raw big-endian bytes (a 2048-bit modulus is 256 bytes — note the card may
1389/// or may not include a leading zero byte; we surface the value verbatim).
1390#[derive(Debug, Clone, PartialEq, Eq)]
1391pub struct PublicKey {
1392    /// RSA modulus *n* (`81`), big-endian.
1393    pub modulus: Vec<u8>,
1394    /// RSA public exponent *e* (`82`), big-endian (commonly `01 00 01`).
1395    pub exponent: Vec<u8>,
1396}
1397
1398/// Parse the public key from a GENERATE / READ PUBLIC KEY response.
1399///
1400/// `buf` may be either the raw value of the `7F49` object *or* the full `7F49`
1401/// envelope; both are accepted. Only the **RSA** case is decoded: the `81`
1402/// modulus and `82` exponent are pulled out (a 2048-bit modulus forces a
1403/// long-form `82` length, which [`parse_tlvs`] handles).
1404///
1405/// An ECC key carries an `86` public point instead of `81`/`82`; that case is
1406/// reported as [`ParseError::MissingTag`] for [`TAG_RSA_MODULUS`] — callers that
1407/// expect ECC should read the raw `86` value via [`parse_tlvs`]/[`find_nested`]
1408/// against [`TAG_EC_PUBLIC_POINT`].
1409pub fn parse_generated_public_key(buf: &[u8]) -> Result<PublicKey, ParseError> {
1410    let top = parse_tlvs(buf)?;
1411    // Accept either the bare value or the wrapping 7F49 envelope.
1412    let inner: &[u8] = find_tag(&top, TAG_PUBLIC_KEY).unwrap_or(buf);
1413    let tlvs = parse_tlvs(inner)?;
1414
1415    let modulus = find_nested(&tlvs, TAG_RSA_MODULUS)
1416        .ok_or(ParseError::MissingTag(TAG_RSA_MODULUS))?
1417        .to_vec();
1418    let exponent = find_nested(&tlvs, TAG_RSA_EXPONENT)
1419        .ok_or(ParseError::MissingTag(TAG_RSA_EXPONENT))?
1420        .to_vec();
1421
1422    Ok(PublicKey { modulus, exponent })
1423}
1424
1425#[cfg(test)]
1426mod tests {
1427    use super::*;
1428
1429    // --- APDU framing ----------------------------------------------------
1430
1431    #[test]
1432    fn select_bytes() {
1433        assert_eq!(
1434            select(),
1435            vec![0x00, 0xA4, 0x04, 0x00, 0x06, 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01]
1436        );
1437    }
1438
1439    #[test]
1440    fn get_data_builders() {
1441        assert_eq!(
1442            get_application_related_data(),
1443            vec![0x00, 0xCA, 0x00, 0x6E, 0x00]
1444        );
1445        assert_eq!(get_pw_status(), vec![0x00, 0xCA, 0x00, 0xC4, 0x00]);
1446        // A 2-byte tag splits across P1/P2: 5F52 -> P1=5F P2=52.
1447        assert_eq!(
1448            get_data(TAG_HISTORICAL_BYTES),
1449            vec![0x00, 0xCA, 0x5F, 0x52, 0x00]
1450        );
1451    }
1452
1453    #[test]
1454    fn verify_bytes() {
1455        // verify(PW1_OTHER, "123456") = 00 20 00 82 06 31 32 33 34 35 36
1456        assert_eq!(
1457            verify(0x82, b"123456"),
1458            vec![0x00, 0x20, 0x00, 0x82, 0x06, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36]
1459        );
1460    }
1461
1462    #[test]
1463    fn get_response_is_case2() {
1464        assert_eq!(get_response(), vec![0x00, 0xC0, 0x00, 0x00, 0x00]);
1465    }
1466
1467    // --- BER-TLV: tags ---------------------------------------------------
1468
1469    #[test]
1470    fn parses_two_byte_high_tag() {
1471        // 5F 52 03 00 11 22  -> tag 0x5F52, value 00 11 22
1472        let buf = [0x5F, 0x52, 0x03, 0x00, 0x11, 0x22];
1473        let tlvs = parse_tlvs(&buf).unwrap();
1474        assert_eq!(tlvs.len(), 1);
1475        assert_eq!(tlvs[0].tag, 0x5F52);
1476        assert_eq!(tlvs[0].value, &[0x00, 0x11, 0x22]);
1477    }
1478
1479    #[test]
1480    fn single_byte_tag_in_low_byte() {
1481        // C4 02 AA BB -> tag 0x00C4
1482        let buf = [0xC4, 0x02, 0xAA, 0xBB];
1483        let tlvs = parse_tlvs(&buf).unwrap();
1484        assert_eq!(tlvs[0].tag, 0x00C4);
1485        assert_eq!(tlvs[0].value, &[0xAA, 0xBB]);
1486    }
1487
1488    // --- BER-TLV: long-form lengths --------------------------------------
1489
1490    #[test]
1491    fn parses_long_form_length_81() {
1492        // 53 81 84 <132 bytes>  (0x84 = 132)
1493        let mut buf = vec![0x53, 0x81, 0x84];
1494        buf.extend(std::iter::repeat(0xCD).take(132));
1495        let tlvs = parse_tlvs(&buf).unwrap();
1496        assert_eq!(tlvs.len(), 1);
1497        assert_eq!(tlvs[0].tag, 0x0053);
1498        assert_eq!(tlvs[0].value.len(), 132);
1499        assert!(tlvs[0].value.iter().all(|&b| b == 0xCD));
1500    }
1501
1502    #[test]
1503    fn parses_long_form_length_82() {
1504        // 53 82 01 00 <256 bytes>
1505        let mut buf = vec![0x53, 0x82, 0x01, 0x00];
1506        buf.extend(std::iter::repeat(0xEE).take(256));
1507        let tlvs = parse_tlvs(&buf).unwrap();
1508        assert_eq!(tlvs[0].value.len(), 256);
1509    }
1510
1511    #[test]
1512    fn rejects_indefinite_and_wide_length() {
1513        assert_eq!(parse_tlvs(&[0xC4, 0x80]), Err(ParseError::UnexpectedLength));
1514        assert_eq!(
1515            parse_tlvs(&[0xC4, 0x83, 0, 0, 1]),
1516            Err(ParseError::UnexpectedLength)
1517        );
1518    }
1519
1520    #[test]
1521    fn detects_truncation() {
1522        // tag C4, claims length 5 but only 2 bytes follow.
1523        assert_eq!(
1524            parse_tlvs(&[0xC4, 0x05, 0xAA, 0xBB]),
1525            Err(ParseError::Truncated)
1526        );
1527        // high tag with no second byte.
1528        assert_eq!(parse_tlvs(&[0x5F]), Err(ParseError::Truncated));
1529    }
1530
1531    // --- BER-TLV: nested find --------------------------------------------
1532
1533    /// Build `6E { 73 { C4(7) C5(60) } }` by hand for the nesting tests.
1534    fn build_6e_with_73() -> (Vec<u8>, [u8; 7], [u8; 60]) {
1535        let c4 = [0x01, 0x20, 0x20, 0x40, 0x03, 0x00, 0x03];
1536        let mut c5 = [0u8; 60];
1537        for (i, b) in c5.iter_mut().enumerate() {
1538            *b = i as u8;
1539        }
1540        // inner 73 value = C4 TLV + C5 TLV
1541        let mut inner73 = Vec::new();
1542        inner73.push(0xC4);
1543        inner73.push(7);
1544        inner73.extend_from_slice(&c4);
1545        inner73.push(0xC5);
1546        inner73.push(60);
1547        inner73.extend_from_slice(&c5);
1548        // 73 wrapper
1549        let mut v73 = Vec::new();
1550        v73.push(0x73);
1551        v73.push(inner73.len() as u8);
1552        v73.extend_from_slice(&inner73);
1553        // 6E wrapper (value is the 73 object)
1554        let mut v6e = Vec::new();
1555        v6e.push(0x6E);
1556        v6e.push(v73.len() as u8);
1557        v6e.extend_from_slice(&v73);
1558        (v6e, c4, c5)
1559    }
1560
1561    #[test]
1562    fn nested_find_locates_c4_and_c5() {
1563        let (v6e, c4, c5) = build_6e_with_73();
1564        let top = parse_tlvs(&v6e).unwrap();
1565        assert_eq!(top.len(), 1);
1566        assert_eq!(top[0].tag, 0x006E);
1567
1568        // find_nested should descend 6E -> 73 -> C4 / C5.
1569        assert_eq!(find_nested(&top, TAG_PW_STATUS), Some(&c4[..]));
1570        assert_eq!(find_nested(&top, TAG_FINGERPRINTS), Some(&c5[..]));
1571    }
1572
1573    // --- PW status -------------------------------------------------------
1574
1575    #[test]
1576    fn parse_pw_status_seven_byte() {
1577        // pw1 multi=01, max 20/00/40, tries 03/00/03
1578        let buf = [0x01, 0x20, 0x00, 0x40, 0x03, 0x00, 0x03];
1579        let s = parse_pw_status(&buf).unwrap();
1580        assert!(s.pw1_valid_multiple);
1581        assert_eq!(s.max_pw1, 0x20);
1582        assert_eq!(s.max_rc, 0x00);
1583        assert_eq!(s.max_pw3, 0x40);
1584        assert_eq!(s.tries_pw1, 0x03);
1585        assert_eq!(s.tries_rc, 0x00);
1586        assert_eq!(s.tries_pw3, 0x03);
1587    }
1588
1589    #[test]
1590    fn parse_pw_status_rejects_legacy_four_byte() {
1591        assert_eq!(
1592            parse_pw_status(&[0x00, 0x20, 0x40, 0x03]),
1593            Err(ParseError::UnexpectedLength)
1594        );
1595    }
1596
1597    // --- Application Related Data ----------------------------------------
1598
1599    /// Decode a hex string, ignoring any embedded whitespace (so test vectors
1600    /// can wrap across lines).
1601    fn hexbytes(s: &str) -> Vec<u8> {
1602        let clean: String = s.chars().filter(|c| c.is_ascii_hexdigit()).collect();
1603        (0..clean.len())
1604            .step_by(2)
1605            .map(|i| u8::from_str_radix(&clean[i..i + 2], 16).unwrap())
1606            .collect()
1607    }
1608
1609    /// Append a TLV (1-byte tag) to `out`, emitting BER long-form length when
1610    /// the value exceeds 127 bytes (e.g. the `73` discretionary object).
1611    fn push(out: &mut Vec<u8>, tag: u8, value: &[u8]) {
1612        out.push(tag);
1613        let len = value.len();
1614        if len < 0x80 {
1615            out.push(len as u8);
1616        } else if len < 0x100 {
1617            out.push(0x81);
1618            out.push(len as u8);
1619        } else {
1620            out.push(0x82);
1621            out.push((len >> 8) as u8);
1622            out.push((len & 0xFF) as u8);
1623        }
1624        out.extend_from_slice(value);
1625    }
1626
1627    #[test]
1628    fn parse_application_related_data_realistic() {
1629        // Assemble a realistic 6E from sub-TLVs:
1630        //   4F  AID (16 bytes, full form)
1631        //   5F52 historical bytes (sits directly under 6E)
1632        //   73 { C0 ext-caps, C1/C2/C3 algo attrs, C4 pw-status, C5 fprs, C6 ca-fprs }
1633        let aid: [u8; 16] = [
1634            0xD2, 0x76, 0x00, 0x01, 0x24, 0x01, 0x03, 0x04, 0x00, 0x05, 0x00, 0x00, 0x12, 0x34,
1635            0x00, 0x00,
1636        ];
1637        let hist = [0x00, 0x73, 0x00, 0x80, 0x05, 0x90, 0x00];
1638        // RSA (01), ECDH (12), EdDSA (16) algorithm ids in the first byte.
1639        let c1 = [0x01, 0x08, 0x00, 0x00, 0x20, 0x00];
1640        let c2 = [
1641            0x12, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01,
1642        ];
1643        let c3 = [0x16, 0x2B, 0x06, 0x01, 0x04, 0x01, 0xDA, 0x47, 0x0F, 0x01];
1644        let c4 = [0x01, 0x7F, 0x7F, 0x7F, 0x03, 0x00, 0x03];
1645        let mut c5 = [0u8; 60];
1646        for (i, b) in c5.iter_mut().enumerate() {
1647            *b = (0xA0 + i) as u8;
1648        }
1649        let c6 = [0u8; 60];
1650
1651        let mut disc = Vec::new(); // 73 value
1652        push(
1653            &mut disc,
1654            0xC0,
1655            &[0x7F, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF],
1656        );
1657        push(&mut disc, 0xC1, &c1);
1658        push(&mut disc, 0xC2, &c2);
1659        push(&mut disc, 0xC3, &c3);
1660        push(&mut disc, 0xC4, &c4);
1661        push(&mut disc, 0xC5, &c5);
1662        push(&mut disc, 0xC6, &c6);
1663
1664        let mut inner = Vec::new(); // 6E value
1665        push(&mut inner, 0x4F, &aid);
1666        // 5F52 is a 2-byte tag; encode by hand.
1667        inner.push(0x5F);
1668        inner.push(0x52);
1669        inner.push(hist.len() as u8);
1670        inner.extend_from_slice(&hist);
1671        push(&mut inner, 0x73, &disc);
1672
1673        let mut blob = Vec::new(); // full 6E
1674        push(&mut blob, 0x6E, &inner);
1675
1676        let ard = parse_application_related_data(&blob).unwrap();
1677        assert_eq!(ard.aid, aid.to_vec());
1678        assert_eq!(ard.sig_algo_id(), Some(0x01));
1679        assert_eq!(ard.dec_algo_id(), Some(0x12));
1680        assert_eq!(ard.aut_algo_id(), Some(0x16));
1681        assert_eq!(ard.fingerprint_sig, {
1682            let mut f = [0u8; 20];
1683            f.copy_from_slice(&c5[0..20]);
1684            f
1685        });
1686        assert_eq!(ard.fingerprint_dec[0], 0xA0 + 20);
1687        assert_eq!(ard.fingerprint_aut[19], (0xA0 + 59) as u8);
1688        assert_eq!(ard.pw_status.tries_pw1, 0x03);
1689        assert_eq!(ard.pw_status.tries_rc, 0x00);
1690        assert_eq!(ard.pw_status.tries_pw3, 0x03);
1691
1692        // Parsing the bare value (without the 6E envelope) works too.
1693        let ard2 = parse_application_related_data(&inner).unwrap();
1694        assert_eq!(ard2.aid, aid.to_vec());
1695    }
1696
1697    #[test]
1698    fn parse_application_related_data_missing_tag() {
1699        // A 6E with only an AID and nothing else -> missing C1.
1700        let mut inner = Vec::new();
1701        push(&mut inner, 0x4F, &[0x00; 16]);
1702        let mut blob = Vec::new();
1703        push(&mut blob, 0x6E, &inner);
1704        assert_eq!(
1705            parse_application_related_data(&blob),
1706            Err(ParseError::MissingTag(TAG_ALGO_ATTR_SIG))
1707        );
1708    }
1709
1710    #[test]
1711    fn parse_application_related_data_real_yubikey() {
1712        // Captured from a real YubiKey 5.7 OpenPGP applet (GET DATA 006E, after
1713        // the 61xx/GET RESPONSE reassembly). Notably its C5 fingerprints object
1714        // is 80 bytes, not the spec's 60 — this case is the regression that the
1715        // hardware surfaced. Also exercises tags the synthetic test omits
1716        // (7F74, DE, 7F66, D6-D9) and a long-form (0x82) length on 6E and 73.
1717        let ard = hexbytes("6e8201374f10d27600012401030400063780684000005f520800730000e00590007f740381012073820110c00a7d000bfe080000ff0000c106010800001100c206010800001100c306010800001100da06010800001100c407017f7f7f030003c550000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c67e128a58628a5196171e0eb3f78e16490c17d7c6500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd10000000000000000000000000683741cdde0801000200030081027f660802020bfe02020bfed6020020d7020020d8020020d9020020");
1718        let a = parse_application_related_data(&ard).expect("real ARD parses");
1719        assert_eq!(a.aid, hexbytes("d2760001240103040006378068400000"));
1720        // RSA (0x01) sig/dec/aut keys.
1721        assert_eq!(a.sig_algo_id(), Some(0x01));
1722        assert_eq!(a.dec_algo_id(), Some(0x01));
1723        assert_eq!(a.aut_algo_id(), Some(0x01));
1724        // PW status: PW1 multiple = false (0x00 byte0 is actually 0x01 here),
1725        // and the three retry counters are 3/0/3.
1726        assert_eq!(a.pw_status.tries_pw1, 3);
1727        assert_eq!(a.pw_status.tries_rc, 0);
1728        assert_eq!(a.pw_status.tries_pw3, 3);
1729        // No keys generated yet -> sig fingerprint all zeros.
1730        assert_eq!(a.fingerprint_sig, [0u8; 20]);
1731    }
1732
1733    // --- Signature counter (7A / 93) -------------------------------------
1734
1735    #[test]
1736    fn parse_signature_counter_from_7a() {
1737        // 7A { 93 03 00 12 34 } -> counter 0x001234 = 4660
1738        let mut inner = Vec::new();
1739        push(&mut inner, 0x93, &[0x00, 0x12, 0x34]);
1740        let mut blob = Vec::new();
1741        push(&mut blob, 0x7A, &inner);
1742        assert_eq!(parse_signature_counter(&blob).unwrap(), 0x0000_1234);
1743        // Bare value form also works.
1744        assert_eq!(parse_signature_counter(&inner).unwrap(), 4660);
1745    }
1746
1747    // --- Operation / write builders --------------------------------------
1748
1749    #[test]
1750    fn generate_key_sign_bytes() {
1751        // GENERATE (P1=80), signature key CRT B6 00, case-4 trailing Le.
1752        assert_eq!(
1753            generate_key(KeyCrt::Sign),
1754            vec![0x00, 0x47, 0x80, 0x00, 0x02, 0xB6, 0x00, 0x00]
1755        );
1756    }
1757
1758    #[test]
1759    fn generate_key_all_slots() {
1760        assert_eq!(
1761            generate_key(KeyCrt::Decrypt),
1762            vec![0x00, 0x47, 0x80, 0x00, 0x02, 0xB8, 0x00, 0x00]
1763        );
1764        assert_eq!(
1765            generate_key(KeyCrt::Auth),
1766            vec![0x00, 0x47, 0x80, 0x00, 0x02, 0xA4, 0x00, 0x00]
1767        );
1768    }
1769
1770    #[test]
1771    fn read_public_key_auth_bytes() {
1772        // READ PUBLIC KEY (P1=81), authentication key CRT A4 00, case-4 Le.
1773        assert_eq!(
1774            read_public_key(KeyCrt::Auth),
1775            vec![0x00, 0x47, 0x81, 0x00, 0x02, 0xA4, 0x00, 0x00]
1776        );
1777        // And the sign/decrypt slots for completeness.
1778        assert_eq!(
1779            read_public_key(KeyCrt::Sign),
1780            vec![0x00, 0x47, 0x81, 0x00, 0x02, 0xB6, 0x00, 0x00]
1781        );
1782    }
1783
1784    #[test]
1785    fn key_crt_tags() {
1786        assert_eq!(KeyCrt::Sign.crt(), [0xB6, 0x00]);
1787        assert_eq!(KeyCrt::Decrypt.crt(), [0xB8, 0x00]);
1788        assert_eq!(KeyCrt::Auth.crt(), [0xA4, 0x00]);
1789    }
1790
1791    #[test]
1792    fn pso_compute_signature_bytes() {
1793        // A small fixed DigestInfo (not a real one; exercises framing only).
1794        let digest_info = [0x30, 0x07, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
1795        // 00 2A 9E 9A <Lc=09> <data...> 00
1796        assert_eq!(
1797            pso_compute_signature(&digest_info),
1798            vec![
1799                0x00, 0x2A, 0x9E, 0x9A, 0x09, 0x30, 0x07, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1800                0x00
1801            ]
1802        );
1803    }
1804
1805    #[test]
1806    fn pso_decipher_bytes() {
1807        // Cipher DO with a leading padding-indicator byte (00) + cryptogram.
1808        let cipher = [0x00, 0xAA, 0xBB, 0xCC];
1809        // 00 2A 80 86 <Lc=04> <data...> 00
1810        assert_eq!(
1811            pso_decipher(&cipher),
1812            vec![0x00, 0x2A, 0x80, 0x86, 0x04, 0x00, 0xAA, 0xBB, 0xCC, 0x00]
1813        );
1814    }
1815
1816    #[test]
1817    fn pso_decipher_extended_for_rsa_2048() {
1818        // Real RSA-2048 case: 0x00 indicator + 256-byte cryptogram = 257 bytes,
1819        // over the short-APDU limit, so it must use extended Lc + extended Le.
1820        let mut data = vec![0x00];
1821        data.extend(std::iter::repeat(0xAB).take(256));
1822        let apdu = pso_decipher(&data);
1823        // Header + extended Lc (00 01 01) + 257 body + extended Le (00 00).
1824        assert_eq!(&apdu[..7], &[0x00, 0x2A, 0x80, 0x86, 0x00, 0x01, 0x01]);
1825        assert_eq!(&apdu[7..7 + 257], &data[..]);
1826        assert_eq!(&apdu[7 + 257..], &[0x00, 0x00]);
1827        assert_eq!(apdu.len(), 7 + 257 + 2);
1828    }
1829
1830    #[test]
1831    fn pso_decipher_chained_links() {
1832        // 257 bytes in 254-byte chunks => two links: 254 (CLA 10) + 3 (CLA 00, +Le).
1833        let mut data = vec![0x00];
1834        data.extend(std::iter::repeat(0xAB).take(256));
1835        let chunks = pso_decipher_chained(&data, 254);
1836        assert_eq!(chunks.len(), 2);
1837        // First link: chaining bit set, no trailing Le.
1838        assert_eq!(&chunks[0][..5], &[0x10, 0x2A, 0x80, 0x86, 0xFE]);
1839        assert_eq!(chunks[0].len(), 5 + 254);
1840        // Last link: CLA 00, 3-byte body, trailing case-4 Le.
1841        assert_eq!(&chunks[1][..5], &[0x00, 0x2A, 0x80, 0x86, 0x03]);
1842        assert_eq!(*chunks[1].last().unwrap(), 0x00);
1843        assert_eq!(chunks[1].len(), 5 + 3 + 1);
1844        // Reassembled bodies (drop 5-byte header from each link, and the final
1845        // link's trailing Le) equal the original cipher DO.
1846        let mut body = chunks[0][5..].to_vec();
1847        body.extend_from_slice(&chunks[1][5..chunks[1].len() - 1]);
1848        assert_eq!(body, data);
1849    }
1850
1851    #[test]
1852    fn change_reference_data_bytes() {
1853        // Change PW1 (P2=81): old "123456" || new "654321".
1854        // 00 24 00 81 <Lc=0C> 31..36 36..31
1855        assert_eq!(
1856            change_reference_data(PW1_SIGN, b"123456", b"654321"),
1857            vec![
1858                0x00, 0x24, 0x00, 0x81, 0x0C, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x36, 0x35, 0x34,
1859                0x33, 0x32, 0x31
1860            ]
1861        );
1862        // Change PW3 (admin, P2=83): old "12345678" || new "87654321".
1863        assert_eq!(
1864            change_reference_data(PW3_ADMIN, b"12345678", b"87654321"),
1865            vec![
1866                0x00, 0x24, 0x00, 0x83, 0x10, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x38,
1867                0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31
1868            ]
1869        );
1870    }
1871
1872    #[test]
1873    fn reset_retry_counter_bytes() {
1874        // Admin-mode unblock, new PW1 "246810":
1875        // 00 2C 02 81 <Lc=06> 32 34 36 38 31 30
1876        assert_eq!(
1877            reset_retry_counter(b"246810"),
1878            vec![0x00, 0x2C, 0x02, 0x81, 0x06, 0x32, 0x34, 0x36, 0x38, 0x31, 0x30]
1879        );
1880    }
1881
1882    // --- Generated public key parsing ------------------------------------
1883
1884    #[test]
1885    fn parse_generated_public_key_rsa_long_form() {
1886        // Build 7F49 { 81 <256-byte modulus> 82 <01 00 01> }. The 256-byte
1887        // modulus forces a long-form (0x82) length on the 81 object, and the
1888        // total 7F49 length is also long-form.
1889        let mut modulus = vec![0u8; 256];
1890        for (i, b) in modulus.iter_mut().enumerate() {
1891            *b = (i & 0xFF) as u8;
1892        }
1893        // Ensure a top bit set somewhere so it reads as a realistic modulus.
1894        modulus[0] = 0xC0;
1895        let exponent = [0x01, 0x00, 0x01];
1896
1897        // Inner value: 81-TLV (long form) + 82-TLV (short form).
1898        // (81 tag, then 82 = long form with 2 length bytes following.)
1899        let mut inner = vec![0x81, 0x82];
1900        inner.push((modulus.len() >> 8) as u8);
1901        inner.push((modulus.len() & 0xFF) as u8);
1902        inner.extend_from_slice(&modulus);
1903        inner.push(0x82);
1904        inner.push(exponent.len() as u8);
1905        inner.extend_from_slice(&exponent);
1906
1907        // Wrap in 7F49 (2-byte tag) with long-form length (82 = 2 length bytes).
1908        let mut blob = vec![0x7F, 0x49, 0x82];
1909        blob.push((inner.len() >> 8) as u8);
1910        blob.push((inner.len() & 0xFF) as u8);
1911        blob.extend_from_slice(&inner);
1912
1913        let pk = parse_generated_public_key(&blob).expect("RSA public key parses");
1914        assert_eq!(pk.modulus, modulus);
1915        assert_eq!(pk.exponent, exponent.to_vec());
1916
1917        // Bare value (without the 7F49 envelope) also works.
1918        let pk2 = parse_generated_public_key(&inner).expect("bare value parses");
1919        assert_eq!(pk2.modulus, modulus);
1920        assert_eq!(pk2.exponent, exponent.to_vec());
1921    }
1922
1923    #[test]
1924    fn parse_generated_public_key_ecc_reports_missing_modulus() {
1925        // An ECC key carries 86 (public point) instead of 81/82.
1926        let mut inner = Vec::new();
1927        inner.push(0x86);
1928        inner.push(0x20);
1929        inner.extend_from_slice(&[0x04; 32]);
1930        let mut blob = Vec::new();
1931        blob.push(0x7F);
1932        blob.push(0x49);
1933        blob.push(inner.len() as u8);
1934        blob.extend_from_slice(&inner);
1935        assert_eq!(
1936            parse_generated_public_key(&blob),
1937            Err(ParseError::MissingTag(TAG_RSA_MODULUS))
1938        );
1939    }
1940
1941    // --- Instruction / constant sanity -----------------------------------
1942
1943    #[test]
1944    fn instruction_codes() {
1945        assert_eq!(Instruction::GetData.code(), 0xCA);
1946        assert_eq!(Instruction::PutData.code(), 0xDA);
1947        assert_eq!(Instruction::Verify.code(), 0x20);
1948        assert_eq!(Instruction::GetResponse.code(), 0xC0);
1949        assert_eq!(Instruction::GenerateAsymmetricKeyPair.code(), 0x47);
1950        assert_eq!(Instruction::PerformSecurityOperation.code(), 0x2A);
1951        assert_eq!(Instruction::ChangeReferenceData.code(), 0x24);
1952        assert_eq!(GENERATE_KEY, 0x80);
1953        assert_eq!(READ_PUBLIC_KEY, 0x81);
1954        assert_eq!(PSO_COMPUTE_SIGNATURE, 0x9E9A);
1955        assert_eq!(PSO_DECIPHER, 0x8086);
1956        assert_eq!(PW1_SIGN, 0x81);
1957        assert_eq!(PW1_OTHER, 0x82);
1958        assert_eq!(PW3_ADMIN, 0x83);
1959    }
1960
1961    // --- PUT DATA builders -----------------------------------------------
1962
1963    #[test]
1964    fn key_crt_fpr_and_time_tags() {
1965        assert_eq!(KeyCrt::Sign.fpr_tag(), 0x00C7);
1966        assert_eq!(KeyCrt::Decrypt.fpr_tag(), 0x00C8);
1967        assert_eq!(KeyCrt::Auth.fpr_tag(), 0x00C9);
1968        assert_eq!(KeyCrt::Sign.time_tag(), 0x00CE);
1969        assert_eq!(KeyCrt::Decrypt.time_tag(), 0x00CF);
1970        assert_eq!(KeyCrt::Auth.time_tag(), 0x00D0);
1971    }
1972
1973    #[test]
1974    fn put_data_generic_bytes() {
1975        // 5F50 splits across P1/P2; case-3 (no Le).
1976        assert_eq!(
1977            put_data(TAG_URL, &[0xAA, 0xBB]),
1978            vec![0x00, 0xDA, 0x5F, 0x50, 0x02, 0xAA, 0xBB]
1979        );
1980    }
1981
1982    #[test]
1983    fn put_cardholder_name_bytes() {
1984        // 00 DA 00 5B <Lc=04> "Test"
1985        assert_eq!(
1986            put_cardholder_name(b"Test"),
1987            vec![0x00, 0xDA, 0x00, 0x5B, 0x04, 0x54, 0x65, 0x73, 0x74]
1988        );
1989    }
1990
1991    #[test]
1992    fn put_url_bytes() {
1993        // 00 DA 5F 50 <Lc=01> "x"
1994        assert_eq!(put_url(b"x"), vec![0x00, 0xDA, 0x5F, 0x50, 0x01, 0x78]);
1995    }
1996
1997    #[test]
1998    fn put_fingerprint_bytes() {
1999        // Signature slot -> C7; 20-byte value of 0xAB.
2000        let apdu = put_fingerprint(KeyCrt::Sign, &[0xAB; 20]);
2001        let mut expected = vec![0x00, 0xDA, 0x00, 0xC7, 0x14];
2002        expected.extend_from_slice(&[0xAB; 20]);
2003        assert_eq!(apdu, expected);
2004        assert_eq!(&apdu[..6], &[0x00, 0xDA, 0x00, 0xC7, 0x14, 0xAB]);
2005    }
2006
2007    #[test]
2008    fn put_generation_time_bytes() {
2009        // Auth slot -> D0; 4-byte big-endian time 0x5D2C0B00.
2010        assert_eq!(
2011            put_generation_time(KeyCrt::Auth, 0x5D2C_0B00),
2012            vec![0x00, 0xDA, 0x00, 0xD0, 0x04, 0x5D, 0x2C, 0x0B, 0x00]
2013        );
2014    }
2015
2016    // --- OpenPGP v4 fingerprint (MPI + SHA-1) ----------------------------
2017
2018    #[test]
2019    fn mpi_known_answers() {
2020        // 8-byte modulus, top byte 0xC1 -> 64 bits (0x40).
2021        assert_eq!(
2022            mpi(&[0xC1, 0xF4, 0xD2, 0xA3, 0xC1, 0xF4, 0xD2, 0xA3]),
2023            vec![0x00, 0x40, 0xC1, 0xF4, 0xD2, 0xA3, 0xC1, 0xF4, 0xD2, 0xA3]
2024        );
2025        // exponent 01 00 01 -> 17 bits (0x11).
2026        assert_eq!(mpi(&[0x01, 0x00, 0x01]), vec![0x00, 0x11, 0x01, 0x00, 0x01]);
2027    }
2028
2029    #[test]
2030    fn mpi_edge_cases() {
2031        // Leading zero bytes stripped: 00 00 01 -> 1 bit.
2032        assert_eq!(mpi(&[0x00, 0x00, 0x01]), vec![0x00, 0x01, 0x01]);
2033        // Top bit set in a single byte: 0x80 -> 8 bits.
2034        assert_eq!(mpi(&[0x80]), vec![0x00, 0x08, 0x80]);
2035        // All-zero / empty integers encode as bit length 0 with no value.
2036        assert_eq!(mpi(&[0x00, 0x00]), vec![0x00, 0x00]);
2037        assert_eq!(mpi(&[]), vec![0x00, 0x00]);
2038    }
2039
2040    #[test]
2041    fn rsa_v4_fingerprint_known_answer() {
2042        let modulus = [0xC1, 0xF4, 0xD2, 0xA3, 0xC1, 0xF4, 0xD2, 0xA3];
2043        let exponent = [0x01, 0x00, 0x01];
2044        let fpr = rsa_v4_fingerprint(&modulus, &exponent, 0x5D2C_0B00);
2045        assert_eq!(
2046            fpr,
2047            [
2048                0x51, 0x64, 0x08, 0xC6, 0xA3, 0x00, 0x39, 0xCD, 0xF3, 0x70, 0x93, 0x9F, 0x06, 0x40,
2049                0x99, 0x5F, 0x21, 0xF3, 0x6C, 0xA5
2050            ]
2051        );
2052
2053        // The PublicKey convenience wrapper agrees.
2054        let key = PublicKey {
2055            modulus: modulus.to_vec(),
2056            exponent: exponent.to_vec(),
2057        };
2058        assert_eq!(rsa_v4_fingerprint_from(&key, 0x5D2C_0B00), fpr);
2059    }
2060
2061    // --- Key import: BER helpers -----------------------------------------
2062
2063    #[test]
2064    fn minimal_be_strips_leading_zeros() {
2065        assert_eq!(minimal_be(0), vec![0x00]);
2066        assert_eq!(minimal_be(3), vec![0x03]);
2067        assert_eq!(minimal_be(0x80), vec![0x80]);
2068        assert_eq!(minimal_be(128), vec![0x80]);
2069        assert_eq!(minimal_be(256), vec![0x01, 0x00]);
2070        assert_eq!(minimal_be(259), vec![0x01, 0x03]);
2071    }
2072
2073    #[test]
2074    fn ber_len_short_and_long_form() {
2075        assert_eq!(ber_len(0x00), vec![0x00]);
2076        assert_eq!(ber_len(0x7F), vec![0x7F]);
2077        // 0x80 crosses into long form: 0x81 + one length octet.
2078        assert_eq!(ber_len(0x80), vec![0x81, 0x80]);
2079        assert_eq!(ber_len(0xFF), vec![0x81, 0xFF]);
2080        // Two-octet lengths use 0x82.
2081        assert_eq!(ber_len(0x0100), vec![0x82, 0x01, 0x00]);
2082        assert_eq!(ber_len(259), vec![0x82, 0x01, 0x03]);
2083    }
2084
2085    #[test]
2086    fn key_template_entry_minimal_lengths() {
2087        // The field length is the tag's BER length, no value: e(3) -> 91 03;
2088        // a 128-byte prime -> 92 81 80 (long-form BER length for 0x80).
2089        assert_eq!(key_template_entry(0x91, 3), vec![0x91, 0x03]);
2090        assert_eq!(key_template_entry(0x92, 128), vec![0x92, 0x81, 0x80]);
2091        // A 256-byte field: 93 82 01 00.
2092        assert_eq!(key_template_entry(0x93, 256), vec![0x93, 0x82, 0x01, 0x00]);
2093    }
2094
2095    // --- Key import: extended header list (exact bytes) ------------------
2096
2097    // A synthetic CRT key used across the import KATs. All six components are
2098    // tiny so the expected bytes can be written out by hand.
2099    fn synthetic_crt_key() -> RsaPrivateKeyParts<'static> {
2100        RsaPrivateKeyParts {
2101            e: &[0x01, 0x00, 0x01], // 3
2102            p: &[0xAA, 0xBB],       // 2
2103            q: &[0xCC, 0xDD],       // 2
2104            u: &[0x11, 0x22],       // 2
2105            dp: &[0x33],            // 1
2106            dq: &[0x44, 0x55],      // 2
2107            n: &[0x99, 0x88, 0x77], // 3 (only emitted for *WithModulus)
2108        }
2109    }
2110
2111    #[test]
2112    fn rsa_algorithm_attributes_parse() {
2113        // 01 | n=0800 (2048) | e=0020 (32) | format=02 (CRT).
2114        let attr = [0x01, 0x08, 0x00, 0x00, 0x20, 0x02];
2115        let got = parse_rsa_algorithm_attributes(&attr).unwrap();
2116        assert_eq!(got.n_bits, 2048);
2117        assert_eq!(got.e_bits, 32);
2118        assert_eq!(got.format, RsaImportFormat::Crt);
2119
2120        // A 5-byte attribute (no format byte) defaults to Standard.
2121        let short = [0x01, 0x08, 0x00, 0x00, 0x11];
2122        assert_eq!(
2123            parse_rsa_algorithm_attributes(&short).unwrap().format,
2124            RsaImportFormat::Standard
2125        );
2126        // Non-RSA (e.g. EdDSA 0x16) is rejected.
2127        assert_eq!(
2128            parse_rsa_algorithm_attributes(&[0x16, 0x2B]),
2129            Err(ParseError::UnsupportedAlgorithm)
2130        );
2131    }
2132
2133    #[test]
2134    fn extended_header_list_crt_synthetic_exact_bytes() {
2135        // CRT format, e_bits = 17 -> e_reqlen = 3 (e already 3 bytes, no pad).
2136        let key = synthetic_crt_key();
2137        let ehl = extended_header_list(KeyCrt::Sign, &key, RsaImportFormat::Crt, 17);
2138
2139        // 7F48 value: 91 03  92 02  93 02  94 02  95 01  96 02
2140        //   (each = tag + BER length, no value) -> 6 x 2 = 12 bytes = 0x0C.
2141        // 5F48 value: e||p||q||u||dp||dq = 3+2+2+2+1+2 = 12 bytes = 0x0C.
2142        // 4D value: B6 00 (2) | 7F48 0C + 12 (15) | 5F48 0C + 12 (15) = 32 = 0x20.
2143        let expected = vec![
2144            0x4D, 0x20, // 4D, len 32
2145            0xB6, 0x00, // empty CRT (sign)
2146            0x7F, 0x48, 0x0C, // 7F48, len 12
2147            0x91, 0x03, //   e  len = 3
2148            0x92, 0x02, //   p  len = 2
2149            0x93, 0x02, //   q  len = 2
2150            0x94, 0x02, //   u  len = 2
2151            0x95, 0x01, //   dp len = 1
2152            0x96, 0x02, //   dq len = 2
2153            0x5F, 0x48, 0x0C, // 5F48, len 12
2154            0x01, 0x00, 0x01, //   e
2155            0xAA, 0xBB, //   p
2156            0xCC, 0xDD, //   q
2157            0x11, 0x22, //   u
2158            0x33, //   dp
2159            0x44, 0x55, //   dq
2160        ];
2161        assert_eq!(ehl, expected);
2162    }
2163
2164    #[test]
2165    fn extended_header_list_pads_exponent_to_e_bits() {
2166        // e_bits = 32 -> e_reqlen = 4; e = 01 00 01 is left-padded to 00 01 00 01.
2167        let key = synthetic_crt_key();
2168        let ehl = extended_header_list(KeyCrt::Sign, &key, RsaImportFormat::Crt, 32);
2169        // 91 entry now declares BER length 4 (2 bytes: 91 04)...
2170        assert_eq!(&ehl[7..9], &[0x91, 0x04]);
2171        // ...the 7F48 value is 12 bytes (6 x 2), so 5F48 starts at offset 19.
2172        assert_eq!(&ehl[19..22], &[0x5F, 0x48, 0x0D]); // 13 = 4+2+2+2+1+2
2173                                                       // ...and the 5F48 data starts with the padded exponent 00 01 00 01.
2174        assert_eq!(&ehl[22..26], &[0x00, 0x01, 0x00, 0x01]);
2175    }
2176
2177    #[test]
2178    fn extended_header_list_standard_omits_crt() {
2179        // Standard format emits only e, p, q (the card derives the rest).
2180        let key = synthetic_crt_key();
2181        let ehl = extended_header_list(KeyCrt::Sign, &key, RsaImportFormat::Standard, 17);
2182        let expected = vec![
2183            0x4D, 0x15, // 4D, len 21
2184            0xB6, 0x00, //
2185            0x7F, 0x48, 0x06, // 7F48, len 6
2186            0x91, 0x03, //   e len = 3
2187            0x92, 0x02, //   p len = 2
2188            0x93, 0x02, //   q len = 2
2189            0x5F, 0x48, 0x07, // 5F48, len 7
2190            0x01, 0x00, 0x01, 0xAA, 0xBB, 0xCC, 0xDD,
2191        ];
2192        assert_eq!(ehl, expected);
2193    }
2194
2195    #[test]
2196    fn extended_header_list_crt_with_modulus_appends_n() {
2197        // CrtWithModulus adds a 97 entry for n and appends n to 5F48.
2198        let key = synthetic_crt_key();
2199        let ehl = extended_header_list(KeyCrt::Sign, &key, RsaImportFormat::CrtWithModulus, 17);
2200        // 7F48 gains 97 03; its value is now 14 bytes (0x0E): 6 CRT entries + 97 03.
2201        assert_eq!(&ehl[4..7], &[0x7F, 0x48, 0x0E]);
2202        // The 97 entry is the last template entry (after 12 bytes of entries).
2203        assert_eq!(&ehl[19..21], &[0x97, 0x03]);
2204        // 5F48 ends with n = 99 88 77.
2205        assert_eq!(&ehl[ehl.len() - 3..], &[0x99, 0x88, 0x77]);
2206    }
2207
2208    #[test]
2209    fn extended_header_list_crt_per_slot() {
2210        let key = synthetic_crt_key();
2211        // The CRT is the empty template at bytes [2..4] of the 4D value.
2212        let sign = extended_header_list(KeyCrt::Sign, &key, RsaImportFormat::Crt, 17);
2213        let dec = extended_header_list(KeyCrt::Decrypt, &key, RsaImportFormat::Crt, 17);
2214        let auth = extended_header_list(KeyCrt::Auth, &key, RsaImportFormat::Crt, 17);
2215        assert_eq!(sign[2..4], [0xB6, 0x00]);
2216        assert_eq!(dec[2..4], [0xB8, 0x00]);
2217        assert_eq!(auth[2..4], [0xA4, 0x00]);
2218    }
2219
2220    // --- Key import: full APDU -------------------------------------------
2221
2222    #[test]
2223    fn import_rsa_key_crt_synthetic_full_apdu() {
2224        let key = synthetic_crt_key();
2225        let apdu = import_rsa_key(KeyCrt::Sign, &key, RsaImportFormat::Crt, 17);
2226
2227        // 4D object = 2 (tag+len) + 32 (value) = 34 bytes = 0x22.
2228        // Extended APDU: 00 DB 3F FF 00 00 22 <34 bytes> (odd PUT DATA).
2229        let mut expected = vec![0x00, 0xDB, 0x3F, 0xFF, 0x00, 0x00, 0x22];
2230        expected.extend_from_slice(&extended_header_list(
2231            KeyCrt::Sign,
2232            &key,
2233            RsaImportFormat::Crt,
2234            17,
2235        ));
2236        assert_eq!(apdu, expected);
2237        assert_eq!(apdu.len(), 7 + 34);
2238    }
2239
2240    // --- Key import: realistic 2048-bit standard case (long-form lengths) --
2241    //
2242    // Standard form (e, p, q) is what real YubiKey 5 cards declare and accept;
2243    // ykman imports RSA to v5 keys this way. The primes are full 128-byte
2244    // fields, so their 7F48 entries use the long-form BER length 81 80.
2245
2246    #[test]
2247    fn extended_header_list_realistic_2048_standard_long_form() {
2248        let e = [0x01u8, 0x00, 0x01];
2249        let f = [0xAAu8; 128];
2250        let key = RsaPrivateKeyParts {
2251            e: &e,
2252            p: &f,
2253            q: &f,
2254            u: &[],
2255            dp: &[],
2256            dq: &[],
2257            n: &[],
2258        };
2259        let ehl = extended_header_list(KeyCrt::Sign, &key, RsaImportFormat::Standard, 17);
2260
2261        // 7F48 value: 91 03  92 81 80  93 81 80 -> 2 + 3 + 3 = 8 bytes = 0x08.
2262        let template = [
2263            0x91u8, 0x03, // e len = 3
2264            0x92, 0x81, 0x80, // p len = 128 (long-form)
2265            0x93, 0x81, 0x80, // q len = 128
2266        ];
2267        // 5F48 value = e||p||q = 3 + 128 + 128 = 259 = 0x0103 -> 5F 48 82 01 03.
2268        let key_data_len = 3 + 128 + 128;
2269        assert_eq!(key_data_len, 259);
2270
2271        // 4D value = B6 00 (2) + (7F 48 08 + 8 = 11) + (5F 48 82 01 03 + 259 = 264)
2272        //          = 2 + 11 + 264 = 277.
2273        let v4d_len = 2 + (3 + template.len()) + (5 + key_data_len);
2274        assert_eq!(v4d_len, 277);
2275
2276        // 277 = 0x0115 -> 4D 82 01 15.
2277        let mut prefix = Vec::new();
2278        prefix.extend_from_slice(&[0x4D, 0x82, 0x01, 0x15]);
2279        prefix.extend_from_slice(&[0xB6, 0x00]);
2280        prefix.extend_from_slice(&[0x7F, 0x48, 0x08]);
2281        prefix.extend_from_slice(&template);
2282        prefix.extend_from_slice(&[0x5F, 0x48, 0x82, 0x01, 0x03]);
2283        prefix.extend_from_slice(&[0x01, 0x00, 0x01]); // e
2284
2285        assert_eq!(&ehl[..prefix.len()], &prefix[..]);
2286        // Total EHL = 4 (4D 82 01 15) + 277 = 281.
2287        assert_eq!(ehl.len(), 4 + 277);
2288    }
2289
2290    #[test]
2291    fn import_rsa_key_realistic_standard_extended_lc() {
2292        let e = [0x01u8, 0x00, 0x01];
2293        let f = [0xAAu8; 128];
2294        let key = RsaPrivateKeyParts {
2295            e: &e,
2296            p: &f,
2297            q: &f,
2298            u: &[],
2299            dp: &[],
2300            dq: &[],
2301            n: &[],
2302        };
2303        let apdu = import_rsa_key(KeyCrt::Sign, &key, RsaImportFormat::Standard, 17);
2304        // EHL = 281 bytes = 0x0119. APDU = 00 DB 3F FF 00 01 19 <281> (odd PUT DATA).
2305        assert_eq!(&apdu[..7], &[0x00, 0xDB, 0x3F, 0xFF, 0x00, 0x01, 0x19]);
2306        assert_eq!(apdu.len(), 7 + 281);
2307    }
2308
2309    // --- Key import: command-chaining fallback ---------------------------
2310
2311    #[test]
2312    fn put_data_odd_chained_multi_chunk() {
2313        // 300 bytes, 254-byte chunks -> two chunks (254 + 46).
2314        let data: Vec<u8> = (0..300u16).map(|i| i as u8).collect();
2315        let chunks = put_data_odd_chained(0x3F, 0xFF, &data, 254);
2316        assert_eq!(chunks.len(), 2);
2317        // Non-final chunk: chaining class bit (CLA 0x10), Lc 0xFE (254).
2318        assert_eq!(&chunks[0][..5], &[0x10, 0xDB, 0x3F, 0xFF, 0xFE]);
2319        assert_eq!(chunks[0].len(), 5 + 254);
2320        // Final chunk: CLA 0x00, Lc 0x2E (46 = 300 - 254).
2321        assert_eq!(&chunks[1][..5], &[0x00, 0xDB, 0x3F, 0xFF, 0x2E]);
2322        assert_eq!(chunks[1].len(), 5 + 46);
2323        // Reassembled chunk bodies equal the original data.
2324        let body: Vec<u8> = chunks.iter().flat_map(|c| c[5..].iter().copied()).collect();
2325        assert_eq!(body, data);
2326    }
2327
2328    #[test]
2329    fn put_data_odd_chained_single_chunk_clears_chain_bit() {
2330        let data = [0xAAu8; 10];
2331        let chunks = put_data_odd_chained(0x3F, 0xFF, &data, 254);
2332        assert_eq!(chunks.len(), 1);
2333        // A lone chunk is the final chunk: CLA 0x00, no chaining bit.
2334        assert_eq!(&chunks[0][..5], &[0x00, 0xDB, 0x3F, 0xFF, 0x0A]);
2335        assert_eq!(&chunks[0][5..], &data);
2336    }
2337
2338    #[test]
2339    fn import_chained_reassembles_to_extended_body() {
2340        // The chained chunks' bodies must reassemble to exactly the
2341        // extended-length APDU's data field (the 4D Extended Header List), so
2342        // the two transport paths present the card byte-identical key material.
2343        let e = [0x01u8, 0x00, 0x01];
2344        let f = [0xAAu8; 128];
2345        let key = RsaPrivateKeyParts {
2346            e: &e,
2347            p: &f,
2348            q: &f,
2349            u: &[],
2350            dp: &[],
2351            dq: &[],
2352            n: &[],
2353        };
2354        let ehl = extended_header_list(KeyCrt::Sign, &key, RsaImportFormat::Standard, 17);
2355        let chunks = import_rsa_key_chained(KeyCrt::Sign, &key, RsaImportFormat::Standard, 17, 254);
2356        // EHL is 281 bytes -> 254 + 27 = two chunks.
2357        assert_eq!(chunks.len(), 2);
2358        assert_eq!(chunks[0][0], 0x10); // chaining bit set on the non-final link
2359        assert_eq!(chunks[1][0], 0x00); // cleared on the final link
2360        let body: Vec<u8> = chunks.iter().flat_map(|c| c[5..].iter().copied()).collect();
2361        assert_eq!(body, ehl);
2362    }
2363
2364    #[test]
2365    fn find_nested_caps_recursion_depth() {
2366        // ~16 000 levels of `73 <len> 73 <len> …` (as deep as 2-byte BER
2367        // lengths allow) — without the depth cap this overflows the stack,
2368        // one recursion frame (plus a Vec) per ~4 input bytes. With it, the
2369        // search just gives up.
2370        let mut v: Vec<u8> = vec![0x73, 0x00];
2371        while v.len() < 65_000 {
2372            let mut w = Vec::with_capacity(v.len() + 4);
2373            w.push(0x73);
2374            if v.len() < 0x80 {
2375                w.push(v.len() as u8);
2376            } else if v.len() <= 0xFF {
2377                w.extend([0x81, v.len() as u8]);
2378            } else {
2379                w.extend([0x82, (v.len() >> 8) as u8, v.len() as u8]);
2380            }
2381            w.extend_from_slice(&v);
2382            v = w;
2383        }
2384        let tlvs = parse_tlvs(&v).unwrap();
2385        assert_eq!(find_nested(&tlvs, 0xC5), None);
2386        // Sanity: realistic nesting (6E → 73 → C5) still resolves.
2387        let inner = [0xC5u8, 0x02, 0xAA, 0xBB];
2388        let mut mid = vec![0x73, inner.len() as u8];
2389        mid.extend_from_slice(&inner);
2390        let mut outer = vec![0x6E, mid.len() as u8];
2391        outer.extend_from_slice(&mid);
2392        let tlvs = parse_tlvs(&outer).unwrap();
2393        assert_eq!(find_nested(&tlvs, 0xC5), Some(&[0xAA, 0xBB][..]));
2394    }
2395}