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}