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")]