Skip to main content

scll_core/workflow/
applet.rs

1//! Steps 7 / 8 — `install_applet`, `delete_applet` (PDD §5.7/§5.8).
2//!
3//! `install_applet`: INSTALL [for Install and Make Selectable] (P1 `0x0C`) from
4//! a LOADED ELF; refuses DAP / Mandated-DAP / Delegated-Management / Security-
5//! Domain privileges (GPCS v2.3.1 Table 6-2). The Install-Params field is
6//! assembled here: mandatory `'C9'` application params, optionally followed by
7//! an `'EF'` system-params TLV. `delete_applet`: DELETE [object] for the
8//! instance, with a library-side ELF cascade (`Never` / `IfLastInstance` /
9//! `Always`) — distinct from §5.5's card-side cascade. `IfLastInstance` cannot
10//! be pre-checked without the inventory, so a blocked ELF delete surfaces as the
11//! card's `6985` → [`ScllError::ElfHasOtherInstances`].
12
13use heapless::Vec;
14
15use crate::aid::Aid;
16use crate::backend::{Scp02Backend, Scp03Backend};
17use crate::command::delete::delete_object;
18use crate::command::install::{install_for_install_make_selectable, PrivLen};
19use crate::error::ScllError;
20use crate::limits::{INSTALL_PARAMS_MAX, MAX_REMOVED_OBJECTS};
21use crate::report::{
22    DeleteCascade, DeleteObjectParams, DeleteObjectReport, DeleteTargetKind, InstallAppletParams,
23    InstallAppletReport,
24};
25use crate::scp::ScpSession;
26use crate::transport::Transport;
27use crate::workflow::session::{self, SW_CONDITIONS, SW_OK, SW_REF_NOT_FOUND, SW_WRONG_DATA};
28
29/// Application-specific install-params TLV tag (GPCS v2.3.1 §11.5.2.3.7).
30const TAG_C9_APP_PARAMS: u8 = 0xC9;
31/// System install-params TLV tag (`'EF'`).
32const TAG_EF_SYS_PARAMS: u8 = 0xEF;
33/// Privileges an applet instance must NOT carry (GPCS Table 6-2, byte 1):
34/// Security Domain (`0x80`), DAP (`0x40`), Delegated Mgmt (`0x20`),
35/// Mandated DAP (`0x01`).
36const PRIV_REFUSED_MASK: u8 = 0x80 | 0x40 | 0x20 | 0x01;
37
38/// Inputs to [`install_applet`].
39pub struct InstallAppletArgs<'a> {
40    pub parent_sd_aid: &'a [u8],
41    pub package_aid: &'a [u8],
42    pub module_aid: &'a [u8],
43    pub instance_aid: &'a [u8],
44    pub privileges: [u8; 3],
45    pub system_install_params: &'a [u8],
46    pub applet_install_params: &'a [u8],
47}
48
49/// §5.7 — instantiate an applet from a loaded ELF into an SSD.
50///
51/// # Errors
52/// [`ScllError::UnsupportedPrivilege`] for a refused privilege,
53/// [`ScllError::PackageNotFound`] (`6A88`), [`ScllError::AidAlreadyExists`]
54/// (`6A80`), or a transport / backend / [`ScllError::Card`] error.
55pub fn install_applet<B>(
56    t: &mut dyn Transport,
57    backend: &B,
58    parent_session: &mut ScpSession,
59    args: &InstallAppletArgs<'_>,
60    priv_len: PrivLen,
61) -> Result<InstallAppletReport, ScllError>
62where
63    B: Scp02Backend + Scp03Backend,
64{
65    if args.privileges[0] & PRIV_REFUSED_MASK != 0 {
66        return Err(ScllError::UnsupportedPrivilege);
67    }
68    let params = build_install_params(args.applet_install_params, args.system_install_params)?;
69    let capdu = install_for_install_make_selectable(
70        args.package_aid,
71        args.module_aid,
72        args.instance_aid,
73        args.privileges,
74        priv_len,
75        &params,
76    )?;
77    let (_d, sw) = session::transmit_in_session(t, backend, parent_session, &capdu)?;
78    match sw {
79        SW_OK => {}
80        SW_REF_NOT_FOUND => return Err(ScllError::PackageNotFound),
81        SW_WRONG_DATA => return Err(ScllError::AidAlreadyExists),
82        other => return Err(ScllError::from_general_sw(other)),
83    }
84    Ok(InstallAppletReport {
85        effective: InstallAppletParams {
86            instance_aid: Aid::new(args.instance_aid)?,
87            package_aid_used: Aid::new(args.package_aid)?,
88            module_aid_used: Aid::new(args.module_aid)?,
89            privileges_used: args.privileges,
90            system_install_params: copy_bounded::<INSTALL_PARAMS_MAX>(args.system_install_params)?,
91            applet_install_params: copy_bounded::<INSTALL_PARAMS_MAX>(args.applet_install_params)?,
92            parent_sd_aid: Aid::new(args.parent_sd_aid)?,
93        },
94        warnings: Vec::new(),
95    })
96}
97
98/// Inputs to [`delete_applet`]. `elf_aid` is required to cascade the ELF;
99/// `cascade_elf` selects whether (and conceptually when) the ELF is removed.
100pub struct DeleteAppletArgs<'a> {
101    pub instance_aid: &'a [u8],
102    pub elf_aid: Option<&'a [u8]>,
103    pub cascade_elf: DeleteCascade,
104}
105
106/// §5.8 — delete an applet instance, optionally cascading its ELF.
107///
108/// # Errors
109/// [`ScllError::ElfHasOtherInstances`] if an ELF cascade is blocked (`6985`),
110/// [`ScllError::TargetNoLongerExists`] (`6A88`), or a transport / backend /
111/// [`ScllError::Card`] error.
112pub fn delete_applet<B>(
113    t: &mut dyn Transport,
114    backend: &B,
115    parent_session: &mut ScpSession,
116    args: &DeleteAppletArgs<'_>,
117) -> Result<DeleteObjectReport, ScllError>
118where
119    B: Scp02Backend + Scp03Backend,
120{
121    // Delete the instance (object only).
122    let capdu = delete_object(args.instance_aid, false)?;
123    let (_d, sw) = session::transmit_in_session(t, backend, parent_session, &capdu)?;
124    match sw {
125        SW_OK => {}
126        SW_REF_NOT_FOUND => return Err(ScllError::TargetNoLongerExists),
127        other => return Err(ScllError::from_general_sw(other)),
128    }
129    let mut instances_removed: Vec<Aid, MAX_REMOVED_OBJECTS> = Vec::new();
130    let _ = instances_removed.push(Aid::new(args.instance_aid)?);
131
132    // Optional ELF cascade.
133    let mut elfs_removed: Vec<Aid, MAX_REMOVED_OBJECTS> = Vec::new();
134    let want_cascade = matches!(
135        args.cascade_elf,
136        DeleteCascade::Always | DeleteCascade::IfLastInstance
137    );
138    let mut cascade_used = false;
139    if want_cascade {
140        if let Some(elf) = args.elf_aid {
141            let ecapdu = delete_object(elf, false)?;
142            let (_ed, esw) = session::transmit_in_session(t, backend, parent_session, &ecapdu)?;
143            match esw {
144                SW_OK => {
145                    let _ = elfs_removed.push(Aid::new(elf)?);
146                    cascade_used = true;
147                }
148                // Other instances still depend on the ELF (Table 11-26).
149                SW_CONDITIONS => return Err(ScllError::ElfHasOtherInstances),
150                SW_REF_NOT_FOUND => return Err(ScllError::TargetNoLongerExists),
151                other => return Err(ScllError::from_general_sw(other)),
152            }
153        }
154    }
155
156    Ok(DeleteObjectReport {
157        effective: DeleteObjectParams {
158            target_aid: Aid::new(args.instance_aid)?,
159            target_kind: DeleteTargetKind::AppletInstance,
160            cascade_requested: args.cascade_elf,
161            cascade_used,
162            instances_removed,
163            elfs_removed,
164        },
165        warnings: Vec::new(),
166    })
167}
168
169/// Assemble the Install-Params field: `'C9' La app_params [ 'EF' Ls sys_params ]`.
170fn build_install_params(
171    app_params: &[u8],
172    sys_params: &[u8],
173) -> Result<Vec<u8, INSTALL_PARAMS_MAX>, ScllError> {
174    let mut out: Vec<u8, INSTALL_PARAMS_MAX> = Vec::new();
175    push_tlv(&mut out, TAG_C9_APP_PARAMS, app_params)?;
176    if !sys_params.is_empty() {
177        push_tlv(&mut out, TAG_EF_SYS_PARAMS, sys_params)?;
178    }
179    Ok(out)
180}
181
182/// Short-hand for the only build error these helpers can raise.
183fn overflow() -> ScllError {
184    ScllError::Build(crate::command::BuildError::Overflow)
185}
186
187/// Push a 1-byte-length BER-TLV (`tag len value`); overflow ⇒ [`ScllError::Build`].
188fn push_tlv<const N: usize>(out: &mut Vec<u8, N>, tag: u8, value: &[u8]) -> Result<(), ScllError> {
189    let len = u8::try_from(value.len()).map_err(|_| overflow())?;
190    out.push(tag).map_err(|_| overflow())?;
191    out.push(len).map_err(|_| overflow())?;
192    out.extend_from_slice(value).map_err(|()| overflow())
193}
194
195/// Copy a slice into a fresh bounded buffer; [`ScllError::Build`] overflow if too long.
196fn copy_bounded<const N: usize>(src: &[u8]) -> Result<Vec<u8, N>, ScllError> {
197    let mut v: Vec<u8, N> = Vec::new();
198    v.extend_from_slice(src).map_err(|()| overflow())?;
199    Ok(v)
200}