scll_core/command/install.rs
1//! INSTALL (CLA 84, INS E6) — PDD §5.4/§5.4a/§5.6/§5.7, GPCS §11.5.
2//!
3//! P1 variants: `0x02` for Load (§11.5.2.1) and `0x0C` Install + Make
4//! Selectable (§11.5.2.3). Length-prefixed fields:
5//! `Lf|ELF_AID Lm|Module_AID La|Instance_AID Lp|Privileges Li|Params Lt|Token`.
6//! `'C9'` install params mandatory even if empty (`C9 00`) — supplied by the
7//! caller in `install_params` (see `install_for_install_make_selectable`).
8
9use crate::command::{build, push_lv, BuildError, Capdu};
10
11/// Encoding length for the GP **Privileges** field (GPCS v2.3.1 §11.1.2,
12/// Tables 11-7..11-9). The field may legally be 1 byte (legacy / pre-2.2) or
13/// 3 bytes (the 2.2+ extended encoding); byte 1 carries the core privileges
14/// (Security Domain `0x80`, Card Lock, Card Terminate, …) and bytes 2–3 carry
15/// the 2.2+ extended privileges (Trusted Path `0x80` in byte 2, …).
16///
17/// When an extended (byte 2/3) bit is actually set, the 3-byte form is
18/// mandatory and this selector is ignored. When bytes 2–3 are zero, the two
19/// forms are value-equivalent *per the spec*, but real implementations differ:
20///
21/// - `Canonical` (3-byte) is the spec-canonical form and the safe default. The
22/// Oracle JCDK simulator requires it: given only the 1-byte form it does not
23/// treat bytes 2–3 as zero and ends up reporting an unintended privilege
24/// (e.g. Trusted Path) for the created SD.
25/// - `Jcop1Byte` collapses to 1 byte. NXP JCOP 4 P71 / J3R150 requires this:
26/// it rejects `Lp = 03` for a privilege that fits in byte 1 and accepts only
27/// `Lp = 01` (e.g. `01 80` for an SSD).
28///
29/// Selection is a per-card property; discovery sets it, the workflow passes it
30/// through. Default to `Canonical` unless a JCOP-P71 quirk is detected.
31#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
32pub enum PrivLen {
33 /// Always emit the full 3-byte Privileges field (spec-canonical; jcsim).
34 #[default]
35 Canonical,
36 /// Collapse to the 1-byte form when bytes 2–3 are zero (NXP JCOP 4 P71).
37 Jcop1Byte,
38}
39
40/// Push the GP **Privileges** field at the length selected by `enc`.
41///
42/// If any extended (byte 2/3) bit is set, the full 3-byte form is emitted
43/// regardless of `enc` (truncating it would silently drop privileges — GPCS
44/// v2.3.1 §11.1.2). Otherwise `enc` chooses between the 3-byte canonical form
45/// (`PrivLen::Canonical`) and the 1-byte legacy form (`PrivLen::Jcop1Byte`).
46fn push_privileges(data: &mut Capdu, privileges: [u8; 3], enc: PrivLen) -> Result<(), BuildError> {
47 let has_extended = privileges[1] != 0 || privileges[2] != 0;
48 let len = if has_extended {
49 3
50 } else {
51 match enc {
52 PrivLen::Canonical => 3,
53 PrivLen::Jcop1Byte => 1,
54 }
55 };
56 push_lv(data, &privileges[..len])
57}
58
59/// INSTALL [for Load] (P1 `0x02`, GPCS §11.5.2.1).
60///
61/// Data = `Lp‖Package_AID Ls‖Target_SD_AID Lh‖LFDB_Hash Lr=00 Lt=00`
62/// (Load Parameters and Load Token empty under AM). `Le=00`.
63///
64/// # Errors
65/// Returns [`BuildError::Overflow`] if the encoded inputs would exceed the
66/// short-APDU plaintext buffer (`CAPDU_MAX`).
67#[allow(clippy::module_name_repetitions)] // GP command name; intentional public API
68pub fn install_for_load(
69 package_aid: &[u8],
70 target_sd_aid: &[u8],
71 lfdb_hash: &[u8],
72) -> Result<Capdu, BuildError> {
73 let mut data = Capdu::new();
74 push_lv(&mut data, package_aid)?;
75 push_lv(&mut data, target_sd_aid)?;
76 push_lv(&mut data, lfdb_hash)?;
77 push_lv(&mut data, &[])?; // Load Parameters: empty (Lr = 00)
78 push_lv(&mut data, &[])?; // Load Token: empty under AM (Lt = 00)
79 build(0x84, 0xE6, 0x02, 0x00, &data, true)
80}
81
82/// INSTALL [for Install and Make Selectable] (P1 `0x0C`). Shared by §5.4 and §5.7.
83///
84/// Data = `Lf‖ELF_AID Lm‖Module_AID La‖Instance_AID Lp‖Privileges(1|3)
85/// Li‖Install_Params Lt=00`. `priv_len` selects the Privileges encoding
86/// length (see [`PrivLen`]); an extended 2.2+ privilege bit always forces the
87/// 3-byte form. `install_params` is the **complete** Install Params field
88/// value and must already contain the mandatory `'C9'` TLV (e.g. `C9 00` when
89/// empty), optionally followed by an `'EF'` system-params TLV (§11.5.2.3.7);
90/// the builder only length-prefixes it. `Le=00`.
91///
92/// # Errors
93/// Returns [`BuildError::Overflow`] if the encoded inputs would exceed the
94/// short-APDU plaintext buffer (`CAPDU_MAX`).
95#[allow(clippy::module_name_repetitions)] // GP command name; intentional public API
96pub fn install_for_install_make_selectable(
97 elf_aid: &[u8],
98 module_aid: &[u8],
99 instance_aid: &[u8],
100 privileges: [u8; 3],
101 priv_len: PrivLen,
102 install_params: &[u8],
103) -> Result<Capdu, BuildError> {
104 let mut data = Capdu::new();
105 push_lv(&mut data, elf_aid)?;
106 push_lv(&mut data, module_aid)?;
107 push_lv(&mut data, instance_aid)?;
108 push_privileges(&mut data, privileges, priv_len)?;
109 push_lv(&mut data, install_params)?;
110 push_lv(&mut data, &[])?; // Install Token: empty under AM (Lt = 00)
111 build(0x84, 0xE6, 0x0C, 0x00, &data, true)
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117 use scll_test_util::HexSlice;
118
119 #[test]
120 fn for_load_lays_out_all_five_fields() {
121 let pkg = [0xA0, 0x00, 0x00, 0x01, 0x51];
122 let sd = [0xA0, 0x00, 0x00, 0x01, 0x51];
123 let hash = [0x11, 0x22, 0x33, 0x44];
124 let apdu = install_for_load(&pkg, &sd, &hash).unwrap();
125 assert_eq!(
126 HexSlice(&apdu),
127 HexSlice([
128 0x84, 0xE6, 0x02, 0x00, 0x13, // Lc = 19
129 0x05, 0xA0, 0x00, 0x00, 0x01, 0x51, // Lp | Package_AID
130 0x05, 0xA0, 0x00, 0x00, 0x01, 0x51, // Ls | Target_SD_AID
131 0x04, 0x11, 0x22, 0x33, 0x44, // Lh | LFDB hash
132 0x00, // Lr (load params empty)
133 0x00, // Lt (load token empty)
134 0x00, // Le
135 ])
136 );
137 }
138
139 #[test]
140 fn for_load_carries_full_sha256_hash_length() {
141 let aid = [0xA0, 0x00, 0x00, 0x01, 0x51];
142 let hash = [0x00u8; 32];
143 let apdu = install_for_load(&aid, &aid, &hash).unwrap();
144 // After Lp(1)|pkg(5) Ls(1)|sd(5), the Lh byte must be 0x20 (32).
145 assert_eq!(apdu[5 + 6 + 6], 0x20);
146 }
147
148 #[test]
149 fn install_make_selectable_canonical_emits_three_byte_privileges() {
150 // Default / spec-canonical form: even an SD-only privilege is sent as
151 // the full 3-byte field `03 80 00 00` (GPCS v2.3.1 §11.1.2). This is
152 // the form the Oracle JCDK simulator requires.
153 let elf = [0xA0, 0x00, 0x00, 0x01, 0x51];
154 let module = [0xB0, 0x00, 0x00, 0x02, 0x52];
155 let instance = [0xC0, 0x00, 0x00, 0x03, 0x53];
156 let privileges = [0x80, 0x00, 0x00]; // SD only
157 let params = [0xC9, 0x00]; // mandatory empty 'C9'
158 let apdu = install_for_install_make_selectable(
159 &elf,
160 &module,
161 &instance,
162 privileges,
163 PrivLen::Canonical,
164 ¶ms,
165 )
166 .unwrap();
167 assert_eq!(
168 HexSlice(&apdu),
169 HexSlice([
170 0x84, 0xE6, 0x0C, 0x00, 0x1A, // Lc = 26
171 0x05, 0xA0, 0x00, 0x00, 0x01, 0x51, // Lf | ELF_AID
172 0x05, 0xB0, 0x00, 0x00, 0x02, 0x52, // Lm | Module_AID
173 0x05, 0xC0, 0x00, 0x00, 0x03, 0x53, // La | Instance_AID
174 0x03, 0x80, 0x00, 0x00, // Lp | Privileges (full 3-byte form)
175 0x02, 0xC9, 0x00, // Li | 'C9' 00
176 0x00, // Lt (token empty)
177 0x00, // Le
178 ])
179 );
180 }
181
182 #[test]
183 fn install_make_selectable_jcop_emits_one_byte_privileges() {
184 // JCOP 4 P71 form: an SD-only privilege collapses to the 1-byte legacy
185 // form `01 80` (GPCS v2.3.1 §11.1.2), which is what JCOP 4 P71 expects.
186 let elf = [0xA0, 0x00, 0x00, 0x01, 0x51];
187 let module = [0xB0, 0x00, 0x00, 0x02, 0x52];
188 let instance = [0xC0, 0x00, 0x00, 0x03, 0x53];
189 let privileges = [0x80, 0x00, 0x00]; // SD only — fits in byte 1
190 let params = [0xC9, 0x00];
191 let apdu = install_for_install_make_selectable(
192 &elf,
193 &module,
194 &instance,
195 privileges,
196 PrivLen::Jcop1Byte,
197 ¶ms,
198 )
199 .unwrap();
200 assert_eq!(
201 HexSlice(&apdu),
202 HexSlice([
203 0x84, 0xE6, 0x0C, 0x00, 0x18, // Lc = 24
204 0x05, 0xA0, 0x00, 0x00, 0x01, 0x51, // Lf | ELF_AID
205 0x05, 0xB0, 0x00, 0x00, 0x02, 0x52, // Lm | Module_AID
206 0x05, 0xC0, 0x00, 0x00, 0x03, 0x53, // La | Instance_AID
207 0x01, 0x80, // Lp | Privileges (minimal 1-byte form)
208 0x02, 0xC9, 0x00, // Li | 'C9' 00
209 0x00, // Lt (token empty)
210 0x00, // Le
211 ])
212 );
213 }
214
215 #[test]
216 fn install_make_selectable_extended_bit_forces_three_bytes_even_for_jcop() {
217 // An extended (2.2+) privilege bit lives in byte 2 or 3, so the full
218 // 3-byte field is required and must NOT be truncated (GPCS §11.1.2),
219 // regardless of the selected PrivLen.
220 let elf = [0xA0, 0x00, 0x00, 0x01, 0x51];
221 let module = [0xB0, 0x00, 0x00, 0x02, 0x52];
222 let instance = [0xC0, 0x00, 0x00, 0x03, 0x53];
223 let privileges = [0x80, 0x00, 0x20]; // SD + an extended-byte-3 bit
224 let params = [0xC9, 0x00];
225 let apdu = install_for_install_make_selectable(
226 &elf,
227 &module,
228 &instance,
229 privileges,
230 PrivLen::Jcop1Byte, // even the 1-byte selector must not truncate
231 ¶ms,
232 )
233 .unwrap();
234 assert_eq!(
235 HexSlice(&apdu),
236 HexSlice([
237 0x84, 0xE6, 0x0C, 0x00, 0x1A, // Lc = 26
238 0x05, 0xA0, 0x00, 0x00, 0x01, 0x51, // Lf | ELF_AID
239 0x05, 0xB0, 0x00, 0x00, 0x02, 0x52, // Lm | Module_AID
240 0x05, 0xC0, 0x00, 0x00, 0x03, 0x53, // La | Instance_AID
241 0x03, 0x80, 0x00, 0x20, // Lp | Privileges (full 3-byte form)
242 0x02, 0xC9, 0x00, // Li | 'C9' 00
243 0x00, // Lt
244 0x00, // Le
245 ])
246 );
247 }
248
249 #[test]
250 fn oversized_params_overflow() {
251 let aid = [0xA0, 0x00, 0x00, 0x01, 0x51];
252 let params = [0x00u8; 255];
253 assert_eq!(
254 install_for_install_make_selectable(
255 &aid,
256 &aid,
257 &aid,
258 [0; 3],
259 PrivLen::Canonical,
260 ¶ms
261 ),
262 Err(BuildError::Overflow)
263 );
264 }
265}