From 028dcc5a089c7bdcd927feac6db8c6a4007ce1a8 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 31 Jan 2023 12:08:39 +0100 Subject: [PATCH 01/65] Move CTAPHID commands under a single vendor command Previously, we used one CTAPHID vendor command per application command. This makes it hard to extend the functionality because we have to synchronize the used vendor commands over all applications in the Trussed ecosystem. As an alternative, this patch introduces a new vendor command that acts as a namespace for the admin application. The actual application command is encoded in the first byte of the payload. In the future, we might want to make this configurable so that the runner can decide the namespace used by CTAPHID apps. For compatibility, the CTAPHID vendor commands are still available. This change also allows us to combine the CTAPHID and APDU command handling into a single dispatch function. --- src/admin.rs | 228 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 146 insertions(+), 82 deletions(-) diff --git a/src/admin.rs b/src/admin.rs index 56cf138..a9a0775 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -1,15 +1,21 @@ -use core::{convert::TryInto, marker::PhantomData}; +use core::{convert::TryInto, marker::PhantomData, time::Duration}; use ctaphid_dispatch::app::{self as hid, Command as HidCommand, Message}; use ctaphid_dispatch::command::VendorCommand; -use apdu_dispatch::{Command, command, response, app as apdu}; +use apdu_dispatch::{Command as ApduCommand, command, response, app as apdu}; use apdu_dispatch::iso7816::Status; use trussed::{ + types::Vec, syscall, Client as TrussedClient, }; pub const USER_PRESENCE_TIMEOUT_SECS: u32 = 15; +// New commands are only available over this vendor command (acting as a namespace for this +// application). The actual application command is stored in the first byte of the packet data. +const ADMIN: VendorCommand = VendorCommand::H72; + +// For compatibility, old commands are also available directly as separate vendor commands. const UPDATE: VendorCommand = VendorCommand::H51; const REBOOT: VendorCommand = VendorCommand::H53; const RNG: VendorCommand = VendorCommand::H60; @@ -17,6 +23,93 @@ const VERSION: VendorCommand = VendorCommand::H61; const UUID: VendorCommand = VendorCommand::H62; const LOCKED: VendorCommand = VendorCommand::H63; +// We also handle the standard wink command. +const WINK: HidCommand = HidCommand::Wink; // 0x08 + +const RNG_DATA_LEN: usize = 57; + +#[derive(PartialEq)] +enum Command { + Update, + Reboot, + Rng, + Version, + Uuid, + Locked, + Wink, +} + +impl TryFrom for Command { + type Error = Error; + + fn try_from(command: u8) -> Result { + // First, check the old commands. + if let Ok(command) = HidCommand::try_from(command) { + if let Ok(command) = command.try_into() { + return Ok(command); + } + } + + // Now check the new commands (none yet). + Err(Error::UnsupportedCommand) + } +} + +impl TryFrom for Command { + type Error = Error; + + fn try_from(command: HidCommand) -> Result { + match command { + WINK => Ok(Command::Wink), + HidCommand::Vendor(command) => command.try_into(), + _ => Err(Error::UnsupportedCommand) + } + } +} + +impl TryFrom for Command { + type Error = Error; + + fn try_from(command: VendorCommand) -> Result { + match command { + UPDATE => Ok(Command::Update), + REBOOT => Ok(Command::Reboot), + RNG => Ok(Command::Rng), + VERSION => Ok(Command::Version), + UUID => Ok(Command::Uuid), + LOCKED => Ok(Command::Locked), + _ => Err(Error::UnsupportedCommand), + } + } +} + +enum Error { + InvalidLength, + NotAvailable, + UnsupportedCommand, +} + +impl From for hid::Error { + fn from(error: Error) -> Self { + match error { + Error::InvalidLength => Self::InvalidLength, + // TODO: use more appropriate error code + Error::NotAvailable => Self::InvalidLength, + Error::UnsupportedCommand => Self::InvalidCommand, + } + } +} + +impl From for Status { + fn from(error: Error) -> Self { + match error { + Error::InvalidLength => Self::WrongLength, + Error::NotAvailable => Self::ConditionsOfUseNotSatisfied, + Error::UnsupportedCommand => Self::InstructionNotSupportedOrInvalid, + } + } +} + pub trait Reboot { /// Reboots the device. fn reboot() -> !; @@ -63,70 +156,77 @@ where T: TrussedClient, user_present.is_ok() } - -} - -impl hid::App for App -where T: TrussedClient, - R: Reboot -{ - fn commands(&self) -> &'static [HidCommand] { - &[ - HidCommand::Wink, - HidCommand::Vendor(UPDATE), - HidCommand::Vendor(REBOOT), - HidCommand::Vendor(RNG), - HidCommand::Vendor(VERSION), - HidCommand::Vendor(UUID), - HidCommand::Vendor(LOCKED), - ] - } - - fn call(&mut self, command: HidCommand, input_data: &Message, response: &mut Message) -> hid::AppResult { + fn exec(&mut self, command: Command, flag: Option, response: &mut Vec) -> Result<(), Error> { match command { - HidCommand::Vendor(REBOOT) => R::reboot(), - HidCommand::Vendor(LOCKED) => { - response.extend_from_slice( - &[R::locked() as u8] - ).ok(); + Command::Reboot => R::reboot(), + Command::Locked => { + response.push(R::locked().into()).ok(); } - HidCommand::Vendor(RNG) => { + Command::Rng => { // Fill the HID packet (57 bytes) response.extend_from_slice( - &syscall!(self.trussed.random_bytes(57)).bytes.as_slice() + &syscall!(self.trussed.random_bytes(RNG_DATA_LEN)).bytes, ).ok(); } - HidCommand::Vendor(UPDATE) => { + Command::Update => { if self.user_present() { - if input_data.len() > 0 && input_data[0] == 0x01 { + if flag == Some(0x01) { R::reboot_to_firmware_update_destructive(); } else { R::reboot_to_firmware_update(); } } else { - return Err(hid::Error::InvalidLength); + return Err(Error::NotAvailable); } } - HidCommand::Vendor(UUID) => { + Command::Uuid => { // Get UUID response.extend_from_slice(&self.uuid).ok(); } - HidCommand::Vendor(VERSION) => { + Command::Version => { // GET VERSION response.extend_from_slice(&self.version.to_be_bytes()).ok(); } - HidCommand::Wink => { + Command::Wink => { debug_now!("winking"); - syscall!(self.trussed.wink(core::time::Duration::from_secs(10))); - } - _ => { - return Err(hid::Error::InvalidCommand); + syscall!(self.trussed.wink(Duration::from_secs(10))); } } Ok(()) } } +impl hid::App for App +where T: TrussedClient, + R: Reboot +{ + fn commands(&self) -> &'static [HidCommand] { + &[ + HidCommand::Wink, + HidCommand::Vendor(ADMIN), + HidCommand::Vendor(UPDATE), + HidCommand::Vendor(REBOOT), + HidCommand::Vendor(RNG), + HidCommand::Vendor(VERSION), + HidCommand::Vendor(UUID), + HidCommand::Vendor(LOCKED), + ] + } + + fn call(&mut self, command: HidCommand, input_data: &Message, response: &mut Message) -> hid::AppResult { + let (command, flag) = if command == HidCommand::Vendor(ADMIN) { + // new mode: first input byte specifies the actual command + let (command, input) = input_data.split_first().ok_or(Error::InvalidLength)?; + let command = Command::try_from(*command)?; + (command, input.first()) + } else { + // old mode: directly use vendor commands + wink + (Command::try_from(command)?, input_data.first()) + }; + self.exec(command, flag.copied(), response).map_err(From::from) + } +} + impl iso7816::App for App where T: TrussedClient, R: Reboot @@ -142,58 +242,22 @@ where T: TrussedClient, R: Reboot { - fn select(&mut self, _apdu: &Command, _reply: &mut response::Data) -> apdu::Result { + fn select(&mut self, _apdu: &ApduCommand, _reply: &mut response::Data) -> apdu::Result { Ok(()) } fn deselect(&mut self) {} - fn call(&mut self, interface: apdu::Interface, apdu: &Command, reply: &mut response::Data) -> apdu::Result { + fn call(&mut self, interface: apdu::Interface, apdu: &ApduCommand, reply: &mut response::Data) -> apdu::Result { let instruction: u8 = apdu.instruction().into(); + let command = Command::try_from(instruction)?; - if instruction == 0x08 { - syscall!(self.trussed.wink(core::time::Duration::from_secs(10))); - return Ok(()); + // Reboot may only be called over USB + if command == Command::Reboot && interface != apdu::Interface::Contact { + return Err(Status::ConditionsOfUseNotSatisfied); } - let command: VendorCommand = instruction.try_into().map_err(|_e| Status::InstructionNotSupportedOrInvalid)?; - - match command { - REBOOT => R::reboot(), - LOCKED => { - // Random bytes - reply.extend_from_slice(&[R::locked() as u8]).ok(); - } - RNG => { - // Random bytes - reply.extend_from_slice(&syscall!(self.trussed.random_bytes(57)).bytes.as_slice()).ok(); - } - UPDATE => { - // Boot to mcuboot (only when contact interface) - if interface == apdu::Interface::Contact && self.user_present() - { - if apdu.p1 == 0x01 { - R::reboot_to_firmware_update_destructive(); - } else { - R::reboot_to_firmware_update(); - } - } - return Err(Status::ConditionsOfUseNotSatisfied); - } - UUID => { - // Get UUID - reply.extend_from_slice(&self.uuid).ok(); - } - VERSION => { - // Get version - reply.extend_from_slice(&self.version.to_be_bytes()[..]).ok(); - } - - _ => return Err(Status::InstructionNotSupportedOrInvalid), - - } - Ok(()) - + self.exec(command, Some(apdu.p1), reply).map_err(From::from) } } From 931081a1930e99e1373146226c7bccb752919144 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 31 Jan 2023 12:18:28 +0100 Subject: [PATCH 02/65] Add alternative Version mode for full version This patch adds an alternative mode to the Version command that makes it possible to query a full version string instead of the major, minor and patch versions only (useful for release candidates, alphas, etc.). --- src/admin.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/admin.rs b/src/admin.rs index a9a0775..2464e27 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -140,6 +140,7 @@ where T: TrussedClient, trussed: T, uuid: [u8; 16], version: u32, + full_version: &'static str, boot_interface: PhantomData, } @@ -147,8 +148,8 @@ impl App where T: TrussedClient, R: Reboot, { - pub fn new(client: T, uuid: [u8; 16], version: u32) -> Self { - Self { trussed: client, uuid, version, boot_interface: PhantomData } + pub fn new(client: T, uuid: [u8; 16], version: u32, full_version: &'static str) -> Self { + Self { trussed: client, uuid, version, full_version, boot_interface: PhantomData } } fn user_present(&mut self) -> bool { @@ -185,7 +186,11 @@ where T: TrussedClient, } Command::Version => { // GET VERSION - response.extend_from_slice(&self.version.to_be_bytes()).ok(); + if flag == Some(0x01) { + response.extend_from_slice(self.full_version.as_bytes()).ok(); + } else { + response.extend_from_slice(&self.version.to_be_bytes()).ok(); + } } Command::Wink => { debug_now!("winking"); From 7471ac9d913af474a05397b70feb9071dd658ba1 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 31 Jan 2023 12:27:42 +0100 Subject: [PATCH 03/65] Add status command This patch adds a new status command that can be used to read out information about the device status. The interpretation of the status code depends on the runner implementation. --- src/admin.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/admin.rs b/src/admin.rs index 2464e27..5c0dd9c 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -14,6 +14,7 @@ pub const USER_PRESENCE_TIMEOUT_SECS: u32 = 15; // New commands are only available over this vendor command (acting as a namespace for this // application). The actual application command is stored in the first byte of the packet data. const ADMIN: VendorCommand = VendorCommand::H72; +const STATUS: u8 = 0x80; // For compatibility, old commands are also available directly as separate vendor commands. const UPDATE: VendorCommand = VendorCommand::H51; @@ -37,6 +38,7 @@ enum Command { Uuid, Locked, Wink, + Status, } impl TryFrom for Command { @@ -50,8 +52,11 @@ impl TryFrom for Command { } } - // Now check the new commands (none yet). - Err(Error::UnsupportedCommand) + // Now check the new commands. + match command { + STATUS => Ok(Command::Status), + _ => Err(Error::UnsupportedCommand), + } } } @@ -141,6 +146,7 @@ where T: TrussedClient, uuid: [u8; 16], version: u32, full_version: &'static str, + init_status: u8, boot_interface: PhantomData, } @@ -148,8 +154,8 @@ impl App where T: TrussedClient, R: Reboot, { - pub fn new(client: T, uuid: [u8; 16], version: u32, full_version: &'static str) -> Self { - Self { trussed: client, uuid, version, full_version, boot_interface: PhantomData } + pub fn new(client: T, uuid: [u8; 16], version: u32, full_version: &'static str, init_status: u8) -> Self { + Self { trussed: client, uuid, version, full_version, init_status, boot_interface: PhantomData } } fn user_present(&mut self) -> bool { @@ -196,6 +202,11 @@ where T: TrussedClient, debug_now!("winking"); syscall!(self.trussed.wink(Duration::from_secs(10))); } + Command::Status => { + // The first response byte is the init status as set by the runner (0 = success). + // More bytes might be added in the future. + response.push(self.init_status).ok(); + } } Ok(()) } From e318dc13216fe39401c9c5cf82f237d59ca12f61 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 28 Feb 2023 17:42:06 +0100 Subject: [PATCH 04/65] Make app generic over status type --- src/admin.rs | 50 +++++++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/src/admin.rs b/src/admin.rs index 5c0dd9c..9a61467 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -138,24 +138,28 @@ pub trait Reboot { fn locked() -> bool; } -pub struct App -where T: TrussedClient, - R: Reboot, +pub struct App +where + T: TrussedClient, + R: Reboot, + S: AsRef<[u8]>, { trussed: T, uuid: [u8; 16], version: u32, full_version: &'static str, - init_status: u8, + status: S, boot_interface: PhantomData, } -impl App -where T: TrussedClient, - R: Reboot, +impl App +where + T: TrussedClient, + R: Reboot, + S: AsRef<[u8]>, { - pub fn new(client: T, uuid: [u8; 16], version: u32, full_version: &'static str, init_status: u8) -> Self { - Self { trussed: client, uuid, version, full_version, init_status, boot_interface: PhantomData } + pub fn new(client: T, uuid: [u8; 16], version: u32, full_version: &'static str, status: S) -> Self { + Self { trussed: client, uuid, version, full_version, status, boot_interface: PhantomData } } fn user_present(&mut self) -> bool { @@ -203,18 +207,18 @@ where T: TrussedClient, syscall!(self.trussed.wink(Duration::from_secs(10))); } Command::Status => { - // The first response byte is the init status as set by the runner (0 = success). - // More bytes might be added in the future. - response.push(self.init_status).ok(); + response.extend_from_slice(self.status.as_ref()).ok(); } } Ok(()) } } -impl hid::App for App -where T: TrussedClient, - R: Reboot +impl hid::App for App +where + T: TrussedClient, + R: Reboot, + S: AsRef<[u8]>, { fn commands(&self) -> &'static [HidCommand] { &[ @@ -243,9 +247,11 @@ where T: TrussedClient, } } -impl iso7816::App for App -where T: TrussedClient, - R: Reboot +impl iso7816::App for App +where + T: TrussedClient, + R: Reboot, + S: AsRef<[u8]>, { // Solo management app fn aid(&self) -> iso7816::Aid { @@ -253,9 +259,11 @@ where T: TrussedClient, } } -impl apdu::App<{command::SIZE}, {response::SIZE}> for App -where T: TrussedClient, - R: Reboot +impl apdu::App<{command::SIZE}, {response::SIZE}> for App +where + T: TrussedClient, + R: Reboot, + S: AsRef<[u8]>, { fn select(&mut self, _apdu: &ApduCommand, _reply: &mut response::Data) -> apdu::Result { From 5cfd368b30fb5f840154475ee7a7c8d90ab8e74a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Tue, 20 Jun 2023 16:06:40 +0200 Subject: [PATCH 05/65] Run cargo fmt --- src/admin.rs | 76 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 24 deletions(-) diff --git a/src/admin.rs b/src/admin.rs index 9a61467..e72000a 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -1,13 +1,9 @@ +use apdu_dispatch::iso7816::Status; +use apdu_dispatch::{app as apdu, command, response, Command as ApduCommand}; use core::{convert::TryInto, marker::PhantomData, time::Duration}; use ctaphid_dispatch::app::{self as hid, Command as HidCommand, Message}; use ctaphid_dispatch::command::VendorCommand; -use apdu_dispatch::{Command as ApduCommand, command, response, app as apdu}; -use apdu_dispatch::iso7816::Status; -use trussed::{ - types::Vec, - syscall, - Client as TrussedClient, -}; +use trussed::{syscall, types::Vec, Client as TrussedClient}; pub const USER_PRESENCE_TIMEOUT_SECS: u32 = 15; @@ -25,7 +21,7 @@ const UUID: VendorCommand = VendorCommand::H62; const LOCKED: VendorCommand = VendorCommand::H63; // We also handle the standard wink command. -const WINK: HidCommand = HidCommand::Wink; // 0x08 +const WINK: HidCommand = HidCommand::Wink; // 0x08 const RNG_DATA_LEN: usize = 57; @@ -67,7 +63,7 @@ impl TryFrom for Command { match command { WINK => Ok(Command::Wink), HidCommand::Vendor(command) => command.try_into(), - _ => Err(Error::UnsupportedCommand) + _ => Err(Error::UnsupportedCommand), } } } @@ -158,16 +154,37 @@ where R: Reboot, S: AsRef<[u8]>, { - pub fn new(client: T, uuid: [u8; 16], version: u32, full_version: &'static str, status: S) -> Self { - Self { trussed: client, uuid, version, full_version, status, boot_interface: PhantomData } + pub fn new( + client: T, + uuid: [u8; 16], + version: u32, + full_version: &'static str, + status: S, + ) -> Self { + Self { + trussed: client, + uuid, + version, + full_version, + status, + boot_interface: PhantomData, + } } fn user_present(&mut self) -> bool { - let user_present = syscall!(self.trussed.confirm_user_present(USER_PRESENCE_TIMEOUT_SECS * 1000)).result; + let user_present = syscall!(self + .trussed + .confirm_user_present(USER_PRESENCE_TIMEOUT_SECS * 1000)) + .result; user_present.is_ok() } - fn exec(&mut self, command: Command, flag: Option, response: &mut Vec) -> Result<(), Error> { + fn exec( + &mut self, + command: Command, + flag: Option, + response: &mut Vec, + ) -> Result<(), Error> { match command { Command::Reboot => R::reboot(), Command::Locked => { @@ -175,9 +192,9 @@ where } Command::Rng => { // Fill the HID packet (57 bytes) - response.extend_from_slice( - &syscall!(self.trussed.random_bytes(RNG_DATA_LEN)).bytes, - ).ok(); + response + .extend_from_slice(&syscall!(self.trussed.random_bytes(RNG_DATA_LEN)).bytes) + .ok(); } Command::Update => { if self.user_present() { @@ -197,7 +214,9 @@ where Command::Version => { // GET VERSION if flag == Some(0x01) { - response.extend_from_slice(self.full_version.as_bytes()).ok(); + response + .extend_from_slice(self.full_version.as_bytes()) + .ok(); } else { response.extend_from_slice(&self.version.to_be_bytes()).ok(); } @@ -233,7 +252,12 @@ where ] } - fn call(&mut self, command: HidCommand, input_data: &Message, response: &mut Message) -> hid::AppResult { + fn call( + &mut self, + command: HidCommand, + input_data: &Message, + response: &mut Message, + ) -> hid::AppResult { let (command, flag) = if command == HidCommand::Vendor(ADMIN) { // new mode: first input byte specifies the actual command let (command, input) = input_data.split_first().ok_or(Error::InvalidLength)?; @@ -243,7 +267,8 @@ where // old mode: directly use vendor commands + wink (Command::try_from(command)?, input_data.first()) }; - self.exec(command, flag.copied(), response).map_err(From::from) + self.exec(command, flag.copied(), response) + .map_err(From::from) } } @@ -255,24 +280,28 @@ where { // Solo management app fn aid(&self) -> iso7816::Aid { - iso7816::Aid::new(&[ 0xA0, 0x00, 0x00, 0x08, 0x47, 0x00, 0x00, 0x00, 0x01]) + iso7816::Aid::new(&[0xA0, 0x00, 0x00, 0x08, 0x47, 0x00, 0x00, 0x00, 0x01]) } } -impl apdu::App<{command::SIZE}, {response::SIZE}> for App +impl apdu::App<{ command::SIZE }, { response::SIZE }> for App where T: TrussedClient, R: Reboot, S: AsRef<[u8]>, { - fn select(&mut self, _apdu: &ApduCommand, _reply: &mut response::Data) -> apdu::Result { Ok(()) } fn deselect(&mut self) {} - fn call(&mut self, interface: apdu::Interface, apdu: &ApduCommand, reply: &mut response::Data) -> apdu::Result { + fn call( + &mut self, + interface: apdu::Interface, + apdu: &ApduCommand, + reply: &mut response::Data, + ) -> apdu::Result { let instruction: u8 = apdu.instruction().into(); let command = Command::try_from(instruction)?; @@ -284,4 +313,3 @@ where self.exec(command, Some(apdu.p1), reply).map_err(From::from) } } - From 5426703bc642d0070e5f4cbf34b749d2458212de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Tue, 20 Jun 2023 17:41:25 +0200 Subject: [PATCH 06/65] Adapt to interrupt mechanism --- Cargo.toml | 5 +++++ src/admin.rs | 8 ++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 438e311..4a9778b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,8 @@ log-info = [] log-debug = [] log-warn = [] log-error = [] + +[patch.crates-io] +ref-swap = { git = "https://github.com/nitrokey/ref-swap.git", rev = "de0330e85b479074ae03bcc05888cfeff682f61e" } +ctaphid-dispatch = { git = "https://github.com/sosthene-nitrokey/ctaphid-dispatch.git", rev = "a5a3696d7cf0665414cf57cdea384dbc8a157f33" } +trussed = { git = "https://github.com/sosthene-nitrokey/trussed.git", rev = "6f095e14b27bd58ab8fa56cf6c616266d92fbfb4" } diff --git a/src/admin.rs b/src/admin.rs index e72000a..ba1200a 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -3,7 +3,7 @@ use apdu_dispatch::{app as apdu, command, response, Command as ApduCommand}; use core::{convert::TryInto, marker::PhantomData, time::Duration}; use ctaphid_dispatch::app::{self as hid, Command as HidCommand, Message}; use ctaphid_dispatch::command::VendorCommand; -use trussed::{syscall, types::Vec, Client as TrussedClient}; +use trussed::{interrupt::InterruptFlag, syscall, types::Vec, Client as TrussedClient}; pub const USER_PRESENCE_TIMEOUT_SECS: u32 = 15; @@ -233,7 +233,7 @@ where } } -impl hid::App for App +impl hid::App<'static> for App where T: TrussedClient, R: Reboot, @@ -270,6 +270,10 @@ where self.exec(command, flag.copied(), response) .map_err(From::from) } + + fn interrupt(&self) -> Option<&'static InterruptFlag> { + self.trussed.interrupt() + } } impl iso7816::App for App From 10fafb424017e45ae8dacdce66783c4684c1d285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Wed, 31 May 2023 10:58:08 +0200 Subject: [PATCH 07/65] Use published ref-swap --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4a9778b..890035c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,5 @@ log-warn = [] log-error = [] [patch.crates-io] -ref-swap = { git = "https://github.com/nitrokey/ref-swap.git", rev = "de0330e85b479074ae03bcc05888cfeff682f61e" } ctaphid-dispatch = { git = "https://github.com/sosthene-nitrokey/ctaphid-dispatch.git", rev = "a5a3696d7cf0665414cf57cdea384dbc8a157f33" } trussed = { git = "https://github.com/sosthene-nitrokey/trussed.git", rev = "6f095e14b27bd58ab8fa56cf6c616266d92fbfb4" } From c30b5f49893bcd482c81c9460736ef83e2eca82e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Tue, 6 Jun 2023 17:38:44 +0200 Subject: [PATCH 08/65] Bump ctaphid-dispatch --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 890035c..d139bf4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,5 +25,5 @@ log-warn = [] log-error = [] [patch.crates-io] -ctaphid-dispatch = { git = "https://github.com/sosthene-nitrokey/ctaphid-dispatch.git", rev = "a5a3696d7cf0665414cf57cdea384dbc8a157f33" } +ctaphid-dispatch = { git = "https://github.com/sosthene-nitrokey/ctaphid-dispatch.git", rev = "cb3c35f622b578d8d364e85c5e29f207201a5060" } trussed = { git = "https://github.com/sosthene-nitrokey/trussed.git", rev = "6f095e14b27bd58ab8fa56cf6c616266d92fbfb4" } From 574570f7595f8f49f93ff5f5a1888e867601f65a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Tue, 20 Jun 2023 17:42:35 +0200 Subject: [PATCH 09/65] Use merged PRs --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d139bf4..542482d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,5 +25,5 @@ log-warn = [] log-error = [] [patch.crates-io] -ctaphid-dispatch = { git = "https://github.com/sosthene-nitrokey/ctaphid-dispatch.git", rev = "cb3c35f622b578d8d364e85c5e29f207201a5060" } -trussed = { git = "https://github.com/sosthene-nitrokey/trussed.git", rev = "6f095e14b27bd58ab8fa56cf6c616266d92fbfb4" } +ctaphid-dispatch = { git = "https://github.com/trussed-dev/ctaphid-dispatch.git", rev = "57cb3317878a8593847595319aa03ef17c29ec5b" } +trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "51e68500d7601d04f884f5e95567d14b9018a6cb" } From f6c223cd515e5b67c874933ab9f0940451bbcf3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 27 Jul 2023 17:29:55 +0200 Subject: [PATCH 10/65] Add se050 test --- Cargo.toml | 8 + src/admin.rs | 78 ++- src/admin/run_tests.rs | 1039 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1120 insertions(+), 5 deletions(-) create mode 100644 src/admin/run_tests.rs diff --git a/Cargo.toml b/Cargo.toml index 542482d..e71f0dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,13 @@ delog = "0.1" iso7816 = "0.1" trussed = "0.1" +se050 = { version = "0.0.1", optional = true } +embedded-hal = { version = "0.2.7", optional = true } +hex-literal = "0.4.1" + [features] +se050 = ["dep:se050", "embedded-hal"] + log-all = [] log-none = [] log-info = [] @@ -27,3 +33,5 @@ log-error = [] [patch.crates-io] ctaphid-dispatch = { git = "https://github.com/trussed-dev/ctaphid-dispatch.git", rev = "57cb3317878a8593847595319aa03ef17c29ec5b" } trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "51e68500d7601d04f884f5e95567d14b9018a6cb" } +iso7816 = { git = "https://github.com/sosthene-nitrokey/iso7816.git", rev = "160ca3bbd8e21ec4e4ee1e0748e1eaa53a45c97f"} +se050 = { git = "https://github.com/sosthene-nitrokey/se050-generation.git", rev = "b645ea9de6c53c18a2173ec8d4d1307580c405c3"} diff --git a/src/admin.rs b/src/admin.rs index ba1200a..d46bb52 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -3,6 +3,10 @@ use apdu_dispatch::{app as apdu, command, response, Command as ApduCommand}; use core::{convert::TryInto, marker::PhantomData, time::Duration}; use ctaphid_dispatch::app::{self as hid, Command as HidCommand, Message}; use ctaphid_dispatch::command::VendorCommand; +#[cfg(feature = "se050")] +use embedded_hal::blocking::delay::DelayUs; +#[cfg(feature = "se050")] +use se050::{se050::Se050, t1::I2CForT1}; use trussed::{interrupt::InterruptFlag, syscall, types::Vec, Client as TrussedClient}; pub const USER_PRESENCE_TIMEOUT_SECS: u32 = 15; @@ -11,6 +15,7 @@ pub const USER_PRESENCE_TIMEOUT_SECS: u32 = 15; // application). The actual application command is stored in the first byte of the packet data. const ADMIN: VendorCommand = VendorCommand::H72; const STATUS: u8 = 0x80; +const TEST_SE050: u8 = 0x81; // For compatibility, old commands are also available directly as separate vendor commands. const UPDATE: VendorCommand = VendorCommand::H51; @@ -25,7 +30,19 @@ const WINK: HidCommand = HidCommand::Wink; // 0x08 const RNG_DATA_LEN: usize = 57; -#[derive(PartialEq)] +mod run_tests; +use run_tests::*; + +/// Trait representing the possible ownership of the SE050 by the admin app. +/// +/// Implemented by `()` and the `Se050` stract +pub trait MaybeSe: RunTests {} + +impl MaybeSe for () {} +#[cfg(feature = "se050")] +impl> MaybeSe for Se050 {} + +#[derive(PartialEq, Debug)] enum Command { Update, Reboot, @@ -35,6 +52,7 @@ enum Command { Locked, Wink, Status, + TestSe050, } impl TryFrom for Command { @@ -51,6 +69,7 @@ impl TryFrom for Command { // Now check the new commands. match command { STATUS => Ok(Command::Status), + TEST_SE050 => Ok(Command::TestSe050), _ => Err(Error::UnsupportedCommand), } } @@ -134,7 +153,7 @@ pub trait Reboot { fn locked() -> bool; } -pub struct App +pub struct App where T: TrussedClient, R: Reboot, @@ -146,6 +165,7 @@ where full_version: &'static str, status: S, boot_interface: PhantomData, + se050: Se050, } impl App @@ -168,9 +188,47 @@ where full_version, status, boot_interface: PhantomData, + se050: (), } } +} +#[cfg(feature = "se050")] +impl App> +where + T: TrussedClient, + R: Reboot, + S: AsRef<[u8]>, + Twi: I2CForT1, + D: DelayUs, +{ + pub fn with_se( + client: T, + uuid: [u8; 16], + version: u32, + full_version: &'static str, + status: S, + se050: Se050, + ) -> Self { + Self { + trussed: client, + uuid, + version, + full_version, + status, + boot_interface: PhantomData, + se050, + } + } +} + +impl App +where + T: TrussedClient, + R: Reboot, + S: AsRef<[u8]>, + Se050: MaybeSe, +{ fn user_present(&mut self) -> bool { let user_present = syscall!(self .trussed @@ -185,6 +243,7 @@ where flag: Option, response: &mut Vec, ) -> Result<(), Error> { + debug_now!("Executing command: {command:?}"); match command { Command::Reboot => R::reboot(), Command::Locked => { @@ -228,16 +287,23 @@ where Command::Status => { response.extend_from_slice(self.status.as_ref()).ok(); } + Command::TestSe050 => { + debug_now!("Running se050 tests"); + if let Err(_err) = self.se050.run_tests(response) { + debug_now!("se050 tests failed: {_err:?}"); + } + } } Ok(()) } } -impl hid::App<'static> for App +impl hid::App<'static> for App where T: TrussedClient, R: Reboot, S: AsRef<[u8]>, + Se: MaybeSe, { fn commands(&self) -> &'static [HidCommand] { &[ @@ -276,11 +342,12 @@ where } } -impl iso7816::App for App +impl iso7816::App for App where T: TrussedClient, R: Reboot, S: AsRef<[u8]>, + Se: MaybeSe, { // Solo management app fn aid(&self) -> iso7816::Aid { @@ -288,11 +355,12 @@ where } } -impl apdu::App<{ command::SIZE }, { response::SIZE }> for App +impl apdu::App<{ command::SIZE }, { response::SIZE }> for App where T: TrussedClient, R: Reboot, S: AsRef<[u8]>, + Se: MaybeSe, { fn select(&mut self, _apdu: &ApduCommand, _reply: &mut response::Data) -> apdu::Result { Ok(()) diff --git a/src/admin/run_tests.rs b/src/admin/run_tests.rs new file mode 100644 index 0000000..7c4ae93 --- /dev/null +++ b/src/admin/run_tests.rs @@ -0,0 +1,1039 @@ +use ctaphid_dispatch::types::Error; + +#[cfg(feature = "se050")] +use embedded_hal::blocking::delay::DelayUs; +use iso7816::Status; +#[cfg(feature = "se050")] +use se050::{ + se050::{ + commands::{ + CreateSession, DeleteAll, DeleteSecureObject, EcdsaSign, EcdsaVerify, GetRandom, + ReadIdList, ReadObject, VerifySessionUserId, WriteBinary, WriteEcKey, WriteUserId, + }, + policies::{ObjectAccessRule, ObjectPolicyFlags, Policy, PolicySet}, + EcCurve, EcDsaSignatureAlgo, ObjectId, P1KeyType, ProcessSessionCmd, Se050Result, + }, + t1::I2CForT1, +}; +use trussed::types::Vec; + +#[cfg(feature = "se050")] +use se050::se050::Se050; + +#[cfg(feature = "se050")] +use hex_literal::hex; + +pub trait RunTests { + fn run_tests(&mut self, _response: &mut Vec) -> Result<(), Error> { + debug_now!("Default run tests"); + Err(Error::InvalidCommand) + } + + fn run_tests_internal( + &mut self, + _response: &mut Vec, + ) -> Result<(), Status> { + Err(Status::NotFound) + } +} + +#[cfg(feature = "se050")] +const BUFFER_LEN: usize = 1024; + +#[cfg(feature = "se050")] +#[derive(Debug)] +#[repr(u8)] +enum Advance { + Enable = 1, + Random1, + Random2, + Random3, + WriteUserId, + CreateSession, + VerifySessionUserId, + DeleteAll, + List, + WriteBinary1, + ReadBinary1, + DeleteBinary1, + WriteBinary2, + ReadBinary2, + DeleteBinary2, + WriteBinary3, + ReadBinary3, + DeleteBinary3, + CreateP256, + ListP256, + GenerateP256, + EcDsaP256, + VerifyP256, + DeleteP256, + CreateP521, + GenerateP521, + EcDsaP521, + VerifyP521, + DeleteP521, + RecreationWriteUserId, + RecreationWriteBinary, + RecreationDeleteAttempt, + RecreationDeleteUserId, + RecreationRecreateUserId, + RecreationCreateSession, + RecreationAuthSession, + RecreationDeleteAttack, + Rsa2048Gen, + Rsa2048Sign, + Rsa2048Verify, + Rsa2048Encrypt, + Rsa2048Decrypt, + Rsa2048Delete, + Rsa3072Gen, + Rsa3072Sign, + Rsa3072Verify, + Rsa3072Encrypt, + Rsa3072Decrypt, + Rsa3072Delete, + Rsa4096Gen, + Rsa4096Sign, + Rsa4096Verify, + Rsa4096Encrypt, + Rsa4096Decrypt, + Rsa4096Delete, + SymmWrite, + SymmEncryptOneShot, + SymmDecryptOneShot, + SymmEncryptCreate, + SymmEncryptInit, + SymmEncryptUpdate1, + SymmEncryptUpdate2, + SymmEncryptFinal, + SymmEncryptDelete, + SymmDecryptCreate, + SymmDecryptInit, + SymmDecryptUpdate1, + SymmDecryptUpdate2, + SymmDecryptFinal, + SymmDecryptDelete, + SymmDelete, + MacWrite, + MacSignOneShot, + MacVerifyOneShot, + MacSignCreate, + MacSignInit, + MacSignUpdate1, + MacSignUpdate2, + MacSignFinal, + MacSignDelete, + MacVerifyCreate, + MacVerifyInit, + MacVerifyUpdate1, + MacVerifyUpdate2, + MacVerifyFinal, + MacVerifyDelete, + MacDelete, +} + +impl RunTests for () {} + +#[cfg(feature = "se050")] +impl> RunTests for Se050 { + fn run_tests(&mut self, response: &mut Vec) -> Result<(), Error> { + debug_now!("Se050 run tests"); + match self.run_tests_internal(response) { + Ok(()) => Ok(()), + Err(err) => { + response.push(0).ok(); + let sw: [u8; 2] = err.into(); + response.extend_from_slice(&sw).ok(); + Ok(()) + } + } + } + fn run_tests_internal( + &mut self, + response: &mut Vec, + ) -> Result<(), Status> { + let atr = self.enable()?; + response + .extend_from_slice(&[ + atr.major, + atr.minor, + atr.patch, + atr.secure_box_major, + atr.secure_box_minor, + ]) + .ok(); + response.push(Advance::Enable as _).ok(); + run_get_random(self, response)?; + run_factory_reset(self, response)?; + run_list(self, response)?; + run_binary(self, response)?; + run_ecc(self, response)?; + run_userid_recreation(self, response)?; + run_rsa2048(self, response)?; + run_rsa3072(self, response)?; + run_rsa4096(self, response)?; + run_symm(self, response)?; + run_mac(self, response)?; + Ok(()) + } +} + +#[cfg(feature = "se050")] +fn run_get_random, const N: usize>( + se050: &mut Se050, + response: &mut Vec, +) -> Result<(), Status> { + let mut buf = [b'a'; BUFFER_LEN]; + let lens = [1, 256, 800]; + let advance = [Advance::Random1, Advance::Random2, Advance::Random3]; + for (len, advance) in lens.into_iter().zip(advance) { + let res = se050.run_command( + &GetRandom { + length: (len as u16).into(), + }, + &mut buf, + )?; + response.push(advance as u8).ok(); + if res.data == &[b'a'; BUFFER_LEN][..len] { + debug!("Failed to get random"); + response.extend_from_slice(&[0, 0, 0]).ok(); + return Ok(()); + } + } + Ok(()) +} + +#[cfg(feature = "se050")] +fn run_factory_reset, const N: usize>( + se050: &mut Se050, + response: &mut Vec, +) -> Result<(), Status> { + let mut buf = [b'a'; BUFFER_LEN]; + let data = &hex!("31323334"); + + se050.run_command( + &WriteUserId { + policy: None, + max_attempts: None, + object_id: ObjectId::FACTORY_RESET, + data, + }, + &mut buf, + )?; + response.push(Advance::WriteUserId as u8).ok(); + let session = se050.run_command( + &CreateSession { + object_id: ObjectId::FACTORY_RESET, + }, + &mut buf, + )?; + response.push(Advance::CreateSession as u8).ok(); + + se050.run_command( + &ProcessSessionCmd { + session_id: session.session_id, + apdu: VerifySessionUserId { user_id: data }, + }, + &mut buf, + )?; + response.push(Advance::VerifySessionUserId as u8).ok(); + + se050.run_command( + &ProcessSessionCmd { + session_id: session.session_id, + apdu: DeleteAll {}, + }, + &mut buf, + )?; + response.push(Advance::DeleteAll as u8).ok(); + Ok(()) +} + +#[cfg(feature = "se050")] +fn run_list, const N: usize>( + se050: &mut Se050, + response: &mut Vec, +) -> Result<(), Status> { + let mut buf = [0; 200]; + se050.run_command( + &ReadIdList { + offset: 0.into(), + filter: se050::se050::SecureObjectFilter::All, + }, + &mut buf, + )?; + response.push(Advance::List as u8).ok(); + Ok(()) +} + +#[cfg(feature = "se050")] +fn run_binary, const N: usize>( + se050: &mut Se050, + response: &mut Vec, +) -> Result<(), Status> { + let mut buf = [b'a'; 400]; + let buf2 = [b'b'; 400]; + let object_id = ObjectId(hex!("01020304")); + let policy = &[Policy { + object_id: ObjectId::INVALID, + access_rule: ObjectAccessRule::from_flags( + ObjectPolicyFlags::ALLOW_DELETE | ObjectPolicyFlags::ALLOW_READ, + ), + }]; + for (((len, advance_write), advance_read), advance_delete) in [1, 255, 300] + .into_iter() + .zip([ + Advance::WriteBinary1, + Advance::WriteBinary2, + Advance::WriteBinary3, + ]) + .zip([ + Advance::ReadBinary1, + Advance::ReadBinary2, + Advance::ReadBinary3, + ]) + .zip([ + Advance::DeleteBinary1, + Advance::DeleteBinary2, + Advance::DeleteBinary3, + ]) + { + se050.run_command( + &WriteBinary { + transient: false, + policy: Some(PolicySet(policy)), + object_id, + offset: None, + file_length: Some(len.into()), + data: Some(&buf2[..len.into()]), + }, + &mut buf, + )?; + response.push(advance_write as u8).ok(); + let res = se050.run_command( + &ReadObject { + object_id, + offset: None, + length: Some(len.into()), + rsa_key_component: None, + }, + &mut buf, + )?; + response.push(advance_read as u8).ok(); + if res.data[..len.into()] != buf2[..len.into()] { + return Err(0x3001.into()); + } + + se050.run_command(&DeleteSecureObject { object_id }, &mut buf)?; + response.push(advance_delete as u8).ok(); + } + Ok(()) +} + +#[cfg(feature = "se050")] +fn run_ecc, const N: usize>( + se050: &mut Se050, + response: &mut Vec, +) -> Result<(), Status> { + use se050::se050::commands::ReadEcCurveList; + + let mut buf = [0; 200]; + let mut buf2 = [0; 200]; + let object_id = ObjectId(hex!("01020304")); + + // *********** P256 *********** // + + se050.create_and_set_curve(EcCurve::NistP256)?; + response.push(Advance::CreateP256 as u8).ok(); + let _res = se050.run_command(&ReadEcCurveList {}, &mut buf)?; + debug_now!("Ec curves list: {:?}", _res); + response.push(Advance::ListP256 as u8).ok(); + se050.run_command( + &WriteEcKey { + transient: false, + is_auth: false, + key_type: Some(P1KeyType::KeyPair), + policy: None, + max_attempts: None, + object_id, + curve: Some(EcCurve::NistP256), + private_key: None, + public_key: None, + }, + &mut buf, + )?; + response.push(Advance::GenerateP256 as u8).ok(); + let res = se050.run_command( + &EcdsaSign { + key_id: object_id, + data: &[52; 32], + algo: EcDsaSignatureAlgo::Sha256, + }, + &mut buf, + )?; + response.push(Advance::EcDsaP256 as u8).ok(); + let res = se050.run_command( + &EcdsaVerify { + key_id: object_id, + data: &[52; 32], + algo: EcDsaSignatureAlgo::Sha256, + signature: res.signature, + }, + &mut buf2, + )?; + if res.result != Se050Result::Success { + return Err(0x3002.into()); + } + response.push(Advance::VerifyP256 as u8).ok(); + se050.run_command(&DeleteSecureObject { object_id }, &mut buf)?; + response.push(Advance::DeleteP256 as u8).ok(); + + // *********** P521 *********** // + + se050.create_and_set_curve(EcCurve::NistP521)?; + response.push(Advance::CreateP521 as u8).ok(); + se050.run_command( + &WriteEcKey { + transient: false, + is_auth: false, + key_type: Some(P1KeyType::KeyPair), + policy: None, + max_attempts: None, + object_id, + curve: Some(EcCurve::NistP521), + private_key: None, + public_key: None, + }, + &mut buf, + )?; + response.push(Advance::GenerateP521 as u8).ok(); + let res = se050.run_command( + &EcdsaSign { + key_id: object_id, + data: &[52; 64], + algo: EcDsaSignatureAlgo::Sha512, + }, + &mut buf, + )?; + response.push(Advance::EcDsaP521 as u8).ok(); + let res = se050.run_command( + &EcdsaVerify { + key_id: object_id, + data: &[52; 64], + algo: EcDsaSignatureAlgo::Sha512, + signature: res.signature, + }, + &mut buf2, + )?; + if res.result != Se050Result::Success { + return Err(0x3003.into()); + } + response.push(Advance::VerifyP521 as u8).ok(); + se050.run_command(&DeleteSecureObject { object_id }, &mut buf)?; + response.push(Advance::DeleteP521 as u8).ok(); + Ok(()) +} + +#[cfg(feature = "se050")] +fn run_userid_recreation, const N: usize>( + se050: &mut Se050, + response: &mut Vec, +) -> Result<(), Status> { + let mut buf = [0; BUFFER_LEN]; + let object_id = ObjectId(hex!("01020304")); + let user_id = ObjectId(hex!("01223344")); + let user_id_good_value = hex!("31323334"); + let user_id_bad_value = hex!("FFFFFFFF"); + let policy_user_id = &[Policy { + object_id: ObjectId::INVALID, + access_rule: ObjectAccessRule::from_flags( + ObjectPolicyFlags::ALLOW_DELETE | ObjectPolicyFlags::ALLOW_WRITE, + ), + }]; + se050.run_command( + &WriteUserId { + policy: Some(PolicySet(policy_user_id)), + max_attempts: None, + object_id: user_id, + data: &user_id_good_value, + }, + &mut buf, + )?; + response.push(Advance::RecreationWriteUserId as u8).ok(); + let policy = &[Policy { + object_id: user_id, + access_rule: ObjectAccessRule::from_flags( + ObjectPolicyFlags::ALLOW_DELETE | ObjectPolicyFlags::ALLOW_READ, + ), + }]; + se050.run_command( + &WriteBinary { + transient: false, + policy: Some(PolicySet(policy)), + object_id, + offset: None, + file_length: Some(2.into()), + data: Some(&[1, 2]), + }, + &mut buf, + )?; + response.push(Advance::RecreationWriteBinary as u8).ok(); + match se050.run_command(&DeleteSecureObject { object_id }, &mut buf) { + Ok(_) => return Err(0x3004.into()), + Err(se050::se050::Error::Status(Status::CommandNotAllowedNoEf)) => {} + Err(_err) => { + debug_now!("Got unexpected error: {_err:?}"); + return Err(0x3007.into()); + } + } + response.push(Advance::RecreationDeleteAttempt as u8).ok(); + se050.run_command(&DeleteSecureObject { object_id: user_id }, &mut buf)?; + response.push(Advance::RecreationDeleteUserId as u8).ok(); + se050.run_command( + &WriteUserId { + policy: None, + max_attempts: None, + object_id: user_id, + data: &user_id_bad_value, + }, + &mut buf, + )?; + response.push(Advance::RecreationRecreateUserId as u8).ok(); + + let session = se050.run_command(&CreateSession { object_id: user_id }, &mut buf)?; + response.push(Advance::RecreationCreateSession as u8).ok(); + + se050.run_command( + &ProcessSessionCmd { + session_id: session.session_id, + apdu: VerifySessionUserId { + user_id: &user_id_bad_value, + }, + }, + &mut buf, + )?; + response.push(Advance::RecreationAuthSession as u8).ok(); + + let attack = se050.run_command( + &ProcessSessionCmd { + session_id: session.session_id, + apdu: DeleteSecureObject { object_id: user_id }, + }, + &mut buf, + ); + + match attack { + Ok(_) => return Err(0x3005.into()), + Err(se050::se050::Error::Status(Status::CommandNotAllowedNoEf)) => {} + Err(_err) => { + debug_now!("Got unexpected error: {_err:?}"); + return Err(0x3006.into()); + } + } + response.push(Advance::RecreationDeleteAttack as u8).ok(); + Ok(()) +} + +#[cfg(feature = "se050")] +fn run_rsa2048, const N: usize>( + se050: &mut Se050, + response: &mut Vec, +) -> Result<(), Status> { + use se050::se050::{ + commands::{GenRsaKey, RsaDecrypt, RsaEncrypt, RsaSign, RsaVerify}, + RsaEncryptionAlgo, RsaSignatureAlgo, + }; + + let mut buf = [0; 1000]; + let mut buf2 = [0; 1000]; + let object_id = ObjectId(hex!("02334455")); + se050.run_command( + &GenRsaKey { + transient: false, + is_auth: false, + policy: None, + max_attempts: None, + object_id, + key_size: Some(2048.into()), + }, + &mut buf, + )?; + response.push(Advance::Rsa2048Gen as u8).ok(); + let res = se050.run_command( + &RsaSign { + key_id: object_id, + data: &[52; 32], + algo: RsaSignatureAlgo::RsaSha256Pkcs1, + }, + &mut buf, + )?; + response.push(Advance::Rsa2048Sign as u8).ok(); + let res = se050.run_command( + &RsaVerify { + key_id: object_id, + data: &[52; 32], + algo: RsaSignatureAlgo::RsaSha256Pkcs1, + signature: res.signature, + }, + &mut buf2, + )?; + response.push(Advance::Rsa2048Verify as u8).ok(); + let res = se050.run_command( + &RsaEncrypt { + key_id: object_id, + plaintext: &[52; 32], + algo: RsaEncryptionAlgo::Pkcs1, + }, + &mut buf2, + )?; + response.push(Advance::Rsa2048Encrypt as u8).ok(); + let res = se050.run_command( + &RsaDecrypt { + key_id: object_id, + algo: RsaEncryptionAlgo::Pkcs1, + ciphertext: res.ciphertext, + }, + &mut buf, + )?; + if res.plaintext != &[52; 32] { + return Err(0x3008.into()); + } + response.push(Advance::Rsa2048Decrypt as u8).ok(); + + se050.run_command(&DeleteSecureObject { object_id }, &mut buf)?; + response.push(Advance::Rsa2048Delete as u8).ok(); + + Ok(()) +} + +#[cfg(feature = "se050")] +fn run_rsa3072, const N: usize>( + se050: &mut Se050, + response: &mut Vec, +) -> Result<(), Status> { + use se050::se050::{ + commands::{GenRsaKey, RsaDecrypt, RsaEncrypt, RsaSign, RsaVerify}, + RsaEncryptionAlgo, RsaSignatureAlgo, + }; + + let mut buf = [0; 1000]; + let mut buf2 = [0; 1000]; + let object_id = ObjectId(hex!("02334455")); + se050.run_command( + &GenRsaKey { + transient: false, + is_auth: false, + policy: None, + max_attempts: None, + object_id, + key_size: Some(3072.into()), + }, + &mut buf, + )?; + response.push(Advance::Rsa3072Gen as u8).ok(); + let res = se050.run_command( + &RsaSign { + key_id: object_id, + data: &[52; 32], + algo: RsaSignatureAlgo::RsaSha256Pkcs1, + }, + &mut buf, + )?; + response.push(Advance::Rsa3072Sign as u8).ok(); + let res = se050.run_command( + &RsaVerify { + key_id: object_id, + data: &[52; 32], + algo: RsaSignatureAlgo::RsaSha256Pkcs1, + signature: res.signature, + }, + &mut buf2, + )?; + response.push(Advance::Rsa3072Verify as u8).ok(); + let res = se050.run_command( + &RsaEncrypt { + key_id: object_id, + plaintext: &[52; 32], + algo: RsaEncryptionAlgo::Pkcs1, + }, + &mut buf2, + )?; + response.push(Advance::Rsa3072Encrypt as u8).ok(); + let res = se050.run_command( + &RsaDecrypt { + key_id: object_id, + algo: RsaEncryptionAlgo::Pkcs1, + ciphertext: res.ciphertext, + }, + &mut buf, + )?; + if res.plaintext != &[52; 32] { + return Err(0x3008.into()); + } + response.push(Advance::Rsa3072Decrypt as u8).ok(); + + se050.run_command(&DeleteSecureObject { object_id }, &mut buf)?; + response.push(Advance::Rsa3072Delete as u8).ok(); + + Ok(()) +} + +#[cfg(feature = "se050")] +fn run_rsa4096, const N: usize>( + se050: &mut Se050, + response: &mut Vec, +) -> Result<(), Status> { + use se050::se050::{ + commands::{GenRsaKey, RsaDecrypt, RsaEncrypt, RsaSign, RsaVerify}, + RsaEncryptionAlgo, RsaSignatureAlgo, + }; + + let mut buf = [0; 1000]; + let mut buf2 = [0; 1000]; + let object_id = ObjectId(hex!("02334455")); + se050.run_command( + &GenRsaKey { + transient: false, + is_auth: false, + policy: None, + max_attempts: None, + object_id, + key_size: Some(4096.into()), + }, + &mut buf, + )?; + response.push(Advance::Rsa4096Gen as u8).ok(); + let res = se050.run_command( + &RsaSign { + key_id: object_id, + data: &[52; 32], + algo: RsaSignatureAlgo::RsaSha256Pkcs1, + }, + &mut buf, + )?; + response.push(Advance::Rsa4096Sign as u8).ok(); + let res = se050.run_command( + &RsaVerify { + key_id: object_id, + data: &[52; 32], + algo: RsaSignatureAlgo::RsaSha256Pkcs1, + signature: res.signature, + }, + &mut buf2, + )?; + response.push(Advance::Rsa4096Verify as u8).ok(); + let res = se050.run_command( + &RsaEncrypt { + key_id: object_id, + plaintext: &[52; 32], + algo: RsaEncryptionAlgo::Pkcs1, + }, + &mut buf2, + )?; + response.push(Advance::Rsa4096Encrypt as u8).ok(); + let res = se050.run_command( + &RsaDecrypt { + key_id: object_id, + algo: RsaEncryptionAlgo::Pkcs1, + ciphertext: res.ciphertext, + }, + &mut buf, + )?; + if res.plaintext != &[52; 32] { + return Err(0x3008.into()); + } + response.push(Advance::Rsa4096Decrypt as u8).ok(); + + se050.run_command(&DeleteSecureObject { object_id }, &mut buf)?; + response.push(Advance::Rsa4096Delete as u8).ok(); + + Ok(()) +} + +#[cfg(feature = "se050")] +fn run_symm, const N: usize>( + se050: &mut Se050, + response: &mut Vec, +) -> Result<(), Status> { + use se050::se050::{ + commands::{ + CipherDecryptInit, CipherEncryptInit, CipherFinal, CipherOneShotDecrypt, + CipherOneShotEncrypt, CipherUpdate, CreateCipherObject, DeleteCryptoObj, WriteSymmKey, + }, + CipherMode, CryptoObjectId, SymmKeyType, + }; + + let mut buf = [0; 1000]; + let mut buf2 = [0; 1000]; + let plaintext_data = [2; 32 * 15]; + let key_id = ObjectId(hex!("03445566")); + let cipher_id = CryptoObjectId(hex!("0123")); + let key = [0x42; 32]; + let iv = [0xFF; 16]; + se050.run_command( + &WriteSymmKey { + transient: true, + is_auth: false, + key_type: SymmKeyType::Aes, + policy: None, + max_attempts: None, + object_id: key_id, + kek_id: None, + value: &key, + }, + &mut buf, + )?; + response.push(Advance::SymmWrite as u8).ok(); + let ciphertext1 = se050.run_command( + &CipherOneShotEncrypt { + key_id, + mode: CipherMode::AesCtr, + plaintext: &plaintext_data, + initialization_vector: Some(&iv), + }, + &mut buf, + )?; + response.push(Advance::SymmEncryptOneShot as u8).ok(); + let plaintext1 = se050.run_command( + &CipherOneShotDecrypt { + key_id, + mode: CipherMode::AesCtr, + ciphertext: &ciphertext1.ciphertext, + initialization_vector: Some(&iv), + }, + &mut buf2, + )?; + response.push(Advance::SymmDecryptOneShot as u8).ok(); + assert_eq!(plaintext1.plaintext, plaintext_data); + se050.run_command( + &CreateCipherObject { + id: cipher_id, + subtype: CipherMode::AesCtr, + }, + &mut buf2, + )?; + response.push(Advance::SymmEncryptCreate as u8).ok(); + se050.run_command( + &CipherEncryptInit { + key_id, + initialization_vector: Some(&iv), + cipher_id, + }, + &mut buf2, + )?; + response.push(Advance::SymmEncryptInit as u8).ok(); + let ciphertext2 = se050.run_command( + &CipherUpdate { + cipher_id, + data: &plaintext_data[0..32 * 10], + }, + &mut buf2, + )?; + response.push(Advance::SymmEncryptUpdate1 as u8).ok(); + let ciphertext3 = se050.run_command( + &CipherUpdate { + cipher_id, + data: &plaintext_data[32 * 10..][..32 * 5], + }, + &mut buf2, + )?; + response.push(Advance::SymmEncryptUpdate2 as u8).ok(); + let ciphertext4 = se050.run_command( + &CipherFinal { + cipher_id, + data: &plaintext_data[32 * 15..], + }, + &mut buf2, + )?; + response.push(Advance::SymmEncryptFinal as u8).ok(); + se050.run_command(&DeleteCryptoObj { id: cipher_id }, &mut buf2)?; + response.push(Advance::SymmEncryptDelete as u8).ok(); + se050.run_command( + &CreateCipherObject { + id: cipher_id, + subtype: CipherMode::AesCtr, + }, + &mut buf2, + )?; + response.push(Advance::SymmDecryptCreate as u8).ok(); + se050.run_command( + &CipherDecryptInit { + key_id, + initialization_vector: Some(&iv), + cipher_id, + }, + &mut buf2, + )?; + response.push(Advance::SymmDecryptInit as u8).ok(); + let ciphertext2 = se050.run_command( + &CipherUpdate { + cipher_id, + data: &ciphertext1.ciphertext[0..32 * 10], + }, + &mut buf2, + )?; + response.push(Advance::SymmDecryptUpdate1 as u8).ok(); + let ciphertext3 = se050.run_command( + &CipherUpdate { + cipher_id, + data: &ciphertext1.ciphertext[32 * 10..][..32 * 5], + }, + &mut buf2, + )?; + response.push(Advance::SymmDecryptUpdate2 as u8).ok(); + let ciphertext4 = se050.run_command( + &CipherFinal { + cipher_id, + data: &ciphertext1.ciphertext[32 * 15..], + }, + &mut buf2, + )?; + response.push(Advance::SymmDecryptFinal as u8).ok(); + se050.run_command(&DeleteCryptoObj { id: cipher_id }, &mut buf2)?; + response.push(Advance::SymmDecryptDelete as u8).ok(); + se050.run_command(&DeleteSecureObject { object_id: key_id }, &mut buf2)?; + response.push(Advance::SymmDelete as u8).ok(); + Ok(()) +} + +#[cfg(feature = "se050")] +fn run_mac, const N: usize>( + se050: &mut Se050, + response: &mut Vec, +) -> Result<(), Status> { + use se050::se050::{ + commands::{ + CreateSignatureObject, DeleteCryptoObj, MacGenerateFinal, MacGenerateInit, + MacOneShotGenerate, MacOneShotValidate, MacUpdate, MacValidateFinal, MacValidateInit, + WriteSymmKey, + }, + CryptoObjectId, MacAlgo, SymmKeyType, + }; + + let mut buf = [0; 1000]; + let mut buf2 = [0; 1000]; + let plaintext_data = [2; 32 * 15]; + let key_id = ObjectId(hex!("03445566")); + let mac_id = CryptoObjectId(hex!("0123")); + let key = [0x42; 32]; + se050.run_command( + &WriteSymmKey { + transient: false, + is_auth: false, + key_type: SymmKeyType::Hmac, + policy: None, + max_attempts: None, + object_id: key_id, + kek_id: None, + value: &key, + }, + &mut buf, + )?; + response.push(Advance::MacWrite as u8).ok(); + let tag1 = se050.run_command( + &MacOneShotGenerate { + key_id, + data: &plaintext_data, + algo: MacAlgo::HmacSha256, + }, + &mut buf, + )?; + response.push(Advance::MacSignOneShot as u8).ok(); + let res = se050.run_command( + &MacOneShotValidate { + key_id, + algo: MacAlgo::HmacSha256, + data: &plaintext_data, + tag: tag1.tag, + }, + &mut buf2, + )?; + response.push(Advance::MacVerifyOneShot as u8).ok(); + if res.result != Se050Result::Success { + return Err(0x6008.into()); + } + se050.run_command( + &CreateSignatureObject { + id: mac_id, + subtype: MacAlgo::HmacSha256, + }, + &mut buf2, + )?; + response.push(Advance::MacSignCreate as u8).ok(); + se050.run_command(&MacGenerateInit { key_id, mac_id }, &mut buf2)?; + response.push(Advance::MacSignInit as u8).ok(); + se050.run_command( + &MacUpdate { + mac_id, + data: &plaintext_data[0..32 * 10], + }, + &mut buf2, + )?; + response.push(Advance::MacSignUpdate1 as u8).ok(); + se050.run_command( + &MacUpdate { + mac_id, + data: &plaintext_data[32 * 10..][..32 * 5], + }, + &mut buf2, + )?; + response.push(Advance::MacSignUpdate2 as u8).ok(); + let tag2 = se050.run_command( + &MacGenerateFinal { + mac_id, + data: &plaintext_data[32 * 15..], + }, + &mut buf2, + )?; + response.push(Advance::MacSignFinal as u8).ok(); + assert_eq!(tag2.tag, tag1.tag); + se050.run_command(&DeleteCryptoObj { id: mac_id }, &mut buf)?; + response.push(Advance::MacSignDelete as u8).ok(); + + se050.run_command( + &CreateSignatureObject { + id: mac_id, + subtype: MacAlgo::HmacSha256, + }, + &mut buf, + )?; + response.push(Advance::MacVerifyCreate as u8).ok(); + se050.run_command(&MacValidateInit { key_id, mac_id }, &mut buf)?; + response.push(Advance::MacVerifyInit as u8).ok(); + se050.run_command( + &MacUpdate { + mac_id, + data: &plaintext_data[0..32 * 10], + }, + &mut buf, + )?; + response.push(Advance::MacVerifyUpdate1 as u8).ok(); + se050.run_command( + &MacUpdate { + mac_id, + data: &plaintext_data[32 * 10..][..32 * 5], + }, + &mut buf, + )?; + response.push(Advance::MacVerifyUpdate2 as u8).ok(); + let res2 = se050.run_command( + &MacValidateFinal { + mac_id, + data: &plaintext_data[32 * 15..], + tag: tag2.tag, + }, + &mut buf, + )?; + if res2.result != Se050Result::Success { + return Err(0x6009.into()); + } + response.push(Advance::MacVerifyFinal as u8).ok(); + + se050.run_command(&DeleteCryptoObj { id: mac_id }, &mut buf)?; + response.push(Advance::MacVerifyDelete as u8).ok(); + + se050.run_command(&DeleteSecureObject { object_id: key_id }, &mut buf2)?; + response.push(Advance::MacDelete as u8).ok(); + Ok(()) +} From c784b0c4758f1e522f7f51933c766495e9b2ae2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Fri, 28 Jul 2023 16:43:16 +0200 Subject: [PATCH 11/65] Add SE050 free memory reporting --- src/admin/run_tests.rs | 61 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/src/admin/run_tests.rs b/src/admin/run_tests.rs index 7c4ae93..8fbace0 100644 --- a/src/admin/run_tests.rs +++ b/src/admin/run_tests.rs @@ -163,6 +163,7 @@ impl> RunTests for Se050 { atr.secure_box_minor, ]) .ok(); + run_free_mem(self, response)?; response.push(Advance::Enable as _).ok(); run_get_random(self, response)?; run_factory_reset(self, response)?; @@ -178,6 +179,43 @@ impl> RunTests for Se050 { Ok(()) } } +#[cfg(feature = "se050")] +fn run_free_mem, const N: usize>( + se050: &mut Se050, + response: &mut Vec, +) -> Result<(), Status> { + use se050::se050::{commands::GetFreeMemory, Memory}; + + let mut buf = [b'a'; BUFFER_LEN]; + let mem = se050.run_command( + &GetFreeMemory { + memory: Memory::Persistent, + }, + &mut buf, + )?; + response + .extend_from_slice(&mem.available.0.to_be_bytes()) + .ok(); + let mem = se050.run_command( + &GetFreeMemory { + memory: Memory::TransientReset, + }, + &mut buf, + )?; + response + .extend_from_slice(&mem.available.0.to_be_bytes()) + .ok(); + let mem = se050.run_command( + &GetFreeMemory { + memory: Memory::TransientDeselect, + }, + &mut buf, + )?; + response + .extend_from_slice(&mem.available.0.to_be_bytes()) + .ok(); + Ok(()) +} #[cfg(feature = "se050")] fn run_get_random, const N: usize>( @@ -447,9 +485,7 @@ fn run_userid_recreation, const N: usize>( let user_id_bad_value = hex!("FFFFFFFF"); let policy_user_id = &[Policy { object_id: ObjectId::INVALID, - access_rule: ObjectAccessRule::from_flags( - ObjectPolicyFlags::ALLOW_DELETE | ObjectPolicyFlags::ALLOW_WRITE, - ), + access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_DELETE), }]; se050.run_command( &WriteUserId { @@ -479,7 +515,15 @@ fn run_userid_recreation, const N: usize>( &mut buf, )?; response.push(Advance::RecreationWriteBinary as u8).ok(); - match se050.run_command(&DeleteSecureObject { object_id }, &mut buf) { + match se050.run_command( + &ReadObject { + object_id, + offset: Some(0.into()), + length: Some(2.into()), + rsa_key_component: None, + }, + &mut buf, + ) { Ok(_) => return Err(0x3004.into()), Err(se050::se050::Error::Status(Status::CommandNotAllowedNoEf)) => {} Err(_err) => { @@ -518,13 +562,18 @@ fn run_userid_recreation, const N: usize>( let attack = se050.run_command( &ProcessSessionCmd { session_id: session.session_id, - apdu: DeleteSecureObject { object_id: user_id }, + apdu: ReadObject { + object_id, + offset: Some(0.into()), + length: Some(2.into()), + rsa_key_component: None, + }, }, &mut buf, ); match attack { - Ok(_) => return Err(0x3005.into()), + Ok(_) => {} Err(se050::se050::Error::Status(Status::CommandNotAllowedNoEf)) => {} Err(_err) => { debug_now!("Got unexpected error: {_err:?}"); From a44adb0a3390af2a76784ec06a094854a42b9e93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Fri, 11 Aug 2023 17:08:26 +0200 Subject: [PATCH 12/65] Add PBKDF2 tests --- Cargo.toml | 6 +- src/admin/run_tests.rs | 219 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 223 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e71f0dc..42b502d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,9 +19,11 @@ trussed = "0.1" se050 = { version = "0.0.1", optional = true } embedded-hal = { version = "0.2.7", optional = true } hex-literal = "0.4.1" +rand_chacha = { version = "0.3.1", optional = true, default-features = false } [features] -se050 = ["dep:se050", "embedded-hal"] +default = ["se050"] +se050 = ["dep:se050", "embedded-hal", "rand_chacha"] log-all = [] log-none = [] @@ -34,4 +36,4 @@ log-error = [] ctaphid-dispatch = { git = "https://github.com/trussed-dev/ctaphid-dispatch.git", rev = "57cb3317878a8593847595319aa03ef17c29ec5b" } trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "51e68500d7601d04f884f5e95567d14b9018a6cb" } iso7816 = { git = "https://github.com/sosthene-nitrokey/iso7816.git", rev = "160ca3bbd8e21ec4e4ee1e0748e1eaa53a45c97f"} -se050 = { git = "https://github.com/sosthene-nitrokey/se050-generation.git", rev = "b645ea9de6c53c18a2173ec8d4d1307580c405c3"} +se050 = { git = "https://github.com/sosthene-nitrokey/se050-generation.git", rev = "b4dc057ed7b3e1dda88a8c1314f77048df961b10"} diff --git a/src/admin/run_tests.rs b/src/admin/run_tests.rs index 8fbace0..e7f6891 100644 --- a/src/admin/run_tests.rs +++ b/src/admin/run_tests.rs @@ -131,6 +131,22 @@ enum Advance { MacVerifyFinal, MacVerifyDelete, MacDelete, + AesSessionCreateKey, + AesSessionCreateBinary, + AesSessionCreateSession, + AesSessionAuthenticate, + AesSessionReadBinary, + AesSessionUpdateKey, + AesSessionCloseSession, + AesSessionRecreateSession, + AesSessionReAuthenticate, + AesSessionReadBinary2, + AesSessionDeleteBinary, + AesSessionDeleteKey, + Pbkdf2WritePin, + Pbkdf2Calculate, + Pbkdf2Compare, + Pbkdf2DeletePin, } impl RunTests for () {} @@ -176,6 +192,8 @@ impl> RunTests for Se050 { run_rsa4096(self, response)?; run_symm(self, response)?; run_mac(self, response)?; + run_aes_session(self, response)?; + run_pbkdf(self, response)?; Ok(()) } } @@ -1086,3 +1104,204 @@ fn run_mac, const N: usize>( response.push(Advance::MacDelete as u8).ok(); Ok(()) } + +#[cfg(feature = "se050")] +fn run_aes_session, const N: usize>( + se050: &mut Se050, + response: &mut Vec, +) -> Result<(), Status> { + use rand_chacha::rand_core::SeedableRng; + use se050::se050::{ + commands::{CloseSession, WriteSymmKey}, + SymmKeyType, + }; + + let mut buf = [0; 1024]; + let key = [0x42; 16]; + let key2 = [0x43; 16]; + let key_id = ObjectId(hex!("03445566")); + let bin_id = ObjectId(hex!("03445567")); + let bin_data = hex!("CAFECAFE"); + let key_policy = &[ + Policy { + object_id: key_id, + access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_WRITE), + }, + Policy { + object_id: ObjectId::INVALID, + access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_DELETE), + }, + ]; + let bin_policy = &[ + Policy { + object_id: key_id, + access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_READ), + }, + Policy { + object_id: ObjectId::INVALID, + access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_DELETE), + }, + ]; + se050.run_command( + &WriteSymmKey { + transient: false, + is_auth: true, + key_type: SymmKeyType::Aes, + policy: Some(PolicySet(key_policy)), + max_attempts: None, + object_id: key_id, + kek_id: None, + value: &key, + }, + &mut buf, + )?; + response.push(Advance::AesSessionCreateKey as _).ok(); + se050.run_command( + &WriteBinary { + transient: false, + policy: Some(PolicySet(bin_policy)), + object_id: bin_id, + offset: None, + file_length: Some((bin_data.len() as u16).into()), + data: Some(&bin_data), + }, + &mut buf, + )?; + response.push(Advance::AesSessionCreateBinary as _).ok(); + + let session = se050.run_command(&CreateSession { object_id: key_id }, &mut buf)?; + let session_id = session.session_id; + response.push(Advance::AesSessionCreateSession as u8).ok(); + debug_now!("Created session"); + + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0xCA; 32]); + se050.authenticate_aes128_session(session_id, &key, &mut rng)?; + response.push(Advance::AesSessionAuthenticate as u8).ok(); + + let data = se050.run_command( + &ProcessSessionCmd { + session_id, + apdu: ReadObject { + object_id: bin_id, + offset: None, + length: Some((bin_data.len() as u16).into()), + rsa_key_component: None, + }, + }, + &mut buf, + )?; + assert_eq!(data.data, &bin_data); + response.push(Advance::AesSessionReadBinary as _).ok(); + + se050.run_command( + &ProcessSessionCmd { + session_id, + apdu: WriteSymmKey { + transient: false, + is_auth: true, + key_type: SymmKeyType::Aes, + policy: None, + max_attempts: None, + object_id: key_id, + kek_id: None, + value: &key2, + }, + }, + &mut buf, + )?; + response.push(Advance::AesSessionUpdateKey as _).ok(); + + se050.run_command( + &ProcessSessionCmd { + session_id, + apdu: CloseSession {}, + }, + &mut buf, + )?; + response.push(Advance::AesSessionCloseSession as _).ok(); + + let session = se050.run_command(&CreateSession { object_id: key_id }, &mut buf)?; + let session_id = session.session_id; + response.push(Advance::AesSessionRecreateSession as u8).ok(); + debug_now!("Created session"); + + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0xCA; 32]); + se050.authenticate_aes128_session(session_id, &key2, &mut rng)?; + response.push(Advance::AesSessionReAuthenticate as u8).ok(); + + let data = se050.run_command( + &ProcessSessionCmd { + session_id, + apdu: ReadObject { + object_id: bin_id, + offset: None, + length: Some((bin_data.len() as u16).into()), + rsa_key_component: None, + }, + }, + &mut buf, + )?; + assert_eq!(data.data, &bin_data); + response.push(Advance::AesSessionReadBinary2 as _).ok(); + + se050.run_command(&DeleteSecureObject { object_id: bin_id }, &mut buf)?; + response.push(Advance::AesSessionDeleteBinary as _).ok(); + se050.run_command(&DeleteSecureObject { object_id: key_id }, &mut buf)?; + response.push(Advance::AesSessionDeleteKey as _).ok(); + + Ok(()) +} + +#[cfg(feature = "se050")] +fn run_pbkdf, const N: usize>( + se050: &mut Se050, + response: &mut Vec, +) -> Result<(), Status> { + use se050::se050::{ + commands::{Pbkdf2, WriteSymmKey}, + SymmKeyType, + }; + + let mut buf = [0; 1024]; + let pin = b"123456"; + let salt = [0x42; 16]; + let pin_id = ObjectId(hex!("03445566")); + + se050.run_command( + &WriteSymmKey { + transient: true, + is_auth: false, + key_type: SymmKeyType::Hmac, + policy: None, + max_attempts: None, + object_id: pin_id, + kek_id: None, + value: pin, + }, + &mut buf, + )?; + + response.push(Advance::Pbkdf2WritePin as u8).ok(); + + let res = se050.run_command( + &Pbkdf2 { + password: pin_id, + salt: Some(&salt), + iterations: 32.into(), + requested_len: 16.into(), + }, + &mut buf, + )?; + response.push(Advance::Pbkdf2Calculate as u8).ok(); + + if res.data != hex!("685126241d909137ecd3385eaea2725f") { + debug_now!("Got HASH: {:02x?}", res.data); + return Err(Status::CorruptedData); + } + response.push(Advance::Pbkdf2Compare as u8).ok(); + + se050.run_command(&DeleteSecureObject { object_id: pin_id }, &mut buf)?; + response.push(Advance::Pbkdf2DeletePin as u8).ok(); + + Ok(()) +} From 8ab34bf89cdcfcc25f4d2b463de225de28eafc8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Wed, 16 Aug 2023 10:19:27 +0200 Subject: [PATCH 13/65] Add import-export tests --- Cargo.toml | 2 +- src/admin/run_tests.rs | 139 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 42b502d..11dbdf4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,4 +36,4 @@ log-error = [] ctaphid-dispatch = { git = "https://github.com/trussed-dev/ctaphid-dispatch.git", rev = "57cb3317878a8593847595319aa03ef17c29ec5b" } trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "51e68500d7601d04f884f5e95567d14b9018a6cb" } iso7816 = { git = "https://github.com/sosthene-nitrokey/iso7816.git", rev = "160ca3bbd8e21ec4e4ee1e0748e1eaa53a45c97f"} -se050 = { git = "https://github.com/sosthene-nitrokey/se050-generation.git", rev = "b4dc057ed7b3e1dda88a8c1314f77048df961b10"} +se050 = { git = "https://github.com/sosthene-nitrokey/se050-generation.git", rev = "b1c9479698ae8afe36d6158a91f988d212d8aaa0"} diff --git a/src/admin/run_tests.rs b/src/admin/run_tests.rs index e7f6891..f4245ba 100644 --- a/src/admin/run_tests.rs +++ b/src/admin/run_tests.rs @@ -147,6 +147,15 @@ enum Advance { Pbkdf2Calculate, Pbkdf2Compare, Pbkdf2DeletePin, + ImportWrite, + ImportCipher, + ImportExport, + ImportDelete, + ImportDeletionWorked, + ImportImport, + ImportCipher2, + ImportComp, + ImportDeleteFinal, } impl RunTests for () {} @@ -194,6 +203,7 @@ impl> RunTests for Se050 { run_mac(self, response)?; run_aes_session(self, response)?; run_pbkdf(self, response)?; + run_export_import(self, response)?; Ok(()) } } @@ -1305,3 +1315,132 @@ fn run_pbkdf, const N: usize>( Ok(()) } + +#[cfg(feature = "se050")] +fn run_export_import, const N: usize>( + se050: &mut Se050, + response: &mut Vec, +) -> Result<(), Status> { + use se050::se050::{ + commands::{CipherOneShotEncrypt, ExportObject, ImportObject, WriteSymmKey}, + CipherMode, RsaKeyComponent, SymmKeyType, + }; + + let mut buf = [0; 128]; + let mut buf2 = [0; 1000]; + let mut buf3 = [0; 1000]; + let plaintext_data = [2; 32]; + let key_id = ObjectId(hex!("03445566")); + let key = [0x42; 32]; + let iv = [0xFF; 16]; + let policy = &[ + Policy { + object_id: ObjectId::INVALID, + access_rule: ObjectAccessRule::from_flags( + ObjectPolicyFlags::ALLOW_WRITE + | ObjectPolicyFlags::ALLOW_ENC + | ObjectPolicyFlags::ALLOW_DELETE + | ObjectPolicyFlags::ALLOW_IMPORT_EXPORT, + ), + }, + Policy { + object_id: ObjectId::INVALID, + access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_DELETE), + }, + ]; + se050.run_command( + &WriteSymmKey { + transient: true, + is_auth: false, + key_type: SymmKeyType::Aes, + policy: Some(PolicySet(policy)), + max_attempts: None, + object_id: key_id, + kek_id: None, + value: &key, + }, + &mut buf, + )?; + response.push(Advance::ImportWrite as u8).ok(); + let ciphertext1 = se050.run_command( + &CipherOneShotEncrypt { + key_id, + mode: CipherMode::AesCtr, + plaintext: &plaintext_data, + initialization_vector: Some(&iv), + }, + &mut buf, + )?; + response.push(Advance::ImportCipher as u8).ok(); + + debug_now!("Exporting"); + let exported = se050 + .run_command( + &ExportObject { + object_id: key_id, + rsa_key_component: RsaKeyComponent::Na, + }, + &mut buf2, + ) + .map_err(|_err| { + debug_now!("Got err: {:?}", _err); + _err + })?; + response.push(Advance::ImportExport as u8).ok(); + + se050.enable()?; + response.push(Advance::ImportDelete as u8).ok(); + + let res = se050.run_command( + &CipherOneShotEncrypt { + key_id, + mode: CipherMode::AesCtr, + plaintext: &plaintext_data, + initialization_vector: Some(&iv), + }, + &mut buf3, + ); + if !matches!( + res, + Err(se050::se050::Error::Status( + Status::ConditionsOfUseNotSatisfied, + )) + ) { + return Err((0x3000 + line!() as u16).into()); + } + response.push(Advance::ImportDeletionWorked as u8).ok(); + + debug_now!("Importing"); + se050.run_command( + &ImportObject { + transient: true, + object_id: key_id, + rsa_key_component: None, + serialized_object: exported.data, + }, + &mut buf3, + )?; + response.push(Advance::ImportImport as u8).ok(); + + debug_now!("Encrypting"); + let ciphertext2 = se050.run_command( + &CipherOneShotEncrypt { + key_id, + mode: CipherMode::AesCtr, + plaintext: &plaintext_data, + initialization_vector: Some(&iv), + }, + &mut buf3, + )?; + response.push(Advance::ImportCipher2 as u8).ok(); + + debug_now!("Comparing"); + if ciphertext1.ciphertext != ciphertext2.ciphertext { + return Err((0x3000 + line!() as u16).into()); + } + response.push(Advance::ImportComp as u8).ok(); + + se050.run_command(&DeleteSecureObject { object_id: key_id }, &mut buf3)?; + response.push(Advance::ImportDeleteFinal as u8).ok(); + Ok(()) +} From 2ddb1ad5f74a973779a0c6535c9bb58979f0da1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Wed, 16 Aug 2023 16:22:16 +0200 Subject: [PATCH 14/65] Use renamed se05x crate --- Cargo.toml | 6 +- src/admin.rs | 24 ++++---- src/admin/run_tests.rs | 123 +++++++++++++++++++++++++---------------- 3 files changed, 90 insertions(+), 63 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 11dbdf4..693c7c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,14 +16,14 @@ delog = "0.1" iso7816 = "0.1" trussed = "0.1" -se050 = { version = "0.0.1", optional = true } +se05x = { version = "0.0.1", optional = true } embedded-hal = { version = "0.2.7", optional = true } hex-literal = "0.4.1" rand_chacha = { version = "0.3.1", optional = true, default-features = false } [features] default = ["se050"] -se050 = ["dep:se050", "embedded-hal", "rand_chacha"] +se050 = ["dep:se05x", "embedded-hal", "rand_chacha"] log-all = [] log-none = [] @@ -36,4 +36,4 @@ log-error = [] ctaphid-dispatch = { git = "https://github.com/trussed-dev/ctaphid-dispatch.git", rev = "57cb3317878a8593847595319aa03ef17c29ec5b" } trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "51e68500d7601d04f884f5e95567d14b9018a6cb" } iso7816 = { git = "https://github.com/sosthene-nitrokey/iso7816.git", rev = "160ca3bbd8e21ec4e4ee1e0748e1eaa53a45c97f"} -se050 = { git = "https://github.com/sosthene-nitrokey/se050-generation.git", rev = "b1c9479698ae8afe36d6158a91f988d212d8aaa0"} +se05x = { git = "https://github.com/Nitrokey/se05x.git", rev = "db1ddea25cc382355b4292352652da656abc3005"} diff --git a/src/admin.rs b/src/admin.rs index d46bb52..a6d819a 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -6,7 +6,7 @@ use ctaphid_dispatch::command::VendorCommand; #[cfg(feature = "se050")] use embedded_hal::blocking::delay::DelayUs; #[cfg(feature = "se050")] -use se050::{se050::Se050, t1::I2CForT1}; +use se05x::{se05x::Se05X, t1::I2CForT1}; use trussed::{interrupt::InterruptFlag, syscall, types::Vec, Client as TrussedClient}; pub const USER_PRESENCE_TIMEOUT_SECS: u32 = 15; @@ -35,12 +35,12 @@ use run_tests::*; /// Trait representing the possible ownership of the SE050 by the admin app. /// -/// Implemented by `()` and the `Se050` stract +/// Implemented by `()` and the `Se05X` stract pub trait MaybeSe: RunTests {} impl MaybeSe for () {} #[cfg(feature = "se050")] -impl> MaybeSe for Se050 {} +impl> MaybeSe for Se05X {} #[derive(PartialEq, Debug)] enum Command { @@ -52,7 +52,7 @@ enum Command { Locked, Wink, Status, - TestSe050, + TestSe05X, } impl TryFrom for Command { @@ -69,7 +69,7 @@ impl TryFrom for Command { // Now check the new commands. match command { STATUS => Ok(Command::Status), - TEST_SE050 => Ok(Command::TestSe050), + TEST_SE050 => Ok(Command::TestSe05X), _ => Err(Error::UnsupportedCommand), } } @@ -153,7 +153,7 @@ pub trait Reboot { fn locked() -> bool; } -pub struct App +pub struct App where T: TrussedClient, R: Reboot, @@ -165,7 +165,7 @@ where full_version: &'static str, status: S, boot_interface: PhantomData, - se050: Se050, + se050: Se05X, } impl App @@ -194,7 +194,7 @@ where } #[cfg(feature = "se050")] -impl App> +impl App> where T: TrussedClient, R: Reboot, @@ -208,7 +208,7 @@ where version: u32, full_version: &'static str, status: S, - se050: Se050, + se050: Se05X, ) -> Self { Self { trussed: client, @@ -222,12 +222,12 @@ where } } -impl App +impl App where T: TrussedClient, R: Reboot, S: AsRef<[u8]>, - Se050: MaybeSe, + Se05X: MaybeSe, { fn user_present(&mut self) -> bool { let user_present = syscall!(self @@ -287,7 +287,7 @@ where Command::Status => { response.extend_from_slice(self.status.as_ref()).ok(); } - Command::TestSe050 => { + Command::TestSe05X => { debug_now!("Running se050 tests"); if let Err(_err) = self.se050.run_tests(response) { debug_now!("se050 tests failed: {_err:?}"); diff --git a/src/admin/run_tests.rs b/src/admin/run_tests.rs index f4245ba..9c007e1 100644 --- a/src/admin/run_tests.rs +++ b/src/admin/run_tests.rs @@ -4,21 +4,21 @@ use ctaphid_dispatch::types::Error; use embedded_hal::blocking::delay::DelayUs; use iso7816::Status; #[cfg(feature = "se050")] -use se050::{ - se050::{ +use se05x::{ + se05x::{ commands::{ CreateSession, DeleteAll, DeleteSecureObject, EcdsaSign, EcdsaVerify, GetRandom, ReadIdList, ReadObject, VerifySessionUserId, WriteBinary, WriteEcKey, WriteUserId, }, policies::{ObjectAccessRule, ObjectPolicyFlags, Policy, PolicySet}, - EcCurve, EcDsaSignatureAlgo, ObjectId, P1KeyType, ProcessSessionCmd, Se050Result, + EcCurve, EcDsaSignatureAlgo, ObjectId, P1KeyType, ProcessSessionCmd, Se05XResult, }, t1::I2CForT1, }; use trussed::types::Vec; #[cfg(feature = "se050")] -use se050::se050::Se050; +use se05x::se05x::Se05X; #[cfg(feature = "se050")] use hex_literal::hex; @@ -161,9 +161,9 @@ enum Advance { impl RunTests for () {} #[cfg(feature = "se050")] -impl> RunTests for Se050 { +impl> RunTests for Se05X { fn run_tests(&mut self, response: &mut Vec) -> Result<(), Error> { - debug_now!("Se050 run tests"); + debug_now!("Se05X run tests"); match self.run_tests_internal(response) { Ok(()) => Ok(()), Err(err) => { @@ -209,10 +209,10 @@ impl> RunTests for Se050 { } #[cfg(feature = "se050")] fn run_free_mem, const N: usize>( - se050: &mut Se050, + se050: &mut Se05X, response: &mut Vec, ) -> Result<(), Status> { - use se050::se050::{commands::GetFreeMemory, Memory}; + use se05x::se05x::{commands::GetFreeMemory, Memory}; let mut buf = [b'a'; BUFFER_LEN]; let mem = se050.run_command( @@ -247,7 +247,7 @@ fn run_free_mem, const N: usize>( #[cfg(feature = "se050")] fn run_get_random, const N: usize>( - se050: &mut Se050, + se050: &mut Se05X, response: &mut Vec, ) -> Result<(), Status> { let mut buf = [b'a'; BUFFER_LEN]; @@ -272,7 +272,7 @@ fn run_get_random, const N: usize>( #[cfg(feature = "se050")] fn run_factory_reset, const N: usize>( - se050: &mut Se050, + se050: &mut Se05X, response: &mut Vec, ) -> Result<(), Status> { let mut buf = [b'a'; BUFFER_LEN]; @@ -318,14 +318,14 @@ fn run_factory_reset, const N: usize>( #[cfg(feature = "se050")] fn run_list, const N: usize>( - se050: &mut Se050, + se050: &mut Se05X, response: &mut Vec, ) -> Result<(), Status> { let mut buf = [0; 200]; se050.run_command( &ReadIdList { offset: 0.into(), - filter: se050::se050::SecureObjectFilter::All, + filter: se05x::se05x::SecureObjectFilter::All, }, &mut buf, )?; @@ -335,7 +335,7 @@ fn run_list, const N: usize>( #[cfg(feature = "se050")] fn run_binary, const N: usize>( - se050: &mut Se050, + se050: &mut Se05X, response: &mut Vec, ) -> Result<(), Status> { let mut buf = [b'a'; 400]; @@ -399,10 +399,10 @@ fn run_binary, const N: usize>( #[cfg(feature = "se050")] fn run_ecc, const N: usize>( - se050: &mut Se050, + se050: &mut Se05X, response: &mut Vec, ) -> Result<(), Status> { - use se050::se050::commands::ReadEcCurveList; + use se05x::se05x::commands::ReadEcCurveList; let mut buf = [0; 200]; let mut buf2 = [0; 200]; @@ -448,7 +448,7 @@ fn run_ecc, const N: usize>( }, &mut buf2, )?; - if res.result != Se050Result::Success { + if res.result != Se05XResult::Success { return Err(0x3002.into()); } response.push(Advance::VerifyP256 as u8).ok(); @@ -492,7 +492,7 @@ fn run_ecc, const N: usize>( }, &mut buf2, )?; - if res.result != Se050Result::Success { + if res.result != Se05XResult::Success { return Err(0x3003.into()); } response.push(Advance::VerifyP521 as u8).ok(); @@ -503,7 +503,7 @@ fn run_ecc, const N: usize>( #[cfg(feature = "se050")] fn run_userid_recreation, const N: usize>( - se050: &mut Se050, + se050: &mut Se05X, response: &mut Vec, ) -> Result<(), Status> { let mut buf = [0; BUFFER_LEN]; @@ -553,7 +553,7 @@ fn run_userid_recreation, const N: usize>( &mut buf, ) { Ok(_) => return Err(0x3004.into()), - Err(se050::se050::Error::Status(Status::CommandNotAllowedNoEf)) => {} + Err(se05x::se05x::Error::Status(Status::CommandNotAllowedNoEf)) => {} Err(_err) => { debug_now!("Got unexpected error: {_err:?}"); return Err(0x3007.into()); @@ -602,7 +602,7 @@ fn run_userid_recreation, const N: usize>( match attack { Ok(_) => {} - Err(se050::se050::Error::Status(Status::CommandNotAllowedNoEf)) => {} + Err(se05x::se05x::Error::Status(Status::CommandNotAllowedNoEf)) => {} Err(_err) => { debug_now!("Got unexpected error: {_err:?}"); return Err(0x3006.into()); @@ -614,10 +614,10 @@ fn run_userid_recreation, const N: usize>( #[cfg(feature = "se050")] fn run_rsa2048, const N: usize>( - se050: &mut Se050, + se050: &mut Se05X, response: &mut Vec, ) -> Result<(), Status> { - use se050::se050::{ + use se05x::se05x::{ commands::{GenRsaKey, RsaDecrypt, RsaEncrypt, RsaSign, RsaVerify}, RsaEncryptionAlgo, RsaSignatureAlgo, }; @@ -655,6 +655,9 @@ fn run_rsa2048, const N: usize>( }, &mut buf2, )?; + if res.result != Se05XResult::Success { + return Err((0x3000 + line!() as u16).into()); + } response.push(Advance::Rsa2048Verify as u8).ok(); let res = se050.run_command( &RsaEncrypt { @@ -673,7 +676,7 @@ fn run_rsa2048, const N: usize>( }, &mut buf, )?; - if res.plaintext != &[52; 32] { + if res.plaintext != [52; 32] { return Err(0x3008.into()); } response.push(Advance::Rsa2048Decrypt as u8).ok(); @@ -686,10 +689,10 @@ fn run_rsa2048, const N: usize>( #[cfg(feature = "se050")] fn run_rsa3072, const N: usize>( - se050: &mut Se050, + se050: &mut Se05X, response: &mut Vec, ) -> Result<(), Status> { - use se050::se050::{ + use se05x::se05x::{ commands::{GenRsaKey, RsaDecrypt, RsaEncrypt, RsaSign, RsaVerify}, RsaEncryptionAlgo, RsaSignatureAlgo, }; @@ -727,6 +730,9 @@ fn run_rsa3072, const N: usize>( }, &mut buf2, )?; + if res.result != Se05XResult::Success { + return Err((0x3000 + line!() as u16).into()); + } response.push(Advance::Rsa3072Verify as u8).ok(); let res = se050.run_command( &RsaEncrypt { @@ -745,7 +751,7 @@ fn run_rsa3072, const N: usize>( }, &mut buf, )?; - if res.plaintext != &[52; 32] { + if res.plaintext != [52; 32] { return Err(0x3008.into()); } response.push(Advance::Rsa3072Decrypt as u8).ok(); @@ -758,10 +764,10 @@ fn run_rsa3072, const N: usize>( #[cfg(feature = "se050")] fn run_rsa4096, const N: usize>( - se050: &mut Se050, + se050: &mut Se05X, response: &mut Vec, ) -> Result<(), Status> { - use se050::se050::{ + use se05x::se05x::{ commands::{GenRsaKey, RsaDecrypt, RsaEncrypt, RsaSign, RsaVerify}, RsaEncryptionAlgo, RsaSignatureAlgo, }; @@ -799,6 +805,9 @@ fn run_rsa4096, const N: usize>( }, &mut buf2, )?; + if res.result != Se05XResult::Success { + return Err((0x3000 + line!() as u16).into()); + } response.push(Advance::Rsa4096Verify as u8).ok(); let res = se050.run_command( &RsaEncrypt { @@ -817,7 +826,7 @@ fn run_rsa4096, const N: usize>( }, &mut buf, )?; - if res.plaintext != &[52; 32] { + if res.plaintext != [52; 32] { return Err(0x3008.into()); } response.push(Advance::Rsa4096Decrypt as u8).ok(); @@ -830,10 +839,10 @@ fn run_rsa4096, const N: usize>( #[cfg(feature = "se050")] fn run_symm, const N: usize>( - se050: &mut Se050, + se050: &mut Se05X, response: &mut Vec, ) -> Result<(), Status> { - use se050::se050::{ + use se05x::se05x::{ commands::{ CipherDecryptInit, CipherEncryptInit, CipherFinal, CipherOneShotDecrypt, CipherOneShotEncrypt, CipherUpdate, CreateCipherObject, DeleteCryptoObj, WriteSymmKey, @@ -876,7 +885,7 @@ fn run_symm, const N: usize>( &CipherOneShotDecrypt { key_id, mode: CipherMode::AesCtr, - ciphertext: &ciphertext1.ciphertext, + ciphertext: ciphertext1.ciphertext, initialization_vector: Some(&iv), }, &mut buf2, @@ -907,6 +916,9 @@ fn run_symm, const N: usize>( }, &mut buf2, )?; + if ciphertext2.data != &ciphertext1.ciphertext[0..32 * 10] { + return Err((0x3000 + line!() as u16).into()); + } response.push(Advance::SymmEncryptUpdate1 as u8).ok(); let ciphertext3 = se050.run_command( &CipherUpdate { @@ -915,6 +927,9 @@ fn run_symm, const N: usize>( }, &mut buf2, )?; + if ciphertext3.data != &ciphertext1.ciphertext[32 * 10..][..32 * 5] { + return Err((0x3000 + line!() as u16).into()); + } response.push(Advance::SymmEncryptUpdate2 as u8).ok(); let ciphertext4 = se050.run_command( &CipherFinal { @@ -923,6 +938,9 @@ fn run_symm, const N: usize>( }, &mut buf2, )?; + if ciphertext4.data != &ciphertext1.ciphertext[32 * 15..] { + return Err((0x3000 + line!() as u16).into()); + } response.push(Advance::SymmEncryptFinal as u8).ok(); se050.run_command(&DeleteCryptoObj { id: cipher_id }, &mut buf2)?; response.push(Advance::SymmEncryptDelete as u8).ok(); @@ -943,29 +961,38 @@ fn run_symm, const N: usize>( &mut buf2, )?; response.push(Advance::SymmDecryptInit as u8).ok(); - let ciphertext2 = se050.run_command( + let plaintext1 = se050.run_command( &CipherUpdate { cipher_id, data: &ciphertext1.ciphertext[0..32 * 10], }, &mut buf2, )?; + if plaintext1.data != &plaintext_data[..32 * 10] { + return Err((0x3000 + line!() as u16).into()); + } response.push(Advance::SymmDecryptUpdate1 as u8).ok(); - let ciphertext3 = se050.run_command( + let plaintext2 = se050.run_command( &CipherUpdate { cipher_id, data: &ciphertext1.ciphertext[32 * 10..][..32 * 5], }, &mut buf2, )?; + if plaintext2.data != &plaintext_data[32 * 10..][..32 * 5] { + return Err((0x3000 + line!() as u16).into()); + } response.push(Advance::SymmDecryptUpdate2 as u8).ok(); - let ciphertext4 = se050.run_command( + let plaintext3 = se050.run_command( &CipherFinal { cipher_id, data: &ciphertext1.ciphertext[32 * 15..], }, &mut buf2, )?; + if plaintext3.data != &plaintext_data[32 * 15..] { + return Err((0x3000 + line!() as u16).into()); + } response.push(Advance::SymmDecryptFinal as u8).ok(); se050.run_command(&DeleteCryptoObj { id: cipher_id }, &mut buf2)?; response.push(Advance::SymmDecryptDelete as u8).ok(); @@ -976,10 +1003,10 @@ fn run_symm, const N: usize>( #[cfg(feature = "se050")] fn run_mac, const N: usize>( - se050: &mut Se050, + se050: &mut Se05X, response: &mut Vec, ) -> Result<(), Status> { - use se050::se050::{ + use se05x::se05x::{ commands::{ CreateSignatureObject, DeleteCryptoObj, MacGenerateFinal, MacGenerateInit, MacOneShotGenerate, MacOneShotValidate, MacUpdate, MacValidateFinal, MacValidateInit, @@ -1027,8 +1054,8 @@ fn run_mac, const N: usize>( &mut buf2, )?; response.push(Advance::MacVerifyOneShot as u8).ok(); - if res.result != Se050Result::Success { - return Err(0x6008.into()); + if res.result != Se05XResult::Success { + return Err((0x3000 + line!() as u16).into()); } se050.run_command( &CreateSignatureObject { @@ -1102,8 +1129,8 @@ fn run_mac, const N: usize>( }, &mut buf, )?; - if res2.result != Se050Result::Success { - return Err(0x6009.into()); + if res2.result != Se05XResult::Success { + return Err((0x3000 + line!() as u16).into()); } response.push(Advance::MacVerifyFinal as u8).ok(); @@ -1117,11 +1144,11 @@ fn run_mac, const N: usize>( #[cfg(feature = "se050")] fn run_aes_session, const N: usize>( - se050: &mut Se050, + se050: &mut Se05X, response: &mut Vec, ) -> Result<(), Status> { use rand_chacha::rand_core::SeedableRng; - use se050::se050::{ + use se05x::se05x::{ commands::{CloseSession, WriteSymmKey}, SymmKeyType, }; @@ -1264,10 +1291,10 @@ fn run_aes_session, const N: usize>( #[cfg(feature = "se050")] fn run_pbkdf, const N: usize>( - se050: &mut Se050, + se050: &mut Se05X, response: &mut Vec, ) -> Result<(), Status> { - use se050::se050::{ + use se05x::se05x::{ commands::{Pbkdf2, WriteSymmKey}, SymmKeyType, }; @@ -1318,10 +1345,10 @@ fn run_pbkdf, const N: usize>( #[cfg(feature = "se050")] fn run_export_import, const N: usize>( - se050: &mut Se050, + se050: &mut Se05X, response: &mut Vec, ) -> Result<(), Status> { - use se050::se050::{ + use se05x::se05x::{ commands::{CipherOneShotEncrypt, ExportObject, ImportObject, WriteSymmKey}, CipherMode, RsaKeyComponent, SymmKeyType, }; @@ -1402,7 +1429,7 @@ fn run_export_import, const N: usize>( ); if !matches!( res, - Err(se050::se050::Error::Status( + Err(se05x::se05x::Error::Status( Status::ConditionsOfUseNotSatisfied, )) ) { From 9d60607a0cb1a665f00bf81b0c30eda57da02461 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Tue, 22 Aug 2023 14:30:47 +0200 Subject: [PATCH 15/65] Fix typo --- src/admin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/admin.rs b/src/admin.rs index a6d819a..3f762c4 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -35,7 +35,7 @@ use run_tests::*; /// Trait representing the possible ownership of the SE050 by the admin app. /// -/// Implemented by `()` and the `Se05X` stract +/// Implemented by `()` and the `Se05X` struct pub trait MaybeSe: RunTests {} impl MaybeSe for () {} From 210af595e3de6f5c79946befa90ff029100f493e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Wed, 23 Aug 2023 09:56:51 +0200 Subject: [PATCH 16/65] Move se050-specific features to its own module to reduce the need for cfgs --- src/admin/run_tests.rs | 2710 ++++++++++++++++++++-------------------- 1 file changed, 1347 insertions(+), 1363 deletions(-) diff --git a/src/admin/run_tests.rs b/src/admin/run_tests.rs index 9c007e1..86da0a5 100644 --- a/src/admin/run_tests.rs +++ b/src/admin/run_tests.rs @@ -1,28 +1,7 @@ use ctaphid_dispatch::types::Error; - -#[cfg(feature = "se050")] -use embedded_hal::blocking::delay::DelayUs; use iso7816::Status; -#[cfg(feature = "se050")] -use se05x::{ - se05x::{ - commands::{ - CreateSession, DeleteAll, DeleteSecureObject, EcdsaSign, EcdsaVerify, GetRandom, - ReadIdList, ReadObject, VerifySessionUserId, WriteBinary, WriteEcKey, WriteUserId, - }, - policies::{ObjectAccessRule, ObjectPolicyFlags, Policy, PolicySet}, - EcCurve, EcDsaSignatureAlgo, ObjectId, P1KeyType, ProcessSessionCmd, Se05XResult, - }, - t1::I2CForT1, -}; use trussed::types::Vec; -#[cfg(feature = "se050")] -use se05x::se05x::Se05X; - -#[cfg(feature = "se050")] -use hex_literal::hex; - pub trait RunTests { fn run_tests(&mut self, _response: &mut Vec) -> Result<(), Error> { debug_now!("Default run tests"); @@ -37,1437 +16,1442 @@ pub trait RunTests { } } -#[cfg(feature = "se050")] -const BUFFER_LEN: usize = 1024; +impl RunTests for () {} #[cfg(feature = "se050")] -#[derive(Debug)] -#[repr(u8)] -enum Advance { - Enable = 1, - Random1, - Random2, - Random3, - WriteUserId, - CreateSession, - VerifySessionUserId, - DeleteAll, - List, - WriteBinary1, - ReadBinary1, - DeleteBinary1, - WriteBinary2, - ReadBinary2, - DeleteBinary2, - WriteBinary3, - ReadBinary3, - DeleteBinary3, - CreateP256, - ListP256, - GenerateP256, - EcDsaP256, - VerifyP256, - DeleteP256, - CreateP521, - GenerateP521, - EcDsaP521, - VerifyP521, - DeleteP521, - RecreationWriteUserId, - RecreationWriteBinary, - RecreationDeleteAttempt, - RecreationDeleteUserId, - RecreationRecreateUserId, - RecreationCreateSession, - RecreationAuthSession, - RecreationDeleteAttack, - Rsa2048Gen, - Rsa2048Sign, - Rsa2048Verify, - Rsa2048Encrypt, - Rsa2048Decrypt, - Rsa2048Delete, - Rsa3072Gen, - Rsa3072Sign, - Rsa3072Verify, - Rsa3072Encrypt, - Rsa3072Decrypt, - Rsa3072Delete, - Rsa4096Gen, - Rsa4096Sign, - Rsa4096Verify, - Rsa4096Encrypt, - Rsa4096Decrypt, - Rsa4096Delete, - SymmWrite, - SymmEncryptOneShot, - SymmDecryptOneShot, - SymmEncryptCreate, - SymmEncryptInit, - SymmEncryptUpdate1, - SymmEncryptUpdate2, - SymmEncryptFinal, - SymmEncryptDelete, - SymmDecryptCreate, - SymmDecryptInit, - SymmDecryptUpdate1, - SymmDecryptUpdate2, - SymmDecryptFinal, - SymmDecryptDelete, - SymmDelete, - MacWrite, - MacSignOneShot, - MacVerifyOneShot, - MacSignCreate, - MacSignInit, - MacSignUpdate1, - MacSignUpdate2, - MacSignFinal, - MacSignDelete, - MacVerifyCreate, - MacVerifyInit, - MacVerifyUpdate1, - MacVerifyUpdate2, - MacVerifyFinal, - MacVerifyDelete, - MacDelete, - AesSessionCreateKey, - AesSessionCreateBinary, - AesSessionCreateSession, - AesSessionAuthenticate, - AesSessionReadBinary, - AesSessionUpdateKey, - AesSessionCloseSession, - AesSessionRecreateSession, - AesSessionReAuthenticate, - AesSessionReadBinary2, - AesSessionDeleteBinary, - AesSessionDeleteKey, - Pbkdf2WritePin, - Pbkdf2Calculate, - Pbkdf2Compare, - Pbkdf2DeletePin, - ImportWrite, - ImportCipher, - ImportExport, - ImportDelete, - ImportDeletionWorked, - ImportImport, - ImportCipher2, - ImportComp, - ImportDeleteFinal, -} +mod se050_tests { + use super::*; + + use embedded_hal::blocking::delay::DelayUs; + use se05x::{ + se05x::{ + commands::{ + CreateSession, DeleteAll, DeleteSecureObject, EcdsaSign, EcdsaVerify, GetRandom, + ReadIdList, ReadObject, VerifySessionUserId, WriteBinary, WriteEcKey, WriteUserId, + }, + policies::{ObjectAccessRule, ObjectPolicyFlags, Policy, PolicySet}, + EcCurve, EcDsaSignatureAlgo, ObjectId, P1KeyType, ProcessSessionCmd, Se05XResult, + }, + t1::I2CForT1, + }; -impl RunTests for () {} + use se05x::se05x::Se05X; + + use hex_literal::hex; + + const BUFFER_LEN: usize = 1024; + + #[derive(Debug)] + #[repr(u8)] + enum Advance { + Enable = 1, + Random1, + Random2, + Random3, + WriteUserId, + CreateSession, + VerifySessionUserId, + DeleteAll, + List, + WriteBinary1, + ReadBinary1, + DeleteBinary1, + WriteBinary2, + ReadBinary2, + DeleteBinary2, + WriteBinary3, + ReadBinary3, + DeleteBinary3, + CreateP256, + ListP256, + GenerateP256, + EcDsaP256, + VerifyP256, + DeleteP256, + CreateP521, + GenerateP521, + EcDsaP521, + VerifyP521, + DeleteP521, + RecreationWriteUserId, + RecreationWriteBinary, + RecreationDeleteAttempt, + RecreationDeleteUserId, + RecreationRecreateUserId, + RecreationCreateSession, + RecreationAuthSession, + RecreationDeleteAttack, + Rsa2048Gen, + Rsa2048Sign, + Rsa2048Verify, + Rsa2048Encrypt, + Rsa2048Decrypt, + Rsa2048Delete, + Rsa3072Gen, + Rsa3072Sign, + Rsa3072Verify, + Rsa3072Encrypt, + Rsa3072Decrypt, + Rsa3072Delete, + Rsa4096Gen, + Rsa4096Sign, + Rsa4096Verify, + Rsa4096Encrypt, + Rsa4096Decrypt, + Rsa4096Delete, + SymmWrite, + SymmEncryptOneShot, + SymmDecryptOneShot, + SymmEncryptCreate, + SymmEncryptInit, + SymmEncryptUpdate1, + SymmEncryptUpdate2, + SymmEncryptFinal, + SymmEncryptDelete, + SymmDecryptCreate, + SymmDecryptInit, + SymmDecryptUpdate1, + SymmDecryptUpdate2, + SymmDecryptFinal, + SymmDecryptDelete, + SymmDelete, + MacWrite, + MacSignOneShot, + MacVerifyOneShot, + MacSignCreate, + MacSignInit, + MacSignUpdate1, + MacSignUpdate2, + MacSignFinal, + MacSignDelete, + MacVerifyCreate, + MacVerifyInit, + MacVerifyUpdate1, + MacVerifyUpdate2, + MacVerifyFinal, + MacVerifyDelete, + MacDelete, + AesSessionCreateKey, + AesSessionCreateBinary, + AesSessionCreateSession, + AesSessionAuthenticate, + AesSessionReadBinary, + AesSessionUpdateKey, + AesSessionCloseSession, + AesSessionRecreateSession, + AesSessionReAuthenticate, + AesSessionReadBinary2, + AesSessionDeleteBinary, + AesSessionDeleteKey, + Pbkdf2WritePin, + Pbkdf2Calculate, + Pbkdf2Compare, + Pbkdf2DeletePin, + ImportWrite, + ImportCipher, + ImportExport, + ImportDelete, + ImportDeletionWorked, + ImportImport, + ImportCipher2, + ImportComp, + ImportDeleteFinal, + } -#[cfg(feature = "se050")] -impl> RunTests for Se05X { - fn run_tests(&mut self, response: &mut Vec) -> Result<(), Error> { - debug_now!("Se05X run tests"); - match self.run_tests_internal(response) { - Ok(()) => Ok(()), - Err(err) => { - response.push(0).ok(); - let sw: [u8; 2] = err.into(); - response.extend_from_slice(&sw).ok(); - Ok(()) + impl> RunTests for Se05X { + fn run_tests(&mut self, response: &mut Vec) -> Result<(), Error> { + debug_now!("Se05X run tests"); + match self.run_tests_internal(response) { + Ok(()) => Ok(()), + Err(err) => { + response.push(0).ok(); + let sw: [u8; 2] = err.into(); + response.extend_from_slice(&sw).ok(); + Ok(()) + } } } + fn run_tests_internal( + &mut self, + response: &mut Vec, + ) -> Result<(), Status> { + let atr = self.enable()?; + response + .extend_from_slice(&[ + atr.major, + atr.minor, + atr.patch, + atr.secure_box_major, + atr.secure_box_minor, + ]) + .ok(); + run_free_mem(self, response)?; + response.push(Advance::Enable as _).ok(); + run_get_random(self, response)?; + run_factory_reset(self, response)?; + run_list(self, response)?; + run_binary(self, response)?; + run_ecc(self, response)?; + run_userid_recreation(self, response)?; + run_rsa2048(self, response)?; + run_rsa3072(self, response)?; + run_rsa4096(self, response)?; + run_symm(self, response)?; + run_mac(self, response)?; + run_aes_session(self, response)?; + run_pbkdf(self, response)?; + run_export_import(self, response)?; + Ok(()) + } } - fn run_tests_internal( - &mut self, + fn run_free_mem, const N: usize>( + se050: &mut Se05X, response: &mut Vec, ) -> Result<(), Status> { - let atr = self.enable()?; + use se05x::se05x::{commands::GetFreeMemory, Memory}; + + let mut buf = [b'a'; BUFFER_LEN]; + let mem = se050.run_command( + &GetFreeMemory { + memory: Memory::Persistent, + }, + &mut buf, + )?; response - .extend_from_slice(&[ - atr.major, - atr.minor, - atr.patch, - atr.secure_box_major, - atr.secure_box_minor, - ]) + .extend_from_slice(&mem.available.0.to_be_bytes()) + .ok(); + let mem = se050.run_command( + &GetFreeMemory { + memory: Memory::TransientReset, + }, + &mut buf, + )?; + response + .extend_from_slice(&mem.available.0.to_be_bytes()) + .ok(); + let mem = se050.run_command( + &GetFreeMemory { + memory: Memory::TransientDeselect, + }, + &mut buf, + )?; + response + .extend_from_slice(&mem.available.0.to_be_bytes()) .ok(); - run_free_mem(self, response)?; - response.push(Advance::Enable as _).ok(); - run_get_random(self, response)?; - run_factory_reset(self, response)?; - run_list(self, response)?; - run_binary(self, response)?; - run_ecc(self, response)?; - run_userid_recreation(self, response)?; - run_rsa2048(self, response)?; - run_rsa3072(self, response)?; - run_rsa4096(self, response)?; - run_symm(self, response)?; - run_mac(self, response)?; - run_aes_session(self, response)?; - run_pbkdf(self, response)?; - run_export_import(self, response)?; Ok(()) } -} -#[cfg(feature = "se050")] -fn run_free_mem, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, -) -> Result<(), Status> { - use se05x::se05x::{commands::GetFreeMemory, Memory}; - - let mut buf = [b'a'; BUFFER_LEN]; - let mem = se050.run_command( - &GetFreeMemory { - memory: Memory::Persistent, - }, - &mut buf, - )?; - response - .extend_from_slice(&mem.available.0.to_be_bytes()) - .ok(); - let mem = se050.run_command( - &GetFreeMemory { - memory: Memory::TransientReset, - }, - &mut buf, - )?; - response - .extend_from_slice(&mem.available.0.to_be_bytes()) - .ok(); - let mem = se050.run_command( - &GetFreeMemory { - memory: Memory::TransientDeselect, - }, - &mut buf, - )?; - response - .extend_from_slice(&mem.available.0.to_be_bytes()) - .ok(); - Ok(()) -} -#[cfg(feature = "se050")] -fn run_get_random, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, -) -> Result<(), Status> { - let mut buf = [b'a'; BUFFER_LEN]; - let lens = [1, 256, 800]; - let advance = [Advance::Random1, Advance::Random2, Advance::Random3]; - for (len, advance) in lens.into_iter().zip(advance) { - let res = se050.run_command( - &GetRandom { - length: (len as u16).into(), + fn run_get_random, const N: usize>( + se050: &mut Se05X, + response: &mut Vec, + ) -> Result<(), Status> { + let mut buf = [b'a'; BUFFER_LEN]; + let lens = [1, 256, 800]; + let advance = [Advance::Random1, Advance::Random2, Advance::Random3]; + for (len, advance) in lens.into_iter().zip(advance) { + let res = se050.run_command( + &GetRandom { + length: (len as u16).into(), + }, + &mut buf, + )?; + response.push(advance as u8).ok(); + if res.data == &[b'a'; BUFFER_LEN][..len] { + debug!("Failed to get random"); + response.extend_from_slice(&[0, 0, 0]).ok(); + return Ok(()); + } + } + Ok(()) + } + + fn run_factory_reset, const N: usize>( + se050: &mut Se05X, + response: &mut Vec, + ) -> Result<(), Status> { + let mut buf = [b'a'; BUFFER_LEN]; + let data = &hex!("31323334"); + + se050.run_command( + &WriteUserId { + policy: None, + max_attempts: None, + object_id: ObjectId::FACTORY_RESET, + data, + }, + &mut buf, + )?; + response.push(Advance::WriteUserId as u8).ok(); + let session = se050.run_command( + &CreateSession { + object_id: ObjectId::FACTORY_RESET, + }, + &mut buf, + )?; + response.push(Advance::CreateSession as u8).ok(); + + se050.run_command( + &ProcessSessionCmd { + session_id: session.session_id, + apdu: VerifySessionUserId { user_id: data }, }, &mut buf, )?; - response.push(advance as u8).ok(); - if res.data == &[b'a'; BUFFER_LEN][..len] { - debug!("Failed to get random"); - response.extend_from_slice(&[0, 0, 0]).ok(); - return Ok(()); + response.push(Advance::VerifySessionUserId as u8).ok(); + + se050.run_command( + &ProcessSessionCmd { + session_id: session.session_id, + apdu: DeleteAll {}, + }, + &mut buf, + )?; + response.push(Advance::DeleteAll as u8).ok(); + Ok(()) + } + + fn run_list, const N: usize>( + se050: &mut Se05X, + response: &mut Vec, + ) -> Result<(), Status> { + let mut buf = [0; 200]; + se050.run_command( + &ReadIdList { + offset: 0.into(), + filter: se05x::se05x::SecureObjectFilter::All, + }, + &mut buf, + )?; + response.push(Advance::List as u8).ok(); + Ok(()) + } + + fn run_binary, const N: usize>( + se050: &mut Se05X, + response: &mut Vec, + ) -> Result<(), Status> { + let mut buf = [b'a'; 400]; + let buf2 = [b'b'; 400]; + let object_id = ObjectId(hex!("01020304")); + let policy = &[Policy { + object_id: ObjectId::INVALID, + access_rule: ObjectAccessRule::from_flags( + ObjectPolicyFlags::ALLOW_DELETE | ObjectPolicyFlags::ALLOW_READ, + ), + }]; + for (((len, advance_write), advance_read), advance_delete) in [1, 255, 300] + .into_iter() + .zip([ + Advance::WriteBinary1, + Advance::WriteBinary2, + Advance::WriteBinary3, + ]) + .zip([ + Advance::ReadBinary1, + Advance::ReadBinary2, + Advance::ReadBinary3, + ]) + .zip([ + Advance::DeleteBinary1, + Advance::DeleteBinary2, + Advance::DeleteBinary3, + ]) + { + se050.run_command( + &WriteBinary { + transient: false, + policy: Some(PolicySet(policy)), + object_id, + offset: None, + file_length: Some(len.into()), + data: Some(&buf2[..len.into()]), + }, + &mut buf, + )?; + response.push(advance_write as u8).ok(); + let res = se050.run_command( + &ReadObject { + object_id, + offset: None, + length: Some(len.into()), + rsa_key_component: None, + }, + &mut buf, + )?; + response.push(advance_read as u8).ok(); + if res.data[..len.into()] != buf2[..len.into()] { + return Err(0x3001.into()); + } + + se050.run_command(&DeleteSecureObject { object_id }, &mut buf)?; + response.push(advance_delete as u8).ok(); } + Ok(()) } - Ok(()) -} -#[cfg(feature = "se050")] -fn run_factory_reset, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, -) -> Result<(), Status> { - let mut buf = [b'a'; BUFFER_LEN]; - let data = &hex!("31323334"); - - se050.run_command( - &WriteUserId { - policy: None, - max_attempts: None, - object_id: ObjectId::FACTORY_RESET, - data, - }, - &mut buf, - )?; - response.push(Advance::WriteUserId as u8).ok(); - let session = se050.run_command( - &CreateSession { - object_id: ObjectId::FACTORY_RESET, - }, - &mut buf, - )?; - response.push(Advance::CreateSession as u8).ok(); - - se050.run_command( - &ProcessSessionCmd { - session_id: session.session_id, - apdu: VerifySessionUserId { user_id: data }, - }, - &mut buf, - )?; - response.push(Advance::VerifySessionUserId as u8).ok(); - - se050.run_command( - &ProcessSessionCmd { - session_id: session.session_id, - apdu: DeleteAll {}, - }, - &mut buf, - )?; - response.push(Advance::DeleteAll as u8).ok(); - Ok(()) -} + fn run_ecc, const N: usize>( + se050: &mut Se05X, + response: &mut Vec, + ) -> Result<(), Status> { + use se05x::se05x::commands::ReadEcCurveList; -#[cfg(feature = "se050")] -fn run_list, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, -) -> Result<(), Status> { - let mut buf = [0; 200]; - se050.run_command( - &ReadIdList { - offset: 0.into(), - filter: se05x::se05x::SecureObjectFilter::All, - }, - &mut buf, - )?; - response.push(Advance::List as u8).ok(); - Ok(()) -} + let mut buf = [0; 200]; + let mut buf2 = [0; 200]; + let object_id = ObjectId(hex!("01020304")); -#[cfg(feature = "se050")] -fn run_binary, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, -) -> Result<(), Status> { - let mut buf = [b'a'; 400]; - let buf2 = [b'b'; 400]; - let object_id = ObjectId(hex!("01020304")); - let policy = &[Policy { - object_id: ObjectId::INVALID, - access_rule: ObjectAccessRule::from_flags( - ObjectPolicyFlags::ALLOW_DELETE | ObjectPolicyFlags::ALLOW_READ, - ), - }]; - for (((len, advance_write), advance_read), advance_delete) in [1, 255, 300] - .into_iter() - .zip([ - Advance::WriteBinary1, - Advance::WriteBinary2, - Advance::WriteBinary3, - ]) - .zip([ - Advance::ReadBinary1, - Advance::ReadBinary2, - Advance::ReadBinary3, - ]) - .zip([ - Advance::DeleteBinary1, - Advance::DeleteBinary2, - Advance::DeleteBinary3, - ]) - { + // *********** P256 *********** // + + se050.create_and_set_curve(EcCurve::NistP256)?; + response.push(Advance::CreateP256 as u8).ok(); + let _res = se050.run_command(&ReadEcCurveList {}, &mut buf)?; + debug_now!("Ec curves list: {:?}", _res); + response.push(Advance::ListP256 as u8).ok(); se050.run_command( - &WriteBinary { + &WriteEcKey { transient: false, - policy: Some(PolicySet(policy)), + is_auth: false, + key_type: Some(P1KeyType::KeyPair), + policy: None, + max_attempts: None, object_id, - offset: None, - file_length: Some(len.into()), - data: Some(&buf2[..len.into()]), + curve: Some(EcCurve::NistP256), + private_key: None, + public_key: None, }, &mut buf, )?; - response.push(advance_write as u8).ok(); + response.push(Advance::GenerateP256 as u8).ok(); let res = se050.run_command( - &ReadObject { - object_id, - offset: None, - length: Some(len.into()), - rsa_key_component: None, + &EcdsaSign { + key_id: object_id, + data: &[52; 32], + algo: EcDsaSignatureAlgo::Sha256, }, &mut buf, )?; - response.push(advance_read as u8).ok(); - if res.data[..len.into()] != buf2[..len.into()] { - return Err(0x3001.into()); + response.push(Advance::EcDsaP256 as u8).ok(); + let res = se050.run_command( + &EcdsaVerify { + key_id: object_id, + data: &[52; 32], + algo: EcDsaSignatureAlgo::Sha256, + signature: res.signature, + }, + &mut buf2, + )?; + if res.result != Se05XResult::Success { + return Err(0x3002.into()); } - + response.push(Advance::VerifyP256 as u8).ok(); se050.run_command(&DeleteSecureObject { object_id }, &mut buf)?; - response.push(advance_delete as u8).ok(); - } - Ok(()) -} + response.push(Advance::DeleteP256 as u8).ok(); -#[cfg(feature = "se050")] -fn run_ecc, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, -) -> Result<(), Status> { - use se05x::se05x::commands::ReadEcCurveList; - - let mut buf = [0; 200]; - let mut buf2 = [0; 200]; - let object_id = ObjectId(hex!("01020304")); - - // *********** P256 *********** // - - se050.create_and_set_curve(EcCurve::NistP256)?; - response.push(Advance::CreateP256 as u8).ok(); - let _res = se050.run_command(&ReadEcCurveList {}, &mut buf)?; - debug_now!("Ec curves list: {:?}", _res); - response.push(Advance::ListP256 as u8).ok(); - se050.run_command( - &WriteEcKey { - transient: false, - is_auth: false, - key_type: Some(P1KeyType::KeyPair), - policy: None, - max_attempts: None, - object_id, - curve: Some(EcCurve::NistP256), - private_key: None, - public_key: None, - }, - &mut buf, - )?; - response.push(Advance::GenerateP256 as u8).ok(); - let res = se050.run_command( - &EcdsaSign { - key_id: object_id, - data: &[52; 32], - algo: EcDsaSignatureAlgo::Sha256, - }, - &mut buf, - )?; - response.push(Advance::EcDsaP256 as u8).ok(); - let res = se050.run_command( - &EcdsaVerify { - key_id: object_id, - data: &[52; 32], - algo: EcDsaSignatureAlgo::Sha256, - signature: res.signature, - }, - &mut buf2, - )?; - if res.result != Se05XResult::Success { - return Err(0x3002.into()); - } - response.push(Advance::VerifyP256 as u8).ok(); - se050.run_command(&DeleteSecureObject { object_id }, &mut buf)?; - response.push(Advance::DeleteP256 as u8).ok(); - - // *********** P521 *********** // - - se050.create_and_set_curve(EcCurve::NistP521)?; - response.push(Advance::CreateP521 as u8).ok(); - se050.run_command( - &WriteEcKey { - transient: false, - is_auth: false, - key_type: Some(P1KeyType::KeyPair), - policy: None, - max_attempts: None, - object_id, - curve: Some(EcCurve::NistP521), - private_key: None, - public_key: None, - }, - &mut buf, - )?; - response.push(Advance::GenerateP521 as u8).ok(); - let res = se050.run_command( - &EcdsaSign { - key_id: object_id, - data: &[52; 64], - algo: EcDsaSignatureAlgo::Sha512, - }, - &mut buf, - )?; - response.push(Advance::EcDsaP521 as u8).ok(); - let res = se050.run_command( - &EcdsaVerify { - key_id: object_id, - data: &[52; 64], - algo: EcDsaSignatureAlgo::Sha512, - signature: res.signature, - }, - &mut buf2, - )?; - if res.result != Se05XResult::Success { - return Err(0x3003.into()); - } - response.push(Advance::VerifyP521 as u8).ok(); - se050.run_command(&DeleteSecureObject { object_id }, &mut buf)?; - response.push(Advance::DeleteP521 as u8).ok(); - Ok(()) -} + // *********** P521 *********** // -#[cfg(feature = "se050")] -fn run_userid_recreation, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, -) -> Result<(), Status> { - let mut buf = [0; BUFFER_LEN]; - let object_id = ObjectId(hex!("01020304")); - let user_id = ObjectId(hex!("01223344")); - let user_id_good_value = hex!("31323334"); - let user_id_bad_value = hex!("FFFFFFFF"); - let policy_user_id = &[Policy { - object_id: ObjectId::INVALID, - access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_DELETE), - }]; - se050.run_command( - &WriteUserId { - policy: Some(PolicySet(policy_user_id)), - max_attempts: None, - object_id: user_id, - data: &user_id_good_value, - }, - &mut buf, - )?; - response.push(Advance::RecreationWriteUserId as u8).ok(); - let policy = &[Policy { - object_id: user_id, - access_rule: ObjectAccessRule::from_flags( - ObjectPolicyFlags::ALLOW_DELETE | ObjectPolicyFlags::ALLOW_READ, - ), - }]; - se050.run_command( - &WriteBinary { - transient: false, - policy: Some(PolicySet(policy)), - object_id, - offset: None, - file_length: Some(2.into()), - data: Some(&[1, 2]), - }, - &mut buf, - )?; - response.push(Advance::RecreationWriteBinary as u8).ok(); - match se050.run_command( - &ReadObject { - object_id, - offset: Some(0.into()), - length: Some(2.into()), - rsa_key_component: None, - }, - &mut buf, - ) { - Ok(_) => return Err(0x3004.into()), - Err(se05x::se05x::Error::Status(Status::CommandNotAllowedNoEf)) => {} - Err(_err) => { - debug_now!("Got unexpected error: {_err:?}"); - return Err(0x3007.into()); + se050.create_and_set_curve(EcCurve::NistP521)?; + response.push(Advance::CreateP521 as u8).ok(); + se050.run_command( + &WriteEcKey { + transient: false, + is_auth: false, + key_type: Some(P1KeyType::KeyPair), + policy: None, + max_attempts: None, + object_id, + curve: Some(EcCurve::NistP521), + private_key: None, + public_key: None, + }, + &mut buf, + )?; + response.push(Advance::GenerateP521 as u8).ok(); + let res = se050.run_command( + &EcdsaSign { + key_id: object_id, + data: &[52; 64], + algo: EcDsaSignatureAlgo::Sha512, + }, + &mut buf, + )?; + response.push(Advance::EcDsaP521 as u8).ok(); + let res = se050.run_command( + &EcdsaVerify { + key_id: object_id, + data: &[52; 64], + algo: EcDsaSignatureAlgo::Sha512, + signature: res.signature, + }, + &mut buf2, + )?; + if res.result != Se05XResult::Success { + return Err(0x3003.into()); } + response.push(Advance::VerifyP521 as u8).ok(); + se050.run_command(&DeleteSecureObject { object_id }, &mut buf)?; + response.push(Advance::DeleteP521 as u8).ok(); + Ok(()) } - response.push(Advance::RecreationDeleteAttempt as u8).ok(); - se050.run_command(&DeleteSecureObject { object_id: user_id }, &mut buf)?; - response.push(Advance::RecreationDeleteUserId as u8).ok(); - se050.run_command( - &WriteUserId { - policy: None, - max_attempts: None, - object_id: user_id, - data: &user_id_bad_value, - }, - &mut buf, - )?; - response.push(Advance::RecreationRecreateUserId as u8).ok(); - - let session = se050.run_command(&CreateSession { object_id: user_id }, &mut buf)?; - response.push(Advance::RecreationCreateSession as u8).ok(); - se050.run_command( - &ProcessSessionCmd { - session_id: session.session_id, - apdu: VerifySessionUserId { - user_id: &user_id_bad_value, + fn run_userid_recreation, const N: usize>( + se050: &mut Se05X, + response: &mut Vec, + ) -> Result<(), Status> { + let mut buf = [0; BUFFER_LEN]; + let object_id = ObjectId(hex!("01020304")); + let user_id = ObjectId(hex!("01223344")); + let user_id_good_value = hex!("31323334"); + let user_id_bad_value = hex!("FFFFFFFF"); + let policy_user_id = &[Policy { + object_id: ObjectId::INVALID, + access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_DELETE), + }]; + se050.run_command( + &WriteUserId { + policy: Some(PolicySet(policy_user_id)), + max_attempts: None, + object_id: user_id, + data: &user_id_good_value, }, - }, - &mut buf, - )?; - response.push(Advance::RecreationAuthSession as u8).ok(); - - let attack = se050.run_command( - &ProcessSessionCmd { - session_id: session.session_id, - apdu: ReadObject { + &mut buf, + )?; + response.push(Advance::RecreationWriteUserId as u8).ok(); + let policy = &[Policy { + object_id: user_id, + access_rule: ObjectAccessRule::from_flags( + ObjectPolicyFlags::ALLOW_DELETE | ObjectPolicyFlags::ALLOW_READ, + ), + }]; + se050.run_command( + &WriteBinary { + transient: false, + policy: Some(PolicySet(policy)), + object_id, + offset: None, + file_length: Some(2.into()), + data: Some(&[1, 2]), + }, + &mut buf, + )?; + response.push(Advance::RecreationWriteBinary as u8).ok(); + match se050.run_command( + &ReadObject { object_id, offset: Some(0.into()), length: Some(2.into()), rsa_key_component: None, }, - }, - &mut buf, - ); - - match attack { - Ok(_) => {} - Err(se05x::se05x::Error::Status(Status::CommandNotAllowedNoEf)) => {} - Err(_err) => { - debug_now!("Got unexpected error: {_err:?}"); - return Err(0x3006.into()); + &mut buf, + ) { + Ok(_) => return Err(0x3004.into()), + Err(se05x::se05x::Error::Status(Status::CommandNotAllowedNoEf)) => {} + Err(_err) => { + debug_now!("Got unexpected error: {_err:?}"); + return Err(0x3007.into()); + } } - } - response.push(Advance::RecreationDeleteAttack as u8).ok(); - Ok(()) -} + response.push(Advance::RecreationDeleteAttempt as u8).ok(); + se050.run_command(&DeleteSecureObject { object_id: user_id }, &mut buf)?; + response.push(Advance::RecreationDeleteUserId as u8).ok(); + se050.run_command( + &WriteUserId { + policy: None, + max_attempts: None, + object_id: user_id, + data: &user_id_bad_value, + }, + &mut buf, + )?; + response.push(Advance::RecreationRecreateUserId as u8).ok(); -#[cfg(feature = "se050")] -fn run_rsa2048, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, -) -> Result<(), Status> { - use se05x::se05x::{ - commands::{GenRsaKey, RsaDecrypt, RsaEncrypt, RsaSign, RsaVerify}, - RsaEncryptionAlgo, RsaSignatureAlgo, - }; + let session = se050.run_command(&CreateSession { object_id: user_id }, &mut buf)?; + response.push(Advance::RecreationCreateSession as u8).ok(); - let mut buf = [0; 1000]; - let mut buf2 = [0; 1000]; - let object_id = ObjectId(hex!("02334455")); - se050.run_command( - &GenRsaKey { - transient: false, - is_auth: false, - policy: None, - max_attempts: None, - object_id, - key_size: Some(2048.into()), - }, - &mut buf, - )?; - response.push(Advance::Rsa2048Gen as u8).ok(); - let res = se050.run_command( - &RsaSign { - key_id: object_id, - data: &[52; 32], - algo: RsaSignatureAlgo::RsaSha256Pkcs1, - }, - &mut buf, - )?; - response.push(Advance::Rsa2048Sign as u8).ok(); - let res = se050.run_command( - &RsaVerify { - key_id: object_id, - data: &[52; 32], - algo: RsaSignatureAlgo::RsaSha256Pkcs1, - signature: res.signature, - }, - &mut buf2, - )?; - if res.result != Se05XResult::Success { - return Err((0x3000 + line!() as u16).into()); - } - response.push(Advance::Rsa2048Verify as u8).ok(); - let res = se050.run_command( - &RsaEncrypt { - key_id: object_id, - plaintext: &[52; 32], - algo: RsaEncryptionAlgo::Pkcs1, - }, - &mut buf2, - )?; - response.push(Advance::Rsa2048Encrypt as u8).ok(); - let res = se050.run_command( - &RsaDecrypt { - key_id: object_id, - algo: RsaEncryptionAlgo::Pkcs1, - ciphertext: res.ciphertext, - }, - &mut buf, - )?; - if res.plaintext != [52; 32] { - return Err(0x3008.into()); + se050.run_command( + &ProcessSessionCmd { + session_id: session.session_id, + apdu: VerifySessionUserId { + user_id: &user_id_bad_value, + }, + }, + &mut buf, + )?; + response.push(Advance::RecreationAuthSession as u8).ok(); + + let attack = se050.run_command( + &ProcessSessionCmd { + session_id: session.session_id, + apdu: ReadObject { + object_id, + offset: Some(0.into()), + length: Some(2.into()), + rsa_key_component: None, + }, + }, + &mut buf, + ); + + match attack { + Ok(_) => {} + Err(se05x::se05x::Error::Status(Status::CommandNotAllowedNoEf)) => {} + Err(_err) => { + debug_now!("Got unexpected error: {_err:?}"); + return Err(0x3006.into()); + } + } + response.push(Advance::RecreationDeleteAttack as u8).ok(); + Ok(()) } - response.push(Advance::Rsa2048Decrypt as u8).ok(); - se050.run_command(&DeleteSecureObject { object_id }, &mut buf)?; - response.push(Advance::Rsa2048Delete as u8).ok(); - - Ok(()) -} + fn run_rsa2048, const N: usize>( + se050: &mut Se05X, + response: &mut Vec, + ) -> Result<(), Status> { + use se05x::se05x::{ + commands::{GenRsaKey, RsaDecrypt, RsaEncrypt, RsaSign, RsaVerify}, + RsaEncryptionAlgo, RsaSignatureAlgo, + }; + + let mut buf = [0; 1000]; + let mut buf2 = [0; 1000]; + let object_id = ObjectId(hex!("02334455")); + se050.run_command( + &GenRsaKey { + transient: false, + is_auth: false, + policy: None, + max_attempts: None, + object_id, + key_size: Some(2048.into()), + }, + &mut buf, + )?; + response.push(Advance::Rsa2048Gen as u8).ok(); + let res = se050.run_command( + &RsaSign { + key_id: object_id, + data: &[52; 32], + algo: RsaSignatureAlgo::RsaSha256Pkcs1, + }, + &mut buf, + )?; + response.push(Advance::Rsa2048Sign as u8).ok(); + let res = se050.run_command( + &RsaVerify { + key_id: object_id, + data: &[52; 32], + algo: RsaSignatureAlgo::RsaSha256Pkcs1, + signature: res.signature, + }, + &mut buf2, + )?; + if res.result != Se05XResult::Success { + return Err((0x3000 + line!() as u16).into()); + } + response.push(Advance::Rsa2048Verify as u8).ok(); + let res = se050.run_command( + &RsaEncrypt { + key_id: object_id, + plaintext: &[52; 32], + algo: RsaEncryptionAlgo::Pkcs1, + }, + &mut buf2, + )?; + response.push(Advance::Rsa2048Encrypt as u8).ok(); + let res = se050.run_command( + &RsaDecrypt { + key_id: object_id, + algo: RsaEncryptionAlgo::Pkcs1, + ciphertext: res.ciphertext, + }, + &mut buf, + )?; + if res.plaintext != [52; 32] { + return Err(0x3008.into()); + } + response.push(Advance::Rsa2048Decrypt as u8).ok(); -#[cfg(feature = "se050")] -fn run_rsa3072, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, -) -> Result<(), Status> { - use se05x::se05x::{ - commands::{GenRsaKey, RsaDecrypt, RsaEncrypt, RsaSign, RsaVerify}, - RsaEncryptionAlgo, RsaSignatureAlgo, - }; + se050.run_command(&DeleteSecureObject { object_id }, &mut buf)?; + response.push(Advance::Rsa2048Delete as u8).ok(); - let mut buf = [0; 1000]; - let mut buf2 = [0; 1000]; - let object_id = ObjectId(hex!("02334455")); - se050.run_command( - &GenRsaKey { - transient: false, - is_auth: false, - policy: None, - max_attempts: None, - object_id, - key_size: Some(3072.into()), - }, - &mut buf, - )?; - response.push(Advance::Rsa3072Gen as u8).ok(); - let res = se050.run_command( - &RsaSign { - key_id: object_id, - data: &[52; 32], - algo: RsaSignatureAlgo::RsaSha256Pkcs1, - }, - &mut buf, - )?; - response.push(Advance::Rsa3072Sign as u8).ok(); - let res = se050.run_command( - &RsaVerify { - key_id: object_id, - data: &[52; 32], - algo: RsaSignatureAlgo::RsaSha256Pkcs1, - signature: res.signature, - }, - &mut buf2, - )?; - if res.result != Se05XResult::Success { - return Err((0x3000 + line!() as u16).into()); - } - response.push(Advance::Rsa3072Verify as u8).ok(); - let res = se050.run_command( - &RsaEncrypt { - key_id: object_id, - plaintext: &[52; 32], - algo: RsaEncryptionAlgo::Pkcs1, - }, - &mut buf2, - )?; - response.push(Advance::Rsa3072Encrypt as u8).ok(); - let res = se050.run_command( - &RsaDecrypt { - key_id: object_id, - algo: RsaEncryptionAlgo::Pkcs1, - ciphertext: res.ciphertext, - }, - &mut buf, - )?; - if res.plaintext != [52; 32] { - return Err(0x3008.into()); + Ok(()) } - response.push(Advance::Rsa3072Decrypt as u8).ok(); - se050.run_command(&DeleteSecureObject { object_id }, &mut buf)?; - response.push(Advance::Rsa3072Delete as u8).ok(); - - Ok(()) -} - -#[cfg(feature = "se050")] -fn run_rsa4096, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, -) -> Result<(), Status> { - use se05x::se05x::{ - commands::{GenRsaKey, RsaDecrypt, RsaEncrypt, RsaSign, RsaVerify}, - RsaEncryptionAlgo, RsaSignatureAlgo, - }; - - let mut buf = [0; 1000]; - let mut buf2 = [0; 1000]; - let object_id = ObjectId(hex!("02334455")); - se050.run_command( - &GenRsaKey { - transient: false, - is_auth: false, - policy: None, - max_attempts: None, - object_id, - key_size: Some(4096.into()), - }, - &mut buf, - )?; - response.push(Advance::Rsa4096Gen as u8).ok(); - let res = se050.run_command( - &RsaSign { - key_id: object_id, - data: &[52; 32], - algo: RsaSignatureAlgo::RsaSha256Pkcs1, - }, - &mut buf, - )?; - response.push(Advance::Rsa4096Sign as u8).ok(); - let res = se050.run_command( - &RsaVerify { - key_id: object_id, - data: &[52; 32], - algo: RsaSignatureAlgo::RsaSha256Pkcs1, - signature: res.signature, - }, - &mut buf2, - )?; - if res.result != Se05XResult::Success { - return Err((0x3000 + line!() as u16).into()); - } - response.push(Advance::Rsa4096Verify as u8).ok(); - let res = se050.run_command( - &RsaEncrypt { - key_id: object_id, - plaintext: &[52; 32], - algo: RsaEncryptionAlgo::Pkcs1, - }, - &mut buf2, - )?; - response.push(Advance::Rsa4096Encrypt as u8).ok(); - let res = se050.run_command( - &RsaDecrypt { - key_id: object_id, - algo: RsaEncryptionAlgo::Pkcs1, - ciphertext: res.ciphertext, - }, - &mut buf, - )?; - if res.plaintext != [52; 32] { - return Err(0x3008.into()); - } - response.push(Advance::Rsa4096Decrypt as u8).ok(); - - se050.run_command(&DeleteSecureObject { object_id }, &mut buf)?; - response.push(Advance::Rsa4096Delete as u8).ok(); - - Ok(()) -} + fn run_rsa3072, const N: usize>( + se050: &mut Se05X, + response: &mut Vec, + ) -> Result<(), Status> { + use se05x::se05x::{ + commands::{GenRsaKey, RsaDecrypt, RsaEncrypt, RsaSign, RsaVerify}, + RsaEncryptionAlgo, RsaSignatureAlgo, + }; + + let mut buf = [0; 1000]; + let mut buf2 = [0; 1000]; + let object_id = ObjectId(hex!("02334455")); + se050.run_command( + &GenRsaKey { + transient: false, + is_auth: false, + policy: None, + max_attempts: None, + object_id, + key_size: Some(3072.into()), + }, + &mut buf, + )?; + response.push(Advance::Rsa3072Gen as u8).ok(); + let res = se050.run_command( + &RsaSign { + key_id: object_id, + data: &[52; 32], + algo: RsaSignatureAlgo::RsaSha256Pkcs1, + }, + &mut buf, + )?; + response.push(Advance::Rsa3072Sign as u8).ok(); + let res = se050.run_command( + &RsaVerify { + key_id: object_id, + data: &[52; 32], + algo: RsaSignatureAlgo::RsaSha256Pkcs1, + signature: res.signature, + }, + &mut buf2, + )?; + if res.result != Se05XResult::Success { + return Err((0x3000 + line!() as u16).into()); + } + response.push(Advance::Rsa3072Verify as u8).ok(); + let res = se050.run_command( + &RsaEncrypt { + key_id: object_id, + plaintext: &[52; 32], + algo: RsaEncryptionAlgo::Pkcs1, + }, + &mut buf2, + )?; + response.push(Advance::Rsa3072Encrypt as u8).ok(); + let res = se050.run_command( + &RsaDecrypt { + key_id: object_id, + algo: RsaEncryptionAlgo::Pkcs1, + ciphertext: res.ciphertext, + }, + &mut buf, + )?; + if res.plaintext != [52; 32] { + return Err(0x3008.into()); + } + response.push(Advance::Rsa3072Decrypt as u8).ok(); -#[cfg(feature = "se050")] -fn run_symm, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, -) -> Result<(), Status> { - use se05x::se05x::{ - commands::{ - CipherDecryptInit, CipherEncryptInit, CipherFinal, CipherOneShotDecrypt, - CipherOneShotEncrypt, CipherUpdate, CreateCipherObject, DeleteCryptoObj, WriteSymmKey, - }, - CipherMode, CryptoObjectId, SymmKeyType, - }; + se050.run_command(&DeleteSecureObject { object_id }, &mut buf)?; + response.push(Advance::Rsa3072Delete as u8).ok(); - let mut buf = [0; 1000]; - let mut buf2 = [0; 1000]; - let plaintext_data = [2; 32 * 15]; - let key_id = ObjectId(hex!("03445566")); - let cipher_id = CryptoObjectId(hex!("0123")); - let key = [0x42; 32]; - let iv = [0xFF; 16]; - se050.run_command( - &WriteSymmKey { - transient: true, - is_auth: false, - key_type: SymmKeyType::Aes, - policy: None, - max_attempts: None, - object_id: key_id, - kek_id: None, - value: &key, - }, - &mut buf, - )?; - response.push(Advance::SymmWrite as u8).ok(); - let ciphertext1 = se050.run_command( - &CipherOneShotEncrypt { - key_id, - mode: CipherMode::AesCtr, - plaintext: &plaintext_data, - initialization_vector: Some(&iv), - }, - &mut buf, - )?; - response.push(Advance::SymmEncryptOneShot as u8).ok(); - let plaintext1 = se050.run_command( - &CipherOneShotDecrypt { - key_id, - mode: CipherMode::AesCtr, - ciphertext: ciphertext1.ciphertext, - initialization_vector: Some(&iv), - }, - &mut buf2, - )?; - response.push(Advance::SymmDecryptOneShot as u8).ok(); - assert_eq!(plaintext1.plaintext, plaintext_data); - se050.run_command( - &CreateCipherObject { - id: cipher_id, - subtype: CipherMode::AesCtr, - }, - &mut buf2, - )?; - response.push(Advance::SymmEncryptCreate as u8).ok(); - se050.run_command( - &CipherEncryptInit { - key_id, - initialization_vector: Some(&iv), - cipher_id, - }, - &mut buf2, - )?; - response.push(Advance::SymmEncryptInit as u8).ok(); - let ciphertext2 = se050.run_command( - &CipherUpdate { - cipher_id, - data: &plaintext_data[0..32 * 10], - }, - &mut buf2, - )?; - if ciphertext2.data != &ciphertext1.ciphertext[0..32 * 10] { - return Err((0x3000 + line!() as u16).into()); - } - response.push(Advance::SymmEncryptUpdate1 as u8).ok(); - let ciphertext3 = se050.run_command( - &CipherUpdate { - cipher_id, - data: &plaintext_data[32 * 10..][..32 * 5], - }, - &mut buf2, - )?; - if ciphertext3.data != &ciphertext1.ciphertext[32 * 10..][..32 * 5] { - return Err((0x3000 + line!() as u16).into()); - } - response.push(Advance::SymmEncryptUpdate2 as u8).ok(); - let ciphertext4 = se050.run_command( - &CipherFinal { - cipher_id, - data: &plaintext_data[32 * 15..], - }, - &mut buf2, - )?; - if ciphertext4.data != &ciphertext1.ciphertext[32 * 15..] { - return Err((0x3000 + line!() as u16).into()); - } - response.push(Advance::SymmEncryptFinal as u8).ok(); - se050.run_command(&DeleteCryptoObj { id: cipher_id }, &mut buf2)?; - response.push(Advance::SymmEncryptDelete as u8).ok(); - se050.run_command( - &CreateCipherObject { - id: cipher_id, - subtype: CipherMode::AesCtr, - }, - &mut buf2, - )?; - response.push(Advance::SymmDecryptCreate as u8).ok(); - se050.run_command( - &CipherDecryptInit { - key_id, - initialization_vector: Some(&iv), - cipher_id, - }, - &mut buf2, - )?; - response.push(Advance::SymmDecryptInit as u8).ok(); - let plaintext1 = se050.run_command( - &CipherUpdate { - cipher_id, - data: &ciphertext1.ciphertext[0..32 * 10], - }, - &mut buf2, - )?; - if plaintext1.data != &plaintext_data[..32 * 10] { - return Err((0x3000 + line!() as u16).into()); - } - response.push(Advance::SymmDecryptUpdate1 as u8).ok(); - let plaintext2 = se050.run_command( - &CipherUpdate { - cipher_id, - data: &ciphertext1.ciphertext[32 * 10..][..32 * 5], - }, - &mut buf2, - )?; - if plaintext2.data != &plaintext_data[32 * 10..][..32 * 5] { - return Err((0x3000 + line!() as u16).into()); - } - response.push(Advance::SymmDecryptUpdate2 as u8).ok(); - let plaintext3 = se050.run_command( - &CipherFinal { - cipher_id, - data: &ciphertext1.ciphertext[32 * 15..], - }, - &mut buf2, - )?; - if plaintext3.data != &plaintext_data[32 * 15..] { - return Err((0x3000 + line!() as u16).into()); + Ok(()) } - response.push(Advance::SymmDecryptFinal as u8).ok(); - se050.run_command(&DeleteCryptoObj { id: cipher_id }, &mut buf2)?; - response.push(Advance::SymmDecryptDelete as u8).ok(); - se050.run_command(&DeleteSecureObject { object_id: key_id }, &mut buf2)?; - response.push(Advance::SymmDelete as u8).ok(); - Ok(()) -} -#[cfg(feature = "se050")] -fn run_mac, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, -) -> Result<(), Status> { - use se05x::se05x::{ - commands::{ - CreateSignatureObject, DeleteCryptoObj, MacGenerateFinal, MacGenerateInit, - MacOneShotGenerate, MacOneShotValidate, MacUpdate, MacValidateFinal, MacValidateInit, - WriteSymmKey, - }, - CryptoObjectId, MacAlgo, SymmKeyType, - }; + fn run_rsa4096, const N: usize>( + se050: &mut Se05X, + response: &mut Vec, + ) -> Result<(), Status> { + use se05x::se05x::{ + commands::{GenRsaKey, RsaDecrypt, RsaEncrypt, RsaSign, RsaVerify}, + RsaEncryptionAlgo, RsaSignatureAlgo, + }; + + let mut buf = [0; 1000]; + let mut buf2 = [0; 1000]; + let object_id = ObjectId(hex!("02334455")); + se050.run_command( + &GenRsaKey { + transient: false, + is_auth: false, + policy: None, + max_attempts: None, + object_id, + key_size: Some(4096.into()), + }, + &mut buf, + )?; + response.push(Advance::Rsa4096Gen as u8).ok(); + let res = se050.run_command( + &RsaSign { + key_id: object_id, + data: &[52; 32], + algo: RsaSignatureAlgo::RsaSha256Pkcs1, + }, + &mut buf, + )?; + response.push(Advance::Rsa4096Sign as u8).ok(); + let res = se050.run_command( + &RsaVerify { + key_id: object_id, + data: &[52; 32], + algo: RsaSignatureAlgo::RsaSha256Pkcs1, + signature: res.signature, + }, + &mut buf2, + )?; + if res.result != Se05XResult::Success { + return Err((0x3000 + line!() as u16).into()); + } + response.push(Advance::Rsa4096Verify as u8).ok(); + let res = se050.run_command( + &RsaEncrypt { + key_id: object_id, + plaintext: &[52; 32], + algo: RsaEncryptionAlgo::Pkcs1, + }, + &mut buf2, + )?; + response.push(Advance::Rsa4096Encrypt as u8).ok(); + let res = se050.run_command( + &RsaDecrypt { + key_id: object_id, + algo: RsaEncryptionAlgo::Pkcs1, + ciphertext: res.ciphertext, + }, + &mut buf, + )?; + if res.plaintext != [52; 32] { + return Err(0x3008.into()); + } + response.push(Advance::Rsa4096Decrypt as u8).ok(); - let mut buf = [0; 1000]; - let mut buf2 = [0; 1000]; - let plaintext_data = [2; 32 * 15]; - let key_id = ObjectId(hex!("03445566")); - let mac_id = CryptoObjectId(hex!("0123")); - let key = [0x42; 32]; - se050.run_command( - &WriteSymmKey { - transient: false, - is_auth: false, - key_type: SymmKeyType::Hmac, - policy: None, - max_attempts: None, - object_id: key_id, - kek_id: None, - value: &key, - }, - &mut buf, - )?; - response.push(Advance::MacWrite as u8).ok(); - let tag1 = se050.run_command( - &MacOneShotGenerate { - key_id, - data: &plaintext_data, - algo: MacAlgo::HmacSha256, - }, - &mut buf, - )?; - response.push(Advance::MacSignOneShot as u8).ok(); - let res = se050.run_command( - &MacOneShotValidate { - key_id, - algo: MacAlgo::HmacSha256, - data: &plaintext_data, - tag: tag1.tag, - }, - &mut buf2, - )?; - response.push(Advance::MacVerifyOneShot as u8).ok(); - if res.result != Se05XResult::Success { - return Err((0x3000 + line!() as u16).into()); + se050.run_command(&DeleteSecureObject { object_id }, &mut buf)?; + response.push(Advance::Rsa4096Delete as u8).ok(); + + Ok(()) } - se050.run_command( - &CreateSignatureObject { - id: mac_id, - subtype: MacAlgo::HmacSha256, - }, - &mut buf2, - )?; - response.push(Advance::MacSignCreate as u8).ok(); - se050.run_command(&MacGenerateInit { key_id, mac_id }, &mut buf2)?; - response.push(Advance::MacSignInit as u8).ok(); - se050.run_command( - &MacUpdate { - mac_id, - data: &plaintext_data[0..32 * 10], - }, - &mut buf2, - )?; - response.push(Advance::MacSignUpdate1 as u8).ok(); - se050.run_command( - &MacUpdate { - mac_id, - data: &plaintext_data[32 * 10..][..32 * 5], - }, - &mut buf2, - )?; - response.push(Advance::MacSignUpdate2 as u8).ok(); - let tag2 = se050.run_command( - &MacGenerateFinal { - mac_id, - data: &plaintext_data[32 * 15..], - }, - &mut buf2, - )?; - response.push(Advance::MacSignFinal as u8).ok(); - assert_eq!(tag2.tag, tag1.tag); - se050.run_command(&DeleteCryptoObj { id: mac_id }, &mut buf)?; - response.push(Advance::MacSignDelete as u8).ok(); - - se050.run_command( - &CreateSignatureObject { - id: mac_id, - subtype: MacAlgo::HmacSha256, - }, - &mut buf, - )?; - response.push(Advance::MacVerifyCreate as u8).ok(); - se050.run_command(&MacValidateInit { key_id, mac_id }, &mut buf)?; - response.push(Advance::MacVerifyInit as u8).ok(); - se050.run_command( - &MacUpdate { - mac_id, - data: &plaintext_data[0..32 * 10], - }, - &mut buf, - )?; - response.push(Advance::MacVerifyUpdate1 as u8).ok(); - se050.run_command( - &MacUpdate { - mac_id, - data: &plaintext_data[32 * 10..][..32 * 5], - }, - &mut buf, - )?; - response.push(Advance::MacVerifyUpdate2 as u8).ok(); - let res2 = se050.run_command( - &MacValidateFinal { - mac_id, - data: &plaintext_data[32 * 15..], - tag: tag2.tag, - }, - &mut buf, - )?; - if res2.result != Se05XResult::Success { - return Err((0x3000 + line!() as u16).into()); + + fn run_symm, const N: usize>( + se050: &mut Se05X, + response: &mut Vec, + ) -> Result<(), Status> { + use se05x::se05x::{ + commands::{ + CipherDecryptInit, CipherEncryptInit, CipherFinal, CipherOneShotDecrypt, + CipherOneShotEncrypt, CipherUpdate, CreateCipherObject, DeleteCryptoObj, + WriteSymmKey, + }, + CipherMode, CryptoObjectId, SymmKeyType, + }; + + let mut buf = [0; 1000]; + let mut buf2 = [0; 1000]; + let plaintext_data = [2; 32 * 15]; + let key_id = ObjectId(hex!("03445566")); + let cipher_id = CryptoObjectId(hex!("0123")); + let key = [0x42; 32]; + let iv = [0xFF; 16]; + se050.run_command( + &WriteSymmKey { + transient: true, + is_auth: false, + key_type: SymmKeyType::Aes, + policy: None, + max_attempts: None, + object_id: key_id, + kek_id: None, + value: &key, + }, + &mut buf, + )?; + response.push(Advance::SymmWrite as u8).ok(); + let ciphertext1 = se050.run_command( + &CipherOneShotEncrypt { + key_id, + mode: CipherMode::AesCtr, + plaintext: &plaintext_data, + initialization_vector: Some(&iv), + }, + &mut buf, + )?; + response.push(Advance::SymmEncryptOneShot as u8).ok(); + let plaintext1 = se050.run_command( + &CipherOneShotDecrypt { + key_id, + mode: CipherMode::AesCtr, + ciphertext: ciphertext1.ciphertext, + initialization_vector: Some(&iv), + }, + &mut buf2, + )?; + response.push(Advance::SymmDecryptOneShot as u8).ok(); + assert_eq!(plaintext1.plaintext, plaintext_data); + se050.run_command( + &CreateCipherObject { + id: cipher_id, + subtype: CipherMode::AesCtr, + }, + &mut buf2, + )?; + response.push(Advance::SymmEncryptCreate as u8).ok(); + se050.run_command( + &CipherEncryptInit { + key_id, + initialization_vector: Some(&iv), + cipher_id, + }, + &mut buf2, + )?; + response.push(Advance::SymmEncryptInit as u8).ok(); + let ciphertext2 = se050.run_command( + &CipherUpdate { + cipher_id, + data: &plaintext_data[0..32 * 10], + }, + &mut buf2, + )?; + if ciphertext2.data != &ciphertext1.ciphertext[0..32 * 10] { + return Err((0x3000 + line!() as u16).into()); + } + response.push(Advance::SymmEncryptUpdate1 as u8).ok(); + let ciphertext3 = se050.run_command( + &CipherUpdate { + cipher_id, + data: &plaintext_data[32 * 10..][..32 * 5], + }, + &mut buf2, + )?; + if ciphertext3.data != &ciphertext1.ciphertext[32 * 10..][..32 * 5] { + return Err((0x3000 + line!() as u16).into()); + } + response.push(Advance::SymmEncryptUpdate2 as u8).ok(); + let ciphertext4 = se050.run_command( + &CipherFinal { + cipher_id, + data: &plaintext_data[32 * 15..], + }, + &mut buf2, + )?; + if ciphertext4.data != &ciphertext1.ciphertext[32 * 15..] { + return Err((0x3000 + line!() as u16).into()); + } + response.push(Advance::SymmEncryptFinal as u8).ok(); + se050.run_command(&DeleteCryptoObj { id: cipher_id }, &mut buf2)?; + response.push(Advance::SymmEncryptDelete as u8).ok(); + se050.run_command( + &CreateCipherObject { + id: cipher_id, + subtype: CipherMode::AesCtr, + }, + &mut buf2, + )?; + response.push(Advance::SymmDecryptCreate as u8).ok(); + se050.run_command( + &CipherDecryptInit { + key_id, + initialization_vector: Some(&iv), + cipher_id, + }, + &mut buf2, + )?; + response.push(Advance::SymmDecryptInit as u8).ok(); + let plaintext1 = se050.run_command( + &CipherUpdate { + cipher_id, + data: &ciphertext1.ciphertext[0..32 * 10], + }, + &mut buf2, + )?; + if plaintext1.data != &plaintext_data[..32 * 10] { + return Err((0x3000 + line!() as u16).into()); + } + response.push(Advance::SymmDecryptUpdate1 as u8).ok(); + let plaintext2 = se050.run_command( + &CipherUpdate { + cipher_id, + data: &ciphertext1.ciphertext[32 * 10..][..32 * 5], + }, + &mut buf2, + )?; + if plaintext2.data != &plaintext_data[32 * 10..][..32 * 5] { + return Err((0x3000 + line!() as u16).into()); + } + response.push(Advance::SymmDecryptUpdate2 as u8).ok(); + let plaintext3 = se050.run_command( + &CipherFinal { + cipher_id, + data: &ciphertext1.ciphertext[32 * 15..], + }, + &mut buf2, + )?; + if plaintext3.data != &plaintext_data[32 * 15..] { + return Err((0x3000 + line!() as u16).into()); + } + response.push(Advance::SymmDecryptFinal as u8).ok(); + se050.run_command(&DeleteCryptoObj { id: cipher_id }, &mut buf2)?; + response.push(Advance::SymmDecryptDelete as u8).ok(); + se050.run_command(&DeleteSecureObject { object_id: key_id }, &mut buf2)?; + response.push(Advance::SymmDelete as u8).ok(); + Ok(()) } - response.push(Advance::MacVerifyFinal as u8).ok(); - se050.run_command(&DeleteCryptoObj { id: mac_id }, &mut buf)?; - response.push(Advance::MacVerifyDelete as u8).ok(); + fn run_mac, const N: usize>( + se050: &mut Se05X, + response: &mut Vec, + ) -> Result<(), Status> { + use se05x::se05x::{ + commands::{ + CreateSignatureObject, DeleteCryptoObj, MacGenerateFinal, MacGenerateInit, + MacOneShotGenerate, MacOneShotValidate, MacUpdate, MacValidateFinal, + MacValidateInit, WriteSymmKey, + }, + CryptoObjectId, MacAlgo, SymmKeyType, + }; + + let mut buf = [0; 1000]; + let mut buf2 = [0; 1000]; + let plaintext_data = [2; 32 * 15]; + let key_id = ObjectId(hex!("03445566")); + let mac_id = CryptoObjectId(hex!("0123")); + let key = [0x42; 32]; + se050.run_command( + &WriteSymmKey { + transient: false, + is_auth: false, + key_type: SymmKeyType::Hmac, + policy: None, + max_attempts: None, + object_id: key_id, + kek_id: None, + value: &key, + }, + &mut buf, + )?; + response.push(Advance::MacWrite as u8).ok(); + let tag1 = se050.run_command( + &MacOneShotGenerate { + key_id, + data: &plaintext_data, + algo: MacAlgo::HmacSha256, + }, + &mut buf, + )?; + response.push(Advance::MacSignOneShot as u8).ok(); + let res = se050.run_command( + &MacOneShotValidate { + key_id, + algo: MacAlgo::HmacSha256, + data: &plaintext_data, + tag: tag1.tag, + }, + &mut buf2, + )?; + response.push(Advance::MacVerifyOneShot as u8).ok(); + if res.result != Se05XResult::Success { + return Err((0x3000 + line!() as u16).into()); + } + se050.run_command( + &CreateSignatureObject { + id: mac_id, + subtype: MacAlgo::HmacSha256, + }, + &mut buf2, + )?; + response.push(Advance::MacSignCreate as u8).ok(); + se050.run_command(&MacGenerateInit { key_id, mac_id }, &mut buf2)?; + response.push(Advance::MacSignInit as u8).ok(); + se050.run_command( + &MacUpdate { + mac_id, + data: &plaintext_data[0..32 * 10], + }, + &mut buf2, + )?; + response.push(Advance::MacSignUpdate1 as u8).ok(); + se050.run_command( + &MacUpdate { + mac_id, + data: &plaintext_data[32 * 10..][..32 * 5], + }, + &mut buf2, + )?; + response.push(Advance::MacSignUpdate2 as u8).ok(); + let tag2 = se050.run_command( + &MacGenerateFinal { + mac_id, + data: &plaintext_data[32 * 15..], + }, + &mut buf2, + )?; + response.push(Advance::MacSignFinal as u8).ok(); + assert_eq!(tag2.tag, tag1.tag); + se050.run_command(&DeleteCryptoObj { id: mac_id }, &mut buf)?; + response.push(Advance::MacSignDelete as u8).ok(); - se050.run_command(&DeleteSecureObject { object_id: key_id }, &mut buf2)?; - response.push(Advance::MacDelete as u8).ok(); - Ok(()) -} + se050.run_command( + &CreateSignatureObject { + id: mac_id, + subtype: MacAlgo::HmacSha256, + }, + &mut buf, + )?; + response.push(Advance::MacVerifyCreate as u8).ok(); + se050.run_command(&MacValidateInit { key_id, mac_id }, &mut buf)?; + response.push(Advance::MacVerifyInit as u8).ok(); + se050.run_command( + &MacUpdate { + mac_id, + data: &plaintext_data[0..32 * 10], + }, + &mut buf, + )?; + response.push(Advance::MacVerifyUpdate1 as u8).ok(); + se050.run_command( + &MacUpdate { + mac_id, + data: &plaintext_data[32 * 10..][..32 * 5], + }, + &mut buf, + )?; + response.push(Advance::MacVerifyUpdate2 as u8).ok(); + let res2 = se050.run_command( + &MacValidateFinal { + mac_id, + data: &plaintext_data[32 * 15..], + tag: tag2.tag, + }, + &mut buf, + )?; + if res2.result != Se05XResult::Success { + return Err((0x3000 + line!() as u16).into()); + } + response.push(Advance::MacVerifyFinal as u8).ok(); -#[cfg(feature = "se050")] -fn run_aes_session, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, -) -> Result<(), Status> { - use rand_chacha::rand_core::SeedableRng; - use se05x::se05x::{ - commands::{CloseSession, WriteSymmKey}, - SymmKeyType, - }; + se050.run_command(&DeleteCryptoObj { id: mac_id }, &mut buf)?; + response.push(Advance::MacVerifyDelete as u8).ok(); - let mut buf = [0; 1024]; - let key = [0x42; 16]; - let key2 = [0x43; 16]; - let key_id = ObjectId(hex!("03445566")); - let bin_id = ObjectId(hex!("03445567")); - let bin_data = hex!("CAFECAFE"); - let key_policy = &[ - Policy { - object_id: key_id, - access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_WRITE), - }, - Policy { - object_id: ObjectId::INVALID, - access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_DELETE), - }, - ]; - let bin_policy = &[ - Policy { - object_id: key_id, - access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_READ), - }, - Policy { - object_id: ObjectId::INVALID, - access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_DELETE), - }, - ]; - se050.run_command( - &WriteSymmKey { - transient: false, - is_auth: true, - key_type: SymmKeyType::Aes, - policy: Some(PolicySet(key_policy)), - max_attempts: None, - object_id: key_id, - kek_id: None, - value: &key, - }, - &mut buf, - )?; - response.push(Advance::AesSessionCreateKey as _).ok(); - se050.run_command( - &WriteBinary { - transient: false, - policy: Some(PolicySet(bin_policy)), - object_id: bin_id, - offset: None, - file_length: Some((bin_data.len() as u16).into()), - data: Some(&bin_data), - }, - &mut buf, - )?; - response.push(Advance::AesSessionCreateBinary as _).ok(); - - let session = se050.run_command(&CreateSession { object_id: key_id }, &mut buf)?; - let session_id = session.session_id; - response.push(Advance::AesSessionCreateSession as u8).ok(); - debug_now!("Created session"); - - let mut rng = rand_chacha::ChaCha8Rng::from_seed([0xCA; 32]); - se050.authenticate_aes128_session(session_id, &key, &mut rng)?; - response.push(Advance::AesSessionAuthenticate as u8).ok(); - - let data = se050.run_command( - &ProcessSessionCmd { - session_id, - apdu: ReadObject { - object_id: bin_id, - offset: None, - length: Some((bin_data.len() as u16).into()), - rsa_key_component: None, + se050.run_command(&DeleteSecureObject { object_id: key_id }, &mut buf2)?; + response.push(Advance::MacDelete as u8).ok(); + Ok(()) + } + + fn run_aes_session, const N: usize>( + se050: &mut Se05X, + response: &mut Vec, + ) -> Result<(), Status> { + use rand_chacha::rand_core::SeedableRng; + use se05x::se05x::{ + commands::{CloseSession, WriteSymmKey}, + SymmKeyType, + }; + + let mut buf = [0; 1024]; + let key = [0x42; 16]; + let key2 = [0x43; 16]; + let key_id = ObjectId(hex!("03445566")); + let bin_id = ObjectId(hex!("03445567")); + let bin_data = hex!("CAFECAFE"); + let key_policy = &[ + Policy { + object_id: key_id, + access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_WRITE), }, - }, - &mut buf, - )?; - assert_eq!(data.data, &bin_data); - response.push(Advance::AesSessionReadBinary as _).ok(); - - se050.run_command( - &ProcessSessionCmd { - session_id, - apdu: WriteSymmKey { + Policy { + object_id: ObjectId::INVALID, + access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_DELETE), + }, + ]; + let bin_policy = &[ + Policy { + object_id: key_id, + access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_READ), + }, + Policy { + object_id: ObjectId::INVALID, + access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_DELETE), + }, + ]; + se050.run_command( + &WriteSymmKey { transient: false, is_auth: true, key_type: SymmKeyType::Aes, - policy: None, + policy: Some(PolicySet(key_policy)), max_attempts: None, object_id: key_id, kek_id: None, - value: &key2, + value: &key, }, - }, - &mut buf, - )?; - response.push(Advance::AesSessionUpdateKey as _).ok(); - - se050.run_command( - &ProcessSessionCmd { - session_id, - apdu: CloseSession {}, - }, - &mut buf, - )?; - response.push(Advance::AesSessionCloseSession as _).ok(); - - let session = se050.run_command(&CreateSession { object_id: key_id }, &mut buf)?; - let session_id = session.session_id; - response.push(Advance::AesSessionRecreateSession as u8).ok(); - debug_now!("Created session"); - - let mut rng = rand_chacha::ChaCha8Rng::from_seed([0xCA; 32]); - se050.authenticate_aes128_session(session_id, &key2, &mut rng)?; - response.push(Advance::AesSessionReAuthenticate as u8).ok(); - - let data = se050.run_command( - &ProcessSessionCmd { - session_id, - apdu: ReadObject { + &mut buf, + )?; + response.push(Advance::AesSessionCreateKey as _).ok(); + se050.run_command( + &WriteBinary { + transient: false, + policy: Some(PolicySet(bin_policy)), object_id: bin_id, offset: None, - length: Some((bin_data.len() as u16).into()), - rsa_key_component: None, + file_length: Some((bin_data.len() as u16).into()), + data: Some(&bin_data), }, - }, - &mut buf, - )?; - assert_eq!(data.data, &bin_data); - response.push(Advance::AesSessionReadBinary2 as _).ok(); + &mut buf, + )?; + response.push(Advance::AesSessionCreateBinary as _).ok(); + + let session = se050.run_command(&CreateSession { object_id: key_id }, &mut buf)?; + let session_id = session.session_id; + response.push(Advance::AesSessionCreateSession as u8).ok(); + debug_now!("Created session"); + + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0xCA; 32]); + se050.authenticate_aes128_session(session_id, &key, &mut rng)?; + response.push(Advance::AesSessionAuthenticate as u8).ok(); + + let data = se050.run_command( + &ProcessSessionCmd { + session_id, + apdu: ReadObject { + object_id: bin_id, + offset: None, + length: Some((bin_data.len() as u16).into()), + rsa_key_component: None, + }, + }, + &mut buf, + )?; + assert_eq!(data.data, &bin_data); + response.push(Advance::AesSessionReadBinary as _).ok(); - se050.run_command(&DeleteSecureObject { object_id: bin_id }, &mut buf)?; - response.push(Advance::AesSessionDeleteBinary as _).ok(); - se050.run_command(&DeleteSecureObject { object_id: key_id }, &mut buf)?; - response.push(Advance::AesSessionDeleteKey as _).ok(); + se050.run_command( + &ProcessSessionCmd { + session_id, + apdu: WriteSymmKey { + transient: false, + is_auth: true, + key_type: SymmKeyType::Aes, + policy: None, + max_attempts: None, + object_id: key_id, + kek_id: None, + value: &key2, + }, + }, + &mut buf, + )?; + response.push(Advance::AesSessionUpdateKey as _).ok(); - Ok(()) -} + se050.run_command( + &ProcessSessionCmd { + session_id, + apdu: CloseSession {}, + }, + &mut buf, + )?; + response.push(Advance::AesSessionCloseSession as _).ok(); + + let session = se050.run_command(&CreateSession { object_id: key_id }, &mut buf)?; + let session_id = session.session_id; + response.push(Advance::AesSessionRecreateSession as u8).ok(); + debug_now!("Created session"); + + let mut rng = rand_chacha::ChaCha8Rng::from_seed([0xCA; 32]); + se050.authenticate_aes128_session(session_id, &key2, &mut rng)?; + response.push(Advance::AesSessionReAuthenticate as u8).ok(); + + let data = se050.run_command( + &ProcessSessionCmd { + session_id, + apdu: ReadObject { + object_id: bin_id, + offset: None, + length: Some((bin_data.len() as u16).into()), + rsa_key_component: None, + }, + }, + &mut buf, + )?; + assert_eq!(data.data, &bin_data); + response.push(Advance::AesSessionReadBinary2 as _).ok(); -#[cfg(feature = "se050")] -fn run_pbkdf, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, -) -> Result<(), Status> { - use se05x::se05x::{ - commands::{Pbkdf2, WriteSymmKey}, - SymmKeyType, - }; + se050.run_command(&DeleteSecureObject { object_id: bin_id }, &mut buf)?; + response.push(Advance::AesSessionDeleteBinary as _).ok(); + se050.run_command(&DeleteSecureObject { object_id: key_id }, &mut buf)?; + response.push(Advance::AesSessionDeleteKey as _).ok(); - let mut buf = [0; 1024]; - let pin = b"123456"; - let salt = [0x42; 16]; - let pin_id = ObjectId(hex!("03445566")); - - se050.run_command( - &WriteSymmKey { - transient: true, - is_auth: false, - key_type: SymmKeyType::Hmac, - policy: None, - max_attempts: None, - object_id: pin_id, - kek_id: None, - value: pin, - }, - &mut buf, - )?; + Ok(()) + } - response.push(Advance::Pbkdf2WritePin as u8).ok(); + fn run_pbkdf, const N: usize>( + se050: &mut Se05X, + response: &mut Vec, + ) -> Result<(), Status> { + use se05x::se05x::{ + commands::{Pbkdf2, WriteSymmKey}, + SymmKeyType, + }; - let res = se050.run_command( - &Pbkdf2 { - password: pin_id, - salt: Some(&salt), - iterations: 32.into(), - requested_len: 16.into(), - }, - &mut buf, - )?; - response.push(Advance::Pbkdf2Calculate as u8).ok(); + let mut buf = [0; 1024]; + let pin = b"123456"; + let salt = [0x42; 16]; + let pin_id = ObjectId(hex!("03445566")); - if res.data != hex!("685126241d909137ecd3385eaea2725f") { - debug_now!("Got HASH: {:02x?}", res.data); - return Err(Status::CorruptedData); - } - response.push(Advance::Pbkdf2Compare as u8).ok(); + se050.run_command( + &WriteSymmKey { + transient: true, + is_auth: false, + key_type: SymmKeyType::Hmac, + policy: None, + max_attempts: None, + object_id: pin_id, + kek_id: None, + value: pin, + }, + &mut buf, + )?; - se050.run_command(&DeleteSecureObject { object_id: pin_id }, &mut buf)?; - response.push(Advance::Pbkdf2DeletePin as u8).ok(); + response.push(Advance::Pbkdf2WritePin as u8).ok(); - Ok(()) -} + let res = se050.run_command( + &Pbkdf2 { + password: pin_id, + salt: Some(&salt), + iterations: 32.into(), + requested_len: 16.into(), + }, + &mut buf, + )?; + response.push(Advance::Pbkdf2Calculate as u8).ok(); -#[cfg(feature = "se050")] -fn run_export_import, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, -) -> Result<(), Status> { - use se05x::se05x::{ - commands::{CipherOneShotEncrypt, ExportObject, ImportObject, WriteSymmKey}, - CipherMode, RsaKeyComponent, SymmKeyType, - }; + if res.data != hex!("685126241d909137ecd3385eaea2725f") { + debug_now!("Got HASH: {:02x?}", res.data); + return Err(Status::CorruptedData); + } + response.push(Advance::Pbkdf2Compare as u8).ok(); - let mut buf = [0; 128]; - let mut buf2 = [0; 1000]; - let mut buf3 = [0; 1000]; - let plaintext_data = [2; 32]; - let key_id = ObjectId(hex!("03445566")); - let key = [0x42; 32]; - let iv = [0xFF; 16]; - let policy = &[ - Policy { - object_id: ObjectId::INVALID, - access_rule: ObjectAccessRule::from_flags( - ObjectPolicyFlags::ALLOW_WRITE - | ObjectPolicyFlags::ALLOW_ENC - | ObjectPolicyFlags::ALLOW_DELETE - | ObjectPolicyFlags::ALLOW_IMPORT_EXPORT, - ), - }, - Policy { - object_id: ObjectId::INVALID, - access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_DELETE), - }, - ]; - se050.run_command( - &WriteSymmKey { - transient: true, - is_auth: false, - key_type: SymmKeyType::Aes, - policy: Some(PolicySet(policy)), - max_attempts: None, - object_id: key_id, - kek_id: None, - value: &key, - }, - &mut buf, - )?; - response.push(Advance::ImportWrite as u8).ok(); - let ciphertext1 = se050.run_command( - &CipherOneShotEncrypt { - key_id, - mode: CipherMode::AesCtr, - plaintext: &plaintext_data, - initialization_vector: Some(&iv), - }, - &mut buf, - )?; - response.push(Advance::ImportCipher as u8).ok(); - - debug_now!("Exporting"); - let exported = se050 - .run_command( - &ExportObject { + se050.run_command(&DeleteSecureObject { object_id: pin_id }, &mut buf)?; + response.push(Advance::Pbkdf2DeletePin as u8).ok(); + + Ok(()) + } + + fn run_export_import, const N: usize>( + se050: &mut Se05X, + response: &mut Vec, + ) -> Result<(), Status> { + use se05x::se05x::{ + commands::{CipherOneShotEncrypt, ExportObject, ImportObject, WriteSymmKey}, + CipherMode, RsaKeyComponent, SymmKeyType, + }; + + let mut buf = [0; 128]; + let mut buf2 = [0; 1000]; + let mut buf3 = [0; 1000]; + let plaintext_data = [2; 32]; + let key_id = ObjectId(hex!("03445566")); + let key = [0x42; 32]; + let iv = [0xFF; 16]; + let policy = &[ + Policy { + object_id: ObjectId::INVALID, + access_rule: ObjectAccessRule::from_flags( + ObjectPolicyFlags::ALLOW_WRITE + | ObjectPolicyFlags::ALLOW_ENC + | ObjectPolicyFlags::ALLOW_DELETE + | ObjectPolicyFlags::ALLOW_IMPORT_EXPORT, + ), + }, + Policy { + object_id: ObjectId::INVALID, + access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_DELETE), + }, + ]; + se050.run_command( + &WriteSymmKey { + transient: true, + is_auth: false, + key_type: SymmKeyType::Aes, + policy: Some(PolicySet(policy)), + max_attempts: None, object_id: key_id, - rsa_key_component: RsaKeyComponent::Na, + kek_id: None, + value: &key, }, - &mut buf2, - ) - .map_err(|_err| { - debug_now!("Got err: {:?}", _err); - _err - })?; - response.push(Advance::ImportExport as u8).ok(); - - se050.enable()?; - response.push(Advance::ImportDelete as u8).ok(); - - let res = se050.run_command( - &CipherOneShotEncrypt { - key_id, - mode: CipherMode::AesCtr, - plaintext: &plaintext_data, - initialization_vector: Some(&iv), - }, - &mut buf3, - ); - if !matches!( - res, - Err(se05x::se05x::Error::Status( - Status::ConditionsOfUseNotSatisfied, - )) - ) { - return Err((0x3000 + line!() as u16).into()); - } - response.push(Advance::ImportDeletionWorked as u8).ok(); - - debug_now!("Importing"); - se050.run_command( - &ImportObject { - transient: true, - object_id: key_id, - rsa_key_component: None, - serialized_object: exported.data, - }, - &mut buf3, - )?; - response.push(Advance::ImportImport as u8).ok(); - - debug_now!("Encrypting"); - let ciphertext2 = se050.run_command( - &CipherOneShotEncrypt { - key_id, - mode: CipherMode::AesCtr, - plaintext: &plaintext_data, - initialization_vector: Some(&iv), - }, - &mut buf3, - )?; - response.push(Advance::ImportCipher2 as u8).ok(); + &mut buf, + )?; + response.push(Advance::ImportWrite as u8).ok(); + let ciphertext1 = se050.run_command( + &CipherOneShotEncrypt { + key_id, + mode: CipherMode::AesCtr, + plaintext: &plaintext_data, + initialization_vector: Some(&iv), + }, + &mut buf, + )?; + response.push(Advance::ImportCipher as u8).ok(); + + debug_now!("Exporting"); + let exported = se050 + .run_command( + &ExportObject { + object_id: key_id, + rsa_key_component: RsaKeyComponent::Na, + }, + &mut buf2, + ) + .map_err(|_err| { + debug_now!("Got err: {:?}", _err); + _err + })?; + response.push(Advance::ImportExport as u8).ok(); + + se050.enable()?; + response.push(Advance::ImportDelete as u8).ok(); - debug_now!("Comparing"); - if ciphertext1.ciphertext != ciphertext2.ciphertext { - return Err((0x3000 + line!() as u16).into()); - } - response.push(Advance::ImportComp as u8).ok(); + let res = se050.run_command( + &CipherOneShotEncrypt { + key_id, + mode: CipherMode::AesCtr, + plaintext: &plaintext_data, + initialization_vector: Some(&iv), + }, + &mut buf3, + ); + if !matches!( + res, + Err(se05x::se05x::Error::Status( + Status::ConditionsOfUseNotSatisfied, + )) + ) { + return Err((0x3000 + line!() as u16).into()); + } + response.push(Advance::ImportDeletionWorked as u8).ok(); - se050.run_command(&DeleteSecureObject { object_id: key_id }, &mut buf3)?; - response.push(Advance::ImportDeleteFinal as u8).ok(); - Ok(()) + debug_now!("Importing"); + se050.run_command( + &ImportObject { + transient: true, + object_id: key_id, + rsa_key_component: None, + serialized_object: exported.data, + }, + &mut buf3, + )?; + response.push(Advance::ImportImport as u8).ok(); + + debug_now!("Encrypting"); + let ciphertext2 = se050.run_command( + &CipherOneShotEncrypt { + key_id, + mode: CipherMode::AesCtr, + plaintext: &plaintext_data, + initialization_vector: Some(&iv), + }, + &mut buf3, + )?; + response.push(Advance::ImportCipher2 as u8).ok(); + + debug_now!("Comparing"); + if ciphertext1.ciphertext != ciphertext2.ciphertext { + return Err((0x3000 + line!() as u16).into()); + } + response.push(Advance::ImportComp as u8).ok(); + + se050.run_command(&DeleteSecureObject { object_id: key_id }, &mut buf3)?; + response.push(Advance::ImportDeleteFinal as u8).ok(); + Ok(()) + } } From f58f759b795514af3424c77ba8030d9ea5548964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 14 Sep 2023 11:47:56 +0200 Subject: [PATCH 17/65] Move tests to a custom backend --- Cargo.toml | 6 ++-- src/admin.rs | 98 ++++++++-------------------------------------------- src/lib.rs | 10 ++++++ 3 files changed, 29 insertions(+), 85 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 693c7c7..a7ceeba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,10 +20,11 @@ se05x = { version = "0.0.1", optional = true } embedded-hal = { version = "0.2.7", optional = true } hex-literal = "0.4.1" rand_chacha = { version = "0.3.1", optional = true, default-features = false } +trussed-se050-backend = { version = "0.1.0", optional = true } [features] default = ["se050"] -se050 = ["dep:se05x", "embedded-hal", "rand_chacha"] +se050 = ["trussed-se050-backend"] log-all = [] log-none = [] @@ -35,5 +36,6 @@ log-error = [] [patch.crates-io] ctaphid-dispatch = { git = "https://github.com/trussed-dev/ctaphid-dispatch.git", rev = "57cb3317878a8593847595319aa03ef17c29ec5b" } trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "51e68500d7601d04f884f5e95567d14b9018a6cb" } +trussed-se050-backend = { git = "https://github.com/Nitrokey/trussed-se050-backend.git", rev = "3395a5b73241a0a9f14a0715952be6fe7f1e56b0" } iso7816 = { git = "https://github.com/sosthene-nitrokey/iso7816.git", rev = "160ca3bbd8e21ec4e4ee1e0748e1eaa53a45c97f"} -se05x = { git = "https://github.com/Nitrokey/se05x.git", rev = "db1ddea25cc382355b4292352652da656abc3005"} +se05x = { git = "https://github.com/Nitrokey/se05x.git", rev = "0b77eb6b152d214897696aadf87767fd84ffcb0e"} diff --git a/src/admin.rs b/src/admin.rs index 3f762c4..ee11ba4 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -1,13 +1,10 @@ +use super::Client as TrussedClient; use apdu_dispatch::iso7816::Status; use apdu_dispatch::{app as apdu, command, response, Command as ApduCommand}; use core::{convert::TryInto, marker::PhantomData, time::Duration}; use ctaphid_dispatch::app::{self as hid, Command as HidCommand, Message}; use ctaphid_dispatch::command::VendorCommand; -#[cfg(feature = "se050")] -use embedded_hal::blocking::delay::DelayUs; -#[cfg(feature = "se050")] -use se05x::{se05x::Se05X, t1::I2CForT1}; -use trussed::{interrupt::InterruptFlag, syscall, types::Vec, Client as TrussedClient}; +use trussed::{interrupt::InterruptFlag, syscall, types::Vec}; pub const USER_PRESENCE_TIMEOUT_SECS: u32 = 15; @@ -30,18 +27,6 @@ const WINK: HidCommand = HidCommand::Wink; // 0x08 const RNG_DATA_LEN: usize = 57; -mod run_tests; -use run_tests::*; - -/// Trait representing the possible ownership of the SE050 by the admin app. -/// -/// Implemented by `()` and the `Se05X` struct -pub trait MaybeSe: RunTests {} - -impl MaybeSe for () {} -#[cfg(feature = "se050")] -impl> MaybeSe for Se05X {} - #[derive(PartialEq, Debug)] enum Command { Update, @@ -153,7 +138,7 @@ pub trait Reboot { fn locked() -> bool; } -pub struct App +pub struct App where T: TrussedClient, R: Reboot, @@ -165,7 +150,6 @@ where full_version: &'static str, status: S, boot_interface: PhantomData, - se050: Se05X, } impl App @@ -173,61 +157,6 @@ where T: TrussedClient, R: Reboot, S: AsRef<[u8]>, -{ - pub fn new( - client: T, - uuid: [u8; 16], - version: u32, - full_version: &'static str, - status: S, - ) -> Self { - Self { - trussed: client, - uuid, - version, - full_version, - status, - boot_interface: PhantomData, - se050: (), - } - } -} - -#[cfg(feature = "se050")] -impl App> -where - T: TrussedClient, - R: Reboot, - S: AsRef<[u8]>, - Twi: I2CForT1, - D: DelayUs, -{ - pub fn with_se( - client: T, - uuid: [u8; 16], - version: u32, - full_version: &'static str, - status: S, - se050: Se05X, - ) -> Self { - Self { - trussed: client, - uuid, - version, - full_version, - status, - boot_interface: PhantomData, - se050, - } - } -} - -impl App -where - T: TrussedClient, - R: Reboot, - S: AsRef<[u8]>, - Se05X: MaybeSe, { fn user_present(&mut self) -> bool { let user_present = syscall!(self @@ -288,9 +217,15 @@ where response.extend_from_slice(self.status.as_ref()).ok(); } Command::TestSe05X => { - debug_now!("Running se050 tests"); - if let Err(_err) = self.se050.run_tests(response) { - debug_now!("se050 tests failed: {_err:?}"); + #[cfg(feature = "se050")] + { + let rep = syscall!(self.trussed.test_se050()); + response.extend_from_slice(&rep.reply).ok(); + return Ok(()); + } + #[cfg(not(feature = "se050"))] + { + return Err(Error::NotAvailable); } } } @@ -298,12 +233,11 @@ where } } -impl hid::App<'static> for App +impl hid::App<'static> for App where T: TrussedClient, R: Reboot, S: AsRef<[u8]>, - Se: MaybeSe, { fn commands(&self) -> &'static [HidCommand] { &[ @@ -342,12 +276,11 @@ where } } -impl iso7816::App for App +impl iso7816::App for App where T: TrussedClient, R: Reboot, S: AsRef<[u8]>, - Se: MaybeSe, { // Solo management app fn aid(&self) -> iso7816::Aid { @@ -355,12 +288,11 @@ where } } -impl apdu::App<{ command::SIZE }, { response::SIZE }> for App +impl apdu::App<{ command::SIZE }, { response::SIZE }> for App where T: TrussedClient, R: Reboot, S: AsRef<[u8]>, - Se: MaybeSe, { fn select(&mut self, _apdu: &ApduCommand, _reply: &mut response::Data) -> apdu::Result { Ok(()) diff --git a/src/lib.rs b/src/lib.rs index ec8a355..485dc7b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,3 +12,13 @@ generate_macros!(); mod admin; pub use admin::{App, Reboot}; + +#[cfg(not(feature = "se050"))] +pub trait Client: trussed::Client {} +#[cfg(not(feature = "se050"))] +impl Client for C {} + +#[cfg(feature = "se050")] +pub trait Client: trussed::Client + trussed_se050_backend::manage::ManageClient {} +#[cfg(feature = "se050")] +impl Client for C {} From 299fd5f754d64ea6fc23baafcff3ab3b603d5a77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 14 Sep 2023 14:52:05 +0200 Subject: [PATCH 18/65] Remove default SE050 feature --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a7ceeba..1467369 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ rand_chacha = { version = "0.3.1", optional = true, default-features = false } trussed-se050-backend = { version = "0.1.0", optional = true } [features] -default = ["se050"] +default = [] se050 = ["trussed-se050-backend"] log-all = [] From 1877e3df6dd89603bae9720cf1a6bdaeee3334da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 14 Sep 2023 15:03:44 +0200 Subject: [PATCH 19/65] Add back `new` method --- src/admin.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/admin.rs b/src/admin.rs index ee11ba4..c9842e9 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -158,6 +158,22 @@ where R: Reboot, S: AsRef<[u8]>, { + pub fn new( + client: T, + uuid: [u8; 16], + version: u32, + full_version: &'static str, + status: S, + ) -> Self { + Self { + trussed: client, + uuid, + version, + full_version, + status, + boot_interface: PhantomData, + } + } fn user_present(&mut self) -> bool { let user_present = syscall!(self .trussed From 5a8aedd1a6ec3d72df1954e8e13fb82982a946ed Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 12 Sep 2023 15:01:42 +0200 Subject: [PATCH 20/65] Add generic config mechanism This patch adds a generic configuration mechanism to the admin app. There are two new commands, GET_CONFIG and SET_CONFIG, that can be used to query and change configuration values by key. The runner is responsible for providing the configuration struct and a mapping from key to field. --- Cargo.toml | 4 ++ src/admin.rs | 118 ++++++++++++++++++++++++++++++++++++-------- src/config.rs | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 ++ 4 files changed, 238 insertions(+), 21 deletions(-) create mode 100644 src/config.rs diff --git a/Cargo.toml b/Cargo.toml index 1467369..50040b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,9 +11,13 @@ description = "Administrative Trussed app for SoloKeys Solo 2 security keys" [dependencies] apdu-dispatch = "0.1" +cbor-smol = "0.4.0" ctaphid-dispatch = "0.1" delog = "0.1" iso7816 = "0.1" +littlefs2 = "0.4" +serde = { version = "1.0.180", default-features = false } +strum_macros = "0.25.2" trussed = "0.1" se05x = { version = "0.0.1", optional = true } diff --git a/src/admin.rs b/src/admin.rs index c9842e9..eabd82d 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -1,10 +1,14 @@ use super::Client as TrussedClient; use apdu_dispatch::iso7816::Status; use apdu_dispatch::{app as apdu, command, response, Command as ApduCommand}; +use cbor_smol::cbor_deserialize; use core::{convert::TryInto, marker::PhantomData, time::Duration}; use ctaphid_dispatch::app::{self as hid, Command as HidCommand, Message}; use ctaphid_dispatch::command::VendorCommand; -use trussed::{interrupt::InterruptFlag, syscall, types::Vec}; +use serde::Deserialize; +use trussed::{interrupt::InterruptFlag, store::filestore::Filestore, syscall, types::Vec}; + +use crate::config::{self, Config, ConfigError}; pub const USER_PRESENCE_TIMEOUT_SECS: u32 = 15; @@ -13,6 +17,8 @@ pub const USER_PRESENCE_TIMEOUT_SECS: u32 = 15; const ADMIN: VendorCommand = VendorCommand::H72; const STATUS: u8 = 0x80; const TEST_SE050: u8 = 0x81; +const GET_CONFIG: u8 = 0x82; +const SET_CONFIG: u8 = 0x83; // For compatibility, old commands are also available directly as separate vendor commands. const UPDATE: VendorCommand = VendorCommand::H51; @@ -27,6 +33,8 @@ const WINK: HidCommand = HidCommand::Wink; // 0x08 const RNG_DATA_LEN: usize = 57; +const CONFIG_OK: u8 = 0x00; + #[derive(PartialEq, Debug)] enum Command { Update, @@ -38,6 +46,8 @@ enum Command { Wink, Status, TestSe05X, + GetConfig, + SetConfig, } impl TryFrom for Command { @@ -55,6 +65,8 @@ impl TryFrom for Command { match command { STATUS => Ok(Command::Status), TEST_SE050 => Ok(Command::TestSe05X), + GET_CONFIG => Ok(Command::GetConfig), + SET_CONFIG => Ok(Command::SetConfig), _ => Err(Error::UnsupportedCommand), } } @@ -115,6 +127,12 @@ impl From for Status { } } +#[derive(Debug, Deserialize)] +struct SetConfigRequest<'a> { + key: &'a str, + value: &'a str, +} + pub trait Reboot { /// Reboots the device. fn reboot() -> !; @@ -138,32 +156,43 @@ pub trait Reboot { fn locked() -> bool; } -pub struct App -where - T: TrussedClient, - R: Reboot, - S: AsRef<[u8]>, -{ +pub struct App { trussed: T, uuid: [u8; 16], version: u32, full_version: &'static str, status: S, boot_interface: PhantomData, + config: C, } -impl App +impl App where T: TrussedClient, R: Reboot, S: AsRef<[u8]>, + C: Config, { - pub fn new( + pub fn load( client: T, + filestore: &mut F, uuid: [u8; 16], version: u32, full_version: &'static str, status: S, + ) -> Self { + config::load(filestore) + .map(|config| Self::new(client, uuid, version, full_version, status, config)) + .unwrap() + } + + fn new( + client: T, + uuid: [u8; 16], + version: u32, + full_version: &'static str, + status: S, + config: C, ) -> Self { Self { trussed: client, @@ -172,8 +201,14 @@ where full_version, status, boot_interface: PhantomData, + config, } } + + pub fn config(&self) -> &C { + &self.config + } + fn user_present(&mut self) -> bool { let user_present = syscall!(self .trussed @@ -185,7 +220,7 @@ where fn exec( &mut self, command: Command, - flag: Option, + input: &[u8], response: &mut Vec, ) -> Result<(), Error> { debug_now!("Executing command: {command:?}"); @@ -202,7 +237,7 @@ where } Command::Update => { if self.user_present() { - if flag == Some(0x01) { + if input.first().copied() == Some(0x01) { R::reboot_to_firmware_update_destructive(); } else { R::reboot_to_firmware_update(); @@ -217,7 +252,7 @@ where } Command::Version => { // GET VERSION - if flag == Some(0x01) { + if input.first().copied() == Some(0x01) { response .extend_from_slice(self.full_version.as_bytes()) .ok(); @@ -244,16 +279,49 @@ where return Err(Error::NotAvailable); } } + Command::GetConfig => { + // Response: 1 status byte, then data if status == 0 + response.push(CONFIG_OK).ok(); + if let Err(error) = self.get_config(input, response) { + response.clear(); + response.push(error.into()).ok(); + } + } + Command::SetConfig => { + // Response: 1 status byte + let status = match self.set_config(input) { + Ok(()) => CONFIG_OK, + Err(error) => error.into(), + }; + response.push(status).ok(); + } } Ok(()) } + + fn get_config( + &mut self, + input: &[u8], + response: &mut Vec, + ) -> Result<(), ConfigError> { + let key = core::str::from_utf8(input).map_err(|_| ConfigError::InvalidKey)?; + config::get(&mut self.config, key, response) + } + + fn set_config(&mut self, input: &[u8]) -> Result<(), ConfigError> { + let request: SetConfigRequest<'_> = + cbor_deserialize(input).map_err(|_| ConfigError::DeserializationFailed)?; + config::set(&mut self.config, request.key, request.value)?; + config::save(&mut self.trussed, &self.config) + } } -impl hid::App<'static> for App +impl hid::App<'static> for App where T: TrussedClient, R: Reboot, S: AsRef<[u8]>, + C: Config, { fn commands(&self) -> &'static [HidCommand] { &[ @@ -274,17 +342,16 @@ where input_data: &Message, response: &mut Message, ) -> hid::AppResult { - let (command, flag) = if command == HidCommand::Vendor(ADMIN) { + let (command, input) = if command == HidCommand::Vendor(ADMIN) { // new mode: first input byte specifies the actual command let (command, input) = input_data.split_first().ok_or(Error::InvalidLength)?; let command = Command::try_from(*command)?; - (command, input.first()) + (command, input) } else { // old mode: directly use vendor commands + wink - (Command::try_from(command)?, input_data.first()) + (Command::try_from(command)?, input_data.as_slice()) }; - self.exec(command, flag.copied(), response) - .map_err(From::from) + self.exec(command, input, response).map_err(From::from) } fn interrupt(&self) -> Option<&'static InterruptFlag> { @@ -292,7 +359,7 @@ where } } -impl iso7816::App for App +impl iso7816::App for App where T: TrussedClient, R: Reboot, @@ -304,11 +371,12 @@ where } } -impl apdu::App<{ command::SIZE }, { response::SIZE }> for App +impl apdu::App<{ command::SIZE }, { response::SIZE }> for App where T: TrussedClient, R: Reboot, S: AsRef<[u8]>, + C: Config, { fn select(&mut self, _apdu: &ApduCommand, _reply: &mut response::Data) -> apdu::Result { Ok(()) @@ -330,6 +398,14 @@ where return Err(Status::ConditionsOfUseNotSatisfied); } - self.exec(command, Some(apdu.p1), reply).map_err(From::from) + // The Update and Version commands use the P1 value to select an operation mode. As we + // cannot model this in the CTAPHID application, we pretend that we received the flag as + // the command payload. + if command == Command::Update || command == Command::Version { + self.exec(command, &[apdu.p1], reply) + } else { + self.exec(command, apdu.data().as_slice(), reply) + } + .map_err(From::from) } } diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..61b0e2c --- /dev/null +++ b/src/config.rs @@ -0,0 +1,134 @@ +use core::{ + fmt::{self, Display, Formatter, Write as _}, + str::FromStr, +}; + +use cbor_smol::{cbor_deserialize, cbor_serialize_bytes}; +use littlefs2::{path, path::Path}; +use serde::{de::DeserializeOwned, Serialize}; +use strum_macros::FromRepr; +use trussed::{ + store::filestore::Filestore, + try_syscall, + types::{Location, Message, Vec}, + Client, +}; + +const LOCATION: Location = Location::Internal; +const FILENAME: &Path = path!("config"); + +pub trait Config: Default + PartialEq + DeserializeOwned + Serialize { + fn field(&mut self, key: &str) -> Option>; +} + +impl Config for () { + fn field(&mut self, _key: &str) -> Option> { + None + } +} + +#[derive(Debug, Serialize)] +pub enum ConfigValueMut<'a> { + Bool(&'a mut bool), + U8(&'a mut u8), +} + +impl<'a> ConfigValueMut<'a> { + fn set(&mut self, value: &str) -> Result<(), ConfigError> { + fn set_value(target: &mut T, s: &str) -> Result<(), ConfigError> { + *target = s.parse().map_err(|_| ConfigError::InvalidValue)?; + Ok(()) + } + + match self { + Self::Bool(r) => set_value(*r, value), + Self::U8(r) => set_value(*r, value), + } + } +} + +impl<'a> Display for ConfigValueMut<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Bool(value) => write!(f, "{}", value), + Self::U8(value) => write!(f, "{}", value), + } + } +} + +#[derive(Debug, FromRepr)] +#[repr(u8)] +pub enum ConfigError { + ReadFailed = 1, + WriteFailed = 2, + DeserializationFailed = 3, + SerializationFailed = 4, + InvalidKey = 5, + InvalidValue = 6, + DataTooLong = 7, +} + +const _: () = assert!( + ConfigError::from_repr(0).is_none(), + "ConfigError may not have a variant with discriminant zero as zero indicates success.", +); + +impl From for u8 { + fn from(error: ConfigError) -> u8 { + error as _ + } +} + +pub fn get( + config: &mut C, + key: &str, + response: &mut Vec, +) -> Result<(), ConfigError> { + let field = config.field(key).ok_or(ConfigError::InvalidKey)?; + write!(response, "{}", field).map_err(|_| ConfigError::DataTooLong) +} + +pub fn set(config: &mut C, key: &str, value: &str) -> Result<(), ConfigError> { + config.field(key).ok_or(ConfigError::InvalidKey)?.set(value) +} + +pub fn load(store: &mut F) -> Result { + let Some(data) = load_if_exists(store, LOCATION, FILENAME)? else { + return Ok(Default::default()); + }; + cbor_deserialize(&data).map_err(|_| ConfigError::DeserializationFailed) +} + +pub fn save(client: &mut T, config: &C) -> Result<(), ConfigError> { + if config == &Default::default() { + if exists(client, LOCATION, FILENAME)? { + try_syscall!(client.remove_file(LOCATION, FILENAME.into())) + .map_err(|_| ConfigError::WriteFailed)?; + } + } else { + let data = cbor_serialize_bytes(config).map_err(|_| ConfigError::SerializationFailed)?; + try_syscall!(client.write_file(LOCATION, FILENAME.into(), data, None)) + .map_err(|_| ConfigError::WriteFailed)?; + } + Ok(()) +} + +fn exists(client: &mut T, location: Location, path: &Path) -> Result { + try_syscall!(client.entry_metadata(location, path.into())) + .map(|r| r.metadata.is_some()) + .map_err(|_| ConfigError::ReadFailed) +} + +fn load_if_exists( + store: &mut F, + location: Location, + path: &Path, +) -> Result, ConfigError> { + store.read(path, location).map(Some).or_else(|_| { + if store.exists(path, location) { + Err(ConfigError::ReadFailed) + } else { + Ok(None) + } + }) +} diff --git a/src/lib.rs b/src/lib.rs index 485dc7b..e9c72c6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,10 @@ extern crate delog; generate_macros!(); mod admin; +mod config; + pub use admin::{App, Reboot}; +pub use config::{Config, ConfigError, ConfigValueMut}; #[cfg(not(feature = "se050"))] pub trait Client: trussed::Client {} From 83863e5446fa2a1b367662b4b96282173ad37f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 28 Sep 2023 16:23:13 +0200 Subject: [PATCH 21/65] Remove outdated run_tests.rs file --- src/admin/run_tests.rs | 1457 ---------------------------------------- 1 file changed, 1457 deletions(-) delete mode 100644 src/admin/run_tests.rs diff --git a/src/admin/run_tests.rs b/src/admin/run_tests.rs deleted file mode 100644 index 86da0a5..0000000 --- a/src/admin/run_tests.rs +++ /dev/null @@ -1,1457 +0,0 @@ -use ctaphid_dispatch::types::Error; -use iso7816::Status; -use trussed::types::Vec; - -pub trait RunTests { - fn run_tests(&mut self, _response: &mut Vec) -> Result<(), Error> { - debug_now!("Default run tests"); - Err(Error::InvalidCommand) - } - - fn run_tests_internal( - &mut self, - _response: &mut Vec, - ) -> Result<(), Status> { - Err(Status::NotFound) - } -} - -impl RunTests for () {} - -#[cfg(feature = "se050")] -mod se050_tests { - use super::*; - - use embedded_hal::blocking::delay::DelayUs; - use se05x::{ - se05x::{ - commands::{ - CreateSession, DeleteAll, DeleteSecureObject, EcdsaSign, EcdsaVerify, GetRandom, - ReadIdList, ReadObject, VerifySessionUserId, WriteBinary, WriteEcKey, WriteUserId, - }, - policies::{ObjectAccessRule, ObjectPolicyFlags, Policy, PolicySet}, - EcCurve, EcDsaSignatureAlgo, ObjectId, P1KeyType, ProcessSessionCmd, Se05XResult, - }, - t1::I2CForT1, - }; - - use se05x::se05x::Se05X; - - use hex_literal::hex; - - const BUFFER_LEN: usize = 1024; - - #[derive(Debug)] - #[repr(u8)] - enum Advance { - Enable = 1, - Random1, - Random2, - Random3, - WriteUserId, - CreateSession, - VerifySessionUserId, - DeleteAll, - List, - WriteBinary1, - ReadBinary1, - DeleteBinary1, - WriteBinary2, - ReadBinary2, - DeleteBinary2, - WriteBinary3, - ReadBinary3, - DeleteBinary3, - CreateP256, - ListP256, - GenerateP256, - EcDsaP256, - VerifyP256, - DeleteP256, - CreateP521, - GenerateP521, - EcDsaP521, - VerifyP521, - DeleteP521, - RecreationWriteUserId, - RecreationWriteBinary, - RecreationDeleteAttempt, - RecreationDeleteUserId, - RecreationRecreateUserId, - RecreationCreateSession, - RecreationAuthSession, - RecreationDeleteAttack, - Rsa2048Gen, - Rsa2048Sign, - Rsa2048Verify, - Rsa2048Encrypt, - Rsa2048Decrypt, - Rsa2048Delete, - Rsa3072Gen, - Rsa3072Sign, - Rsa3072Verify, - Rsa3072Encrypt, - Rsa3072Decrypt, - Rsa3072Delete, - Rsa4096Gen, - Rsa4096Sign, - Rsa4096Verify, - Rsa4096Encrypt, - Rsa4096Decrypt, - Rsa4096Delete, - SymmWrite, - SymmEncryptOneShot, - SymmDecryptOneShot, - SymmEncryptCreate, - SymmEncryptInit, - SymmEncryptUpdate1, - SymmEncryptUpdate2, - SymmEncryptFinal, - SymmEncryptDelete, - SymmDecryptCreate, - SymmDecryptInit, - SymmDecryptUpdate1, - SymmDecryptUpdate2, - SymmDecryptFinal, - SymmDecryptDelete, - SymmDelete, - MacWrite, - MacSignOneShot, - MacVerifyOneShot, - MacSignCreate, - MacSignInit, - MacSignUpdate1, - MacSignUpdate2, - MacSignFinal, - MacSignDelete, - MacVerifyCreate, - MacVerifyInit, - MacVerifyUpdate1, - MacVerifyUpdate2, - MacVerifyFinal, - MacVerifyDelete, - MacDelete, - AesSessionCreateKey, - AesSessionCreateBinary, - AesSessionCreateSession, - AesSessionAuthenticate, - AesSessionReadBinary, - AesSessionUpdateKey, - AesSessionCloseSession, - AesSessionRecreateSession, - AesSessionReAuthenticate, - AesSessionReadBinary2, - AesSessionDeleteBinary, - AesSessionDeleteKey, - Pbkdf2WritePin, - Pbkdf2Calculate, - Pbkdf2Compare, - Pbkdf2DeletePin, - ImportWrite, - ImportCipher, - ImportExport, - ImportDelete, - ImportDeletionWorked, - ImportImport, - ImportCipher2, - ImportComp, - ImportDeleteFinal, - } - - impl> RunTests for Se05X { - fn run_tests(&mut self, response: &mut Vec) -> Result<(), Error> { - debug_now!("Se05X run tests"); - match self.run_tests_internal(response) { - Ok(()) => Ok(()), - Err(err) => { - response.push(0).ok(); - let sw: [u8; 2] = err.into(); - response.extend_from_slice(&sw).ok(); - Ok(()) - } - } - } - fn run_tests_internal( - &mut self, - response: &mut Vec, - ) -> Result<(), Status> { - let atr = self.enable()?; - response - .extend_from_slice(&[ - atr.major, - atr.minor, - atr.patch, - atr.secure_box_major, - atr.secure_box_minor, - ]) - .ok(); - run_free_mem(self, response)?; - response.push(Advance::Enable as _).ok(); - run_get_random(self, response)?; - run_factory_reset(self, response)?; - run_list(self, response)?; - run_binary(self, response)?; - run_ecc(self, response)?; - run_userid_recreation(self, response)?; - run_rsa2048(self, response)?; - run_rsa3072(self, response)?; - run_rsa4096(self, response)?; - run_symm(self, response)?; - run_mac(self, response)?; - run_aes_session(self, response)?; - run_pbkdf(self, response)?; - run_export_import(self, response)?; - Ok(()) - } - } - fn run_free_mem, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, - ) -> Result<(), Status> { - use se05x::se05x::{commands::GetFreeMemory, Memory}; - - let mut buf = [b'a'; BUFFER_LEN]; - let mem = se050.run_command( - &GetFreeMemory { - memory: Memory::Persistent, - }, - &mut buf, - )?; - response - .extend_from_slice(&mem.available.0.to_be_bytes()) - .ok(); - let mem = se050.run_command( - &GetFreeMemory { - memory: Memory::TransientReset, - }, - &mut buf, - )?; - response - .extend_from_slice(&mem.available.0.to_be_bytes()) - .ok(); - let mem = se050.run_command( - &GetFreeMemory { - memory: Memory::TransientDeselect, - }, - &mut buf, - )?; - response - .extend_from_slice(&mem.available.0.to_be_bytes()) - .ok(); - Ok(()) - } - - fn run_get_random, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, - ) -> Result<(), Status> { - let mut buf = [b'a'; BUFFER_LEN]; - let lens = [1, 256, 800]; - let advance = [Advance::Random1, Advance::Random2, Advance::Random3]; - for (len, advance) in lens.into_iter().zip(advance) { - let res = se050.run_command( - &GetRandom { - length: (len as u16).into(), - }, - &mut buf, - )?; - response.push(advance as u8).ok(); - if res.data == &[b'a'; BUFFER_LEN][..len] { - debug!("Failed to get random"); - response.extend_from_slice(&[0, 0, 0]).ok(); - return Ok(()); - } - } - Ok(()) - } - - fn run_factory_reset, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, - ) -> Result<(), Status> { - let mut buf = [b'a'; BUFFER_LEN]; - let data = &hex!("31323334"); - - se050.run_command( - &WriteUserId { - policy: None, - max_attempts: None, - object_id: ObjectId::FACTORY_RESET, - data, - }, - &mut buf, - )?; - response.push(Advance::WriteUserId as u8).ok(); - let session = se050.run_command( - &CreateSession { - object_id: ObjectId::FACTORY_RESET, - }, - &mut buf, - )?; - response.push(Advance::CreateSession as u8).ok(); - - se050.run_command( - &ProcessSessionCmd { - session_id: session.session_id, - apdu: VerifySessionUserId { user_id: data }, - }, - &mut buf, - )?; - response.push(Advance::VerifySessionUserId as u8).ok(); - - se050.run_command( - &ProcessSessionCmd { - session_id: session.session_id, - apdu: DeleteAll {}, - }, - &mut buf, - )?; - response.push(Advance::DeleteAll as u8).ok(); - Ok(()) - } - - fn run_list, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, - ) -> Result<(), Status> { - let mut buf = [0; 200]; - se050.run_command( - &ReadIdList { - offset: 0.into(), - filter: se05x::se05x::SecureObjectFilter::All, - }, - &mut buf, - )?; - response.push(Advance::List as u8).ok(); - Ok(()) - } - - fn run_binary, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, - ) -> Result<(), Status> { - let mut buf = [b'a'; 400]; - let buf2 = [b'b'; 400]; - let object_id = ObjectId(hex!("01020304")); - let policy = &[Policy { - object_id: ObjectId::INVALID, - access_rule: ObjectAccessRule::from_flags( - ObjectPolicyFlags::ALLOW_DELETE | ObjectPolicyFlags::ALLOW_READ, - ), - }]; - for (((len, advance_write), advance_read), advance_delete) in [1, 255, 300] - .into_iter() - .zip([ - Advance::WriteBinary1, - Advance::WriteBinary2, - Advance::WriteBinary3, - ]) - .zip([ - Advance::ReadBinary1, - Advance::ReadBinary2, - Advance::ReadBinary3, - ]) - .zip([ - Advance::DeleteBinary1, - Advance::DeleteBinary2, - Advance::DeleteBinary3, - ]) - { - se050.run_command( - &WriteBinary { - transient: false, - policy: Some(PolicySet(policy)), - object_id, - offset: None, - file_length: Some(len.into()), - data: Some(&buf2[..len.into()]), - }, - &mut buf, - )?; - response.push(advance_write as u8).ok(); - let res = se050.run_command( - &ReadObject { - object_id, - offset: None, - length: Some(len.into()), - rsa_key_component: None, - }, - &mut buf, - )?; - response.push(advance_read as u8).ok(); - if res.data[..len.into()] != buf2[..len.into()] { - return Err(0x3001.into()); - } - - se050.run_command(&DeleteSecureObject { object_id }, &mut buf)?; - response.push(advance_delete as u8).ok(); - } - Ok(()) - } - - fn run_ecc, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, - ) -> Result<(), Status> { - use se05x::se05x::commands::ReadEcCurveList; - - let mut buf = [0; 200]; - let mut buf2 = [0; 200]; - let object_id = ObjectId(hex!("01020304")); - - // *********** P256 *********** // - - se050.create_and_set_curve(EcCurve::NistP256)?; - response.push(Advance::CreateP256 as u8).ok(); - let _res = se050.run_command(&ReadEcCurveList {}, &mut buf)?; - debug_now!("Ec curves list: {:?}", _res); - response.push(Advance::ListP256 as u8).ok(); - se050.run_command( - &WriteEcKey { - transient: false, - is_auth: false, - key_type: Some(P1KeyType::KeyPair), - policy: None, - max_attempts: None, - object_id, - curve: Some(EcCurve::NistP256), - private_key: None, - public_key: None, - }, - &mut buf, - )?; - response.push(Advance::GenerateP256 as u8).ok(); - let res = se050.run_command( - &EcdsaSign { - key_id: object_id, - data: &[52; 32], - algo: EcDsaSignatureAlgo::Sha256, - }, - &mut buf, - )?; - response.push(Advance::EcDsaP256 as u8).ok(); - let res = se050.run_command( - &EcdsaVerify { - key_id: object_id, - data: &[52; 32], - algo: EcDsaSignatureAlgo::Sha256, - signature: res.signature, - }, - &mut buf2, - )?; - if res.result != Se05XResult::Success { - return Err(0x3002.into()); - } - response.push(Advance::VerifyP256 as u8).ok(); - se050.run_command(&DeleteSecureObject { object_id }, &mut buf)?; - response.push(Advance::DeleteP256 as u8).ok(); - - // *********** P521 *********** // - - se050.create_and_set_curve(EcCurve::NistP521)?; - response.push(Advance::CreateP521 as u8).ok(); - se050.run_command( - &WriteEcKey { - transient: false, - is_auth: false, - key_type: Some(P1KeyType::KeyPair), - policy: None, - max_attempts: None, - object_id, - curve: Some(EcCurve::NistP521), - private_key: None, - public_key: None, - }, - &mut buf, - )?; - response.push(Advance::GenerateP521 as u8).ok(); - let res = se050.run_command( - &EcdsaSign { - key_id: object_id, - data: &[52; 64], - algo: EcDsaSignatureAlgo::Sha512, - }, - &mut buf, - )?; - response.push(Advance::EcDsaP521 as u8).ok(); - let res = se050.run_command( - &EcdsaVerify { - key_id: object_id, - data: &[52; 64], - algo: EcDsaSignatureAlgo::Sha512, - signature: res.signature, - }, - &mut buf2, - )?; - if res.result != Se05XResult::Success { - return Err(0x3003.into()); - } - response.push(Advance::VerifyP521 as u8).ok(); - se050.run_command(&DeleteSecureObject { object_id }, &mut buf)?; - response.push(Advance::DeleteP521 as u8).ok(); - Ok(()) - } - - fn run_userid_recreation, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, - ) -> Result<(), Status> { - let mut buf = [0; BUFFER_LEN]; - let object_id = ObjectId(hex!("01020304")); - let user_id = ObjectId(hex!("01223344")); - let user_id_good_value = hex!("31323334"); - let user_id_bad_value = hex!("FFFFFFFF"); - let policy_user_id = &[Policy { - object_id: ObjectId::INVALID, - access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_DELETE), - }]; - se050.run_command( - &WriteUserId { - policy: Some(PolicySet(policy_user_id)), - max_attempts: None, - object_id: user_id, - data: &user_id_good_value, - }, - &mut buf, - )?; - response.push(Advance::RecreationWriteUserId as u8).ok(); - let policy = &[Policy { - object_id: user_id, - access_rule: ObjectAccessRule::from_flags( - ObjectPolicyFlags::ALLOW_DELETE | ObjectPolicyFlags::ALLOW_READ, - ), - }]; - se050.run_command( - &WriteBinary { - transient: false, - policy: Some(PolicySet(policy)), - object_id, - offset: None, - file_length: Some(2.into()), - data: Some(&[1, 2]), - }, - &mut buf, - )?; - response.push(Advance::RecreationWriteBinary as u8).ok(); - match se050.run_command( - &ReadObject { - object_id, - offset: Some(0.into()), - length: Some(2.into()), - rsa_key_component: None, - }, - &mut buf, - ) { - Ok(_) => return Err(0x3004.into()), - Err(se05x::se05x::Error::Status(Status::CommandNotAllowedNoEf)) => {} - Err(_err) => { - debug_now!("Got unexpected error: {_err:?}"); - return Err(0x3007.into()); - } - } - response.push(Advance::RecreationDeleteAttempt as u8).ok(); - se050.run_command(&DeleteSecureObject { object_id: user_id }, &mut buf)?; - response.push(Advance::RecreationDeleteUserId as u8).ok(); - se050.run_command( - &WriteUserId { - policy: None, - max_attempts: None, - object_id: user_id, - data: &user_id_bad_value, - }, - &mut buf, - )?; - response.push(Advance::RecreationRecreateUserId as u8).ok(); - - let session = se050.run_command(&CreateSession { object_id: user_id }, &mut buf)?; - response.push(Advance::RecreationCreateSession as u8).ok(); - - se050.run_command( - &ProcessSessionCmd { - session_id: session.session_id, - apdu: VerifySessionUserId { - user_id: &user_id_bad_value, - }, - }, - &mut buf, - )?; - response.push(Advance::RecreationAuthSession as u8).ok(); - - let attack = se050.run_command( - &ProcessSessionCmd { - session_id: session.session_id, - apdu: ReadObject { - object_id, - offset: Some(0.into()), - length: Some(2.into()), - rsa_key_component: None, - }, - }, - &mut buf, - ); - - match attack { - Ok(_) => {} - Err(se05x::se05x::Error::Status(Status::CommandNotAllowedNoEf)) => {} - Err(_err) => { - debug_now!("Got unexpected error: {_err:?}"); - return Err(0x3006.into()); - } - } - response.push(Advance::RecreationDeleteAttack as u8).ok(); - Ok(()) - } - - fn run_rsa2048, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, - ) -> Result<(), Status> { - use se05x::se05x::{ - commands::{GenRsaKey, RsaDecrypt, RsaEncrypt, RsaSign, RsaVerify}, - RsaEncryptionAlgo, RsaSignatureAlgo, - }; - - let mut buf = [0; 1000]; - let mut buf2 = [0; 1000]; - let object_id = ObjectId(hex!("02334455")); - se050.run_command( - &GenRsaKey { - transient: false, - is_auth: false, - policy: None, - max_attempts: None, - object_id, - key_size: Some(2048.into()), - }, - &mut buf, - )?; - response.push(Advance::Rsa2048Gen as u8).ok(); - let res = se050.run_command( - &RsaSign { - key_id: object_id, - data: &[52; 32], - algo: RsaSignatureAlgo::RsaSha256Pkcs1, - }, - &mut buf, - )?; - response.push(Advance::Rsa2048Sign as u8).ok(); - let res = se050.run_command( - &RsaVerify { - key_id: object_id, - data: &[52; 32], - algo: RsaSignatureAlgo::RsaSha256Pkcs1, - signature: res.signature, - }, - &mut buf2, - )?; - if res.result != Se05XResult::Success { - return Err((0x3000 + line!() as u16).into()); - } - response.push(Advance::Rsa2048Verify as u8).ok(); - let res = se050.run_command( - &RsaEncrypt { - key_id: object_id, - plaintext: &[52; 32], - algo: RsaEncryptionAlgo::Pkcs1, - }, - &mut buf2, - )?; - response.push(Advance::Rsa2048Encrypt as u8).ok(); - let res = se050.run_command( - &RsaDecrypt { - key_id: object_id, - algo: RsaEncryptionAlgo::Pkcs1, - ciphertext: res.ciphertext, - }, - &mut buf, - )?; - if res.plaintext != [52; 32] { - return Err(0x3008.into()); - } - response.push(Advance::Rsa2048Decrypt as u8).ok(); - - se050.run_command(&DeleteSecureObject { object_id }, &mut buf)?; - response.push(Advance::Rsa2048Delete as u8).ok(); - - Ok(()) - } - - fn run_rsa3072, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, - ) -> Result<(), Status> { - use se05x::se05x::{ - commands::{GenRsaKey, RsaDecrypt, RsaEncrypt, RsaSign, RsaVerify}, - RsaEncryptionAlgo, RsaSignatureAlgo, - }; - - let mut buf = [0; 1000]; - let mut buf2 = [0; 1000]; - let object_id = ObjectId(hex!("02334455")); - se050.run_command( - &GenRsaKey { - transient: false, - is_auth: false, - policy: None, - max_attempts: None, - object_id, - key_size: Some(3072.into()), - }, - &mut buf, - )?; - response.push(Advance::Rsa3072Gen as u8).ok(); - let res = se050.run_command( - &RsaSign { - key_id: object_id, - data: &[52; 32], - algo: RsaSignatureAlgo::RsaSha256Pkcs1, - }, - &mut buf, - )?; - response.push(Advance::Rsa3072Sign as u8).ok(); - let res = se050.run_command( - &RsaVerify { - key_id: object_id, - data: &[52; 32], - algo: RsaSignatureAlgo::RsaSha256Pkcs1, - signature: res.signature, - }, - &mut buf2, - )?; - if res.result != Se05XResult::Success { - return Err((0x3000 + line!() as u16).into()); - } - response.push(Advance::Rsa3072Verify as u8).ok(); - let res = se050.run_command( - &RsaEncrypt { - key_id: object_id, - plaintext: &[52; 32], - algo: RsaEncryptionAlgo::Pkcs1, - }, - &mut buf2, - )?; - response.push(Advance::Rsa3072Encrypt as u8).ok(); - let res = se050.run_command( - &RsaDecrypt { - key_id: object_id, - algo: RsaEncryptionAlgo::Pkcs1, - ciphertext: res.ciphertext, - }, - &mut buf, - )?; - if res.plaintext != [52; 32] { - return Err(0x3008.into()); - } - response.push(Advance::Rsa3072Decrypt as u8).ok(); - - se050.run_command(&DeleteSecureObject { object_id }, &mut buf)?; - response.push(Advance::Rsa3072Delete as u8).ok(); - - Ok(()) - } - - fn run_rsa4096, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, - ) -> Result<(), Status> { - use se05x::se05x::{ - commands::{GenRsaKey, RsaDecrypt, RsaEncrypt, RsaSign, RsaVerify}, - RsaEncryptionAlgo, RsaSignatureAlgo, - }; - - let mut buf = [0; 1000]; - let mut buf2 = [0; 1000]; - let object_id = ObjectId(hex!("02334455")); - se050.run_command( - &GenRsaKey { - transient: false, - is_auth: false, - policy: None, - max_attempts: None, - object_id, - key_size: Some(4096.into()), - }, - &mut buf, - )?; - response.push(Advance::Rsa4096Gen as u8).ok(); - let res = se050.run_command( - &RsaSign { - key_id: object_id, - data: &[52; 32], - algo: RsaSignatureAlgo::RsaSha256Pkcs1, - }, - &mut buf, - )?; - response.push(Advance::Rsa4096Sign as u8).ok(); - let res = se050.run_command( - &RsaVerify { - key_id: object_id, - data: &[52; 32], - algo: RsaSignatureAlgo::RsaSha256Pkcs1, - signature: res.signature, - }, - &mut buf2, - )?; - if res.result != Se05XResult::Success { - return Err((0x3000 + line!() as u16).into()); - } - response.push(Advance::Rsa4096Verify as u8).ok(); - let res = se050.run_command( - &RsaEncrypt { - key_id: object_id, - plaintext: &[52; 32], - algo: RsaEncryptionAlgo::Pkcs1, - }, - &mut buf2, - )?; - response.push(Advance::Rsa4096Encrypt as u8).ok(); - let res = se050.run_command( - &RsaDecrypt { - key_id: object_id, - algo: RsaEncryptionAlgo::Pkcs1, - ciphertext: res.ciphertext, - }, - &mut buf, - )?; - if res.plaintext != [52; 32] { - return Err(0x3008.into()); - } - response.push(Advance::Rsa4096Decrypt as u8).ok(); - - se050.run_command(&DeleteSecureObject { object_id }, &mut buf)?; - response.push(Advance::Rsa4096Delete as u8).ok(); - - Ok(()) - } - - fn run_symm, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, - ) -> Result<(), Status> { - use se05x::se05x::{ - commands::{ - CipherDecryptInit, CipherEncryptInit, CipherFinal, CipherOneShotDecrypt, - CipherOneShotEncrypt, CipherUpdate, CreateCipherObject, DeleteCryptoObj, - WriteSymmKey, - }, - CipherMode, CryptoObjectId, SymmKeyType, - }; - - let mut buf = [0; 1000]; - let mut buf2 = [0; 1000]; - let plaintext_data = [2; 32 * 15]; - let key_id = ObjectId(hex!("03445566")); - let cipher_id = CryptoObjectId(hex!("0123")); - let key = [0x42; 32]; - let iv = [0xFF; 16]; - se050.run_command( - &WriteSymmKey { - transient: true, - is_auth: false, - key_type: SymmKeyType::Aes, - policy: None, - max_attempts: None, - object_id: key_id, - kek_id: None, - value: &key, - }, - &mut buf, - )?; - response.push(Advance::SymmWrite as u8).ok(); - let ciphertext1 = se050.run_command( - &CipherOneShotEncrypt { - key_id, - mode: CipherMode::AesCtr, - plaintext: &plaintext_data, - initialization_vector: Some(&iv), - }, - &mut buf, - )?; - response.push(Advance::SymmEncryptOneShot as u8).ok(); - let plaintext1 = se050.run_command( - &CipherOneShotDecrypt { - key_id, - mode: CipherMode::AesCtr, - ciphertext: ciphertext1.ciphertext, - initialization_vector: Some(&iv), - }, - &mut buf2, - )?; - response.push(Advance::SymmDecryptOneShot as u8).ok(); - assert_eq!(plaintext1.plaintext, plaintext_data); - se050.run_command( - &CreateCipherObject { - id: cipher_id, - subtype: CipherMode::AesCtr, - }, - &mut buf2, - )?; - response.push(Advance::SymmEncryptCreate as u8).ok(); - se050.run_command( - &CipherEncryptInit { - key_id, - initialization_vector: Some(&iv), - cipher_id, - }, - &mut buf2, - )?; - response.push(Advance::SymmEncryptInit as u8).ok(); - let ciphertext2 = se050.run_command( - &CipherUpdate { - cipher_id, - data: &plaintext_data[0..32 * 10], - }, - &mut buf2, - )?; - if ciphertext2.data != &ciphertext1.ciphertext[0..32 * 10] { - return Err((0x3000 + line!() as u16).into()); - } - response.push(Advance::SymmEncryptUpdate1 as u8).ok(); - let ciphertext3 = se050.run_command( - &CipherUpdate { - cipher_id, - data: &plaintext_data[32 * 10..][..32 * 5], - }, - &mut buf2, - )?; - if ciphertext3.data != &ciphertext1.ciphertext[32 * 10..][..32 * 5] { - return Err((0x3000 + line!() as u16).into()); - } - response.push(Advance::SymmEncryptUpdate2 as u8).ok(); - let ciphertext4 = se050.run_command( - &CipherFinal { - cipher_id, - data: &plaintext_data[32 * 15..], - }, - &mut buf2, - )?; - if ciphertext4.data != &ciphertext1.ciphertext[32 * 15..] { - return Err((0x3000 + line!() as u16).into()); - } - response.push(Advance::SymmEncryptFinal as u8).ok(); - se050.run_command(&DeleteCryptoObj { id: cipher_id }, &mut buf2)?; - response.push(Advance::SymmEncryptDelete as u8).ok(); - se050.run_command( - &CreateCipherObject { - id: cipher_id, - subtype: CipherMode::AesCtr, - }, - &mut buf2, - )?; - response.push(Advance::SymmDecryptCreate as u8).ok(); - se050.run_command( - &CipherDecryptInit { - key_id, - initialization_vector: Some(&iv), - cipher_id, - }, - &mut buf2, - )?; - response.push(Advance::SymmDecryptInit as u8).ok(); - let plaintext1 = se050.run_command( - &CipherUpdate { - cipher_id, - data: &ciphertext1.ciphertext[0..32 * 10], - }, - &mut buf2, - )?; - if plaintext1.data != &plaintext_data[..32 * 10] { - return Err((0x3000 + line!() as u16).into()); - } - response.push(Advance::SymmDecryptUpdate1 as u8).ok(); - let plaintext2 = se050.run_command( - &CipherUpdate { - cipher_id, - data: &ciphertext1.ciphertext[32 * 10..][..32 * 5], - }, - &mut buf2, - )?; - if plaintext2.data != &plaintext_data[32 * 10..][..32 * 5] { - return Err((0x3000 + line!() as u16).into()); - } - response.push(Advance::SymmDecryptUpdate2 as u8).ok(); - let plaintext3 = se050.run_command( - &CipherFinal { - cipher_id, - data: &ciphertext1.ciphertext[32 * 15..], - }, - &mut buf2, - )?; - if plaintext3.data != &plaintext_data[32 * 15..] { - return Err((0x3000 + line!() as u16).into()); - } - response.push(Advance::SymmDecryptFinal as u8).ok(); - se050.run_command(&DeleteCryptoObj { id: cipher_id }, &mut buf2)?; - response.push(Advance::SymmDecryptDelete as u8).ok(); - se050.run_command(&DeleteSecureObject { object_id: key_id }, &mut buf2)?; - response.push(Advance::SymmDelete as u8).ok(); - Ok(()) - } - - fn run_mac, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, - ) -> Result<(), Status> { - use se05x::se05x::{ - commands::{ - CreateSignatureObject, DeleteCryptoObj, MacGenerateFinal, MacGenerateInit, - MacOneShotGenerate, MacOneShotValidate, MacUpdate, MacValidateFinal, - MacValidateInit, WriteSymmKey, - }, - CryptoObjectId, MacAlgo, SymmKeyType, - }; - - let mut buf = [0; 1000]; - let mut buf2 = [0; 1000]; - let plaintext_data = [2; 32 * 15]; - let key_id = ObjectId(hex!("03445566")); - let mac_id = CryptoObjectId(hex!("0123")); - let key = [0x42; 32]; - se050.run_command( - &WriteSymmKey { - transient: false, - is_auth: false, - key_type: SymmKeyType::Hmac, - policy: None, - max_attempts: None, - object_id: key_id, - kek_id: None, - value: &key, - }, - &mut buf, - )?; - response.push(Advance::MacWrite as u8).ok(); - let tag1 = se050.run_command( - &MacOneShotGenerate { - key_id, - data: &plaintext_data, - algo: MacAlgo::HmacSha256, - }, - &mut buf, - )?; - response.push(Advance::MacSignOneShot as u8).ok(); - let res = se050.run_command( - &MacOneShotValidate { - key_id, - algo: MacAlgo::HmacSha256, - data: &plaintext_data, - tag: tag1.tag, - }, - &mut buf2, - )?; - response.push(Advance::MacVerifyOneShot as u8).ok(); - if res.result != Se05XResult::Success { - return Err((0x3000 + line!() as u16).into()); - } - se050.run_command( - &CreateSignatureObject { - id: mac_id, - subtype: MacAlgo::HmacSha256, - }, - &mut buf2, - )?; - response.push(Advance::MacSignCreate as u8).ok(); - se050.run_command(&MacGenerateInit { key_id, mac_id }, &mut buf2)?; - response.push(Advance::MacSignInit as u8).ok(); - se050.run_command( - &MacUpdate { - mac_id, - data: &plaintext_data[0..32 * 10], - }, - &mut buf2, - )?; - response.push(Advance::MacSignUpdate1 as u8).ok(); - se050.run_command( - &MacUpdate { - mac_id, - data: &plaintext_data[32 * 10..][..32 * 5], - }, - &mut buf2, - )?; - response.push(Advance::MacSignUpdate2 as u8).ok(); - let tag2 = se050.run_command( - &MacGenerateFinal { - mac_id, - data: &plaintext_data[32 * 15..], - }, - &mut buf2, - )?; - response.push(Advance::MacSignFinal as u8).ok(); - assert_eq!(tag2.tag, tag1.tag); - se050.run_command(&DeleteCryptoObj { id: mac_id }, &mut buf)?; - response.push(Advance::MacSignDelete as u8).ok(); - - se050.run_command( - &CreateSignatureObject { - id: mac_id, - subtype: MacAlgo::HmacSha256, - }, - &mut buf, - )?; - response.push(Advance::MacVerifyCreate as u8).ok(); - se050.run_command(&MacValidateInit { key_id, mac_id }, &mut buf)?; - response.push(Advance::MacVerifyInit as u8).ok(); - se050.run_command( - &MacUpdate { - mac_id, - data: &plaintext_data[0..32 * 10], - }, - &mut buf, - )?; - response.push(Advance::MacVerifyUpdate1 as u8).ok(); - se050.run_command( - &MacUpdate { - mac_id, - data: &plaintext_data[32 * 10..][..32 * 5], - }, - &mut buf, - )?; - response.push(Advance::MacVerifyUpdate2 as u8).ok(); - let res2 = se050.run_command( - &MacValidateFinal { - mac_id, - data: &plaintext_data[32 * 15..], - tag: tag2.tag, - }, - &mut buf, - )?; - if res2.result != Se05XResult::Success { - return Err((0x3000 + line!() as u16).into()); - } - response.push(Advance::MacVerifyFinal as u8).ok(); - - se050.run_command(&DeleteCryptoObj { id: mac_id }, &mut buf)?; - response.push(Advance::MacVerifyDelete as u8).ok(); - - se050.run_command(&DeleteSecureObject { object_id: key_id }, &mut buf2)?; - response.push(Advance::MacDelete as u8).ok(); - Ok(()) - } - - fn run_aes_session, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, - ) -> Result<(), Status> { - use rand_chacha::rand_core::SeedableRng; - use se05x::se05x::{ - commands::{CloseSession, WriteSymmKey}, - SymmKeyType, - }; - - let mut buf = [0; 1024]; - let key = [0x42; 16]; - let key2 = [0x43; 16]; - let key_id = ObjectId(hex!("03445566")); - let bin_id = ObjectId(hex!("03445567")); - let bin_data = hex!("CAFECAFE"); - let key_policy = &[ - Policy { - object_id: key_id, - access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_WRITE), - }, - Policy { - object_id: ObjectId::INVALID, - access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_DELETE), - }, - ]; - let bin_policy = &[ - Policy { - object_id: key_id, - access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_READ), - }, - Policy { - object_id: ObjectId::INVALID, - access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_DELETE), - }, - ]; - se050.run_command( - &WriteSymmKey { - transient: false, - is_auth: true, - key_type: SymmKeyType::Aes, - policy: Some(PolicySet(key_policy)), - max_attempts: None, - object_id: key_id, - kek_id: None, - value: &key, - }, - &mut buf, - )?; - response.push(Advance::AesSessionCreateKey as _).ok(); - se050.run_command( - &WriteBinary { - transient: false, - policy: Some(PolicySet(bin_policy)), - object_id: bin_id, - offset: None, - file_length: Some((bin_data.len() as u16).into()), - data: Some(&bin_data), - }, - &mut buf, - )?; - response.push(Advance::AesSessionCreateBinary as _).ok(); - - let session = se050.run_command(&CreateSession { object_id: key_id }, &mut buf)?; - let session_id = session.session_id; - response.push(Advance::AesSessionCreateSession as u8).ok(); - debug_now!("Created session"); - - let mut rng = rand_chacha::ChaCha8Rng::from_seed([0xCA; 32]); - se050.authenticate_aes128_session(session_id, &key, &mut rng)?; - response.push(Advance::AesSessionAuthenticate as u8).ok(); - - let data = se050.run_command( - &ProcessSessionCmd { - session_id, - apdu: ReadObject { - object_id: bin_id, - offset: None, - length: Some((bin_data.len() as u16).into()), - rsa_key_component: None, - }, - }, - &mut buf, - )?; - assert_eq!(data.data, &bin_data); - response.push(Advance::AesSessionReadBinary as _).ok(); - - se050.run_command( - &ProcessSessionCmd { - session_id, - apdu: WriteSymmKey { - transient: false, - is_auth: true, - key_type: SymmKeyType::Aes, - policy: None, - max_attempts: None, - object_id: key_id, - kek_id: None, - value: &key2, - }, - }, - &mut buf, - )?; - response.push(Advance::AesSessionUpdateKey as _).ok(); - - se050.run_command( - &ProcessSessionCmd { - session_id, - apdu: CloseSession {}, - }, - &mut buf, - )?; - response.push(Advance::AesSessionCloseSession as _).ok(); - - let session = se050.run_command(&CreateSession { object_id: key_id }, &mut buf)?; - let session_id = session.session_id; - response.push(Advance::AesSessionRecreateSession as u8).ok(); - debug_now!("Created session"); - - let mut rng = rand_chacha::ChaCha8Rng::from_seed([0xCA; 32]); - se050.authenticate_aes128_session(session_id, &key2, &mut rng)?; - response.push(Advance::AesSessionReAuthenticate as u8).ok(); - - let data = se050.run_command( - &ProcessSessionCmd { - session_id, - apdu: ReadObject { - object_id: bin_id, - offset: None, - length: Some((bin_data.len() as u16).into()), - rsa_key_component: None, - }, - }, - &mut buf, - )?; - assert_eq!(data.data, &bin_data); - response.push(Advance::AesSessionReadBinary2 as _).ok(); - - se050.run_command(&DeleteSecureObject { object_id: bin_id }, &mut buf)?; - response.push(Advance::AesSessionDeleteBinary as _).ok(); - se050.run_command(&DeleteSecureObject { object_id: key_id }, &mut buf)?; - response.push(Advance::AesSessionDeleteKey as _).ok(); - - Ok(()) - } - - fn run_pbkdf, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, - ) -> Result<(), Status> { - use se05x::se05x::{ - commands::{Pbkdf2, WriteSymmKey}, - SymmKeyType, - }; - - let mut buf = [0; 1024]; - let pin = b"123456"; - let salt = [0x42; 16]; - let pin_id = ObjectId(hex!("03445566")); - - se050.run_command( - &WriteSymmKey { - transient: true, - is_auth: false, - key_type: SymmKeyType::Hmac, - policy: None, - max_attempts: None, - object_id: pin_id, - kek_id: None, - value: pin, - }, - &mut buf, - )?; - - response.push(Advance::Pbkdf2WritePin as u8).ok(); - - let res = se050.run_command( - &Pbkdf2 { - password: pin_id, - salt: Some(&salt), - iterations: 32.into(), - requested_len: 16.into(), - }, - &mut buf, - )?; - response.push(Advance::Pbkdf2Calculate as u8).ok(); - - if res.data != hex!("685126241d909137ecd3385eaea2725f") { - debug_now!("Got HASH: {:02x?}", res.data); - return Err(Status::CorruptedData); - } - response.push(Advance::Pbkdf2Compare as u8).ok(); - - se050.run_command(&DeleteSecureObject { object_id: pin_id }, &mut buf)?; - response.push(Advance::Pbkdf2DeletePin as u8).ok(); - - Ok(()) - } - - fn run_export_import, const N: usize>( - se050: &mut Se05X, - response: &mut Vec, - ) -> Result<(), Status> { - use se05x::se05x::{ - commands::{CipherOneShotEncrypt, ExportObject, ImportObject, WriteSymmKey}, - CipherMode, RsaKeyComponent, SymmKeyType, - }; - - let mut buf = [0; 128]; - let mut buf2 = [0; 1000]; - let mut buf3 = [0; 1000]; - let plaintext_data = [2; 32]; - let key_id = ObjectId(hex!("03445566")); - let key = [0x42; 32]; - let iv = [0xFF; 16]; - let policy = &[ - Policy { - object_id: ObjectId::INVALID, - access_rule: ObjectAccessRule::from_flags( - ObjectPolicyFlags::ALLOW_WRITE - | ObjectPolicyFlags::ALLOW_ENC - | ObjectPolicyFlags::ALLOW_DELETE - | ObjectPolicyFlags::ALLOW_IMPORT_EXPORT, - ), - }, - Policy { - object_id: ObjectId::INVALID, - access_rule: ObjectAccessRule::from_flags(ObjectPolicyFlags::ALLOW_DELETE), - }, - ]; - se050.run_command( - &WriteSymmKey { - transient: true, - is_auth: false, - key_type: SymmKeyType::Aes, - policy: Some(PolicySet(policy)), - max_attempts: None, - object_id: key_id, - kek_id: None, - value: &key, - }, - &mut buf, - )?; - response.push(Advance::ImportWrite as u8).ok(); - let ciphertext1 = se050.run_command( - &CipherOneShotEncrypt { - key_id, - mode: CipherMode::AesCtr, - plaintext: &plaintext_data, - initialization_vector: Some(&iv), - }, - &mut buf, - )?; - response.push(Advance::ImportCipher as u8).ok(); - - debug_now!("Exporting"); - let exported = se050 - .run_command( - &ExportObject { - object_id: key_id, - rsa_key_component: RsaKeyComponent::Na, - }, - &mut buf2, - ) - .map_err(|_err| { - debug_now!("Got err: {:?}", _err); - _err - })?; - response.push(Advance::ImportExport as u8).ok(); - - se050.enable()?; - response.push(Advance::ImportDelete as u8).ok(); - - let res = se050.run_command( - &CipherOneShotEncrypt { - key_id, - mode: CipherMode::AesCtr, - plaintext: &plaintext_data, - initialization_vector: Some(&iv), - }, - &mut buf3, - ); - if !matches!( - res, - Err(se05x::se05x::Error::Status( - Status::ConditionsOfUseNotSatisfied, - )) - ) { - return Err((0x3000 + line!() as u16).into()); - } - response.push(Advance::ImportDeletionWorked as u8).ok(); - - debug_now!("Importing"); - se050.run_command( - &ImportObject { - transient: true, - object_id: key_id, - rsa_key_component: None, - serialized_object: exported.data, - }, - &mut buf3, - )?; - response.push(Advance::ImportImport as u8).ok(); - - debug_now!("Encrypting"); - let ciphertext2 = se050.run_command( - &CipherOneShotEncrypt { - key_id, - mode: CipherMode::AesCtr, - plaintext: &plaintext_data, - initialization_vector: Some(&iv), - }, - &mut buf3, - )?; - response.push(Advance::ImportCipher2 as u8).ok(); - - debug_now!("Comparing"); - if ciphertext1.ciphertext != ciphertext2.ciphertext { - return Err((0x3000 + line!() as u16).into()); - } - response.push(Advance::ImportComp as u8).ok(); - - se050.run_command(&DeleteSecureObject { object_id: key_id }, &mut buf3)?; - response.push(Advance::ImportDeleteFinal as u8).ok(); - Ok(()) - } -} From e1179efd7f35c92bbd1acc2b7efd3e017d7dc824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Mon, 2 Oct 2023 10:44:43 +0200 Subject: [PATCH 22/65] Return UnsuportedCommand (INVALID_COMMAND) instead of NotAvailable (INVALID_LENGTH) This allows nitropy to easily skip the test if the command is not supported --- src/admin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/admin.rs b/src/admin.rs index eabd82d..3da6806 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -276,7 +276,7 @@ where } #[cfg(not(feature = "se050"))] { - return Err(Error::NotAvailable); + return Err(Error::UnsupportedCommand); } } Command::GetConfig => { From 6c88a4bd58f2b6516c424b4dbf9581989ffa915e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Wed, 8 Nov 2023 11:39:43 +0100 Subject: [PATCH 23/65] Update apdu-dispatch --- Cargo.toml | 1 + src/admin.rs | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 50040b0..bed84af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ log-error = [] [patch.crates-io] ctaphid-dispatch = { git = "https://github.com/trussed-dev/ctaphid-dispatch.git", rev = "57cb3317878a8593847595319aa03ef17c29ec5b" } trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "51e68500d7601d04f884f5e95567d14b9018a6cb" } +apdu-dispatch = { git = "https://github.com/trussed-dev/apdu-dispatch.git", rev = "915fc237103fcecc29d0f0b73391f19abf6576de" } trussed-se050-backend = { git = "https://github.com/Nitrokey/trussed-se050-backend.git", rev = "3395a5b73241a0a9f14a0715952be6fe7f1e56b0" } iso7816 = { git = "https://github.com/sosthene-nitrokey/iso7816.git", rev = "160ca3bbd8e21ec4e4ee1e0748e1eaa53a45c97f"} se05x = { git = "https://github.com/Nitrokey/se05x.git", rev = "0b77eb6b152d214897696aadf87767fd84ffcb0e"} diff --git a/src/admin.rs b/src/admin.rs index 3da6806..8b62de0 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -1,6 +1,6 @@ use super::Client as TrussedClient; use apdu_dispatch::iso7816::Status; -use apdu_dispatch::{app as apdu, command, response, Command as ApduCommand}; +use apdu_dispatch::{app as apdu, command, dispatch::Interface, response, Command as ApduCommand}; use cbor_smol::cbor_deserialize; use core::{convert::TryInto, marker::PhantomData, time::Duration}; use ctaphid_dispatch::app::{self as hid, Command as HidCommand, Message}; @@ -378,7 +378,12 @@ where S: AsRef<[u8]>, C: Config, { - fn select(&mut self, _apdu: &ApduCommand, _reply: &mut response::Data) -> apdu::Result { + fn select( + &mut self, + _interface: Interface, + _apdu: &ApduCommand, + _reply: &mut response::Data, + ) -> apdu::Result { Ok(()) } From f72eccbc3b8335e9a24eeb94b082b1948d0c1c75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Fri, 10 Nov 2023 15:29:06 +0100 Subject: [PATCH 24/65] Implement factory reset command --- Cargo.toml | 2 ++ src/admin.rs | 7 +++++++ src/lib.rs | 5 +++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bed84af..7c3361c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ embedded-hal = { version = "0.2.7", optional = true } hex-literal = "0.4.1" rand_chacha = { version = "0.3.1", optional = true, default-features = false } trussed-se050-backend = { version = "0.1.0", optional = true } +trussed-staging = { version = "0.1.0", features = ["manage"] } [features] default = [] @@ -44,3 +45,4 @@ apdu-dispatch = { git = "https://github.com/trussed-dev/apdu-dispatch.git", rev trussed-se050-backend = { git = "https://github.com/Nitrokey/trussed-se050-backend.git", rev = "3395a5b73241a0a9f14a0715952be6fe7f1e56b0" } iso7816 = { git = "https://github.com/sosthene-nitrokey/iso7816.git", rev = "160ca3bbd8e21ec4e4ee1e0748e1eaa53a45c97f"} se05x = { git = "https://github.com/Nitrokey/se05x.git", rev = "0b77eb6b152d214897696aadf87767fd84ffcb0e"} +trussed-staging = { git = "https://github.com/trussed-dev/trussed-staging.git", rev = "10baac2608e98e25ea77ade974a4e26f778d6c8e" } diff --git a/src/admin.rs b/src/admin.rs index 8b62de0..00c5c1c 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -19,6 +19,7 @@ const STATUS: u8 = 0x80; const TEST_SE050: u8 = 0x81; const GET_CONFIG: u8 = 0x82; const SET_CONFIG: u8 = 0x83; +const FACTORY_RESET: u8 = 0x84; // For compatibility, old commands are also available directly as separate vendor commands. const UPDATE: VendorCommand = VendorCommand::H51; @@ -48,6 +49,7 @@ enum Command { TestSe05X, GetConfig, SetConfig, + FactoryReset, } impl TryFrom for Command { @@ -67,6 +69,7 @@ impl TryFrom for Command { TEST_SE050 => Ok(Command::TestSe05X), GET_CONFIG => Ok(Command::GetConfig), SET_CONFIG => Ok(Command::SetConfig), + FACTORY_RESET => Ok(Command::FactoryReset), _ => Err(Error::UnsupportedCommand), } } @@ -295,6 +298,10 @@ where }; response.push(status).ok(); } + Command::FactoryReset => { + debug_now!("Factory resetting the device"); + syscall!(self.trussed.factory_reset_device()); + } } Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index e9c72c6..705e29d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,11 +15,12 @@ mod config; pub use admin::{App, Reboot}; pub use config::{Config, ConfigError, ConfigValueMut}; +use trussed_staging::manage::ManageClient; #[cfg(not(feature = "se050"))] -pub trait Client: trussed::Client {} +pub trait Client: trussed::Client + ManageClient {} #[cfg(not(feature = "se050"))] -impl Client for C {} +impl Client for C {} #[cfg(feature = "se050")] pub trait Client: trussed::Client + trussed_se050_backend::manage::ManageClient {} From 4be67cfd66059b9eef645c41215c155d71fa3a2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Fri, 10 Nov 2023 16:16:30 +0100 Subject: [PATCH 25/65] Implement factory-reset of clients on config change --- src/admin.rs | 9 ++++++++- src/config.rs | 23 ++++++++++++++++++----- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/admin.rs b/src/admin.rs index 00c5c1c..2c8dab3 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -2,6 +2,7 @@ use super::Client as TrussedClient; use apdu_dispatch::iso7816::Status; use apdu_dispatch::{app as apdu, command, dispatch::Interface, response, Command as ApduCommand}; use cbor_smol::cbor_deserialize; +use core::sync::atomic::Ordering; use core::{convert::TryInto, marker::PhantomData, time::Duration}; use ctaphid_dispatch::app::{self as hid, Command as HidCommand, Message}; use ctaphid_dispatch::command::VendorCommand; @@ -318,7 +319,13 @@ where fn set_config(&mut self, input: &[u8]) -> Result<(), ConfigError> { let request: SetConfigRequest<'_> = cbor_deserialize(input).map_err(|_| ConfigError::DeserializationFailed)?; - config::set(&mut self.config, request.key, request.value)?; + let reset = config::set(&mut self.config, request.key, request.value)?; + if let Some(flag) = reset.signal { + flag.store(true, Ordering::Relaxed) + } + if let Some(client) = reset.client_id { + syscall!(self.trussed.factory_reset_client(client)); + } config::save(&mut self.trussed, &self.config) } } diff --git a/src/config.rs b/src/config.rs index 61b0e2c..881f060 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ use core::{ fmt::{self, Display, Formatter, Write as _}, str::FromStr, + sync::atomic::AtomicBool, }; use cbor_smol::{cbor_deserialize, cbor_serialize_bytes}; @@ -14,15 +15,25 @@ use trussed::{ Client, }; +#[must_use] +/// Tell the admin APP whether a given configuration change requires factory-resetting another application +#[derive(Default, Debug, Clone)] +pub struct ResetClient { + /// Boolean that must be set to true by the admin app to signal that the associated config value has been changed + pub signal: Option<&'static AtomicBool>, + /// Client ID to factory-reset if the associated configuration option is changed + pub client_id: Option<&'static Path>, +} + const LOCATION: Location = Location::Internal; const FILENAME: &Path = path!("config"); pub trait Config: Default + PartialEq + DeserializeOwned + Serialize { - fn field(&mut self, key: &str) -> Option>; + fn field(&mut self, key: &str) -> Option<(ConfigValueMut<'_>, ResetClient)>; } impl Config for () { - fn field(&mut self, _key: &str) -> Option> { + fn field(&mut self, _key: &str) -> Option<(ConfigValueMut<'_>, ResetClient)> { None } } @@ -84,12 +95,14 @@ pub fn get( key: &str, response: &mut Vec, ) -> Result<(), ConfigError> { - let field = config.field(key).ok_or(ConfigError::InvalidKey)?; + let field = config.field(key).ok_or(ConfigError::InvalidKey)?.0; write!(response, "{}", field).map_err(|_| ConfigError::DataTooLong) } -pub fn set(config: &mut C, key: &str, value: &str) -> Result<(), ConfigError> { - config.field(key).ok_or(ConfigError::InvalidKey)?.set(value) +pub fn set(config: &mut C, key: &str, value: &str) -> Result { + let (mut ref_mut, reset) = config.field(key).ok_or(ConfigError::InvalidKey)?; + ref_mut.set(value)?; + Ok(reset) } pub fn load(store: &mut F) -> Result { From d326489e5fdcbfd3a2c7e6f490a6bf0349426cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Fri, 10 Nov 2023 16:24:04 +0100 Subject: [PATCH 26/65] Reboot after a full-device factory reset --- src/admin.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/admin.rs b/src/admin.rs index 2c8dab3..2007d40 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -302,6 +302,7 @@ where Command::FactoryReset => { debug_now!("Factory resetting the device"); syscall!(self.trussed.factory_reset_device()); + R::reboot(); } } Ok(()) From 26a8466f080ec7a9975491bc44f6cb81ea79b116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Mon, 13 Nov 2023 15:43:41 +0100 Subject: [PATCH 27/65] Make Config trait update backward compatible --- src/config.rs | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/config.rs b/src/config.rs index 881f060..399c946 100644 --- a/src/config.rs +++ b/src/config.rs @@ -29,11 +29,21 @@ const LOCATION: Location = Location::Internal; const FILENAME: &Path = path!("config"); pub trait Config: Default + PartialEq + DeserializeOwned + Serialize { - fn field(&mut self, key: &str) -> Option<(ConfigValueMut<'_>, ResetClient)>; + fn field(&mut self, key: &str) -> Option>; + + /// Client ID to factory-reset if the associated configuration option is changed + fn reset_client_id(&self, _key: &str) -> Option<&'static Path> { + None + } + + /// Boolean that must be set to true by the admin app to signal that the associated config value has been changed + fn reset_signal(&self, _key: &str) -> Option<&'static AtomicBool> { + None + } } impl Config for () { - fn field(&mut self, _key: &str) -> Option<(ConfigValueMut<'_>, ResetClient)> { + fn field(&mut self, _key: &str) -> Option> { None } } @@ -95,14 +105,19 @@ pub fn get( key: &str, response: &mut Vec, ) -> Result<(), ConfigError> { - let field = config.field(key).ok_or(ConfigError::InvalidKey)?.0; + let field = config.field(key).ok_or(ConfigError::InvalidKey)?; write!(response, "{}", field).map_err(|_| ConfigError::DataTooLong) } pub fn set(config: &mut C, key: &str, value: &str) -> Result { - let (mut ref_mut, reset) = config.field(key).ok_or(ConfigError::InvalidKey)?; - ref_mut.set(value)?; - Ok(reset) + config + .field(key) + .ok_or(ConfigError::InvalidKey)? + .set(value)?; + Ok(ResetClient { + signal: config.reset_signal(key), + client_id: config.reset_client_id(key), + }) } pub fn load(store: &mut F) -> Result { From c011a3a1d97f1a4a933a10956bafe22a85fad02b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Mon, 13 Nov 2023 15:57:37 +0100 Subject: [PATCH 28/65] Require user confirmation for factory reset --- src/admin.rs | 11 +++++++++++ src/config.rs | 1 + 2 files changed, 12 insertions(+) diff --git a/src/admin.rs b/src/admin.rs index 2007d40..f1a177a 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -36,6 +36,7 @@ const WINK: HidCommand = HidCommand::Wink; // 0x08 const RNG_DATA_LEN: usize = 57; const CONFIG_OK: u8 = 0x00; +const FACTORY_RESET_ERROR: u8 = 0x00; #[derive(PartialEq, Debug)] enum Command { @@ -301,6 +302,12 @@ where } Command::FactoryReset => { debug_now!("Factory resetting the device"); + let res = syscall!(self.trussed.confirm_user_present(15 * 1000)).result; + if let Err(_err) = res { + debug_now!("Failed to verify user presence: {_err:?}"); + response.push(FACTORY_RESET_ERROR).ok(); + return Err(Error::InvalidLength); + } syscall!(self.trussed.factory_reset_device()); R::reboot(); } @@ -325,6 +332,10 @@ where flag.store(true, Ordering::Relaxed) } if let Some(client) = reset.client_id { + if let Err(_err) = syscall!(self.trussed.confirm_user_present(15 * 1000)).result { + debug_now!("Failed to verify user presence: {_err:?}"); + return Err(ConfigError::NotConfirmed); + } syscall!(self.trussed.factory_reset_client(client)); } config::save(&mut self.trussed, &self.config) diff --git a/src/config.rs b/src/config.rs index 399c946..de863fd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -87,6 +87,7 @@ pub enum ConfigError { InvalidKey = 5, InvalidValue = 6, DataTooLong = 7, + NotConfirmed = 8, } const _: () = assert!( From 8a9270ddff00c775c9d5d3739e6aa1669cec76f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Mon, 13 Nov 2023 16:49:46 +0100 Subject: [PATCH 29/65] Add per-app factory reset mechansim --- src/admin.rs | 43 +++++++++++++++++++++++++++++------ src/config.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++++--- src/lib.rs | 2 +- 3 files changed, 96 insertions(+), 11 deletions(-) diff --git a/src/admin.rs b/src/admin.rs index f1a177a..3781fa9 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -2,10 +2,10 @@ use super::Client as TrussedClient; use apdu_dispatch::iso7816::Status; use apdu_dispatch::{app as apdu, command, dispatch::Interface, response, Command as ApduCommand}; use cbor_smol::cbor_deserialize; -use core::sync::atomic::Ordering; use core::{convert::TryInto, marker::PhantomData, time::Duration}; use ctaphid_dispatch::app::{self as hid, Command as HidCommand, Message}; use ctaphid_dispatch::command::VendorCommand; +use littlefs2::path::PathBuf; use serde::Deserialize; use trussed::{interrupt::InterruptFlag, store::filestore::Filestore, syscall, types::Vec}; @@ -21,6 +21,7 @@ const TEST_SE050: u8 = 0x81; const GET_CONFIG: u8 = 0x82; const SET_CONFIG: u8 = 0x83; const FACTORY_RESET: u8 = 0x84; +const FACTORY_RESET_APP: u8 = 0x85; // For compatibility, old commands are also available directly as separate vendor commands. const UPDATE: VendorCommand = VendorCommand::H51; @@ -36,7 +37,10 @@ const WINK: HidCommand = HidCommand::Wink; // 0x08 const RNG_DATA_LEN: usize = 57; const CONFIG_OK: u8 = 0x00; -const FACTORY_RESET_ERROR: u8 = 0x00; +const FACTORY_RESET_OK: u8 = 0x00; +const FACTORY_RESET_NOT_CONFIRMED: u8 = 0x01; +const FACTORY_RESET_APP_NOT_ALLOWED: u8 = 0x02; +const FACTORY_RESET_APP_FAILED_PARSE: u8 = 0x03; #[derive(PartialEq, Debug)] enum Command { @@ -52,6 +56,7 @@ enum Command { GetConfig, SetConfig, FactoryReset, + FactoryResetApp, } impl TryFrom for Command { @@ -72,6 +77,7 @@ impl TryFrom for Command { GET_CONFIG => Ok(Command::GetConfig), SET_CONFIG => Ok(Command::SetConfig), FACTORY_RESET => Ok(Command::FactoryReset), + FACTORY_RESET_APP => Ok(Command::FactoryResetApp), _ => Err(Error::UnsupportedCommand), } } @@ -302,15 +308,38 @@ where } Command::FactoryReset => { debug_now!("Factory resetting the device"); - let res = syscall!(self.trussed.confirm_user_present(15 * 1000)).result; - if let Err(_err) = res { + if let Err(_err) = syscall!(self.trussed.confirm_user_present(15 * 1000)).result { debug_now!("Failed to verify user presence: {_err:?}"); - response.push(FACTORY_RESET_ERROR).ok(); - return Err(Error::InvalidLength); + response.push(FACTORY_RESET_NOT_CONFIRMED).ok(); + return Ok(()); } syscall!(self.trussed.factory_reset_device()); R::reboot(); } + Command::FactoryResetApp => { + let Ok(client) = core::str::from_utf8(input) else { + response.push(FACTORY_RESET_APP_FAILED_PARSE).ok(); + return Ok(()); + }; + + let Some(flag) = self.config().can_reset(client) else { + response.push(FACTORY_RESET_APP_NOT_ALLOWED).ok(); + return Ok(()); + }; + + if let Err(_err) = syscall!(self.trussed.confirm_user_present(15 * 1000)).result { + debug_now!("Failed to verify user presence: {_err:?}"); + response.push(FACTORY_RESET_NOT_CONFIRMED).ok(); + return Ok(()); + } + let path = PathBuf::from(client); + + // No need to factory reset is already factory reset + if flag.set_factory_reset() { + syscall!(self.trussed.factory_reset_client(&path)); + } + response.push(FACTORY_RESET_OK).ok(); + } } Ok(()) } @@ -329,7 +358,7 @@ where cbor_deserialize(input).map_err(|_| ConfigError::DeserializationFailed)?; let reset = config::set(&mut self.config, request.key, request.value)?; if let Some(flag) = reset.signal { - flag.store(true, Ordering::Relaxed) + flag.set_config_changed(); } if let Some(client) = reset.client_id { if let Err(_err) = syscall!(self.trussed.confirm_user_present(15 * 1000)).result { diff --git a/src/config.rs b/src/config.rs index de863fd..b10fbe4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,7 +1,7 @@ use core::{ fmt::{self, Display, Formatter, Write as _}, str::FromStr, - sync::atomic::AtomicBool, + sync::atomic::{AtomicU8, Ordering}, }; use cbor_smol::{cbor_deserialize, cbor_serialize_bytes}; @@ -15,12 +15,63 @@ use trussed::{ Client, }; +#[derive(Debug)] +/// Structure meant to be stored in a `static` to signal applications that they have been factory-resetted by the admin app +pub struct ResetSignalAllocation(AtomicU8); + +impl ResetSignalAllocation { + pub fn load(&self) -> ResetSignal { + let v = self.0.load(Ordering::Relaxed); + ResetSignal::from_repr(v).expect("A reset signal value") + } + + pub fn set_factory_reset(&self) -> bool { + self.0 + .compare_exchange( + ResetSignal::None as u8, + ResetSignal::FactoryReset as u8, + Ordering::Relaxed, + Ordering::Relaxed, + ) + .is_ok() + } + + pub fn set_config_changed(&self) { + self.0 + .store(ResetSignal::ConfigChanged as u8, Ordering::Relaxed) + } + + /// Factory reset can be acknowledged so that the application can restart working + /// + /// A configuration change cannot be acknowledged as it requires a power cycle to be taken into account. + pub fn ack_factory_reset(&self) { + self.0 + .store(ResetSignal::ConfigChanged as u8, Ordering::Relaxed) + } +} + +#[derive(Debug, FromRepr, Default)] +#[repr(u8)] +pub enum ResetSignal { + #[default] + /// The App can continue operating + None, + /// The app has had it state factory reseted by the admin app + /// + /// It should delete any runtime state it is currently holding, then [`acknowledge`](ResetSignalAllocation::ack_factory_reset) the reset and continue working. + FactoryReset, + /// A configuration relevant to the application has been changed. + /// + /// The application must reject all incoming request and store no persistent state until a power cycle. + ConfigChanged, +} + #[must_use] /// Tell the admin APP whether a given configuration change requires factory-resetting another application #[derive(Default, Debug, Clone)] pub struct ResetClient { /// Boolean that must be set to true by the admin app to signal that the associated config value has been changed - pub signal: Option<&'static AtomicBool>, + pub signal: Option<&'static ResetSignalAllocation>, /// Client ID to factory-reset if the associated configuration option is changed pub client_id: Option<&'static Path>, } @@ -37,7 +88,12 @@ pub trait Config: Default + PartialEq + DeserializeOwned + Serialize { } /// Boolean that must be set to true by the admin app to signal that the associated config value has been changed - fn reset_signal(&self, _key: &str) -> Option<&'static AtomicBool> { + fn reset_signal(&self, _key: &str) -> Option<&'static ResetSignalAllocation> { + None + } + + /// Can the client be factory-reset by the admin app? + fn can_reset(&self, _client: &str) -> Option<&'static ResetSignalAllocation> { None } } diff --git a/src/lib.rs b/src/lib.rs index 705e29d..7304cfb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ mod admin; mod config; pub use admin::{App, Reboot}; -pub use config::{Config, ConfigError, ConfigValueMut}; +pub use config::{Config, ConfigError, ConfigValueMut, ResetSignal, ResetSignalAllocation}; use trussed_staging::manage::ManageClient; #[cfg(not(feature = "se050"))] From dcc14675c47aece249fbe34b104b01b9b5951095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Tue, 14 Nov 2023 09:08:44 +0100 Subject: [PATCH 30/65] Fix factory reset ack --- src/config.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index b10fbe4..01f1d4d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -45,8 +45,7 @@ impl ResetSignalAllocation { /// /// A configuration change cannot be acknowledged as it requires a power cycle to be taken into account. pub fn ack_factory_reset(&self) { - self.0 - .store(ResetSignal::ConfigChanged as u8, Ordering::Relaxed) + self.0.store(ResetSignal::None as u8, Ordering::Relaxed) } } From 76d9a1e332b2627bd6cafa88bb78fe566d6ffb28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Tue, 14 Nov 2023 09:35:27 +0100 Subject: [PATCH 31/65] Add constructor for ResetSignalAllocation --- src/config.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/config.rs b/src/config.rs index 01f1d4d..6d68560 100644 --- a/src/config.rs +++ b/src/config.rs @@ -20,6 +20,10 @@ use trussed::{ pub struct ResetSignalAllocation(AtomicU8); impl ResetSignalAllocation { + pub const fn new() -> Self { + Self(AtomicU8::new(ResetSignal::None as u8)) + } + pub fn load(&self) -> ResetSignal { let v = self.0.load(Ordering::Relaxed); ResetSignal::from_repr(v).expect("A reset signal value") From 4e9e7bd9cd83c729b78c23f738fb05d559b06ab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Tue, 14 Nov 2023 09:35:59 +0100 Subject: [PATCH 32/65] Fix typo --- src/admin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/admin.rs b/src/admin.rs index 3781fa9..b69bc09 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -334,7 +334,7 @@ where } let path = PathBuf::from(client); - // No need to factory reset is already factory reset + // No need to factory reset if already factory reset if flag.set_factory_reset() { syscall!(self.trussed.factory_reset_client(&path)); } From 8dd9fec0fe90dd52cb5fd6486da035122f6f6b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Tue, 14 Nov 2023 09:54:00 +0100 Subject: [PATCH 33/65] Fix clearing ConfigChanged --- src/config.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index 6d68560..3d6bea1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -48,8 +48,15 @@ impl ResetSignalAllocation { /// Factory reset can be acknowledged so that the application can restart working /// /// A configuration change cannot be acknowledged as it requires a power cycle to be taken into account. - pub fn ack_factory_reset(&self) { - self.0.store(ResetSignal::None as u8, Ordering::Relaxed) + pub fn ack_factory_reset(&self) -> bool { + self.0 + .compare_exchange( + ResetSignal::FactoryReset as u8, + ResetSignal::None as u8, + Ordering::Relaxed, + Ordering::Relaxed, + ) + .is_ok() } } From 6537f7782f3fa1f32a7179c44f8291acc6e407f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Tue, 14 Nov 2023 17:02:23 +0100 Subject: [PATCH 34/65] Fix compilation with se050 feature enabled --- src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7304cfb..5fcad01 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,9 @@ pub trait Client: trussed::Client + ManageClient {} impl Client for C {} #[cfg(feature = "se050")] -pub trait Client: trussed::Client + trussed_se050_backend::manage::ManageClient {} +pub trait Client: + trussed::Client + trussed_se050_backend::manage::ManageClient + ManageClient +{ +} #[cfg(feature = "se050")] -impl Client for C {} +impl Client for C {} From 410899311ae7b194360366ff477f74d4d278e056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Fri, 17 Nov 2023 11:37:22 +0100 Subject: [PATCH 35/65] Use improved config trait --- src/admin.rs | 16 ++++++++----- src/config.rs | 65 ++++++++++++++++++++++++++++++--------------------- 2 files changed, 49 insertions(+), 32 deletions(-) diff --git a/src/admin.rs b/src/admin.rs index b69bc09..9d7f72a 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -322,7 +322,7 @@ where return Ok(()); }; - let Some(flag) = self.config().can_reset(client) else { + let Some((_, flag)) = self.config().reset_client_id(client) else { response.push(FACTORY_RESET_APP_NOT_ALLOWED).ok(); return Ok(()); }; @@ -356,17 +356,21 @@ where fn set_config(&mut self, input: &[u8]) -> Result<(), ConfigError> { let request: SetConfigRequest<'_> = cbor_deserialize(input).map_err(|_| ConfigError::DeserializationFailed)?; - let reset = config::set(&mut self.config, request.key, request.value)?; - if let Some(flag) = reset.signal { - flag.set_config_changed(); - } - if let Some(client) = reset.client_id { + let reset_client_id = self.config.reset_client_id(request.key); + + if reset_client_id.is_some() { if let Err(_err) = syscall!(self.trussed.confirm_user_present(15 * 1000)).result { debug_now!("Failed to verify user presence: {_err:?}"); return Err(ConfigError::NotConfirmed); } + } + + config::set(&mut self.config, request.key, request.value)?; + if let Some((client, signal)) = reset_client_id { + signal.set_config_changed(); syscall!(self.trussed.factory_reset_client(client)); } + config::save(&mut self.trussed, &self.config) } } diff --git a/src/config.rs b/src/config.rs index 3d6bea1..13fde86 100644 --- a/src/config.rs +++ b/src/config.rs @@ -17,6 +17,34 @@ use trussed::{ #[derive(Debug)] /// Structure meant to be stored in a `static` to signal applications that they have been factory-resetted by the admin app +/// +/// It is expected to have one such structure for each application supporting factory-reset by the admin-app +/// +/// ```rust +///# use admin_app::{ResetSignalAllocation, ConfigValueMut}; +///# use littlefs2::{path::Path, path}; +/// #[derive(Default, PartialEq, serde::Deserialize, serde::Serialize)] +/// struct Config { +/// use_new_backend: bool, +///}; +/// static OPCARD_RESET: ResetSignalAllocation = ResetSignalAllocation::new(); +/// impl admin_app::Config for Config { +/// fn field(&mut self, key: &str) -> Option> { +/// match key { +/// "opcard.use_new_backend" => Some(ConfigValueMut::Bool(&mut self.use_new_backend)), +/// _ => None, +/// } +/// } +/// /// Client ID to factory-reset if the associated configuration option is changed +/// fn reset_client_id(&self, key: &str) -> Option<(&'static Path, &'static ResetSignalAllocation)> { +/// match key { +/// "opcard" => Some((path!("opcard"), &OPCARD_RESET)), +/// "opcard.use_new_backend" =>Some((path!("opcard"), &OPCARD_RESET)), +/// _ => None, +/// } +/// } +/// } +/// ``` pub struct ResetSignalAllocation(AtomicU8); impl ResetSignalAllocation { @@ -76,16 +104,6 @@ pub enum ResetSignal { ConfigChanged, } -#[must_use] -/// Tell the admin APP whether a given configuration change requires factory-resetting another application -#[derive(Default, Debug, Clone)] -pub struct ResetClient { - /// Boolean that must be set to true by the admin app to signal that the associated config value has been changed - pub signal: Option<&'static ResetSignalAllocation>, - /// Client ID to factory-reset if the associated configuration option is changed - pub client_id: Option<&'static Path>, -} - const LOCATION: Location = Location::Internal; const FILENAME: &Path = path!("config"); @@ -93,17 +111,15 @@ pub trait Config: Default + PartialEq + DeserializeOwned + Serialize { fn field(&mut self, key: &str) -> Option>; /// Client ID to factory-reset if the associated configuration option is changed - fn reset_client_id(&self, _key: &str) -> Option<&'static Path> { - None - } - - /// Boolean that must be set to true by the admin app to signal that the associated config value has been changed - fn reset_signal(&self, _key: &str) -> Option<&'static ResetSignalAllocation> { - None - } - - /// Can the client be factory-reset by the admin app? - fn can_reset(&self, _client: &str) -> Option<&'static ResetSignalAllocation> { + /// + /// # If the Request is for a `client_id`: + /// + /// - MUST return `Some` to indicate that the client can be factory reset by the admin app, + /// - MUST return None otherwise. + fn reset_client_id( + &self, + _key: &str, + ) -> Option<(&'static Path, &'static ResetSignalAllocation)> { None } } @@ -176,15 +192,12 @@ pub fn get( write!(response, "{}", field).map_err(|_| ConfigError::DataTooLong) } -pub fn set(config: &mut C, key: &str, value: &str) -> Result { +pub fn set(config: &mut C, key: &str, value: &str) -> Result<(), ConfigError> { config .field(key) .ok_or(ConfigError::InvalidKey)? .set(value)?; - Ok(ResetClient { - signal: config.reset_signal(key), - client_id: config.reset_client_id(key), - }) + Ok(()) } pub fn load(store: &mut F) -> Result { From 6ce6b9dcd93eab724d20c82ff329704661ab853e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Tue, 21 Nov 2023 17:41:35 +0100 Subject: [PATCH 36/65] Add feature-flag for factory-reset --- Cargo.toml | 1 + src/admin.rs | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 7c3361c..03a4518 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ trussed-staging = { version = "0.1.0", features = ["manage"] } default = [] se050 = ["trussed-se050-backend"] +factory-reset = [] log-all = [] log-none = [] log-info = [] diff --git a/src/admin.rs b/src/admin.rs index 9d7f72a..0028c45 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -5,6 +5,7 @@ use cbor_smol::cbor_deserialize; use core::{convert::TryInto, marker::PhantomData, time::Duration}; use ctaphid_dispatch::app::{self as hid, Command as HidCommand, Message}; use ctaphid_dispatch::command::VendorCommand; +#[cfg(feature = "factory-reset")] use littlefs2::path::PathBuf; use serde::Deserialize; use trussed::{interrupt::InterruptFlag, store::filestore::Filestore, syscall, types::Vec}; @@ -20,7 +21,9 @@ const STATUS: u8 = 0x80; const TEST_SE050: u8 = 0x81; const GET_CONFIG: u8 = 0x82; const SET_CONFIG: u8 = 0x83; +#[cfg(feature = "factory-reset")] const FACTORY_RESET: u8 = 0x84; +#[cfg(feature = "factory-reset")] const FACTORY_RESET_APP: u8 = 0x85; // For compatibility, old commands are also available directly as separate vendor commands. @@ -37,9 +40,13 @@ const WINK: HidCommand = HidCommand::Wink; // 0x08 const RNG_DATA_LEN: usize = 57; const CONFIG_OK: u8 = 0x00; +#[cfg(feature = "factory-reset")] const FACTORY_RESET_OK: u8 = 0x00; +#[cfg(feature = "factory-reset")] const FACTORY_RESET_NOT_CONFIRMED: u8 = 0x01; +#[cfg(feature = "factory-reset")] const FACTORY_RESET_APP_NOT_ALLOWED: u8 = 0x02; +#[cfg(feature = "factory-reset")] const FACTORY_RESET_APP_FAILED_PARSE: u8 = 0x03; #[derive(PartialEq, Debug)] @@ -55,7 +62,9 @@ enum Command { TestSe05X, GetConfig, SetConfig, + #[cfg(feature = "factory-reset")] FactoryReset, + #[cfg(feature = "factory-reset")] FactoryResetApp, } @@ -76,7 +85,9 @@ impl TryFrom for Command { TEST_SE050 => Ok(Command::TestSe05X), GET_CONFIG => Ok(Command::GetConfig), SET_CONFIG => Ok(Command::SetConfig), + #[cfg(feature = "factory-reset")] FACTORY_RESET => Ok(Command::FactoryReset), + #[cfg(feature = "factory-reset")] FACTORY_RESET_APP => Ok(Command::FactoryResetApp), _ => Err(Error::UnsupportedCommand), } @@ -306,6 +317,7 @@ where }; response.push(status).ok(); } + #[cfg(feature = "factory-reset")] Command::FactoryReset => { debug_now!("Factory resetting the device"); if let Err(_err) = syscall!(self.trussed.confirm_user_present(15 * 1000)).result { @@ -316,6 +328,7 @@ where syscall!(self.trussed.factory_reset_device()); R::reboot(); } + #[cfg(feature = "factory-reset")] Command::FactoryResetApp => { let Ok(client) = core::str::from_utf8(input) else { response.push(FACTORY_RESET_APP_FAILED_PARSE).ok(); From c49c596df5e8e7248d6689bf990777961bcbcdd7 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Wed, 29 Nov 2023 15:27:20 +0100 Subject: [PATCH 37/65] Add App::without_config constructor For testing, e. g. in websmartcard, the config feature is not necessary. This patch adds a without_config constructor to the App to make it easier to setup the app in such cases. --- src/admin.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/admin.rs b/src/admin.rs index 0028c45..d59dc94 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -178,7 +178,7 @@ pub trait Reboot { fn locked() -> bool; } -pub struct App { +pub struct App { trussed: T, uuid: [u8; 16], version: u32, @@ -188,6 +188,27 @@ pub struct App { config: C, } +impl App +where + T: TrussedClient, + R: Reboot, + S: AsRef<[u8]>, +{ + /// Create an admin app instance without the configuration mechanism. + /// + /// This is only intended for testing and example code. Firmware runners should use + /// [`App::load`][]. + pub fn without_config( + client: T, + uuid: [u8; 16], + version: u32, + full_version: &'static str, + status: S, + ) -> Self { + Self::new(client, uuid, version, full_version, status, ()) + } +} + impl App where T: TrussedClient, @@ -195,6 +216,7 @@ where S: AsRef<[u8]>, C: Config, { + /// Create an admin app instance, loading the configuration from the filesystem. pub fn load( client: T, filestore: &mut F, From c15d43597928a9a7fc0b3dfdea97d175ca8a017a Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Fri, 1 Dec 2023 10:48:35 +0100 Subject: [PATCH 38/65] Make App::load fallible Previously, we assumed that we can always read from the IFS and panicked if that failed. This patch changes App::load to return an error instead. The runner can then decide whether it panics, skips the admin app or uses the default config (App::without_config). --- src/admin.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/admin.rs b/src/admin.rs index d59dc94..02d01fa 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -224,10 +224,13 @@ where version: u32, full_version: &'static str, status: S, - ) -> Self { + ) -> Result { config::load(filestore) .map(|config| Self::new(client, uuid, version, full_version, status, config)) - .unwrap() + .map_err(|err| { + error!("failed to load configuration: {:?}", err); + err + }) } fn new( From c25f80e5cef3e7f9609593b3cbe54024aa2c6ac0 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Fri, 1 Dec 2023 12:39:03 +0100 Subject: [PATCH 39/65] Refactor constructors This patch renames App::load to App::load_config and App::without_config to App::with_default_config. It also extends App::with_default_config to work with any Config implementation, not only (). --- src/admin.rs | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/admin.rs b/src/admin.rs index 02d01fa..020f150 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -188,27 +188,6 @@ pub struct App { config: C, } -impl App -where - T: TrussedClient, - R: Reboot, - S: AsRef<[u8]>, -{ - /// Create an admin app instance without the configuration mechanism. - /// - /// This is only intended for testing and example code. Firmware runners should use - /// [`App::load`][]. - pub fn without_config( - client: T, - uuid: [u8; 16], - version: u32, - full_version: &'static str, - status: S, - ) -> Self { - Self::new(client, uuid, version, full_version, status, ()) - } -} - impl App where T: TrussedClient, @@ -217,7 +196,7 @@ where C: Config, { /// Create an admin app instance, loading the configuration from the filesystem. - pub fn load( + pub fn load_config( client: T, filestore: &mut F, uuid: [u8; 16], @@ -233,6 +212,28 @@ where }) } + /// Create an admin app instance without the configuration mechanism, using the default config + /// values. + /// + /// This is only intended for debugging, testing and example code. In production, + /// [`App::load_config`][] should be used. + pub fn with_default_config( + client: T, + uuid: [u8; 16], + version: u32, + full_version: &'static str, + status: S, + ) -> Self { + Self::new( + client, + uuid, + version, + full_version, + status, + Default::default(), + ) + } + fn new( client: T, uuid: [u8; 16], From 7ecc984590625b03c2ba09b4b2db16e4076a0cc6 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Fri, 1 Dec 2023 12:51:51 +0100 Subject: [PATCH 40/65] Return client on failure in App::load_config This makes it possible to construct the App instance with App::with_default_config instead using the same client. --- src/admin.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/admin.rs b/src/admin.rs index 020f150..0300ab8 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -203,13 +203,21 @@ where version: u32, full_version: &'static str, status: S, - ) -> Result { - config::load(filestore) - .map(|config| Self::new(client, uuid, version, full_version, status, config)) - .map_err(|err| { + ) -> Result { + match config::load(filestore) { + Ok(config) => Ok(Self::new( + client, + uuid, + version, + full_version, + status, + config, + )), + Err(err) => { error!("failed to load configuration: {:?}", err); - err - }) + Err((client, err)) + } + } } /// Create an admin app instance without the configuration mechanism, using the default config From c6c87cfad4d94c4910c87e870bae759330f6f634 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 4 Mar 2024 14:57:39 +0100 Subject: [PATCH 41/65] Update dependencies --- Cargo.toml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 03a4518..69c0552 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,11 +20,10 @@ serde = { version = "1.0.180", default-features = false } strum_macros = "0.25.2" trussed = "0.1" -se05x = { version = "0.0.1", optional = true } embedded-hal = { version = "0.2.7", optional = true } hex-literal = "0.4.1" rand_chacha = { version = "0.3.1", optional = true, default-features = false } -trussed-se050-backend = { version = "0.1.0", optional = true } +trussed-se050-backend = { version = "0.2.0", optional = true } trussed-staging = { version = "0.1.0", features = ["manage"] } [features] @@ -40,10 +39,11 @@ log-warn = [] log-error = [] [patch.crates-io] -ctaphid-dispatch = { git = "https://github.com/trussed-dev/ctaphid-dispatch.git", rev = "57cb3317878a8593847595319aa03ef17c29ec5b" } -trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "51e68500d7601d04f884f5e95567d14b9018a6cb" } apdu-dispatch = { git = "https://github.com/trussed-dev/apdu-dispatch.git", rev = "915fc237103fcecc29d0f0b73391f19abf6576de" } -trussed-se050-backend = { git = "https://github.com/Nitrokey/trussed-se050-backend.git", rev = "3395a5b73241a0a9f14a0715952be6fe7f1e56b0" } -iso7816 = { git = "https://github.com/sosthene-nitrokey/iso7816.git", rev = "160ca3bbd8e21ec4e4ee1e0748e1eaa53a45c97f"} -se05x = { git = "https://github.com/Nitrokey/se05x.git", rev = "0b77eb6b152d214897696aadf87767fd84ffcb0e"} -trussed-staging = { git = "https://github.com/trussed-dev/trussed-staging.git", rev = "10baac2608e98e25ea77ade974a4e26f778d6c8e" } +ctaphid-dispatch = { git = "https://github.com/trussed-dev/ctaphid-dispatch.git", rev = "57cb3317878a8593847595319aa03ef17c29ec5b" } +littlefs2 = { git = "https://github.com/trussed-dev/littlefs2.git", rev = "ebd27e49ca321089d01d8c9b169c4aeb58ceeeca" } +trussed = { git = "https://github.com/Nitrokey/trussed.git", tag = "v0.1.0-nitrokey.18" } +trussed-auth = { git = "https://github.com/trussed-dev/trussed-auth", rev = "4b8191f248c26cb074cdac887c7f3f48f9c449a4" } +trussed-rsa-alloc = { git = "https://github.com/Nitrokey/trussed-rsa-backend.git", rev = "2088e2f8a8d706276c1559717b4c6b6d4f270253" } +trussed-se050-backend = { git = "https://github.com/Nitrokey/trussed-se050-backend.git", tag = "v0.2.0" } +trussed-staging = { git = "https://github.com/nitrokey/trussed-staging.git", tag = "v0.1.0-nitrokey-hmac256p256.3" } From 0ba0e766cba65a1fe7b0865f343de589e4202d82 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Wed, 13 Mar 2024 19:05:15 +0100 Subject: [PATCH 42/65] Use extension crates instead of backends This patch replaces the dependencies on trussed-staging and trussed-se050-backend with the extension crates trussed-manage and trussed-se050-manage. See the these PRs for more information: - https://github.com/trussed-dev/trussed-staging/pull/19 - https://github.com/Nitrokey/trussed-se050-backend/pull/13 --- Cargo.toml | 12 +++++------- src/lib.rs | 8 +++++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 69c0552..b4eff52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,12 +23,12 @@ trussed = "0.1" embedded-hal = { version = "0.2.7", optional = true } hex-literal = "0.4.1" rand_chacha = { version = "0.3.1", optional = true, default-features = false } -trussed-se050-backend = { version = "0.2.0", optional = true } -trussed-staging = { version = "0.1.0", features = ["manage"] } +trussed-manage = { version = "0.1.0" } +trussed-se050-manage = { version = "0.1.0", optional = true } [features] default = [] -se050 = ["trussed-se050-backend"] +se050 = ["trussed-se050-manage"] factory-reset = [] log-all = [] @@ -43,7 +43,5 @@ apdu-dispatch = { git = "https://github.com/trussed-dev/apdu-dispatch.git", rev ctaphid-dispatch = { git = "https://github.com/trussed-dev/ctaphid-dispatch.git", rev = "57cb3317878a8593847595319aa03ef17c29ec5b" } littlefs2 = { git = "https://github.com/trussed-dev/littlefs2.git", rev = "ebd27e49ca321089d01d8c9b169c4aeb58ceeeca" } trussed = { git = "https://github.com/Nitrokey/trussed.git", tag = "v0.1.0-nitrokey.18" } -trussed-auth = { git = "https://github.com/trussed-dev/trussed-auth", rev = "4b8191f248c26cb074cdac887c7f3f48f9c449a4" } -trussed-rsa-alloc = { git = "https://github.com/Nitrokey/trussed-rsa-backend.git", rev = "2088e2f8a8d706276c1559717b4c6b6d4f270253" } -trussed-se050-backend = { git = "https://github.com/Nitrokey/trussed-se050-backend.git", tag = "v0.2.0" } -trussed-staging = { git = "https://github.com/nitrokey/trussed-staging.git", tag = "v0.1.0-nitrokey-hmac256p256.3" } +trussed-manage = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "manage-v0.1.0" } +trussed-se050-manage = { git = "https://github.com/Nitrokey/trussed-se050-backend.git", tag = "se050-manage-v0.1.0" } diff --git a/src/lib.rs b/src/lib.rs index 5fcad01..a2e9315 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,9 @@ mod config; pub use admin::{App, Reboot}; pub use config::{Config, ConfigError, ConfigValueMut, ResetSignal, ResetSignalAllocation}; -use trussed_staging::manage::ManageClient; +use trussed_manage::ManageClient; +#[cfg(feature = "se050")] +use trussed_se050_manage::Se050ManageClient; #[cfg(not(feature = "se050"))] pub trait Client: trussed::Client + ManageClient {} @@ -24,8 +26,8 @@ impl Client for C {} #[cfg(feature = "se050")] pub trait Client: - trussed::Client + trussed_se050_backend::manage::ManageClient + ManageClient + trussed::Client + Se050ManageClient + ManageClient { } #[cfg(feature = "se050")] -impl Client for C {} +impl Client for C {} From 9f832f3b7f79108353b82e28f78449f10883abdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Mon, 25 Mar 2024 11:34:01 +0100 Subject: [PATCH 43/65] Add migration mechanism --- src/admin.rs | 41 +++++++++++++++++++++++++++++++++++++++++ src/config.rs | 18 ++++++++++++++++++ src/lib.rs | 6 ++---- src/migrations.rs | 12 ++++++++++++ 4 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 src/migrations.rs diff --git a/src/admin.rs b/src/admin.rs index 0300ab8..bcadb8d 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -8,9 +8,11 @@ use ctaphid_dispatch::command::VendorCommand; #[cfg(feature = "factory-reset")] use littlefs2::path::PathBuf; use serde::Deserialize; +use trussed::store::Store; use trussed::{interrupt::InterruptFlag, store::filestore::Filestore, syscall, types::Vec}; use crate::config::{self, Config, ConfigError}; +use crate::migrations::Migrator; pub const USER_PRESENCE_TIMEOUT_SECS: u32 = 15; @@ -186,6 +188,7 @@ pub struct App { status: S, boot_interface: PhantomData, config: C, + migrations: &'static [Migrator], } impl App @@ -203,6 +206,7 @@ where version: u32, full_version: &'static str, status: S, + migrations: &'static [Migrator], ) -> Result { match config::load(filestore) { Ok(config) => Ok(Self::new( @@ -212,6 +216,7 @@ where full_version, status, config, + migrations, )), Err(err) => { error!("failed to load configuration: {:?}", err); @@ -220,6 +225,38 @@ where } } + pub fn migrate(&mut self, to_version: u32, store: impl Store) -> Result<(), ConfigError> { + let Some(current_version) = self.config.migration_version() else { + // Migrate cannot be done for configurations that don't provide storage of the filesystem version + return Err(ConfigError::InvalidValue); + }; + + if current_version == to_version { + return Ok(()); + } + + if to_version < current_version { + return Err(ConfigError::InvalidValue); + } + + let internal = store.ifs(); + let external = store.efs(); + + for migration in self.migrations { + if migration.version > current_version && migration.version <= to_version { + (migration.migrate)(&**internal, &**external).map_err(|_err| { + error_now!("Migration failed: {_err}"); + ConfigError::WriteFailed + })?; + } + } + + if !self.config.set_migration_version(to_version) { + return Err(ConfigError::InvalidValue); + } + config::save(&mut self.trussed, &self.config) + } + /// Create an admin app instance without the configuration mechanism, using the default config /// values. /// @@ -231,6 +268,7 @@ where version: u32, full_version: &'static str, status: S, + migrations: &'static [Migrator], ) -> Self { Self::new( client, @@ -239,6 +277,7 @@ where full_version, status, Default::default(), + migrations, ) } @@ -249,6 +288,7 @@ where full_version: &'static str, status: S, config: C, + migrations: &'static [Migrator], ) -> Self { Self { trussed: client, @@ -258,6 +298,7 @@ where status, boot_interface: PhantomData, config, + migrations, } } diff --git a/src/config.rs b/src/config.rs index 13fde86..e06d4bb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -122,12 +122,30 @@ pub trait Config: Default + PartialEq + DeserializeOwned + Serialize { ) -> Option<(&'static Path, &'static ResetSignalAllocation)> { None } + + /// The migration version + /// + /// Return None if the configuration does not support storing the migration version + fn migration_version(&self) -> Option; + + /// Set the migration version + /// + /// Return false if the configuration does not support storing the migration version + fn set_migration_version(&mut self, _version: u32) -> bool; } impl Config for () { fn field(&mut self, _key: &str) -> Option> { None } + + fn migration_version(&self) -> Option { + None + } + + fn set_migration_version(&mut self, _version: u32) -> bool { + false + } } #[derive(Debug, Serialize)] diff --git a/src/lib.rs b/src/lib.rs index a2e9315..e1db412 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,6 +12,7 @@ generate_macros!(); mod admin; mod config; +pub mod migrations; pub use admin::{App, Reboot}; pub use config::{Config, ConfigError, ConfigValueMut, ResetSignal, ResetSignalAllocation}; @@ -25,9 +26,6 @@ pub trait Client: trussed::Client + ManageClient {} impl Client for C {} #[cfg(feature = "se050")] -pub trait Client: - trussed::Client + Se050ManageClient + ManageClient -{ -} +pub trait Client: trussed::Client + Se050ManageClient + ManageClient {} #[cfg(feature = "se050")] impl Client for C {} diff --git a/src/migrations.rs b/src/migrations.rs new file mode 100644 index 0000000..caea01a --- /dev/null +++ b/src/migrations.rs @@ -0,0 +1,12 @@ +use littlefs2::object_safe::DynFilesystem; + +#[derive(Debug)] +pub struct Migrator { + /// The function performing the migration + /// + /// First argument is the Internal Filesystem, second argument is the External + pub migrate: fn(&dyn DynFilesystem, &dyn DynFilesystem) -> Result<(), littlefs2::io::Error>, + + /// The version of the storage for which the migration needs to be run + pub version: u32, +} From dc8f91b85b3c12f45fe38f525ec5017df806f236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Mon, 25 Mar 2024 12:15:26 +0100 Subject: [PATCH 44/65] Add migration testing utils --- Cargo.toml | 3 + src/migrations.rs | 136 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index b4eff52..f4a9619 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,9 @@ log-debug = [] log-warn = [] log-error = [] +# Utils to test migration +migration-tests = [] + [patch.crates-io] apdu-dispatch = { git = "https://github.com/trussed-dev/apdu-dispatch.git", rev = "915fc237103fcecc29d0f0b73391f19abf6576de" } ctaphid-dispatch = { git = "https://github.com/trussed-dev/ctaphid-dispatch.git", rev = "57cb3317878a8593847595319aa03ef17c29ec5b" } diff --git a/src/migrations.rs b/src/migrations.rs index caea01a..519549c 100644 --- a/src/migrations.rs +++ b/src/migrations.rs @@ -10,3 +10,139 @@ pub struct Migrator { /// The version of the storage for which the migration needs to be run pub version: u32, } + +#[cfg(feature = "migration-tests")] +pub mod test_utils { + + // Copyright (C) Nitrokey GmbH + // SPDX-License-Identifier: Apache-2.0 or MIT + + use littlefs2::{ + fs::Filesystem, io::Error, object_safe::DynFilesystem, path, path::Path, ram_storage, + }; + + /// Represent a directory of data + pub enum FsValues { + Dir(&'static [(&'static Path, FsValues)]), + File(usize), + } + + type Result = core::result::Result; + + /// Prepare the filesystem for a given tests values + pub fn prepare_fs(fs: &dyn DynFilesystem, value: &FsValues, path: &Path) { + match value { + FsValues::File(f_data_len) => { + fs.create_file_and_then(path, &mut |f| { + f.set_len(*f_data_len).unwrap(); + Ok(()) + }) + .unwrap(); + } + FsValues::Dir(d) => { + if path != path!("/") { + fs.create_dir(path).unwrap(); + } + for (p, v) in *d { + prepare_fs(fs, v, &path.join(p)); + } + } + } + } + + /// Test equality between the filesystem and the expected values + pub fn test_fs_equality(fs: &dyn DynFilesystem, value: &FsValues, path: &Path) { + match value { + FsValues::Dir(d) => { + let mut expected_iter = d.iter(); + fs.read_dir_and_then(path, &mut |dir| { + // skip . and .. + dir.next().unwrap().unwrap(); + dir.next().unwrap().unwrap(); + for (expected_path, expected_values) in expected_iter.by_ref() { + let entry = dir.next().unwrap().unwrap(); + assert_eq!(entry.file_name(), *expected_path); + test_fs_equality(fs, expected_values, &path.join(expected_path)); + } + assert!(dir.next().is_none()); + Ok(()) + }) + .unwrap(); + } + FsValues::File(f_data_len) => { + fs.open_file_and_then(path, &mut |f| { + let mut buf = [0; 512]; + let data = f.read(&mut buf).unwrap(); + assert_eq!(data, *f_data_len); + Ok(()) + }) + .unwrap(); + } + } + } + + ram_storage!( + name=NoBackendStorage, + backend=RamDirect, + trait=littlefs2::driver::Storage, + erase_value=0xff, + read_size=16, + write_size=16, + cache_size_ty=littlefs2::consts::U512, + block_size=512, + block_count=128, + lookahead_size_ty=littlefs2::consts::U8, + filename_max_plus_one_ty=littlefs2::consts::U256, + path_max_plus_one_ty=littlefs2::consts::U256, + result=Result, + ); + + pub fn test_migration_one( + before: &FsValues, + after: &FsValues, + migrate: impl Fn(&dyn DynFilesystem) -> Result<(), Error>, + ) { + test_migration( + before, + after, + &FsValues::Dir(&[]), + &FsValues::Dir(&[]), + |ifs, _efs| migrate(ifs), + ); + } + + pub fn test_migration( + before_ifs: &FsValues, + after_ifs: &FsValues, + before_efs: &FsValues, + after_efs: &FsValues, + migrate: impl Fn(&dyn DynFilesystem, &dyn DynFilesystem) -> Result<(), Error>, + ) { + let mut storage_ifs = RamDirect::default(); + let mut storage_efs = RamDirect::default(); + + let backend_efs = &mut NoBackendStorage::new(&mut storage_efs); + let backend_ifs = &mut NoBackendStorage::new(&mut storage_ifs); + + Filesystem::format(backend_ifs).unwrap(); + Filesystem::format(backend_efs).unwrap(); + + Filesystem::mount_and_then(backend_ifs, |ifs| { + Filesystem::mount_and_then(backend_efs, |efs| { + prepare_fs(ifs, before_ifs, path!("/")); + prepare_fs(efs, before_efs, path!("/")); + + test_fs_equality(ifs, before_ifs, path!("/")); + test_fs_equality(efs, before_efs, path!("/")); + + migrate(ifs, efs).unwrap(); + test_fs_equality(efs, after_efs, path!("/")); + test_fs_equality(ifs, after_ifs, path!("/")); + Ok(()) + }) + .unwrap(); + Ok(()) + }) + .unwrap(); + } +} From 5c0f1010273e764cf2837205c45820f5086c52cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Tue, 26 Mar 2024 09:30:02 +0100 Subject: [PATCH 45/65] Fix compilation with log-error --- src/admin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/admin.rs b/src/admin.rs index bcadb8d..039f56e 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -245,7 +245,7 @@ where for migration in self.migrations { if migration.version > current_version && migration.version <= to_version { (migration.migrate)(&**internal, &**external).map_err(|_err| { - error_now!("Migration failed: {_err}"); + error_now!("Migration failed: {_err:?}"); ConfigError::WriteFailed })?; } From 8b352509e3f18444e66f3323fd98478eca9c49d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Tue, 26 Mar 2024 10:02:08 +0100 Subject: [PATCH 46/65] Add access to status --- src/admin.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/admin.rs b/src/admin.rs index 039f56e..191b7fd 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -461,6 +461,14 @@ where config::save(&mut self.trussed, &self.config) } + + pub fn status(&self) -> &S { + &self.status + } + + pub fn status_mut(&mut self) -> &mut S { + &mut self.status + } } impl hid::App<'static> for App From 6db11c60f8efb1277dc327860172a1356fa02106 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Tue, 26 Mar 2024 14:20:55 +0100 Subject: [PATCH 47/65] Use clientfilestore to store config after migration (migrations run before trussed is up) --- src/admin.rs | 9 +++++++-- src/config.rs | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/admin.rs b/src/admin.rs index 191b7fd..d3d8c48 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -225,7 +225,12 @@ where } } - pub fn migrate(&mut self, to_version: u32, store: impl Store) -> Result<(), ConfigError> { + pub fn migrate( + &mut self, + to_version: u32, + store: impl Store, + filestore: &mut F, + ) -> Result<(), ConfigError> { let Some(current_version) = self.config.migration_version() else { // Migrate cannot be done for configurations that don't provide storage of the filesystem version return Err(ConfigError::InvalidValue); @@ -254,7 +259,7 @@ where if !self.config.set_migration_version(to_version) { return Err(ConfigError::InvalidValue); } - config::save(&mut self.trussed, &self.config) + config::save_filestore(filestore, &self.config) } /// Create an admin app instance without the configuration mechanism, using the default config diff --git a/src/config.rs b/src/config.rs index e06d4bb..ea5c136 100644 --- a/src/config.rs +++ b/src/config.rs @@ -225,6 +225,26 @@ pub fn load(store: &mut F) -> Result { cbor_deserialize(&data).map_err(|_| ConfigError::DeserializationFailed) } +pub fn save_filestore( + store: &mut F, + config: &C, +) -> Result<(), ConfigError> { + if config == &C::default() { + if store.exists(FILENAME, LOCATION) { + store + .remove_file(FILENAME, LOCATION) + .map_err(|_| ConfigError::WriteFailed)?; + } + } else { + let data: Message = + cbor_serialize_bytes(config).map_err(|_| ConfigError::SerializationFailed)?; + store + .write(FILENAME, LOCATION, &data) + .map_err(|_| ConfigError::SerializationFailed)?; + } + Ok(()) +} + pub fn save(client: &mut T, config: &C) -> Result<(), ConfigError> { if config == &Default::default() { if exists(client, LOCATION, FILENAME)? { From da6ccda351c4b7edbd7677ff636b7d0c9edb5199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 4 Apr 2024 17:28:43 +0200 Subject: [PATCH 48/65] Allow mutable access to the config --- src/admin.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/admin.rs b/src/admin.rs index d3d8c48..8a7745e 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -311,6 +311,10 @@ where &self.config } + pub fn config_mut(&mut self) -> &mut C { + &mut self.config + } + fn user_present(&mut self) -> bool { let user_present = syscall!(self .trussed From 054536c2b46722b657fdc4d5806a5edcb10b5256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Fri, 5 Apr 2024 12:14:57 +0200 Subject: [PATCH 49/65] Reset the config on application factory-reset --- src/admin.rs | 19 ++++++++++++++++--- src/config.rs | 35 +++++++++++++++++++++++++++++++++++ src/lib.rs | 4 +++- 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/admin.rs b/src/admin.rs index 8a7745e..6b9b1f0 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -431,10 +431,23 @@ where } let path = PathBuf::from(client); - // No need to factory reset if already factory reset - if flag.set_factory_reset() { - syscall!(self.trussed.factory_reset_client(&path)); + match self.config.reset_client_config(client) { + crate::config::ResetConfigResult::Changed => { + flag.set_config_changed(); + syscall!(self.trussed.factory_reset_client(&path)); + } + crate::config::ResetConfigResult::Unchanged => { + // No need to factory reset if already factory reset + if flag.set_factory_reset() { + syscall!(self.trussed.factory_reset_client(&path)); + } + } + crate::config::ResetConfigResult::WrongKey => { + response.push(FACTORY_RESET_APP_NOT_ALLOWED).ok(); + return Ok(()); + } } + response.push(FACTORY_RESET_OK).ok(); } } diff --git a/src/config.rs b/src/config.rs index ea5c136..260b961 100644 --- a/src/config.rs +++ b/src/config.rs @@ -107,6 +107,28 @@ pub enum ResetSignal { const LOCATION: Location = Location::Internal; const FILENAME: &Path = path!("config"); +#[derive(Debug, Clone, Copy)] +pub enum ResetConfigResult { + /// The config was changed as a result of the reset to default + Changed, + /// The config was at the default value + Unchanged, + /// The key does not correspond to any application that can be reset + WrongKey, +} + +impl ResetConfigResult { + pub fn is_changed(&self) -> bool { + matches!(self, Self::Changed) + } + pub fn is_unchanged(&self) -> bool { + matches!(self, Self::Unchanged) + } + pub fn is_error(&self) -> bool { + matches!(self, Self::WrongKey) + } +} + pub trait Config: Default + PartialEq + DeserializeOwned + Serialize { fn field(&mut self, key: &str) -> Option>; @@ -115,6 +137,8 @@ pub trait Config: Default + PartialEq + DeserializeOwned + Serialize { /// # If the Request is for a `client_id`: /// /// - MUST return `Some` to indicate that the client can be factory reset by the admin app, + /// In that case, the path is the clientid that must be reset, and the allocation must point to a + /// signal that id checked by the application. /// - MUST return None otherwise. fn reset_client_id( &self, @@ -123,6 +147,13 @@ pub trait Config: Default + PartialEq + DeserializeOwned + Serialize { None } + /// Reset the config of a client to its default value + /// + /// Returns `true` if the config has been changed as a result + fn reset_client_config(&mut self, _key: &str) -> ResetConfigResult { + ResetConfigResult::WrongKey + } + /// The migration version /// /// Return None if the configuration does not support storing the migration version @@ -139,6 +170,10 @@ impl Config for () { None } + fn reset_client_config(&mut self, _key: &str) -> ResetConfigResult { + ResetConfigResult::WrongKey + } + fn migration_version(&self) -> Option { None } diff --git a/src/lib.rs b/src/lib.rs index e1db412..b080079 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,9 @@ mod config; pub mod migrations; pub use admin::{App, Reboot}; -pub use config::{Config, ConfigError, ConfigValueMut, ResetSignal, ResetSignalAllocation}; +pub use config::{ + Config, ConfigError, ConfigValueMut, ResetConfigResult, ResetSignal, ResetSignalAllocation, +}; use trussed_manage::ManageClient; #[cfg(feature = "se050")] use trussed_se050_manage::Se050ManageClient; From 779f6cb07bfee2ff3f8e639e717166007a8de9bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Tue, 9 Apr 2024 11:14:14 +0200 Subject: [PATCH 50/65] Implement saving the config after mutating it --- src/admin.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/admin.rs b/src/admin.rs index 6b9b1f0..9bab0c6 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -315,6 +315,13 @@ where &mut self.config } + pub fn save_config_filestore( + &mut self, + filestore: &mut F, + ) -> Result<(), ConfigError> { + config::save_filestore(filestore, &self.config) + } + fn user_present(&mut self) -> bool { let user_present = syscall!(self .trussed From c257432dbe2efb53424d6847d82d90ddb527c53b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Tue, 9 Apr 2024 11:28:44 +0200 Subject: [PATCH 51/65] Save config after factory-reset-app --- src/admin.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/admin.rs b/src/admin.rs index 9bab0c6..58245f3 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -441,6 +441,10 @@ where match self.config.reset_client_config(client) { crate::config::ResetConfigResult::Changed => { flag.set_config_changed(); + config::save(&mut self.trussed, &self.config).map_err(|_err| { + error_now!("Failed to save config: {_err:?}"); + Error::InvalidLength + })?; syscall!(self.trussed.factory_reset_client(&path)); } crate::config::ResetConfigResult::Unchanged => { From 914da07862097d6d9915439e4421d0eaae959e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Mon, 29 Jul 2024 17:20:09 +0200 Subject: [PATCH 52/65] Add trait for StatusBytes and allow lazyly configuring the initialization of the RNG --- src/admin.rs | 28 +++++++++++++++++++++++----- src/lib.rs | 2 +- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/admin.rs b/src/admin.rs index 58245f3..87bd9d2 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -9,6 +9,7 @@ use ctaphid_dispatch::command::VendorCommand; use littlefs2::path::PathBuf; use serde::Deserialize; use trussed::store::Store; +use trussed::try_syscall; use trussed::{interrupt::InterruptFlag, store::filestore::Filestore, syscall, types::Vec}; use crate::config::{self, Config, ConfigError}; @@ -180,6 +181,17 @@ pub trait Reboot { fn locked() -> bool; } +/// Trait indicating that a value can be used as a status +pub trait StatusBytes { + type Serialized: AsRef<[u8]>; + /// Set the flag indicating that the random generator could properly be created (`false`) or not (`true`) + fn set_random_error(&mut self, value: bool); + /// Get the flag indicating that the random generator could properly be created (`false`) or not (`true`) + fn get_random_error(&self) -> bool; + /// Serialize the StatusBytes to raw bytes + fn serialize(&self) -> Self::Serialized; +} + pub struct App { trussed: T, uuid: [u8; 16], @@ -195,7 +207,7 @@ impl App where T: TrussedClient, R: Reboot, - S: AsRef<[u8]>, + S: StatusBytes, C: Config, { /// Create an admin app instance, loading the configuration from the filesystem. @@ -378,7 +390,13 @@ where syscall!(self.trussed.wink(Duration::from_secs(10))); } Command::Status => { - response.extend_from_slice(self.status.as_ref()).ok(); + if !self.status.get_random_error() { + let is_random_working = try_syscall!(self.trussed.random_bytes(1)).is_ok(); + self.status.set_random_error(!is_random_working); + } + response + .extend_from_slice(self.status.serialize().as_ref()) + .ok(); } Command::TestSe05X => { #[cfg(feature = "se050")] @@ -508,7 +526,7 @@ impl hid::App<'static> for App where T: TrussedClient, R: Reboot, - S: AsRef<[u8]>, + S: StatusBytes, C: Config, { fn commands(&self) -> &'static [HidCommand] { @@ -551,7 +569,7 @@ impl iso7816::App for App where T: TrussedClient, R: Reboot, - S: AsRef<[u8]>, + S: StatusBytes, { // Solo management app fn aid(&self) -> iso7816::Aid { @@ -563,7 +581,7 @@ impl apdu::App<{ command::SIZE }, { response::SIZE }> for App, + S: StatusBytes, C: Config, { fn select( diff --git a/src/lib.rs b/src/lib.rs index b080079..fa8a2ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ mod admin; mod config; pub mod migrations; -pub use admin::{App, Reboot}; +pub use admin::{App, Reboot, StatusBytes}; pub use config::{ Config, ConfigError, ConfigValueMut, ResetConfigResult, ResetSignal, ResetSignalAllocation, }; From 37d543725752d427988273ddbf27723945067414 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Thu, 1 Aug 2024 11:29:25 +0200 Subject: [PATCH 53/65] Increase timeout for reboot to bootloader Previously, we had a timeout of 15 seconds for the user presence check when rebooting to bootloader. This can be too short in some situations, see for example https://github.com/Nitrokey/nitrokey-3-firmware/issues/519. This patch increases the timeout to 30 seconds. --- src/admin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/admin.rs b/src/admin.rs index 87bd9d2..a1992cf 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -15,7 +15,7 @@ use trussed::{interrupt::InterruptFlag, store::filestore::Filestore, syscall, ty use crate::config::{self, Config, ConfigError}; use crate::migrations::Migrator; -pub const USER_PRESENCE_TIMEOUT_SECS: u32 = 15; +pub const USER_PRESENCE_TIMEOUT_SECS: u32 = 30; // New commands are only available over this vendor command (acting as a namespace for this // application). The actual application command is stored in the first byte of the packet data. From 29a48fea312a0e1714a535bf7ffe92091352e20e Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Thu, 10 Oct 2024 13:19:57 +0200 Subject: [PATCH 54/65] Revert "Increase timeout for reboot to bootloader" This reverts commit 37d543725752d427988273ddbf27723945067414. I misunderstood the requirement described in https://github.com/Nitrokey/nitrokey-3-firmware/issues/519, so this change was unnecessary. --- src/admin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/admin.rs b/src/admin.rs index a1992cf..87bd9d2 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -15,7 +15,7 @@ use trussed::{interrupt::InterruptFlag, store::filestore::Filestore, syscall, ty use crate::config::{self, Config, ConfigError}; use crate::migrations::Migrator; -pub const USER_PRESENCE_TIMEOUT_SECS: u32 = 30; +pub const USER_PRESENCE_TIMEOUT_SECS: u32 = 15; // New commands are only available over this vendor command (acting as a namespace for this // application). The actual application command is stored in the first byte of the packet data. From c5bddba53dbc0ca9ce9dce2ac03a28ed22f5b060 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 3 Oct 2024 11:06:36 +0200 Subject: [PATCH 55/65] Add command to list configuration options --- Cargo.toml | 3 ++- src/admin.rs | 9 +++++++- src/config.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++- src/lib.rs | 3 ++- 4 files changed, 69 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f4a9619..1977299 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ description = "Administrative Trussed app for SoloKeys Solo 2 security keys" [dependencies] apdu-dispatch = "0.1" -cbor-smol = "0.4.0" +cbor-smol = { version = "0.4.0", features = ["heapless-v0-7", "heapless-bytes-v0-3"] } ctaphid-dispatch = "0.1" delog = "0.1" iso7816 = "0.1" @@ -48,3 +48,4 @@ littlefs2 = { git = "https://github.com/trussed-dev/littlefs2.git", rev = "ebd27 trussed = { git = "https://github.com/Nitrokey/trussed.git", tag = "v0.1.0-nitrokey.18" } trussed-manage = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "manage-v0.1.0" } trussed-se050-manage = { git = "https://github.com/Nitrokey/trussed-se050-backend.git", tag = "se050-manage-v0.1.0" } +cbor-smol = { git = "https://github.com/trussed-dev/cbor-smol.git", rev = "4a368461e06e3ca52d79638b9ab8f34b038491fc" } diff --git a/src/admin.rs b/src/admin.rs index 87bd9d2..11407c6 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -1,7 +1,7 @@ use super::Client as TrussedClient; use apdu_dispatch::iso7816::Status; use apdu_dispatch::{app as apdu, command, dispatch::Interface, response, Command as ApduCommand}; -use cbor_smol::cbor_deserialize; +use cbor_smol::{cbor_deserialize, cbor_serialize_to}; use core::{convert::TryInto, marker::PhantomData, time::Duration}; use ctaphid_dispatch::app::{self as hid, Command as HidCommand, Message}; use ctaphid_dispatch::command::VendorCommand; @@ -28,6 +28,7 @@ const SET_CONFIG: u8 = 0x83; const FACTORY_RESET: u8 = 0x84; #[cfg(feature = "factory-reset")] const FACTORY_RESET_APP: u8 = 0x85; +const LIST_AVAILABLE_FIELDS: u8 = 0x86; // For compatibility, old commands are also available directly as separate vendor commands. const UPDATE: VendorCommand = VendorCommand::H51; @@ -69,6 +70,7 @@ enum Command { FactoryReset, #[cfg(feature = "factory-reset")] FactoryResetApp, + ListAvailableFields, } impl TryFrom for Command { @@ -92,6 +94,7 @@ impl TryFrom for Command { FACTORY_RESET => Ok(Command::FactoryReset), #[cfg(feature = "factory-reset")] FACTORY_RESET_APP => Ok(Command::FactoryResetApp), + LIST_AVAILABLE_FIELDS => Ok(Command::ListAvailableFields), _ => Err(Error::UnsupportedCommand), } } @@ -426,6 +429,10 @@ where }; response.push(status).ok(); } + Command::ListAvailableFields => { + cbor_serialize_to(&self.config.list_available_fields(), response).ok(); + return Ok(()); + } #[cfg(feature = "factory-reset")] Command::FactoryReset => { debug_now!("Factory resetting the device"); diff --git a/src/config.rs b/src/config.rs index 260b961..dec1dcd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -20,7 +20,7 @@ use trussed::{ /// /// It is expected to have one such structure for each application supporting factory-reset by the admin-app /// -/// ```rust +/// ```rust,ignore ///# use admin_app::{ResetSignalAllocation, ConfigValueMut}; ///# use littlefs2::{path::Path, path}; /// #[derive(Default, PartialEq, serde::Deserialize, serde::Serialize)] @@ -163,6 +163,34 @@ pub trait Config: Default + PartialEq + DeserializeOwned + Serialize { /// /// Return false if the configuration does not support storing the migration version fn set_migration_version(&mut self, _version: u32) -> bool; + + fn list_available_fields(&self) -> &'static [ConfigField]; +} + +// No need to rename, cbor-smol already packs enum using ids +#[derive(Serialize)] +#[non_exhaustive] +pub enum FieldType { + Bool, + U8, +} + +#[derive(Serialize)] +pub struct ConfigField { + #[serde(rename = "n")] + pub name: &'static str, + /// Changing the config field requires a touch + #[serde(rename = "c")] + pub requires_touch_confirmation: bool, + /// Changing the config field requires a power cycle + #[serde(rename = "r")] + pub requires_reboot: bool, + /// Changing the config field deletes data + #[serde(rename = "d")] + pub destructive: bool, + /// The type of data stored in this field + #[serde(rename = "t")] + pub ty: FieldType, } impl Config for () { @@ -181,6 +209,10 @@ impl Config for () { fn set_migration_version(&mut self, _version: u32) -> bool { false } + + fn list_available_fields(&self) -> &'static [ConfigField] { + &[] + } } #[derive(Debug, Serialize)] @@ -313,3 +345,27 @@ fn load_if_exists( } }) } + +#[cfg(test)] +mod tests { + use hex_literal::hex; + + use super::*; + + #[test] + fn config_field() { + let fields = &[ConfigField { + name: "test_name", + requires_touch_confirmation: true, + requires_reboot: false, + destructive: true, + ty: FieldType::Bool, + }]; + let mut bytes: trussed::types::Vec = Default::default(); + cbor_smol::cbor_serialize_to(fields, &mut bytes).unwrap(); + assert_eq!( + &bytes, + &hex!("81A5616E69746573745F6E616D656163F56172F46164F5617400") + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index fa8a2ea..97a34fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,8 @@ pub mod migrations; pub use admin::{App, Reboot, StatusBytes}; pub use config::{ - Config, ConfigError, ConfigValueMut, ResetConfigResult, ResetSignal, ResetSignalAllocation, + Config, ConfigError, ConfigField, ConfigValueMut, FieldType, ResetConfigResult, ResetSignal, + ResetSignalAllocation, }; use trussed_manage::ManageClient; #[cfg(feature = "se050")] From 21ddbf12a3a5f60916c4a47e1bf3e49ce5cf03cd Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Thu, 17 Oct 2024 23:15:14 +0200 Subject: [PATCH 56/65] Use apdu-app instead of apdu-dispatch --- Cargo.toml | 3 +-- src/admin.rs | 23 +++++++++++------------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1977299..25d2968 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ description = "Administrative Trussed app for SoloKeys Solo 2 security keys" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -apdu-dispatch = "0.1" +apdu-app = "0.1" cbor-smol = { version = "0.4.0", features = ["heapless-v0-7", "heapless-bytes-v0-3"] } ctaphid-dispatch = "0.1" delog = "0.1" @@ -42,7 +42,6 @@ log-error = [] migration-tests = [] [patch.crates-io] -apdu-dispatch = { git = "https://github.com/trussed-dev/apdu-dispatch.git", rev = "915fc237103fcecc29d0f0b73391f19abf6576de" } ctaphid-dispatch = { git = "https://github.com/trussed-dev/ctaphid-dispatch.git", rev = "57cb3317878a8593847595319aa03ef17c29ec5b" } littlefs2 = { git = "https://github.com/trussed-dev/littlefs2.git", rev = "ebd27e49ca321089d01d8c9b169c4aeb58ceeeca" } trussed = { git = "https://github.com/Nitrokey/trussed.git", tag = "v0.1.0-nitrokey.18" } diff --git a/src/admin.rs b/src/admin.rs index 11407c6..3c9a1fd 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -1,6 +1,5 @@ use super::Client as TrussedClient; -use apdu_dispatch::iso7816::Status; -use apdu_dispatch::{app as apdu, command, dispatch::Interface, response, Command as ApduCommand}; +use apdu_app::{CommandView, Interface, Status}; use cbor_smol::{cbor_deserialize, cbor_serialize_to}; use core::{convert::TryInto, marker::PhantomData, time::Duration}; use ctaphid_dispatch::app::{self as hid, Command as HidCommand, Message}; @@ -584,7 +583,7 @@ where } } -impl apdu::App<{ command::SIZE }, { response::SIZE }> for App +impl apdu_app::App for App where T: TrussedClient, R: Reboot, @@ -594,9 +593,9 @@ where fn select( &mut self, _interface: Interface, - _apdu: &ApduCommand, - _reply: &mut response::Data, - ) -> apdu::Result { + _apdu: CommandView<'_>, + _reply: &mut apdu_app::Data, + ) -> apdu_app::Result { Ok(()) } @@ -604,15 +603,15 @@ where fn call( &mut self, - interface: apdu::Interface, - apdu: &ApduCommand, - reply: &mut response::Data, - ) -> apdu::Result { + interface: Interface, + apdu: CommandView<'_>, + reply: &mut apdu_app::Data, + ) -> apdu_app::Result { let instruction: u8 = apdu.instruction().into(); let command = Command::try_from(instruction)?; // Reboot may only be called over USB - if command == Command::Reboot && interface != apdu::Interface::Contact { + if command == Command::Reboot && interface != Interface::Contact { return Err(Status::ConditionsOfUseNotSatisfied); } @@ -622,7 +621,7 @@ where if command == Command::Update || command == Command::Version { self.exec(command, &[apdu.p1], reply) } else { - self.exec(command, apdu.data().as_slice(), reply) + self.exec(command, apdu.data(), reply) } .map_err(From::from) } From f66f5263db2d4ff36e7a288288cce6b81b3741ed Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 21 Oct 2024 21:42:09 +0200 Subject: [PATCH 57/65] Update cbor-smol to v0.5.0 --- Cargo.toml | 3 +-- src/config.rs | 9 +++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 25d2968..ebb1222 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ description = "Administrative Trussed app for SoloKeys Solo 2 security keys" [dependencies] apdu-app = "0.1" -cbor-smol = { version = "0.4.0", features = ["heapless-v0-7", "heapless-bytes-v0-3"] } +cbor-smol = { version = "0.5.0", features = ["heapless-v0-7", "heapless-bytes-v0-3"] } ctaphid-dispatch = "0.1" delog = "0.1" iso7816 = "0.1" @@ -47,4 +47,3 @@ littlefs2 = { git = "https://github.com/trussed-dev/littlefs2.git", rev = "ebd27 trussed = { git = "https://github.com/Nitrokey/trussed.git", tag = "v0.1.0-nitrokey.18" } trussed-manage = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "manage-v0.1.0" } trussed-se050-manage = { git = "https://github.com/Nitrokey/trussed-se050-backend.git", tag = "se050-manage-v0.1.0" } -cbor-smol = { git = "https://github.com/trussed-dev/cbor-smol.git", rev = "4a368461e06e3ca52d79638b9ab8f34b038491fc" } diff --git a/src/config.rs b/src/config.rs index dec1dcd..7b9bd1d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,7 +4,7 @@ use core::{ sync::atomic::{AtomicU8, Ordering}, }; -use cbor_smol::{cbor_deserialize, cbor_serialize_bytes}; +use cbor_smol::{cbor_deserialize, cbor_serialize_to}; use littlefs2::{path, path::Path}; use serde::{de::DeserializeOwned, Serialize}; use strum_macros::FromRepr; @@ -303,8 +303,8 @@ pub fn save_filestore( .map_err(|_| ConfigError::WriteFailed)?; } } else { - let data: Message = - cbor_serialize_bytes(config).map_err(|_| ConfigError::SerializationFailed)?; + let mut data = Message::new(); + cbor_serialize_to(config, &mut data).map_err(|_| ConfigError::SerializationFailed)?; store .write(FILENAME, LOCATION, &data) .map_err(|_| ConfigError::SerializationFailed)?; @@ -319,7 +319,8 @@ pub fn save(client: &mut T, config: &C) -> Result<(), Conf .map_err(|_| ConfigError::WriteFailed)?; } } else { - let data = cbor_serialize_bytes(config).map_err(|_| ConfigError::SerializationFailed)?; + let mut data = Message::new(); + cbor_serialize_to(config, &mut data).map_err(|_| ConfigError::SerializationFailed)?; try_syscall!(client.write_file(LOCATION, FILENAME.into(), data, None)) .map_err(|_| ConfigError::WriteFailed)?; } From 599d205e47a7430dfed1c37263fa565597b6597f Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 14 Oct 2024 15:54:45 +0200 Subject: [PATCH 58/65] Update littlefs2 to v0.5.0 --- Cargo.toml | 8 ++++---- src/admin.rs | 7 +++++-- src/config.rs | 2 +- src/migrations.rs | 4 ++-- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ebb1222..b5aa712 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,8 @@ cbor-smol = { version = "0.5.0", features = ["heapless-v0-7", "heapless-bytes-v0 ctaphid-dispatch = "0.1" delog = "0.1" iso7816 = "0.1" -littlefs2 = "0.4" +littlefs2 = { version = "0.5", optional = true } +littlefs2-core = { version = "0.1", features = ["heapless-bytes03"] } serde = { version = "1.0.180", default-features = false } strum_macros = "0.25.2" trussed = "0.1" @@ -39,11 +40,10 @@ log-warn = [] log-error = [] # Utils to test migration -migration-tests = [] +migration-tests = ["dep:littlefs2"] [patch.crates-io] ctaphid-dispatch = { git = "https://github.com/trussed-dev/ctaphid-dispatch.git", rev = "57cb3317878a8593847595319aa03ef17c29ec5b" } -littlefs2 = { git = "https://github.com/trussed-dev/littlefs2.git", rev = "ebd27e49ca321089d01d8c9b169c4aeb58ceeeca" } -trussed = { git = "https://github.com/Nitrokey/trussed.git", tag = "v0.1.0-nitrokey.18" } +trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "046478b7a4f6e2315acf9112d98308379c2e3eee" } trussed-manage = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "manage-v0.1.0" } trussed-se050-manage = { git = "https://github.com/Nitrokey/trussed-se050-backend.git", tag = "se050-manage-v0.1.0" } diff --git a/src/admin.rs b/src/admin.rs index 3c9a1fd..85b3580 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -5,7 +5,7 @@ use core::{convert::TryInto, marker::PhantomData, time::Duration}; use ctaphid_dispatch::app::{self as hid, Command as HidCommand, Message}; use ctaphid_dispatch::command::VendorCommand; #[cfg(feature = "factory-reset")] -use littlefs2::path::PathBuf; +use littlefs2_core::PathBuf; use serde::Deserialize; use trussed::store::Store; use trussed::try_syscall; @@ -449,6 +449,10 @@ where response.push(FACTORY_RESET_APP_FAILED_PARSE).ok(); return Ok(()); }; + let Ok(path) = PathBuf::try_from(client) else { + response.push(FACTORY_RESET_APP_FAILED_PARSE).ok(); + return Ok(()); + }; let Some((_, flag)) = self.config().reset_client_id(client) else { response.push(FACTORY_RESET_APP_NOT_ALLOWED).ok(); @@ -460,7 +464,6 @@ where response.push(FACTORY_RESET_NOT_CONFIRMED).ok(); return Ok(()); } - let path = PathBuf::from(client); match self.config.reset_client_config(client) { crate::config::ResetConfigResult::Changed => { diff --git a/src/config.rs b/src/config.rs index 7b9bd1d..0bb70c5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,7 +5,7 @@ use core::{ }; use cbor_smol::{cbor_deserialize, cbor_serialize_to}; -use littlefs2::{path, path::Path}; +use littlefs2_core::{path, Path}; use serde::{de::DeserializeOwned, Serialize}; use strum_macros::FromRepr; use trussed::{ diff --git a/src/migrations.rs b/src/migrations.rs index 519549c..5c19aac 100644 --- a/src/migrations.rs +++ b/src/migrations.rs @@ -1,11 +1,11 @@ -use littlefs2::object_safe::DynFilesystem; +use littlefs2_core::DynFilesystem; #[derive(Debug)] pub struct Migrator { /// The function performing the migration /// /// First argument is the Internal Filesystem, second argument is the External - pub migrate: fn(&dyn DynFilesystem, &dyn DynFilesystem) -> Result<(), littlefs2::io::Error>, + pub migrate: fn(&dyn DynFilesystem, &dyn DynFilesystem) -> Result<(), littlefs2_core::Error>, /// The version of the storage for which the migration needs to be run pub version: u32, From 541d97a2e6493b6b8a45aeca751c851e3bc723b6 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Thu, 19 Dec 2024 22:30:37 +0100 Subject: [PATCH 59/65] Update dependencies for trussed-core and ctaphid-app --- Cargo.toml | 14 ++++++++------ src/admin.rs | 20 ++++++++++---------- src/config.rs | 17 +++++++++++------ src/lib.rs | 25 +++++++++++++++++++++---- 4 files changed, 50 insertions(+), 26 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b5aa712..5107c66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,14 +12,17 @@ description = "Administrative Trussed app for SoloKeys Solo 2 security keys" [dependencies] apdu-app = "0.1" cbor-smol = { version = "0.5.0", features = ["heapless-v0-7", "heapless-bytes-v0-3"] } -ctaphid-dispatch = "0.1" +ctaphid-app = "0.1.0-rc.1" delog = "0.1" +heapless = "0.7" +heapless-bytes = "0.3" iso7816 = "0.1" littlefs2 = { version = "0.5", optional = true } littlefs2-core = { version = "0.1", features = ["heapless-bytes03"] } serde = { version = "1.0.180", default-features = false } strum_macros = "0.25.2" -trussed = "0.1" +trussed = { version = "0.1", default-features = false } +trussed-core = { version = "0.1.0-rc.1", features = ["crypto-client", "filesystem-client", "management-client", "ui-client"] } embedded-hal = { version = "0.2.7", optional = true } hex-literal = "0.4.1" @@ -43,7 +46,6 @@ log-error = [] migration-tests = ["dep:littlefs2"] [patch.crates-io] -ctaphid-dispatch = { git = "https://github.com/trussed-dev/ctaphid-dispatch.git", rev = "57cb3317878a8593847595319aa03ef17c29ec5b" } -trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "046478b7a4f6e2315acf9112d98308379c2e3eee" } -trussed-manage = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "manage-v0.1.0" } -trussed-se050-manage = { git = "https://github.com/Nitrokey/trussed-se050-backend.git", tag = "se050-manage-v0.1.0" } +trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "6bba8fde36d05c0227769eb63345744e87d84b2b" } +trussed-manage = { git = "https://github.com/trussed-dev/trussed-staging.git", rev = "9355f700831c1a278c334f76382fbf98d82aedcd" } +trussed-se050-manage = { git = "https://github.com/Nitrokey/trussed-se050-backend.git", rev = "a0bb4b92bffb2dc119fcc0cc47259ea7ed59d8a4" } diff --git a/src/admin.rs b/src/admin.rs index 85b3580..6ceccf8 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -2,14 +2,14 @@ use super::Client as TrussedClient; use apdu_app::{CommandView, Interface, Status}; use cbor_smol::{cbor_deserialize, cbor_serialize_to}; use core::{convert::TryInto, marker::PhantomData, time::Duration}; -use ctaphid_dispatch::app::{self as hid, Command as HidCommand, Message}; -use ctaphid_dispatch::command::VendorCommand; +use ctaphid_app::{self as hid, Command as HidCommand, VendorCommand}; +use heapless::Vec; +use heapless_bytes::Bytes; #[cfg(feature = "factory-reset")] use littlefs2_core::PathBuf; use serde::Deserialize; -use trussed::store::Store; -use trussed::try_syscall; -use trussed::{interrupt::InterruptFlag, store::filestore::Filestore, syscall, types::Vec}; +use trussed::store::{filestore::Filestore, Store}; +use trussed_core::{syscall, try_syscall, InterruptFlag}; use crate::config::{self, Config, ConfigError}; use crate::migrations::Migrator; @@ -531,7 +531,7 @@ where } } -impl hid::App<'static> for App +impl hid::App<'static, N> for App where T: TrussedClient, R: Reboot, @@ -554,9 +554,9 @@ where fn call( &mut self, command: HidCommand, - input_data: &Message, - response: &mut Message, - ) -> hid::AppResult { + input_data: &[u8], + response: &mut Bytes, + ) -> Result<(), hid::Error> { let (command, input) = if command == HidCommand::Vendor(ADMIN) { // new mode: first input byte specifies the actual command let (command, input) = input_data.split_first().ok_or(Error::InvalidLength)?; @@ -564,7 +564,7 @@ where (command, input) } else { // old mode: directly use vendor commands + wink - (Command::try_from(command)?, input_data.as_slice()) + (Command::try_from(command)?, input_data) }; self.exec(command, input, response).map_err(From::from) } diff --git a/src/config.rs b/src/config.rs index 0bb70c5..3df212c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,14 +5,15 @@ use core::{ }; use cbor_smol::{cbor_deserialize, cbor_serialize_to}; +use heapless::Vec; use littlefs2_core::{path, Path}; use serde::{de::DeserializeOwned, Serialize}; use strum_macros::FromRepr; -use trussed::{ - store::filestore::Filestore, +use trussed::store::filestore::Filestore; +use trussed_core::{ try_syscall, - types::{Location, Message, Vec}, - Client, + types::{Location, Message}, + FilesystemClient, }; #[derive(Debug)] @@ -312,7 +313,7 @@ pub fn save_filestore( Ok(()) } -pub fn save(client: &mut T, config: &C) -> Result<(), ConfigError> { +pub fn save(client: &mut T, config: &C) -> Result<(), ConfigError> { if config == &Default::default() { if exists(client, LOCATION, FILENAME)? { try_syscall!(client.remove_file(LOCATION, FILENAME.into())) @@ -327,7 +328,11 @@ pub fn save(client: &mut T, config: &C) -> Result<(), Conf Ok(()) } -fn exists(client: &mut T, location: Location, path: &Path) -> Result { +fn exists( + client: &mut T, + location: Location, + path: &Path, +) -> Result { try_syscall!(client.entry_metadata(location, path.into())) .map(|r| r.metadata.is_some()) .map_err(|_| ConfigError::ReadFailed) diff --git a/src/lib.rs b/src/lib.rs index 97a34fd..04d897e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,8 @@ //! It directly implements the APDU and CTAPHID dispatch App interfaces. #![no_std] +use trussed_core::{CryptoClient, FilesystemClient, ManagementClient, UiClient}; + #[macro_use] extern crate delog; generate_macros!(); @@ -24,11 +26,26 @@ use trussed_manage::ManageClient; use trussed_se050_manage::Se050ManageClient; #[cfg(not(feature = "se050"))] -pub trait Client: trussed::Client + ManageClient {} +pub trait Client: + CryptoClient + FilesystemClient + ManagementClient + UiClient + ManageClient +{ +} #[cfg(not(feature = "se050"))] -impl Client for C {} +impl Client for C {} #[cfg(feature = "se050")] -pub trait Client: trussed::Client + Se050ManageClient + ManageClient {} +pub trait Client: + CryptoClient + FilesystemClient + ManagementClient + UiClient + Se050ManageClient + ManageClient +{ +} #[cfg(feature = "se050")] -impl Client for C {} +impl< + C: CryptoClient + + FilesystemClient + + ManagementClient + + UiClient + + Se050ManageClient + + ManageClient, + > Client for C +{ +} From d5f1c6df405e4edeb6524f908c1c713139173e81 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Wed, 8 Jan 2025 21:51:40 +0100 Subject: [PATCH 60/65] Use released dependencies --- Cargo.toml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5107c66..3fc33b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,8 +27,8 @@ trussed-core = { version = "0.1.0-rc.1", features = ["crypto-client", "filesyste embedded-hal = { version = "0.2.7", optional = true } hex-literal = "0.4.1" rand_chacha = { version = "0.3.1", optional = true, default-features = false } -trussed-manage = { version = "0.1.0" } -trussed-se050-manage = { version = "0.1.0", optional = true } +trussed-manage = { version = "0.2.0" } +trussed-se050-manage = { version = "0.2.0", optional = true } [features] default = [] @@ -47,5 +47,3 @@ migration-tests = ["dep:littlefs2"] [patch.crates-io] trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "6bba8fde36d05c0227769eb63345744e87d84b2b" } -trussed-manage = { git = "https://github.com/trussed-dev/trussed-staging.git", rev = "9355f700831c1a278c334f76382fbf98d82aedcd" } -trussed-se050-manage = { git = "https://github.com/Nitrokey/trussed-se050-backend.git", rev = "a0bb4b92bffb2dc119fcc0cc47259ea7ed59d8a4" } From ff574d810f718c7ce0912512f6c551ca2ea7c88a Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Fri, 24 Jan 2025 11:16:36 +0100 Subject: [PATCH 61/65] Update trussed The simplified Store trait no longer uses the Fs indirection so we can simplify the code calling the migration functions. This patch also updates littlefs2 to avoid duplicate dependencies. --- Cargo.toml | 4 ++-- src/admin.rs | 2 +- src/migrations.rs | 24 +++++++++++------------- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3fc33b2..e449a07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ delog = "0.1" heapless = "0.7" heapless-bytes = "0.3" iso7816 = "0.1" -littlefs2 = { version = "0.5", optional = true } +littlefs2 = { version = "0.6", optional = true } littlefs2-core = { version = "0.1", features = ["heapless-bytes03"] } serde = { version = "1.0.180", default-features = false } strum_macros = "0.25.2" @@ -46,4 +46,4 @@ log-error = [] migration-tests = ["dep:littlefs2"] [patch.crates-io] -trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "6bba8fde36d05c0227769eb63345744e87d84b2b" } +trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "5003249c3187dca841f83551ba625921611a5ace" } diff --git a/src/admin.rs b/src/admin.rs index 6ceccf8..075a52d 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -263,7 +263,7 @@ where for migration in self.migrations { if migration.version > current_version && migration.version <= to_version { - (migration.migrate)(&**internal, &**external).map_err(|_err| { + (migration.migrate)(internal, external).map_err(|_err| { error_now!("Migration failed: {_err:?}"); ConfigError::WriteFailed })?; diff --git a/src/migrations.rs b/src/migrations.rs index 5c19aac..3dc03da 100644 --- a/src/migrations.rs +++ b/src/migrations.rs @@ -82,19 +82,17 @@ pub mod test_utils { } ram_storage!( - name=NoBackendStorage, - backend=RamDirect, - trait=littlefs2::driver::Storage, - erase_value=0xff, - read_size=16, - write_size=16, - cache_size_ty=littlefs2::consts::U512, - block_size=512, - block_count=128, - lookahead_size_ty=littlefs2::consts::U8, - filename_max_plus_one_ty=littlefs2::consts::U256, - path_max_plus_one_ty=littlefs2::consts::U256, - result=Result, + name = NoBackendStorage, + backend = RamDirect, + erase_value = 0xff, + read_size = 16, + write_size = 16, + cache_size_ty = littlefs2::consts::U512, + block_size = 512, + block_count = 128, + lookahead_size_ty = littlefs2::consts::U8, + filename_max_plus_one_ty = littlefs2::consts::U256, + path_max_plus_one_ty = littlefs2::consts::U256, ); pub fn test_migration_one( From 8693599e14b61cb111ad9fda148bef402472b07b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Mon, 1 Sep 2025 10:26:41 +0200 Subject: [PATCH 62/65] Update to heapless 0.9 --- Cargo.toml | 27 ++++++++++++++++++--------- src/admin.rs | 40 +++++++++++++++++++++------------------- src/config.rs | 6 +++--- 3 files changed, 42 insertions(+), 31 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e449a07..b2b1cef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,18 +11,18 @@ description = "Administrative Trussed app for SoloKeys Solo 2 security keys" [dependencies] apdu-app = "0.1" -cbor-smol = { version = "0.5.0", features = ["heapless-v0-7", "heapless-bytes-v0-3"] } -ctaphid-app = "0.1.0-rc.1" +cbor-smol = { version = "0.5.0", features = ["heapless-v0-9", "heapless-bytes-v0-5"] } +ctaphid-app = "0.1.0" delog = "0.1" -heapless = "0.7" -heapless-bytes = "0.3" -iso7816 = "0.1" -littlefs2 = { version = "0.6", optional = true } -littlefs2-core = { version = "0.1", features = ["heapless-bytes03"] } +heapless = "0.9" +heapless-bytes = { version = "0.5", features = ["heapless-0.9"] } +iso7816 = "0.2" +littlefs2 = { version = "0.7", optional = true } +littlefs2-core = { version = "0.1", features = ["heapless-bytes05"] } serde = { version = "1.0.180", default-features = false } strum_macros = "0.25.2" trussed = { version = "0.1", default-features = false } -trussed-core = { version = "0.1.0-rc.1", features = ["crypto-client", "filesystem-client", "management-client", "ui-client"] } +trussed-core = { version = "0.1.0", features = ["crypto-client", "filesystem-client", "management-client", "ui-client"] } embedded-hal = { version = "0.2.7", optional = true } hex-literal = "0.4.1" @@ -36,6 +36,7 @@ se050 = ["trussed-se050-manage"] factory-reset = [] log-all = [] +log-trace = [] log-none = [] log-info = [] log-debug = [] @@ -46,4 +47,12 @@ log-error = [] migration-tests = ["dep:littlefs2"] [patch.crates-io] -trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "5003249c3187dca841f83551ba625921611a5ace" } +trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "1e7b09a983dc8ae64a7ad8401ce541a9a77e5939" } +trussed-core = { git = "https://github.com/trussed-dev/trussed.git", rev = "1e7b09a983dc8ae64a7ad8401ce541a9a77e5939" } +ctaphid-app = { git = "https://github.com/trussed-dev/ctaphid-dispatch.git", rev = "f11fdcbc62d06b8be23e6cbae21bcfefd52c0661" } +trussed-manage = { git = "https://github.com/trussed-dev/trussed-staging.git", rev = "eff09d1613641531630d962f81136f64f3dd2716" } +littlefs2 = { git = "https://github.com/trussed-dev/littlefs2.git", rev = "e9d3a1ca98f80e92cd20ee9b94707067810b9036" } +littlefs2-core = { git = "https://github.com/trussed-dev/littlefs2.git", rev = "e9d3a1ca98f80e92cd20ee9b94707067810b9036" } +littlefs2-sys = { git = "https://github.com/trussed-dev/littlefs2-sys", rev = "v0.3.1-nitrokey.1" } +salty = { git = "https://github.com/ycrypto/salty.git", rev = "ae17f04ec965913e08032d266f6a47c001031f06" } +apdu-app = { git = "https://github.com/trussed-dev/apdu-dispatch.git", rev = "931c4269d1d293954fae2012d809e9663cd2cb7e" } diff --git a/src/admin.rs b/src/admin.rs index 075a52d..459d79c 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -1,10 +1,11 @@ use super::Client as TrussedClient; -use apdu_app::{CommandView, Interface, Status}; +use apdu_app::{CommandView, Interface}; use cbor_smol::{cbor_deserialize, cbor_serialize_to}; use core::{convert::TryInto, marker::PhantomData, time::Duration}; use ctaphid_app::{self as hid, Command as HidCommand, VendorCommand}; -use heapless::Vec; -use heapless_bytes::Bytes; +use heapless::VecView; +use heapless_bytes::BytesView; +use iso7816::Status; #[cfg(feature = "factory-reset")] use littlefs2_core::PathBuf; use serde::Deserialize; @@ -258,8 +259,8 @@ where return Err(ConfigError::InvalidValue); } - let internal = store.ifs(); - let external = store.efs(); + let internal = &*store.ifs(); + let external = &*store.efs(); for migration in self.migrations { if migration.version > current_version && migration.version <= to_version { @@ -344,11 +345,11 @@ where user_present.is_ok() } - fn exec( + fn exec( &mut self, command: Command, input: &[u8], - response: &mut Vec, + response: &mut VecView, ) -> Result<(), Error> { debug_now!("Executing command: {command:?}"); match command { @@ -429,7 +430,11 @@ where response.push(status).ok(); } Command::ListAvailableFields => { - cbor_serialize_to(&self.config.list_available_fields(), response).ok(); + cbor_serialize_to::<_, &mut VecView>( + &self.config.list_available_fields(), + response, + ) + .ok(); return Ok(()); } #[cfg(feature = "factory-reset")] @@ -492,11 +497,7 @@ where Ok(()) } - fn get_config( - &mut self, - input: &[u8], - response: &mut Vec, - ) -> Result<(), ConfigError> { + fn get_config(&mut self, input: &[u8], response: &mut VecView) -> Result<(), ConfigError> { let key = core::str::from_utf8(input).map_err(|_| ConfigError::InvalidKey)?; config::get(&mut self.config, key, response) } @@ -531,7 +532,7 @@ where } } -impl hid::App<'static, N> for App +impl hid::App<'static> for App where T: TrussedClient, R: Reboot, @@ -555,7 +556,7 @@ where &mut self, command: HidCommand, input_data: &[u8], - response: &mut Bytes, + response: &mut BytesView, ) -> Result<(), hid::Error> { let (command, input) = if command == HidCommand::Vendor(ADMIN) { // new mode: first input byte specifies the actual command @@ -566,7 +567,8 @@ where // old mode: directly use vendor commands + wink (Command::try_from(command)?, input_data) }; - self.exec(command, input, response).map_err(From::from) + self.exec(command, input, response.as_mut()) + .map_err(From::from) } fn interrupt(&self) -> Option<&'static InterruptFlag> { @@ -586,7 +588,7 @@ where } } -impl apdu_app::App for App +impl apdu_app::App for App where T: TrussedClient, R: Reboot, @@ -597,7 +599,7 @@ where &mut self, _interface: Interface, _apdu: CommandView<'_>, - _reply: &mut apdu_app::Data, + _reply: &mut heapless::VecView, ) -> apdu_app::Result { Ok(()) } @@ -608,7 +610,7 @@ where &mut self, interface: Interface, apdu: CommandView<'_>, - reply: &mut apdu_app::Data, + reply: &mut heapless::VecView, ) -> apdu_app::Result { let instruction: u8 = apdu.instruction().into(); let command = Command::try_from(instruction)?; diff --git a/src/config.rs b/src/config.rs index 3df212c..279b4e7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,7 +5,7 @@ use core::{ }; use cbor_smol::{cbor_deserialize, cbor_serialize_to}; -use heapless::Vec; +use heapless::VecView; use littlefs2_core::{path, Path}; use serde::{de::DeserializeOwned, Serialize}; use strum_macros::FromRepr; @@ -269,10 +269,10 @@ impl From for u8 { } } -pub fn get( +pub fn get( config: &mut C, key: &str, - response: &mut Vec, + response: &mut VecView, ) -> Result<(), ConfigError> { let field = config.field(key).ok_or(ConfigError::InvalidKey)?; write!(response, "{}", field).map_err(|_| ConfigError::DataTooLong) From 2b3f758016afbc56535d8a65f98a067d5a2d843e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Mon, 6 Oct 2025 17:51:05 +0200 Subject: [PATCH 63/65] Fix clippy warnings --- src/admin.rs | 4 ++-- src/config.rs | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/admin.rs b/src/admin.rs index 459d79c..9f6e0c2 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -259,8 +259,8 @@ where return Err(ConfigError::InvalidValue); } - let internal = &*store.ifs(); - let external = &*store.efs(); + let internal = store.ifs(); + let external = store.efs(); for migration in self.migrations { if migration.version > current_version && migration.version <= to_version { diff --git a/src/config.rs b/src/config.rs index 279b4e7..45d6889 100644 --- a/src/config.rs +++ b/src/config.rs @@ -48,6 +48,12 @@ use trussed_core::{ /// ``` pub struct ResetSignalAllocation(AtomicU8); +impl Default for ResetSignalAllocation { + fn default() -> Self { + Self::new() + } +} + impl ResetSignalAllocation { pub const fn new() -> Self { Self(AtomicU8::new(ResetSignal::None as u8)) From 261c47d791f7862a8fd1b52d485860ff82216478 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 23 Mar 2026 10:53:35 +0100 Subject: [PATCH 64/65] Update to trussed-core v0.2 --- Cargo.toml | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b2b1cef..5ef8085 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,9 @@ description = "Administrative Trussed app for SoloKeys Solo 2 security keys" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -apdu-app = "0.1" +apdu-app = "0.2" cbor-smol = { version = "0.5.0", features = ["heapless-v0-9", "heapless-bytes-v0-5"] } -ctaphid-app = "0.1.0" +ctaphid-app = "0.2" delog = "0.1" heapless = "0.9" heapless-bytes = { version = "0.5", features = ["heapless-0.9"] } @@ -22,13 +22,13 @@ littlefs2-core = { version = "0.1", features = ["heapless-bytes05"] } serde = { version = "1.0.180", default-features = false } strum_macros = "0.25.2" trussed = { version = "0.1", default-features = false } -trussed-core = { version = "0.1.0", features = ["crypto-client", "filesystem-client", "management-client", "ui-client"] } +trussed-core = { version = "0.2", features = ["crypto-client", "filesystem-client", "management-client", "ui-client"] } embedded-hal = { version = "0.2.7", optional = true } hex-literal = "0.4.1" rand_chacha = { version = "0.3.1", optional = true, default-features = false } -trussed-manage = { version = "0.2.0" } -trussed-se050-manage = { version = "0.2.0", optional = true } +trussed-manage = { version = "0.3" } +trussed-se050-manage = { version = "0.3.0", optional = true } [features] default = [] @@ -47,12 +47,4 @@ log-error = [] migration-tests = ["dep:littlefs2"] [patch.crates-io] -trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "1e7b09a983dc8ae64a7ad8401ce541a9a77e5939" } -trussed-core = { git = "https://github.com/trussed-dev/trussed.git", rev = "1e7b09a983dc8ae64a7ad8401ce541a9a77e5939" } -ctaphid-app = { git = "https://github.com/trussed-dev/ctaphid-dispatch.git", rev = "f11fdcbc62d06b8be23e6cbae21bcfefd52c0661" } -trussed-manage = { git = "https://github.com/trussed-dev/trussed-staging.git", rev = "eff09d1613641531630d962f81136f64f3dd2716" } -littlefs2 = { git = "https://github.com/trussed-dev/littlefs2.git", rev = "e9d3a1ca98f80e92cd20ee9b94707067810b9036" } -littlefs2-core = { git = "https://github.com/trussed-dev/littlefs2.git", rev = "e9d3a1ca98f80e92cd20ee9b94707067810b9036" } -littlefs2-sys = { git = "https://github.com/trussed-dev/littlefs2-sys", rev = "v0.3.1-nitrokey.1" } -salty = { git = "https://github.com/ycrypto/salty.git", rev = "ae17f04ec965913e08032d266f6a47c001031f06" } -apdu-app = { git = "https://github.com/trussed-dev/apdu-dispatch.git", rev = "931c4269d1d293954fae2012d809e9663cd2cb7e" } +trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "0f8df68be879acdde1f8cf428c11e5d29692a47b" } From 70086d9d99868def0a022c29c03b3270a4436385 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Fri, 5 Jun 2026 15:20:52 +0200 Subject: [PATCH 65/65] Update to trussed v0.2.0-rc.1 --- Cargo.toml | 7 +------ src/admin.rs | 2 +- src/config.rs | 4 ++-- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5ef8085..3aaf0ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,6 @@ edition = "2021" license = "Apache-2.0 OR MIT" description = "Administrative Trussed app for SoloKeys Solo 2 security keys" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] apdu-app = "0.2" cbor-smol = { version = "0.5.0", features = ["heapless-v0-9", "heapless-bytes-v0-5"] } @@ -21,7 +19,7 @@ littlefs2 = { version = "0.7", optional = true } littlefs2-core = { version = "0.1", features = ["heapless-bytes05"] } serde = { version = "1.0.180", default-features = false } strum_macros = "0.25.2" -trussed = { version = "0.1", default-features = false } +trussed = { version = "=0.2.0-rc.1", default-features = false } trussed-core = { version = "0.2", features = ["crypto-client", "filesystem-client", "management-client", "ui-client"] } embedded-hal = { version = "0.2.7", optional = true } @@ -45,6 +43,3 @@ log-error = [] # Utils to test migration migration-tests = ["dep:littlefs2"] - -[patch.crates-io] -trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "0f8df68be879acdde1f8cf428c11e5d29692a47b" } diff --git a/src/admin.rs b/src/admin.rs index 9f6e0c2..f2ee6f7 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -9,7 +9,7 @@ use iso7816::Status; #[cfg(feature = "factory-reset")] use littlefs2_core::PathBuf; use serde::Deserialize; -use trussed::store::{filestore::Filestore, Store}; +use trussed::store::{Filestore, Store}; use trussed_core::{syscall, try_syscall, InterruptFlag}; use crate::config::{self, Config, ConfigError}; diff --git a/src/config.rs b/src/config.rs index 45d6889..7031d2d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -9,7 +9,7 @@ use heapless::VecView; use littlefs2_core::{path, Path}; use serde::{de::DeserializeOwned, Serialize}; use strum_macros::FromRepr; -use trussed::store::filestore::Filestore; +use trussed::store::Filestore; use trussed_core::{ try_syscall, types::{Location, Message}, @@ -373,7 +373,7 @@ mod tests { destructive: true, ty: FieldType::Bool, }]; - let mut bytes: trussed::types::Vec = Default::default(); + let mut bytes: heapless::Vec = Default::default(); cbor_smol::cbor_serialize_to(fields, &mut bytes).unwrap(); assert_eq!( &bytes,