Skip to main content

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            &params,
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            &params,
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            &params,
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                &params
261            ),
262            Err(BuildError::Overflow)
263        );
264    }
265}