diff --git a/Cargo.toml b/Cargo.toml index a4ca8773..f48f7cbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ trussed = "0.1.0" trussed-rsa-alloc = { git = "https://github.com/Nitrokey/trussed-rsa-backend", rev = "311d2366f99cc300b03d61e7f6a0a07abd3e8700", optional = true } serde_repr = "0.1" hex-literal = "0.3.4" +trussed-auth = "0.1.0" # optional dependencies apdu-dispatch = { version = "0.1", optional = true } @@ -53,12 +54,16 @@ serde_cbor = "0.11" hex = { version = "0.4", features = ["serde"] } [features] +default = [] std = [] -virtual = ["std", "vpicc"] +virtual = ["std", "vpicc", "virt"] +virt = ["std", "trussed/virt"] + rsa = ["trussed-rsa-alloc"] rsa2048 = ["rsa"] rsa4096 = ["rsa2048"] rsa4096-gen = ["rsa4096"] + dangerous-test-real-card = [] # used for delog @@ -71,9 +76,10 @@ log-error = [] [patch.crates-io] interchange = { git = "https://github.com/trussed-dev/interchange", rev = "fe5633466640e1e9a8c06d9b5dd1d0af08c272af" } -p256-cortex-m4 = { git = "https://github.com/Nitrokey/p256-cortex-m4", tag = "v0.1.0-alpha.6-nitrokey-1" } littlefs2 = { git = "https://github.com/Nitrokey/littlefs2", tag = "v0.3.2-nitrokey-2" } +p256-cortex-m4 = { git = "https://github.com/Nitrokey/p256-cortex-m4", tag = "v0.1.0-alpha.6-nitrokey-1" } trussed = { git = "https://github.com/Nitrokey/trussed" , tag = "v0.1.0-nitrokey.8" } +trussed-auth = { git = "https://github.com/trussed-dev/trussed-auth.git", tag= "v0.1.0"} [package.metadata.docs.rs] all-features = true diff --git a/examples/virtual.rs b/examples/virtual.rs index 4ab58c07..14f21c37 100644 --- a/examples/virtual.rs +++ b/examples/virtual.rs @@ -25,15 +25,10 @@ // TODO: add CLI -#[cfg(not(feature = "rsa"))] -use trussed::virt::with_ram_client; -#[cfg(feature = "rsa")] -use trussed_rsa_alloc::virt::with_ram_client; - fn main() { env_logger::init(); - with_ram_client("opcard", |client| { + opcard::virt::with_ram_client("opcard", |client| { let card = opcard::Card::new(client, opcard::Options::default()); let mut virtual_card = opcard::VirtualCard::new(card); let vpicc = vpicc::connect().expect("failed to connect to vpicc"); diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 56461f36..e681c807 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -25,7 +25,7 @@ log = { version = "0.4", optional = true } [dependencies.opcard] path = ".." -features = ["virtual"] +features = ["virt"] [[bin]] name = "fuzz_target_1" diff --git a/fuzz/fuzz_targets/fuzz_target_1.rs b/fuzz/fuzz_targets/fuzz_target_1.rs index 53c000ed..d4a80e6c 100644 --- a/fuzz/fuzz_targets/fuzz_target_1.rs +++ b/fuzz/fuzz_targets/fuzz_target_1.rs @@ -13,7 +13,7 @@ fuzz_target!(|input: Input| { #[cfg(feature = "log")] env_logger::builder().is_test(true).try_init().ok(); - trussed::virt::with_ram_client("opcard", move |client| { + opcard::virt::with_ram_client("opcard", move |client| { let Input { commands, manufacturer, diff --git a/src/backend.rs b/src/backend.rs index 5b35d393..4a0a2131 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -10,27 +10,25 @@ use core::fmt::Debug; use trussed::try_syscall; -use trussed::types::Location; +use trussed_auth::AuthClient; -use crate::command::Password; use crate::error::Error; -use crate::state; /// Backend that provides data storage and cryptography operations. /// Mostly a wrapper around a trussed client #[derive(Clone)] -pub struct Backend { +pub struct Backend { client: T, } -impl Debug for Backend { +impl Debug for Backend { fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let Self { client: _client } = self; fmt.debug_struct("Backend").finish() } } -impl Backend { +impl Backend { /// Create new backend from a trussed client pub fn new(client: T) -> Self { Self { client } @@ -41,19 +39,6 @@ impl Backend { &mut self.client } - /// Checks whether the given value matches the pin of the given type. - pub fn verify_pin( - &mut self, - storage: Location, - pin: Password, - value: &[u8], - state: &mut state::Persistent, - ) -> bool { - state - .verify_pin(&mut self.client, storage, value, pin) - .is_ok() - } - /// Ask for confirmation of presence from the user with a default timeout of 15 seconds pub fn confirm_user_present(&mut self) -> Result { try_syscall!(self.client_mut().confirm_user_present(15_000)) diff --git a/src/card.rs b/src/card.rs index d4e601e0..5a2218a9 100644 --- a/src/card.rs +++ b/src/card.rs @@ -4,6 +4,7 @@ use hex_literal::hex; use iso7816::Status; use trussed::types::Location; +use trussed_auth::AuthClient; pub(crate) mod reply; @@ -24,13 +25,13 @@ pub const PGP_SMARTCARD_VERSION: [u8; 2] = [3, 4]; /// This is the main entry point for this crate. It takes care of the command handling and state /// management. #[derive(Clone, Debug)] -pub struct Card { +pub struct Card { backend: Backend, options: Options, state: State, } -impl Card { +impl Card { /// Creates a new OpenPGP card with the given backend and options. pub fn new(client: T, options: Options) -> Self { let state = State::default(); @@ -66,12 +67,19 @@ impl Card { /// Resets the state of the card. pub fn reset(&mut self) { + self.state.volatile.clear(self.backend.client_mut()); let state = State::default(); self.state = state; } } -impl iso7816::App for Card { +impl Drop for Card { + fn drop(&mut self) { + self.reset() + } +} + +impl iso7816::App for Card { fn aid(&self) -> iso7816::Aid { // TODO: check truncation length iso7816::Aid::new_truncatable(&self.options.aid(), RID.len()) @@ -79,7 +87,9 @@ impl iso7816::App for Card { } #[cfg(feature = "apdu-dispatch")] -impl apdu_dispatch::App for Card { +impl apdu_dispatch::App + for Card +{ fn select( &mut self, command: &iso7816::Command, @@ -162,7 +172,7 @@ impl Default for Options { } #[derive(Debug)] -pub struct Context<'a, const R: usize, T: trussed::Client> { +pub struct Context<'a, const R: usize, T: trussed::Client + AuthClient> { pub backend: &'a mut Backend, pub options: &'a Options, pub state: &'a mut State, @@ -170,7 +180,7 @@ pub struct Context<'a, const R: usize, T: trussed::Client> { pub reply: Reply<'a, R>, } -impl<'a, const R: usize, T: trussed::Client> Context<'a, R, T> { +impl<'a, const R: usize, T: trussed::Client + AuthClient> Context<'a, R, T> { pub fn load_state(&mut self) -> Result, Status> { Ok(LoadedContext { state: self @@ -201,7 +211,7 @@ impl<'a, const R: usize, T: trussed::Client> Context<'a, R, T> { #[derive(Debug)] /// Context with the persistent state loaded from flash -pub struct LoadedContext<'a, const R: usize, T: trussed::Client> { +pub struct LoadedContext<'a, const R: usize, T: trussed::Client + AuthClient> { pub backend: &'a mut Backend, pub options: &'a Options, pub state: LoadedState<'a>, @@ -209,7 +219,7 @@ pub struct LoadedContext<'a, const R: usize, T: trussed::Client> { pub reply: Reply<'a, R>, } -impl<'a, const R: usize, T: trussed::Client> LoadedContext<'a, R, T> { +impl<'a, const R: usize, T: trussed::Client + AuthClient> LoadedContext<'a, R, T> { /// Lend the context /// /// The resulting `LoadedContext` has a shorter lifetime than the original one, meaning that it diff --git a/src/command.rs b/src/command.rs index f4471bb1..1ca7170e 100644 --- a/src/command.rs +++ b/src/command.rs @@ -8,6 +8,7 @@ mod pso; use hex_literal::hex; use iso7816::Status; +use trussed_auth::{AuthClient, PinId}; use crate::card::{Context, LoadedContext, RID}; use crate::error::Error; @@ -54,7 +55,7 @@ impl Command { } } - pub fn exec( + pub fn exec( &self, mut ctx: Context<'_, R, T>, ) -> Result<(), Status> { @@ -198,6 +199,17 @@ pub enum Password { ResetCode, } +impl From for PinId { + fn from(v: Password) -> Self { + match v { + Password::Pw1 => 0, + Password::Pw3 => 1, + Password::ResetCode => 2, + } + .into() + } +} + impl From for Password { fn from(value: PasswordMode) -> Password { match value { @@ -324,7 +336,9 @@ impl TryFrom for ManageSecurityEnvironmentMode { } // § 7.2.1 -fn select(context: Context<'_, R, T>) -> Result<(), Status> { +fn select( + context: Context<'_, R, T>, +) -> Result<(), Status> { if context.data.starts_with(&RID) { context.state.volatile.cur_do = None; context.state.volatile.keyrefs = Default::default(); @@ -336,8 +350,8 @@ fn select(context: Context<'_, R, T>) -> Res } // § 7.2.2 -fn verify( - ctx: LoadedContext<'_, R, T>, +fn verify( + mut ctx: LoadedContext<'_, R, T>, mode: VerifyMode, password: PasswordMode, ) -> Result<(), Status> { @@ -345,41 +359,41 @@ fn verify( VerifyMode::SetOrCheck => { if ctx.data.is_empty() { let already_validated = match password { - PasswordMode::Pw1Sign => ctx.state.volatile.sign_verified, - PasswordMode::Pw1Other => ctx.state.volatile.other_verified, - PasswordMode::Pw3 => ctx.state.volatile.admin_verified, + PasswordMode::Pw1Sign => ctx.state.volatile.sign_verified(), + PasswordMode::Pw1Other => ctx.state.volatile.other_verified(), + PasswordMode::Pw3 => ctx.state.volatile.admin_verified(), }; if already_validated { Ok(()) } else { Err(Status::RemainingRetries( - ctx.state.persistent.remaining_tries(password.into()), + ctx.state + .persistent + .remaining_tries(ctx.backend.client_mut(), password.into()), )) } } else { - let pin = password.into(); - if ctx - .backend - .verify_pin(ctx.options.storage, pin, ctx.data, ctx.state.persistent) - { - match password { - PasswordMode::Pw1Sign => ctx.state.volatile.sign_verified = true, - PasswordMode::Pw1Other => ctx.state.volatile.other_verified = true, - PasswordMode::Pw3 => ctx.state.volatile.admin_verified = true, - } - Ok(()) - } else { - Err(Status::RemainingRetries( - ctx.state.persistent.remaining_tries(password.into()), - )) - } + ctx.state + .verify_pin( + ctx.backend.client_mut(), + ctx.options.storage, + ctx.data, + password, + ) + .map_err(|_| { + Status::RemainingRetries( + ctx.state + .persistent + .remaining_tries(ctx.backend.client_mut(), password.into()), + ) + }) } } VerifyMode::Reset => { match password { - PasswordMode::Pw1Sign => ctx.state.volatile.sign_verified = false, - PasswordMode::Pw1Other => ctx.state.volatile.other_verified = false, - PasswordMode::Pw3 => ctx.state.volatile.admin_verified = false, + PasswordMode::Pw1Sign => ctx.state.volatile.clear_sign(ctx.backend.client_mut()), + PasswordMode::Pw1Other => ctx.state.volatile.clear_other(ctx.backend.client_mut()), + PasswordMode::Pw3 => ctx.state.volatile.clear_admin(ctx.backend.client_mut()), } Ok(()) } @@ -387,8 +401,8 @@ fn verify( } // § 7.2.3 -fn change_reference_data( - ctx: LoadedContext<'_, R, T>, +fn change_reference_data( + mut ctx: LoadedContext<'_, R, T>, password: Password, ) -> Result<(), Status> { let min_len = match password { @@ -411,8 +425,7 @@ fn change_reference_data( // Verify the old pin before returning for wrong length to avoid leaking information about the // length of the PIN ctx.state - .persistent - .verify_pin(client_mut, ctx.options.storage, old, password) + .check_pin(client_mut, old, password) .map_err(|_| Status::VerificationFailed)?; if current_len + min_len > ctx.data.len() { @@ -420,12 +433,12 @@ fn change_reference_data( } ctx.state .persistent - .change_pin(client_mut, ctx.options.storage, new, password) + .change_pin(client_mut, ctx.options.storage, old, new, password) .map_err(|_| Status::WrongLength) } // § 7.2.14 -fn gen_keypair( +fn gen_keypair( context: LoadedContext<'_, R, T>, mode: GenerateAsymmetricKeyPairMode, ) -> Result<(), Status> { @@ -439,7 +452,7 @@ fn gen_keypair( }; } - if !context.state.volatile.admin_verified { + if !context.state.volatile.admin_verified() { return Err(Status::SecurityStatusNotSatisfied); } @@ -451,11 +464,16 @@ fn gen_keypair( } // § 7.2.16 -fn terminate_df( +fn terminate_df( mut ctx: Context<'_, R, T>, ) -> Result<(), Status> { if let Ok(ctx) = ctx.load_state() { - if ctx.state.volatile.admin_verified || ctx.state.persistent.is_locked(Password::Pw3) { + if ctx.state.volatile.admin_verified() + || ctx + .state + .persistent + .is_locked(ctx.backend.client_mut(), Password::Pw3) + { State::terminate_df(ctx.backend.client_mut(), ctx.options.storage)?; } else { return Err(Status::ConditionsOfUseNotSatisfied); @@ -472,7 +490,10 @@ fn unspecified_delete_error(_err: E) -> Status { Status::UnspecifiedPersistentExecutionError } -fn factory_reset(ctx: Context<'_, R, T>) -> Result<(), Status> { +fn factory_reset( + ctx: Context<'_, R, T>, +) -> Result<(), Status> { + ctx.state.volatile.clear(ctx.backend.client_mut()); *ctx.state = Default::default(); try_syscall!(ctx .backend @@ -495,11 +516,12 @@ fn factory_reset(ctx: Context<'_, R, T>) -> .map_err(unspecified_delete_error)?; try_syscall!(ctx.backend.client_mut().delete_all(Location::Volatile)) .map_err(unspecified_delete_error)?; + try_syscall!(ctx.backend.client_mut().delete_all_pins()).map_err(unspecified_delete_error)?; Ok(()) } // § 7.2.17 -fn activate_file( +fn activate_file( mut ctx: Context<'_, R, T>, ) -> Result<(), Status> { if State::lifecycle(ctx.backend.client_mut(), ctx.options.storage) == LifeCycle::Operational { @@ -507,7 +529,6 @@ fn activate_file( } factory_reset(ctx.lend())?; - *ctx.state = Default::default(); let ctx = ctx.load_state()?; ctx.state .persistent @@ -521,7 +542,7 @@ fn activate_file( } // § 7.2.4 -fn reset_retry_conter( +fn reset_retry_conter( ctx: LoadedContext<'_, R, T>, mode: ResetRetryCounterMode, ) -> Result<(), Status> { @@ -531,7 +552,7 @@ fn reset_retry_conter( } } -fn reset_retry_conter_with_p3( +fn reset_retry_conter_with_p3( ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { if ctx.data.len() < MIN_LENGTH_USER_PIN || ctx.data.len() > MAX_PIN_LENGTH { @@ -542,13 +563,13 @@ fn reset_retry_conter_with_p3( return Err(Status::IncorrectDataParameter); } - if !ctx.state.volatile.admin_verified { + if !ctx.state.volatile.admin_verified() { return Err(Status::SecurityStatusNotSatisfied); } ctx.state .persistent - .change_pin( + .set_pin( ctx.backend.client_mut(), ctx.options.storage, ctx.data, @@ -560,8 +581,8 @@ fn reset_retry_conter_with_p3( }) } -fn reset_retry_conter_with_code( - ctx: LoadedContext<'_, R, T>, +fn reset_retry_conter_with_code( + mut ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { let code_len = ctx.state.persistent.reset_code_len().ok_or_else(|| { warn!("Attempt to use reset when not set"); @@ -577,23 +598,22 @@ fn reset_retry_conter_with_code( ctx.data.split_at(code_len) }; - let res = ctx.state.persistent.verify_pin( - ctx.backend.client_mut(), - ctx.options.storage, - old, - Password::ResetCode, - ); + let res = ctx + .state + .check_pin(ctx.backend.client_mut(), old, Password::ResetCode); match res { - Err(Error::TooManyTries) | Err(Error::InvalidPin) => { + Err(Error::InvalidPin) => { return Err(Status::RemainingRetries( - ctx.state.persistent.remaining_tries(Password::ResetCode), + ctx.state + .persistent + .remaining_tries(ctx.backend.client_mut(), Password::ResetCode), )) } Err(_err) => { error!("Failed to check reset code: {_err:?}"); return Err(Status::UnspecifiedNonpersistentExecutionError); } - Ok(()) => {} + Ok(_reset_kek) => {} } if new.len() > MAX_PIN_LENGTH || new.len() < MIN_LENGTH_USER_PIN { @@ -603,7 +623,7 @@ fn reset_retry_conter_with_code( ctx.state .persistent - .change_pin( + .set_pin( ctx.backend.client_mut(), ctx.options.storage, new, @@ -616,7 +636,7 @@ fn reset_retry_conter_with_code( } // § 7.2.5 -fn select_data( +fn select_data( ctx: Context<'_, R, T>, occurrence: Occurrence, ) -> Result<(), Status> { @@ -633,7 +653,7 @@ fn select_data( } // § 7.2.15 -fn get_challenge( +fn get_challenge( mut ctx: Context<'_, R, T>, expected: usize, ) -> Result<(), Status> { @@ -655,7 +675,7 @@ fn get_challenge( } // § 7.2.18 -fn manage_security_environment( +fn manage_security_environment( ctx: Context<'_, R, T>, mode: ManageSecurityEnvironmentMode, ) -> Result<(), Status> { diff --git a/src/command/data.rs b/src/command/data.rs index bed70962..c4df8004 100644 --- a/src/command/data.rs +++ b/src/command/data.rs @@ -8,6 +8,7 @@ use trussed::{ syscall, try_syscall, types::{KeySerialization, Mechanism}, }; +use trussed_auth::AuthClient; use crate::{ card::{Context, LoadedContext, Options}, @@ -341,7 +342,7 @@ impl GetDataObject { | Self::DigitalSignatureCounter ) } - fn reply( + fn reply( self, mut context: Context<'_, R, T>, ) -> Result<(), Status> { @@ -443,7 +444,7 @@ const EXTENDED_CAPABILITIES: [u8; 10] = [ ]; // § 7.2.6 -pub fn get_data( +pub fn get_data( mut context: Context<'_, R, T>, mode: GetDataMode, tag: Tag, @@ -474,7 +475,7 @@ pub fn get_data( } // § 7.2.7 -pub fn get_next_data( +pub fn get_next_data( context: Context<'_, R, T>, tag: Tag, ) -> Result<(), Status> { @@ -505,7 +506,7 @@ fn filtered_objects( objects.iter().filter(move |o| !to_filter.contains(o)) } -fn get_constructed_data( +fn get_constructed_data( mut ctx: Context<'_, R, T>, objects: &'static [GetDataObject], ) -> Result<(), Status> { @@ -529,7 +530,7 @@ fn get_constructed_data( Ok(()) } -pub fn historical_bytes( +pub fn historical_bytes( mut ctx: Context<'_, R, T>, ) -> Result<(), Status> { ctx.reply.expand(&ctx.options.historical_bytes)?; @@ -539,7 +540,7 @@ pub fn historical_bytes( Ok(()) } -fn cardholder_cert( +fn cardholder_cert( ctx: Context<'_, R, T>, ) -> Result<(), Status> { let occ = match ctx.state.volatile.cur_do { @@ -554,7 +555,7 @@ fn cardholder_cert( get_arbitrary_do(ctx, to_load) } -fn pw_status_bytes( +fn pw_status_bytes( mut ctx: Context<'_, R, T>, ) -> Result<(), Status> { let status = if let Ok(ctx) = ctx.load_state() { @@ -563,9 +564,18 @@ fn pw_status_bytes( max_length_pw1: MAX_PIN_LENGTH as u8, max_length_rc: MAX_PIN_LENGTH as u8, max_length_pw3: MAX_PIN_LENGTH as u8, - error_counter_pw1: ctx.state.persistent.remaining_tries(Password::Pw1), - error_counter_rc: ctx.state.persistent.remaining_tries(Password::ResetCode), - error_counter_pw3: ctx.state.persistent.remaining_tries(Password::Pw3), + error_counter_pw1: ctx + .state + .persistent + .remaining_tries(ctx.backend.client_mut(), Password::Pw1), + error_counter_rc: ctx + .state + .persistent + .remaining_tries(ctx.backend.client_mut(), Password::ResetCode), + error_counter_pw3: ctx + .state + .persistent + .remaining_tries(ctx.backend.client_mut(), Password::Pw3), } } else { // If the state doesn't load, return placeholder so that gpg presents the option to factory reset @@ -584,7 +594,9 @@ fn pw_status_bytes( ctx.reply.expand(&status) } -fn algo_info(mut ctx: Context<'_, R, T>) -> Result<(), Status> { +fn algo_info( + mut ctx: Context<'_, R, T>, +) -> Result<(), Status> { for alg in SignatureAlgorithm::iter_all() { ctx.reply.expand(&[0xC1])?; let offset = ctx.reply.len(); @@ -606,7 +618,7 @@ fn algo_info(mut ctx: Context<'_, R, T>) -> Ok(()) } -fn alg_attr_sign( +fn alg_attr_sign( mut ctx: Context<'_, R, T>, ) -> Result<(), Status> { if let Ok(mut ctx) = ctx.load_state() { @@ -618,7 +630,7 @@ fn alg_attr_sign( } } -fn alg_attr_dec( +fn alg_attr_dec( mut ctx: Context<'_, R, T>, ) -> Result<(), Status> { if let Ok(mut ctx) = ctx.load_state() { @@ -631,7 +643,7 @@ fn alg_attr_dec( } } -fn alg_attr_aut( +fn alg_attr_aut( mut ctx: Context<'_, R, T>, ) -> Result<(), Status> { if let Ok(mut ctx) = ctx.load_state() { @@ -644,7 +656,7 @@ fn alg_attr_aut( } } -fn fingerprints( +fn fingerprints( mut ctx: Context<'_, R, T>, ) -> Result<(), Status> { if let Ok(mut ctx) = ctx.load_state() { @@ -655,7 +667,7 @@ fn fingerprints( } } -fn ca_fingerprints( +fn ca_fingerprints( mut ctx: Context<'_, R, T>, ) -> Result<(), Status> { if let Ok(mut ctx) = ctx.load_state() { @@ -666,7 +678,7 @@ fn ca_fingerprints( } } -fn keygen_dates( +fn keygen_dates( mut ctx: Context<'_, R, T>, ) -> Result<(), Status> { if let Ok(mut ctx) = ctx.load_state() { @@ -685,7 +697,9 @@ fn key_info_byte(data: Option) -> u8 { } } -fn key_info(mut ctx: Context<'_, R, T>) -> Result<(), Status> { +fn key_info( + mut ctx: Context<'_, R, T>, +) -> Result<(), Status> { if let Ok(mut ctx) = ctx.load_state() { // Key-Ref. : Sig = 1, Dec = 2, Aut = 3 (see §7.2.18) ctx.reply.expand(&[ @@ -707,7 +721,7 @@ fn key_info(mut ctx: Context<'_, R, T>) -> R } } -fn uif( +fn uif( mut ctx: Context<'_, R, T>, key: KeyType, ) -> Result<(), Status> { @@ -729,7 +743,7 @@ fn uif( } } -fn cardholder_name( +fn cardholder_name( mut ctx: Context<'_, R, T>, ) -> Result<(), Status> { if let Ok(mut ctx) = ctx.load_state() { @@ -740,7 +754,7 @@ fn cardholder_name( } } -fn cardholder_sex( +fn cardholder_sex( mut ctx: Context<'_, R, T>, ) -> Result<(), Status> { if let Ok(mut ctx) = ctx.load_state() { @@ -752,7 +766,7 @@ fn cardholder_sex( } } -fn language_preferences( +fn language_preferences( mut ctx: Context<'_, R, T>, ) -> Result<(), Status> { if let Ok(mut ctx) = ctx.load_state() { @@ -764,7 +778,7 @@ fn language_preferences( } } -fn signature_counter( +fn signature_counter( mut ctx: Context<'_, R, T>, ) -> Result<(), Status> { // Counter is only on 3 bytes @@ -777,15 +791,15 @@ fn signature_counter( } } -fn get_arbitrary_do( +fn get_arbitrary_do( mut ctx: Context<'_, R, T>, obj: ArbitraryDO, ) -> Result<(), Status> { match obj.read_permission() { - PermissionRequirement::User if !ctx.state.volatile.other_verified => { + PermissionRequirement::User if !ctx.state.volatile.other_verified() => { return Err(Status::SecurityStatusNotSatisfied); } - PermissionRequirement::Admin if !ctx.state.volatile.admin_verified => { + PermissionRequirement::Admin if !ctx.state.volatile.admin_verified() => { return Err(Status::SecurityStatusNotSatisfied); } _ => {} @@ -798,7 +812,7 @@ fn get_arbitrary_do( } // § 7.2.8 -pub fn put_data( +pub fn put_data( mut context: Context<'_, R, T>, mode: PutDataMode, tag: Tag, @@ -813,11 +827,11 @@ pub fn put_data( } match object.write_perm() { - PermissionRequirement::Admin if !context.state.volatile.admin_verified => { + PermissionRequirement::Admin if !context.state.volatile.admin_verified() => { warn!("Put data for admin authorized object: {object:?}"); return Err(Status::SecurityStatusNotSatisfied); } - PermissionRequirement::User if !context.state.volatile.other_verified => { + PermissionRequirement::User if !context.state.volatile.other_verified() => { warn!("Put data for user authorized object: {object:?}"); return Err(Status::SecurityStatusNotSatisfied); } @@ -880,7 +894,7 @@ impl PutDataObject { } } - fn put_data( + fn put_data( self, mut ctx: Context<'_, R, T>, ) -> Result<(), Status> { @@ -922,7 +936,7 @@ impl PutDataObject { } } -fn put_cardholder_cert( +fn put_cardholder_cert( ctx: Context<'_, R, T>, ) -> Result<(), Status> { let occ = match ctx.state.volatile.cur_do { @@ -939,7 +953,7 @@ fn put_cardholder_cert( const AES256_KEY_LEN: usize = 32; -fn put_enc_dec_key( +fn put_enc_dec_key( ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { if ctx.data.len() != AES256_KEY_LEN { @@ -977,7 +991,7 @@ fn put_enc_dec_key( Ok(()) } -fn put_resetting_code( +fn put_resetting_code( ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { if ctx.data.is_empty() { @@ -998,9 +1012,10 @@ fn put_resetting_code( ); return Err(Status::IncorrectDataParameter); } + ctx.state .persistent - .change_pin( + .set_pin( ctx.backend.client_mut(), ctx.options.storage, ctx.data, @@ -1012,7 +1027,7 @@ fn put_resetting_code( }) } -fn put_uif( +fn put_uif( ctx: LoadedContext<'_, R, T>, key: KeyType, ) -> Result<(), Status> { @@ -1042,7 +1057,7 @@ fn put_uif( .map_err(|_| Status::UnspecifiedPersistentExecutionError) } -fn put_status_bytes( +fn put_status_bytes( ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { if ctx.data.len() != 4 && ctx.data.len() != 1 { @@ -1072,7 +1087,7 @@ fn put_status_bytes( Ok(()) } -fn put_language_prefs( +fn put_language_prefs( ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { let bytes = if ctx.data.len() % 2 == 0 { @@ -1095,7 +1110,7 @@ fn put_language_prefs( .map_err(|_| Status::UnspecifiedPersistentExecutionError) } -fn put_cardholder_sex( +fn put_cardholder_sex( ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { if ctx.data.len() != 1 { @@ -1116,7 +1131,7 @@ fn put_cardholder_sex( .map_err(|_| Status::UnspecifiedPersistentExecutionError) } -fn put_cardholder_name( +fn put_cardholder_name( ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { let bytes = heapless::Vec::try_from(ctx.data) @@ -1134,7 +1149,7 @@ fn put_cardholder_name( .map_err(|_| Status::UnspecifiedPersistentExecutionError) } -fn put_alg_attributes_sign( +fn put_alg_attributes_sign( ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { let alg = SignatureAlgorithm::try_from(ctx.data).map_err(|_| { @@ -1151,7 +1166,7 @@ fn put_alg_attributes_sign( .map_err(|_| Status::UnspecifiedNonpersistentExecutionError) } -fn put_alg_attributes_dec( +fn put_alg_attributes_dec( ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { let alg = DecryptionAlgorithm::try_from(ctx.data).map_err(|_| { @@ -1168,7 +1183,7 @@ fn put_alg_attributes_dec( .map_err(|_| Status::UnspecifiedNonpersistentExecutionError) } -fn put_alg_attributes_aut( +fn put_alg_attributes_aut( ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { let alg = AuthenticationAlgorithm::try_from(ctx.data).map_err(|_| { @@ -1185,7 +1200,7 @@ fn put_alg_attributes_aut( .map_err(|_| Status::UnspecifiedNonpersistentExecutionError) } -fn put_arbitrary_do( +fn put_arbitrary_do( ctx: Context<'_, R, T>, obj: ArbitraryDO, ) -> Result<(), Status> { @@ -1196,7 +1211,7 @@ fn put_arbitrary_do( .map_err(|_| Status::UnspecifiedPersistentExecutionError) } -fn put_fingerprint( +fn put_fingerprint( ctx: LoadedContext<'_, R, T>, for_key: KeyType, ) -> Result<(), Status> { @@ -1212,7 +1227,7 @@ fn put_fingerprint( .map_err(|_| Status::UnspecifiedNonpersistentExecutionError) } -fn put_ca_fingerprint( +fn put_ca_fingerprint( ctx: LoadedContext<'_, R, T>, for_key: KeyType, ) -> Result<(), Status> { @@ -1227,7 +1242,7 @@ fn put_ca_fingerprint( .map_err(|_| Status::UnspecifiedNonpersistentExecutionError) } -fn put_keygen_date( +fn put_keygen_date( ctx: LoadedContext<'_, R, T>, for_key: KeyType, ) -> Result<(), Status> { @@ -1242,11 +1257,13 @@ fn put_keygen_date( .map_err(|_| Status::UnspecifiedNonpersistentExecutionError) } -#[cfg(test)] +#[cfg(all(test, feature = "virt"))] mod tests { #![allow(clippy::unwrap_used, clippy::expect_used)] + use super::*; use hex_literal::hex; + use trussed::types::Location; #[test] fn tags() { @@ -1340,18 +1357,16 @@ mod tests { #[test] fn constructed_dos_tlv() { - trussed::virt::with_ram_client("constructed_dos_tlv", |client| { - use crate::state::{self, State}; + crate::virt::with_ram_client("constructed_dos_tlv", |client| { + use crate::state::State; use crate::tlv::*; let mut backend = crate::backend::Backend::new(client); let mut reply: heapless::Vec = Default::default(); - let volatile = Default::default(); - let persistent = state::Persistent::test_default(); + let mut state = State::default(); + state + .load(backend.client_mut(), Location::External) + .unwrap(); let options = Default::default(); - let mut state = State { - persistent: Some(persistent), - volatile, - }; let context = Context { state: &mut state, @@ -1405,7 +1420,7 @@ mod tests { max_length_rc: 127, max_length_pw3: 127, error_counter_pw1: 3, - error_counter_rc: 3, + error_counter_rc: 0, error_counter_pw3: 3, }), ), diff --git a/src/command/gen.rs b/src/command/gen.rs index 77622e86..fa3df7fc 100644 --- a/src/command/gen.rs +++ b/src/command/gen.rs @@ -5,6 +5,7 @@ use hex_literal::hex; use iso7816::Status; use trussed::types::{KeyId, KeySerialization, Location, Mechanism, StorageAttributes}; use trussed::{syscall, try_syscall}; +use trussed_auth::AuthClient; use crate::card::LoadedContext; use crate::state::KeyOrigin; @@ -16,7 +17,7 @@ const KEYGEN_DO_TAG: &[u8] = &hex!("7f49"); #[cfg(feature = "rsa")] use trussed_rsa_alloc::RsaPublicParts; -fn serialize_pub( +fn serialize_pub( algo: CurveAlgo, ctx: LoadedContext<'_, R, T>, public_key: &[u8], @@ -27,7 +28,7 @@ fn serialize_pub( } } -pub fn sign( +pub fn sign( mut ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { let algo = ctx.state.persistent.sign_alg(); @@ -52,7 +53,7 @@ pub fn sign( } } -pub fn dec( +pub fn dec( mut ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { let algo = ctx.state.persistent.dec_alg(); @@ -75,7 +76,7 @@ pub fn dec( } } -pub fn aut( +pub fn aut( mut ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { let algo = ctx.state.persistent.aut_alg(); @@ -101,7 +102,7 @@ pub fn aut( } #[cfg(feature = "rsa")] -fn gen_rsa_key( +fn gen_rsa_key( ctx: LoadedContext<'_, R, T>, key: KeyType, mechanism: Mechanism, @@ -138,7 +139,7 @@ fn gen_rsa_key( read_rsa_key(ctx, key_id, mechanism) } -fn gen_ec_key( +fn gen_ec_key( ctx: LoadedContext<'_, R, T>, key: KeyType, curve: CurveAlgo, @@ -174,7 +175,7 @@ fn gen_ec_key( read_ec_key(ctx, key_id, curve) } -pub fn read_sign( +pub fn read_sign( mut ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { let key_id = ctx @@ -193,7 +194,7 @@ pub fn read_sign( } } -pub fn read_dec( +pub fn read_dec( mut ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { let key_id = ctx @@ -218,7 +219,7 @@ pub fn read_dec( } } -pub fn read_aut( +pub fn read_aut( mut ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { let key_id = ctx @@ -243,7 +244,7 @@ pub fn read_aut( } } -fn serialize_p256( +fn serialize_p256( mut ctx: LoadedContext<'_, R, T>, serialized: &[u8], ) -> Result<(), Status> { @@ -253,7 +254,7 @@ fn serialize_p256( ctx.reply.expand(serialized) } -fn serialize_25519( +fn serialize_25519( mut ctx: LoadedContext<'_, R, T>, serialized: &[u8], ) -> Result<(), Status> { @@ -262,7 +263,7 @@ fn serialize_25519( ctx.reply.expand(serialized) } -fn read_ec_key( +fn read_ec_key( mut ctx: LoadedContext<'_, R, T>, key_id: KeyId, curve: CurveAlgo, @@ -291,7 +292,7 @@ fn read_ec_key( } #[cfg(feature = "rsa")] -fn read_rsa_key( +fn read_rsa_key( mut ctx: LoadedContext<'_, R, T>, key_id: KeyId, mechanism: Mechanism, @@ -336,7 +337,7 @@ fn read_rsa_key( } #[cfg(not(feature = "rsa"))] -fn gen_rsa_key( +fn gen_rsa_key( _ctx: LoadedContext<'_, R, T>, _key: KeyType, _mechanism: Mechanism, @@ -345,7 +346,7 @@ fn gen_rsa_key( } #[cfg(not(feature = "rsa"))] -fn read_rsa_key( +fn read_rsa_key( _ctx: LoadedContext<'_, R, T>, _key_id: KeyId, _mechanism: Mechanism, diff --git a/src/command/private_key_template.rs b/src/command/private_key_template.rs index 1f87deee..96ab559e 100644 --- a/src/command/private_key_template.rs +++ b/src/command/private_key_template.rs @@ -4,6 +4,7 @@ use iso7816::Status; use trussed::types::{KeyId, KeySerialization, Mechanism}; use trussed::{syscall, try_syscall}; +use trussed_auth::AuthClient; use crate::card::LoadedContext; use crate::state::KeyOrigin; @@ -17,7 +18,7 @@ const CONCATENATION_KEY_DATA_DO: u16 = 0x5F48; use trussed_rsa_alloc::RsaImportFormat; // § 4.4.3.12 -pub fn put_private_key_template( +pub fn put_private_key_template( ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { let data = get_do(&[PRIVATE_KEY_TEMPLATE_DO], ctx.data).ok_or_else(|| { @@ -36,7 +37,7 @@ pub fn put_private_key_template( Ok(()) } -pub fn put_sign( +pub fn put_sign( mut ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { let attr = ctx.state.persistent.sign_alg(); @@ -67,7 +68,7 @@ pub fn put_sign( Ok(()) } -pub fn put_dec( +pub fn put_dec( mut ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { let attr = ctx.state.persistent.dec_alg(); @@ -98,7 +99,7 @@ pub fn put_dec( Ok(()) } -pub fn put_aut( +pub fn put_aut( mut ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { let attr = ctx.state.persistent.aut_alg(); @@ -129,7 +130,7 @@ pub fn put_aut( Ok(()) } -fn put_ec( +fn put_ec( ctx: LoadedContext<'_, R, T>, curve: CurveAlgo, ) -> Result, Status> { @@ -206,7 +207,7 @@ fn parse_rsa_template(data: &[u8]) -> Option { } #[cfg(feature = "rsa")] -fn put_rsa( +fn put_rsa( ctx: LoadedContext<'_, R, T>, mechanism: Mechanism, ) -> Result, Status> { @@ -236,7 +237,7 @@ fn put_rsa( } #[cfg(not(feature = "rsa"))] -fn put_rsa( +fn put_rsa( _ctx: LoadedContext<'_, R, T>, _mechanism: Mechanism, ) -> Result, Status> { diff --git a/src/command/pso.rs b/src/command/pso.rs index 59b07a34..c64a9942 100644 --- a/src/command/pso.rs +++ b/src/command/pso.rs @@ -5,13 +5,14 @@ use iso7816::Status; use trussed::types::*; use trussed::{syscall, try_syscall}; +use trussed_auth::AuthClient; use crate::card::LoadedContext; use crate::state::KeyRef; use crate::tlv::get_do; use crate::types::*; -fn check_uif( +fn check_uif( ctx: LoadedContext<'_, R, T>, key: KeyType, ) -> Result<(), Status> { @@ -22,7 +23,7 @@ fn check_uif( } } -fn prompt_uif( +fn prompt_uif( ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { let success = ctx @@ -39,21 +40,21 @@ fn prompt_uif( } // § 7.2.10 -pub fn sign( +pub fn sign( mut ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { let key_id = ctx.state.persistent.key_id(KeyType::Sign).ok_or_else(|| { warn!("Attempt to sign without a key set"); Status::KeyReferenceNotFound })?; - if !ctx.state.volatile.sign_verified { + if !ctx.state.volatile.sign_verified() { warn!("Attempt to sign without PW1 verified"); return Err(Status::SecurityStatusNotSatisfied); } check_uif(ctx.lend(), KeyType::Sign)?; if !ctx.state.persistent.pw1_valid_multiple() { - ctx.state.volatile.sign_verified = false; + ctx.state.volatile.clear_sign(ctx.backend.client_mut()) } ctx.state .persistent @@ -77,7 +78,7 @@ pub fn sign( } } -fn sign_ec( +fn sign_ec( mut ctx: LoadedContext<'_, R, T>, key_id: KeyId, mechanism: Mechanism, @@ -96,7 +97,7 @@ fn sign_ec( ctx.reply.expand(&signature) } -fn sign_rsa( +fn sign_rsa( mut ctx: LoadedContext<'_, R, T>, key_id: KeyId, mechanism: Mechanism, @@ -120,7 +121,7 @@ enum RsaOrEcc { Ecc, } -fn int_aut_key_mecha_uif( +fn int_aut_key_mecha_uif( ctx: LoadedContext<'_, R, T>, ) -> Result<(KeyId, Mechanism, bool, RsaOrEcc), Status> { let (key_type, (mechanism, key_kind)) = match ctx.state.volatile.keyrefs.internal_aut { @@ -170,10 +171,10 @@ fn int_aut_key_mecha_uif( } // § 7.2.13 -pub fn internal_authenticate( +pub fn internal_authenticate( mut ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { - if !ctx.state.volatile.other_verified { + if !ctx.state.volatile.other_verified() { warn!("Attempt to sign without PW1 verified"); return Err(Status::SecurityStatusNotSatisfied); } @@ -189,7 +190,7 @@ pub fn internal_authenticate( } } -fn decipher_key_mecha_uif( +fn decipher_key_mecha_uif( ctx: LoadedContext<'_, R, T>, ) -> Result<(KeyId, Mechanism, bool, RsaOrEcc), Status> { let (key_type, (mechanism, key_kind)) = match ctx.state.volatile.keyrefs.pso_decipher { @@ -231,10 +232,10 @@ fn decipher_key_mecha_uif( } // § 7.2.11 -pub fn decipher( +pub fn decipher( mut ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { - if !ctx.state.volatile.other_verified { + if !ctx.state.volatile.other_verified() { warn!("Attempt to sign without PW1 verified"); return Err(Status::SecurityStatusNotSatisfied); } @@ -256,7 +257,7 @@ pub fn decipher( } } -fn decrypt_rsa( +fn decrypt_rsa( mut ctx: LoadedContext<'_, R, T>, private_key: KeyId, mechanism: Mechanism, @@ -284,7 +285,7 @@ fn decrypt_rsa( ctx.reply.expand(&plaintext) } -fn decrypt_ec( +fn decrypt_ec( mut ctx: LoadedContext<'_, R, T>, private_key: KeyId, mechanism: Mechanism, @@ -363,7 +364,7 @@ fn decrypt_ec( ctx.reply.expand(&data) } -fn decipher_aes( +fn decipher_aes( mut ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { let key_id = ctx.state.persistent.aes_key().ok_or_else(|| { @@ -392,10 +393,10 @@ fn decipher_aes( ctx.reply.expand(&plaintext) } -pub fn encipher( +pub fn encipher( mut ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { - if !ctx.state.volatile.other_verified { + if !ctx.state.volatile.other_verified() { warn!("Attempt to encipher without PW1 verified"); return Err(Status::SecurityStatusNotSatisfied); } diff --git a/src/error.rs b/src/error.rs index d93b9114..cc81924d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,8 +6,6 @@ pub enum Error { Loading, Saving, InvalidPin, - TooManyTries, - RequestTooLarge, BadRequest, UserInteraction, } @@ -18,8 +16,6 @@ impl core::fmt::Display for Error { Error::Loading => "Failed to load or deserialize from filesystem", Error::Saving => "Failed to save to filesystem", Error::InvalidPin => "Failed PIN authentication", - Error::TooManyTries => "PIN is locked", - Error::RequestTooLarge => "Request data is larger than supported", Error::BadRequest => "Request data invalid", Error::UserInteraction => "Failed to get user presence", }; diff --git a/src/lib.rs b/src/lib.rs index 23aeb215..9a82d058 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,6 +59,9 @@ mod utils; #[cfg(feature = "virtual")] mod vpicc; +#[cfg(feature = "virt")] +pub mod virt; + #[cfg(feature = "virtual")] pub use self::vpicc::VirtualCard; pub use card::{Card, Options}; diff --git a/src/state.rs b/src/state.rs index 905d92c3..d85d8c8e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,21 +1,21 @@ // Copyright (C) 2022 Nitrokey GmbH // SPDX-License-Identifier: LGPL-3.0-only -use core::mem::swap; +use core::mem::{swap, take}; use heapless_bytes::Bytes; use hex_literal::hex; use iso7816::Status; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_repr::{Deserialize_repr, Serialize_repr}; -use subtle::ConstantTimeEq; use trussed::api::reply::Metadata; use trussed::config::MAX_MESSAGE_LENGTH; -use trussed::try_syscall; use trussed::types::{KeyId, Location, PathBuf}; +use trussed::{syscall, try_syscall}; +use trussed_auth::AuthClient; -use crate::command::Password; +use crate::command::{Password, PasswordMode}; use crate::error::Error; use crate::types::*; use crate::utils::serde_bytes; @@ -164,7 +164,7 @@ pub struct State { impl State { /// Loads the persistent state from flash - pub fn load<'s, T: trussed::Client>( + pub fn load<'s, T: trussed::Client + AuthClient>( &'s mut self, client: &mut T, storage: Location, @@ -198,15 +198,18 @@ impl State { fn lifecycle_path() -> PathBuf { PathBuf::from(Self::LIFECYCLE_PATH) } - pub fn lifecycle(client: &mut impl trussed::Client, storage: Location) -> LifeCycle { + pub fn lifecycle( + client: &mut T, + storage: Location, + ) -> LifeCycle { match try_syscall!(client.entry_metadata(storage, Self::lifecycle_path())) { Ok(Metadata { metadata: Some(_) }) => LifeCycle::Initialization, _ => LifeCycle::Operational, } } - pub fn terminate_df( - client: &mut impl trussed::Client, + pub fn terminate_df( + client: &mut T, storage: Location, ) -> Result<(), Status> { try_syscall!(client.write_file(storage, Self::lifecycle_path(), Bytes::new(), None,)) @@ -217,8 +220,8 @@ impl State { }) } - pub fn activate_file( - client: &mut impl trussed::Client, + pub fn activate_file( + client: &mut T, storage: Location, ) -> Result<(), Status> { try_syscall!(client.remove_file(storage, Self::lifecycle_path(),)).ok(); @@ -245,6 +248,71 @@ impl<'a> LoadedState<'a> { volatile: self.volatile, } } + + pub fn verify_pin( + &mut self, + client: &mut T, + storage: Location, + value: &[u8], + password: PasswordMode, + ) -> Result<(), Error> { + let pin = Bytes::from_slice(value).map_err(|_| { + warn!("Attempt to verify pin that is too long"); + Error::InvalidPin + })?; + let key_exists = match password { + PasswordMode::Pw1Sign | PasswordMode::Pw1Other => self.volatile.user_kek(), + PasswordMode::Pw3 => self.volatile.admin_kek(), + }; + let pin_id: Password = password.into(); + + let checked_key = if let Some(k) = key_exists { + // If the pin key is alraedy available, don't derive it again to save memory + let res = try_syscall!(client.check_pin(pin_id, pin.clone())).map_err(|_err| { + error!("Failed to verify pin: {:?}", _err); + Error::InvalidPin + })?; + + if !res.success { + return Err(Error::InvalidPin); + } + k + } else { + try_syscall!(client.get_pin_key(pin_id, pin.clone())) + .map_err(|_err| { + error!("Failed to verify pin: {:?}", _err); + Error::InvalidPin + })? + .result + .ok_or(Error::InvalidPin)? + }; + + match password { + PasswordMode::Pw1Sign => self.volatile.user.verify_sign(checked_key), + PasswordMode::Pw1Other => self.volatile.user.verify_other(checked_key), + PasswordMode::Pw3 => self.volatile.admin.verify(checked_key), + }; + + // Reset the pin length in case it was incorrect due to the lack of atomicity of operations. + self.persistent + .set_pin_len(client, storage, pin.len(), pin_id)?; + Ok(()) + } + pub fn check_pin( + &mut self, + client: &mut T, + value: &[u8], + password: Password, + ) -> Result { + let pin = Bytes::from_slice(value).map_err(|_| { + warn!("Attempt to verify pin that is too long"); + Error::InvalidPin + })?; + try_syscall!(client.get_pin_key(password, pin)) + .map_err(|_err| Error::InvalidPin)? + .result + .ok_or(Error::InvalidPin) + } } enum_u8! { @@ -257,6 +325,8 @@ enum_u8! { } } +// #[derive(Default)] Conflicts with `enum_u8!` +#[allow(clippy::derivable_impls)] impl Default for Sex { fn default() -> Sex { Sex::NotKnown @@ -272,13 +342,10 @@ pub enum KeyOrigin { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Persistent { - user_pin_tries: u8, - admin_pin_tries: u8, - reset_code_tries: u8, pw1_valid_multiple: bool, - user_pin: Bytes, - admin_pin: Bytes, - reset_code_pin: Option>, + user_pin_len: u8, + admin_pin_len: u8, + reset_code_pin_len: Option, signing_key: Option<(KeyId, KeyOrigin)>, confidentiality_key: Option<(KeyId, KeyOrigin)>, aut_key: Option<(KeyId, KeyOrigin)>, @@ -306,17 +373,11 @@ impl Persistent { #[allow(clippy::unwrap_used)] fn default() -> Self { - // § 4.3.1 - let admin_pin = Bytes::from_slice(DEFAULT_ADMIN_PIN).unwrap(); - let user_pin = Bytes::from_slice(DEFAULT_USER_PIN).unwrap(); Self { - user_pin_tries: 0, - admin_pin_tries: 0, - reset_code_tries: 0, - reset_code_pin: None, + reset_code_pin_len: None, pw1_valid_multiple: false, - admin_pin, - user_pin, + admin_pin_len: DEFAULT_ADMIN_PIN.len() as u8, + user_pin_len: DEFAULT_USER_PIN.len() as u8, cardholder_name: Bytes::new(), cardholder_sex: Sex::default(), language_preferences: Bytes::new(), @@ -337,22 +398,40 @@ impl Persistent { } } - #[cfg(test)] - pub fn test_default() -> Self { - Self::default() - } - fn path() -> PathBuf { PathBuf::from(Self::FILENAME) } - pub fn load(client: &mut T, storage: Location) -> Result { + fn init_pins(client: &mut T) -> Result<(), Error> { + #[allow(clippy::unwrap_used)] + let default_user_pin = Bytes::from_slice(DEFAULT_USER_PIN).unwrap(); + #[allow(clippy::unwrap_used)] + let default_admin_pin = Bytes::from_slice(DEFAULT_ADMIN_PIN).unwrap(); + syscall!(client.set_pin( + Password::Pw1, + default_user_pin, + Some(Self::MAX_RETRIES), + true, + )); + syscall!(client.set_pin( + Password::Pw3, + default_admin_pin, + Some(Self::MAX_RETRIES), + true, + )); + Ok(()) + } + pub fn load( + client: &mut T, + storage: Location, + ) -> Result { if let Some(data) = load_if_exists(client, storage, &Self::path())? { trussed::cbor_deserialize(&data).map_err(|_err| { error!("failed to deserialize persistent state: {_err}"); Error::Loading }) } else { + Self::init_pins(client)?; Ok(Self::default()) } } @@ -369,129 +448,90 @@ impl Persistent { Ok(()) } - pub fn remaining_tries(&self, password: Password) -> u8 { - match password { - Password::Pw1 => Self::MAX_RETRIES.saturating_sub(self.user_pin_tries), - Password::Pw3 => Self::MAX_RETRIES.saturating_sub(self.admin_pin_tries), - Password::ResetCode => Self::MAX_RETRIES.saturating_sub(self.reset_code_tries), - } + pub fn remaining_tries( + &self, + client: &mut T, + password: Password, + ) -> u8 { + try_syscall!(client.pin_retries(password)) + .map(|r| r.retries.unwrap_or_default()) + .unwrap_or(0) } - pub fn is_locked(&self, password: Password) -> bool { + pub fn is_locked( + &self, + client: &mut T, + password: Password, + ) -> bool { + self.remaining_tries(client, password) == 0 + } + + /// Panics if password is ResetCode, use [reset_code_len](Self::reset_code_len) instead + pub fn pin_len(&self, password: Password) -> usize { match password { - Password::Pw1 => self.user_pin_tries >= Self::MAX_RETRIES, - Password::Pw3 => self.admin_pin_tries >= Self::MAX_RETRIES, - Password::ResetCode => self.reset_code_tries >= Self::MAX_RETRIES, + Password::Pw1 => self.user_pin_len as usize, + Password::Pw3 => self.admin_pin_len as usize, + Password::ResetCode => unreachable!(), } } - pub fn decrement_counter( - &mut self, - client: &mut T, - storage: Location, - password: Password, - ) -> Result<(), Error> { - if !self.is_locked(password) { - match password { - Password::Pw1 => self.user_pin_tries += 1, - Password::Pw3 => self.admin_pin_tries += 1, - Password::ResetCode => self.reset_code_tries += 1, - } - self.save(client, storage) - } else { - Ok(()) - } + /// Returns None if no code has been set + pub fn reset_code_len(&self) -> Option { + self.reset_code_pin_len.map(Into::into) } - pub fn reset_counter( + pub fn set_pin( &mut self, client: &mut T, storage: Location, + new_value: &[u8], password: Password, ) -> Result<(), Error> { - match password { - Password::Pw1 => self.user_pin_tries = 0, - Password::Pw3 => self.admin_pin_tries = 0, - Password::ResetCode => self.reset_code_tries = 0, - } - self.save(client, storage) - } - - fn pin(&self, password: Password) -> Option<&[u8]> { - match password { - Password::Pw1 => Some(&self.user_pin), - Password::Pw3 => Some(&self.admin_pin), - Password::ResetCode => self.reset_code_pin.as_ref().map(|d| &d[..]), - } + let new_pin = Bytes::from_slice(new_value).map_err(|_| Error::InvalidPin)?; + syscall!(client.set_pin(password, new_pin.clone(), Some(Self::MAX_RETRIES), true)); + self.set_pin_len(client, storage, new_pin.len(), password) } - pub fn verify_pin( + pub fn change_pin( &mut self, client: &mut T, storage: Location, - value: &[u8], + old_value: &[u8], + new_value: &[u8], password: Password, ) -> Result<(), Error> { - if self.is_locked(password) { - return Err(Error::TooManyTries); - } - - self.decrement_counter(client, storage, password)?; - let pin = self.pin(password).ok_or(Error::BadRequest)?; - if (!value.ct_eq(pin)).into() { - return Err(Error::InvalidPin); - } - - self.reset_counter(client, storage, password)?; - Ok(()) - } - - /// Panics if password is ResetCode, use [reset_code_len](Self::reset_code_len) instead - pub fn pin_len(&self, password: Password) -> usize { - match password { - Password::Pw1 => self.user_pin.len(), - Password::Pw3 => self.admin_pin.len(), - Password::ResetCode => unreachable!(), - } + let new_pin = Bytes::from_slice(new_value).map_err(|_| Error::InvalidPin)?; + let old_pin = Bytes::from_slice(old_value).map_err(|_| Error::InvalidPin)?; + try_syscall!(client.change_pin(password, old_pin, new_pin.clone())) + .map_err(|_| Error::InvalidPin)?; + self.set_pin_len(client, storage, new_pin.len(), password) } - /// Returns None if no code has been set - pub fn reset_code_len(&self) -> Option { - self.reset_code_pin.as_ref().map(|d| d.len()) - } - - pub fn change_pin( + fn set_pin_len( &mut self, client: &mut T, storage: Location, - value: &[u8], + new_len: usize, password: Password, ) -> Result<(), Error> { - let new_pin = Bytes::from_slice(value).map_err(|_| Error::RequestTooLarge)?; - let (pin, tries) = match password { - Password::Pw1 => (&mut self.user_pin, &mut self.user_pin_tries), - Password::Pw3 => (&mut self.admin_pin, &mut self.admin_pin_tries), - Password::ResetCode => { - self.reset_code_pin = Some(Default::default()); - ( - #[allow(clippy::unwrap_used)] - self.reset_code_pin.as_mut().unwrap(), - &mut self.reset_code_tries, - ) - } - }; - *pin = new_pin; - *tries = 0; + match password { + Password::Pw1 => self.user_pin_len = new_len as u8, + Password::Pw3 => self.admin_pin_len = new_len as u8, + Password::ResetCode => self.reset_code_pin_len = Some(new_len as u8), + } self.save(client, storage) } - pub fn remove_reset_code( + pub fn remove_reset_code( &mut self, client: &mut T, storage: Location, ) -> Result<(), Error> { - self.reset_code_tries = 0; - self.reset_code_pin = None; + if self.reset_code_pin_len.is_some() { + // Possible race condition so we ignore the error + try_syscall!(client.delete_pin(Password::ResetCode)).ok(); + } + self.reset_code_pin_len = None; self.save(client, storage) } @@ -787,15 +827,173 @@ impl Default for KeyRefs { } } +#[derive(Debug, Default, Clone, PartialEq, Eq)] +enum UserVerifiedInner { + #[default] + None, + Other(KeyId), + Sign(KeyId), + #[allow(unused)] + OtherAndSign(KeyId), +} + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +struct UserVerified(UserVerifiedInner); + +impl Drop for UserVerified { + fn drop(&mut self) { + if self.0.user_kek().is_none() { + return; + } + + #[cfg(all(debug_assertions, feature = "std"))] + if !std::thread::panicking() { + panic!("User dropped with kek still available"); + } + + error!("Error: User dropped with kek still available"); + } +} + +impl UserVerified { + fn verify_sign(&mut self, k: KeyId) { + self.0.verify_sign(k) + } + + fn verify_other(&mut self, k: KeyId) { + self.0.verify_other(k) + } +} + +impl UserVerifiedInner { + fn verify_sign(&mut self, k: KeyId) { + match self { + Self::None => *self = Self::Sign(k), + Self::Other(old_k) => { + debug_assert_eq!(*old_k, k); + *self = Self::OtherAndSign(k) + } + _ => {} + } + } + + fn verify_other(&mut self, k: KeyId) { + match self { + Self::None => *self = Self::Other(k), + Self::Sign(old_k) => { + debug_assert_eq!(*old_k, k); + *self = Self::OtherAndSign(k) + } + _ => {} + } + } + + fn sign_verified(&self) -> bool { + matches!(self, Self::Sign(_) | Self::OtherAndSign(_)) + } + fn other_verified(&self) -> bool { + matches!(self, Self::Other(_) | Self::OtherAndSign(_)) + } + fn user_kek(&self) -> Option { + match self { + Self::Other(k) | Self::Sign(k) | Self::OtherAndSign(k) => Some(*k), + _ => None, + } + } + fn clear(&mut self, client: &mut impl trussed::Client) { + let this = take(self); + if let Some(k) = this.user_kek() { + syscall!(client.delete(k)); + } + } + + fn clear_sign(&mut self, client: &mut impl trussed::Client) { + match self { + Self::Sign(_k) => self.clear(client), + Self::OtherAndSign(k) => *self = Self::Other(*k), + _ => {} + }; + } + fn clear_other(&mut self, client: &mut impl trussed::Client) { + match self { + Self::Other(_k) => self.clear(client), + Self::OtherAndSign(k) => *self = Self::Sign(*k), + _ => {} + }; + } +} + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +struct AdminVerified(Option); + +impl AdminVerified { + fn verify(&mut self, k: KeyId) { + if let Some(old_k) = self.0 { + debug_assert_eq!(old_k, k); + } + self.0 = Some(k); + } +} + +impl Drop for AdminVerified { + fn drop(&mut self) { + if self.0.is_none() { + return; + } + + #[cfg(all(debug_assertions, feature = "std"))] + if !std::thread::panicking() { + panic!("Admin dropped with kek still available"); + } + + error!("Error: Admin dropped with kek still available"); + } +} + #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct Volatile { - pub sign_verified: bool, - pub other_verified: bool, - pub admin_verified: bool, + user: UserVerified, + admin: AdminVerified, pub cur_do: Option<(Tag, Occurrence)>, pub keyrefs: KeyRefs, } +impl Volatile { + pub fn admin_verified(&self) -> bool { + self.admin.0.is_some() + } + pub fn admin_kek(&self) -> Option { + self.admin.0 + } + + pub fn clear_admin(&mut self, client: &mut impl trussed::Client) { + if let Some(k) = self.admin.0.take() { + syscall!(client.delete(k)); + } + } + pub fn sign_verified(&self) -> bool { + self.user.0.sign_verified() + } + pub fn other_verified(&self) -> bool { + self.user.0.other_verified() + } + pub fn user_kek(&self) -> Option { + self.user.0.user_kek() + } + + pub fn clear(&mut self, client: &mut impl trussed::Client) { + self.user.0.clear(client); + self.clear_admin(client) + } + + pub fn clear_sign(&mut self, client: &mut impl trussed::Client) { + self.user.0.clear_sign(client) + } + pub fn clear_other(&mut self, client: &mut impl trussed::Client) { + self.user.0.clear_other(client) + } +} + /// DOs that can store arbitrary data from the user /// /// They are stored each in their own files and are loaded only diff --git a/src/types.rs b/src/types.rs index 8cc167dc..25d928ab 100644 --- a/src/types.rs +++ b/src/types.rs @@ -289,20 +289,15 @@ impl KeyType { } } -#[derive(Clone, Debug, Eq, PartialEq, Copy, Deserialize_repr, Serialize_repr)] +#[derive(Clone, Debug, Eq, PartialEq, Copy, Deserialize_repr, Serialize_repr, Default)] #[repr(u8)] pub enum Uif { + #[default] Disabled = 0, Enabled = 1, PermanentlyEnabled = 2, } -impl Default for Uif { - fn default() -> Self { - Uif::Disabled - } -} - impl TryFrom for Uif { type Error = Error; fn try_from(v: u8) -> Result { diff --git a/src/virt.rs b/src/virt.rs new file mode 100644 index 00000000..309a75fd --- /dev/null +++ b/src/virt.rs @@ -0,0 +1,188 @@ +// Copyright (C) 2022 Nitrokey GmbH +// SPDX-License-Identifier: LGPL-3.0-only + +//! Virtual trussed client (mostly for testing) + +mod dispatch { + + use trussed::{ + api::{reply, request, Reply, Request}, + backend::{Backend as _, BackendId}, + error::Error, + platform::Platform, + serde_extensions::{ExtensionDispatch, ExtensionId, ExtensionImpl as _}, + service::ServiceResources, + types::{Bytes, Context, Location}, + }; + use trussed_auth::{AuthBackend, AuthContext, AuthExtension, MAX_HW_KEY_LEN}; + + #[cfg(feature = "rsa")] + use trussed_rsa_alloc::SoftwareRsa; + + /// Backends used by opcard + pub const BACKENDS: &[BackendId] = &[ + BackendId::Custom(Backend::Auth), + #[cfg(feature = "rsa")] + BackendId::Custom(Backend::Rsa), + BackendId::Core, + ]; + + #[derive(Debug, Clone, Copy)] + pub enum Backend { + Auth, + #[cfg(feature = "rsa")] + Rsa, + } + + #[derive(Debug, Clone, Copy)] + pub enum Extension { + Auth, + } + + impl From for u8 { + fn from(extension: Extension) -> Self { + match extension { + Extension::Auth => 0, + } + } + } + + impl TryFrom for Extension { + type Error = Error; + + fn try_from(id: u8) -> Result { + match id { + 0 => Ok(Extension::Auth), + _ => Err(Error::InternalError), + } + } + } + + /// Dispatch implementation with the backends required by opcard + #[derive(Debug)] + pub struct Dispatch { + auth: AuthBackend, + } + + /// Dispatch context for the backends required by opcard + #[derive(Default, Debug)] + pub struct DispatchContext { + auth: AuthContext, + } + + impl Dispatch { + pub fn new() -> Self { + Self { + auth: AuthBackend::new(Location::Internal), + } + } + + pub fn with_hw_key(hw_key: Bytes) -> Self { + Self { + auth: AuthBackend::with_hw_key(Location::Internal, hw_key), + } + } + } + + impl Default for Dispatch { + fn default() -> Self { + Self::new() + } + } + + impl ExtensionDispatch for Dispatch { + type BackendId = Backend; + type Context = DispatchContext; + type ExtensionId = Extension; + + fn core_request( + &mut self, + backend: &Self::BackendId, + ctx: &mut Context, + request: &Request, + resources: &mut ServiceResources

, + ) -> Result { + match backend { + Backend::Auth => { + self.auth + .request(&mut ctx.core, &mut ctx.backends.auth, request, resources) + } + #[cfg(feature = "rsa")] + Backend::Rsa => SoftwareRsa.request(&mut ctx.core, &mut (), request, resources), + } + } + + fn extension_request( + &mut self, + backend: &Self::BackendId, + extension: &Self::ExtensionId, + ctx: &mut Context, + request: &request::SerdeExtension, + resources: &mut ServiceResources

, + ) -> Result { + match backend { + Backend::Auth => match extension { + Extension::Auth => self.auth.extension_request_serialized( + &mut ctx.core, + &mut ctx.backends.auth, + request, + resources, + ), + }, + #[cfg(feature = "rsa")] + Backend::Rsa => Err(Error::RequestNotAvailable), + } + } + } + + impl ExtensionId for Dispatch { + type Id = Extension; + + const ID: Self::Id = Self::Id::Auth; + } +} + +use std::path::PathBuf; +use trussed::{ + types::Bytes, + virt::{self, Client, Filesystem, Ram, StoreProvider}, +}; + +/// Client type using a dispatcher with the backends required by opcard +pub type VirtClient = Client; + +/// Run a client using a provided store +pub fn with_client(store: S, client_id: &str, f: F) -> R +where + F: FnOnce(VirtClient) -> R, + S: StoreProvider, +{ + #[allow(clippy::unwrap_used)] + virt::with_platform(store, |platform| { + platform.run_client_with_backends( + client_id, + dispatch::Dispatch::with_hw_key(Bytes::from_slice(b"some bytes").unwrap()), + dispatch::BACKENDS, + f, + ) + }) +} + +/// Run the backend with the extensions required by opcard +/// using storage backed by a file +pub fn with_fs_client(internal: P, client_id: &str, f: F) -> R +where + F: FnOnce(VirtClient) -> R, + P: Into, +{ + with_client(Filesystem::new(internal), client_id, f) +} + +/// Run the backend with the extensions required by opcard +/// using a RAM file storage +pub fn with_ram_client(client_id: &str, f: F) -> R +where + F: FnOnce(VirtClient) -> R, +{ + with_client(Ram::default(), client_id, f) +} diff --git a/src/vpicc.rs b/src/vpicc.rs index b037ec5c..c12a0384 100644 --- a/src/vpicc.rs +++ b/src/vpicc.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only use iso7816::{command::FromSliceError, Command, Status}; +use trussed_auth::AuthClient; use crate::card::Card; @@ -13,13 +14,13 @@ const RESPONSE_LEN: usize = 7609; /// This struct provides a virtual OpenPGP smart card implementation that can be used with /// `vpicc-rs` and [`vsmartcard`](https://frankmorgner.github.io/vsmartcard/) to emulate the card. #[derive(Clone, Debug)] -pub struct VirtualCard { +pub struct VirtualCard { request_buffer: RequestBuffer, response_buffer: ResponseBuffer, card: Card, } -impl VirtualCard { +impl VirtualCard { /// Creates a new virtual smart card from the given card. pub fn new(card: Card) -> Self { Self { @@ -44,7 +45,7 @@ impl VirtualCard { } } -impl vpicc::VSmartCard for VirtualCard { +impl vpicc::VSmartCard for VirtualCard { fn power_on(&mut self) {} fn power_off(&mut self) { diff --git a/tests/card/mod.rs b/tests/card/mod.rs index 5f4a88f4..dfdee7d7 100644 --- a/tests/card/mod.rs +++ b/tests/card/mod.rs @@ -5,6 +5,8 @@ use std::sync::{Arc, Mutex}; use iso7816::{command::FromSliceError, Command, Status}; +#[cfg(not(feature = "dangerous-test-real-card"))] +use opcard::virt::VirtClient; use opcard::Options; use openpgp_card::{ CardBackend, CardCaps, CardTransaction, Error, OpenPgp, OpenPgpTransaction, PinType, @@ -13,19 +15,17 @@ use trussed::{ virt::{Platform, Ram}, Service, }; - -#[cfg(not(feature = "rsa"))] -use trussed::virt::{with_ram_client, Client}; -#[cfg(feature = "rsa")] -use trussed_rsa_alloc::virt::{with_ram_client, Client}; +use trussed_auth::AuthClient; const REQUEST_LEN: usize = 7609; const RESPONSE_LEN: usize = 7609; #[derive(Debug)] -pub struct Card(Arc>>); +pub struct Card( + Arc>>, +); -impl Card { +impl Card { pub fn new(client: T) -> Self { Self::with_options(client, Options::default()) } @@ -51,7 +51,7 @@ impl Card { } } -impl CardBackend for Card { +impl CardBackend for Card { fn transaction(&mut self) -> Result, Error> { // TODO: use reference instead of cloning Ok(Box::new(Transaction { @@ -62,12 +62,12 @@ impl CardBackend for Card { } #[derive(Debug)] -pub struct Transaction { +pub struct Transaction { card: Arc>>, buffer: heapless::Vec, } -impl Transaction { +impl Transaction { fn handle(&mut self, command: &[u8]) -> Result<(), Status> { self.buffer.clear(); let command = Command::::try_from(command).map_err(|err| match err { @@ -82,7 +82,7 @@ impl Transaction { } } -impl CardTransaction for Transaction { +impl CardTransaction for Transaction { fn transmit(&mut self, command: &[u8], _buf_size: usize) -> Result, Error> { let status = self.handle(command).err().unwrap_or_default(); let status: [u8; 2] = status.into(); @@ -117,24 +117,29 @@ impl CardTransaction for Transaction } } -pub fn with_card_options>) -> R, R>(options: Options, f: F) -> R { - with_ram_client("opcard", |client| { +#[cfg(not(feature = "dangerous-test-real-card"))] +pub fn with_card_options>) -> R, R>(options: Options, f: F) -> R { + opcard::virt::with_ram_client("opcard", |client| { f(Card::from_opcard(opcard::Card::new(client, options))) }) } -pub fn with_card>) -> R, R>(f: F) -> R { +#[cfg(not(feature = "dangerous-test-real-card"))] +pub fn with_card>) -> R, R>(f: F) -> R { with_card_options(Options::default(), f) } +#[cfg(not(feature = "dangerous-test-real-card"))] pub fn with_tx_options) -> R, R>(options: Options, f: F) -> R { with_card_options(options, move |mut card| card.with_tx(f)) } +#[cfg(not(feature = "dangerous-test-real-card"))] pub fn with_tx) -> R, R>(f: F) -> R { with_card(move |mut card| card.with_tx(f)) } +#[cfg(not(feature = "dangerous-test-real-card"))] pub fn error_to_retries(err: Result<(), openpgp_card::Error>) -> Option { match err { Ok(()) => None, diff --git a/tests/change-pin.rs b/tests/change-pin.rs index 931de311..c1bbee31 100644 --- a/tests/change-pin.rs +++ b/tests/change-pin.rs @@ -1,5 +1,6 @@ // Copyright (C) 2022 Nitrokey GmbH // SPDX-License-Identifier: LGPL-3.0-only +#![cfg(all(feature = "virt", not(feature = "dangerous-test-real-card")))] use openpgp_card::StatusBytes; use test_log::test; @@ -51,7 +52,7 @@ fn change() { // Pin validation routine didn't run assert_eq!(error_to_retries(tx.check_pw1_sign()), Some(2)); // New pin too long - assert!(tx.change_pw1(DEFAULT_USER_PIN, &[55; 128]).is_err()); + assert!(tx.change_pw1(DEFAULT_USER_PIN, &[55; 129]).is_err()); // The pin validation part still ran assert_eq!(error_to_retries(tx.check_pw1_sign()), Some(3)); @@ -63,7 +64,7 @@ fn change() { let unicode = "ハローワールド".as_bytes(); // More than 127 bytes (max supported length) - assert!(tx.change_pw1(&[255; 8], &[0xcc; 128]).is_err()); + assert!(tx.change_pw1(&[255; 8], &[0xcc; 129]).is_err()); assert!(tx.change_pw1(&[255; 8], &unicode[0..10]).is_ok()); assert!(tx.verify_pw1_user(&unicode[0..10]).is_ok()); assert!(tx.change_pw1(&unicode[0..10], b"new pin").is_ok()); @@ -92,7 +93,7 @@ fn change() { let unicode = "😀😃😄😁😆".as_bytes(); // More than 127 bytes (max supported length) - assert!(tx.change_pw3(&[255; 8], &[0xde; 128]).is_err()); + assert!(tx.change_pw3(&[255; 8], &[0xde; 129]).is_err()); assert!(tx.change_pw3(&[255; 8], &unicode[0..13]).is_ok()); assert!(tx.verify_pw3(&unicode[0..13]).is_ok()); assert!(tx.change_pw3(&unicode[0..13], b"new pin2").is_ok()); diff --git a/tests/command-response.rs b/tests/command-response.rs index a2c2c407..bb216616 100644 --- a/tests/command-response.rs +++ b/tests/command-response.rs @@ -1,16 +1,18 @@ // Copyright (C) 2022 Nitrokey GmbH // SPDX-License-Identifier: LGPL-3.0-only -#![cfg(feature = "virtual")] +#![cfg(feature = "virt")] use std::borrow::Cow; use hex_literal::hex; use ron::{extensions::Extensions, Options}; use serde::Deserialize; +use trussed_auth::AuthClient; // iso7816::Status doesn't support serde -#[derive(Deserialize, Debug, PartialEq, Clone, Copy)] +#[derive(Deserialize, Debug, PartialEq, Clone, Copy, Default)] enum Status { + #[default] Success, MoreAvailable(u8), VerificationFailed, @@ -198,12 +200,6 @@ impl TryFrom for Status { } } -impl Default for Status { - fn default() -> Status { - Status::Success - } -} - #[derive(Deserialize, Debug, Clone, Copy)] enum KeyType { Sign, @@ -428,7 +424,7 @@ enum IoCmd { const MATCH_EMPTY: OutputMatcher = OutputMatcher::Len(0); impl IoCmd { - fn run(&self, card: &mut opcard::Card) { + fn run(&self, card: &mut opcard::Card) { match self { Self::FactoryReset { already_failed } => Self::run_factory_reset(*already_failed, card), Self::Select => Self::run_select(card), @@ -481,7 +477,7 @@ impl IoCmd { } } - fn run_bytes( + fn run_bytes( input: &[u8], output: &OutputMatcher, expected_status: Status, @@ -508,7 +504,7 @@ impl IoCmd { } } - fn run_select(card: &mut opcard::Card) { + fn run_select(card: &mut opcard::Card) { Self::run_bytes( &hex!("00 A4 0400 06 D27600012401"), &MATCH_EMPTY, @@ -517,7 +513,10 @@ impl IoCmd { ) } - fn run_factory_reset(already_failed: u8, card: &mut opcard::Card) { + fn run_factory_reset( + already_failed: u8, + card: &mut opcard::Card, + ) { for i in 0..(3 - already_failed) { Self::run_verify( Pin::Pw3, @@ -532,7 +531,7 @@ impl IoCmd { Self::run_bytes(&hex!("00 44 00 00"), &MATCH_EMPTY, Status::Success, card); } - fn run_iodata( + fn run_iodata( input: &str, output: &OutputMatcher, expected_status: Status, @@ -541,7 +540,7 @@ impl IoCmd { Self::run_bytes(&parse_hex(input), output, expected_status, card) } - fn run_put_data( + fn run_put_data( data_object: DataObject, data: &[u8], expected_status: Status, @@ -553,7 +552,7 @@ impl IoCmd { Self::run_bytes(&input, &OutputMatcher::Len(0), expected_status, card) } - fn run_import( + fn run_import( key: &str, key_type: Option, key_kind: &KeyKind, @@ -579,7 +578,7 @@ impl IoCmd { Self::run_bytes(&input, &OutputMatcher::Len(0), expected_status, card) } - fn run_set_attributes( + fn run_set_attributes( key_kind: &KeyKind, key_type: &KeyType, card: &mut opcard::Card, @@ -595,7 +594,7 @@ impl IoCmd { Self::run_bytes(&input, &OutputMatcher::Len(0), Status::Success, card) } - fn run_verify( + fn run_verify( pin: Pin, value: &Option, expected_status: Status, @@ -606,7 +605,7 @@ impl IoCmd { let input = build_command(0x00, 0x20, 0x00, pin as u8, value, 0); Self::run_bytes(&input, &MATCH_EMPTY, expected_status, card) } - fn run_change( + fn run_change( pin: Pin, old_value: &Option, new_value: &Option, @@ -622,7 +621,7 @@ impl IoCmd { Self::run_bytes(&input, &MATCH_EMPTY, expected_status, card) } - fn run_unblock_pin( + fn run_unblock_pin( reset_code: &Option, new_value: &Option, expected_status: Status, @@ -650,7 +649,7 @@ impl IoCmd { } } - fn run_read_key( + fn run_read_key( key_kind: &KeyKind, key_type: &KeyType, public_key: &str, @@ -674,7 +673,11 @@ impl IoCmd { ) } - fn run_sign(input: &str, output: &str, card: &mut opcard::Card) { + fn run_sign( + input: &str, + output: &str, + card: &mut opcard::Card, + ) { let input = build_command(0x00, 0x2A, 0x9E, 0x9A, &parse_hex(input), 0); Self::run_bytes( &input, @@ -684,7 +687,7 @@ impl IoCmd { ) } - fn run_decrypt( + fn run_decrypt( input: &str, output: &str, key_kind: &KeyKind, @@ -712,11 +715,6 @@ impl IoCmd { } } -#[cfg(not(feature = "rsa"))] -use trussed::virt::with_ram_client; -#[cfg(feature = "rsa")] -use trussed_rsa_alloc::virt::with_ram_client; - #[test_log::test] fn command_response() { let data = std::fs::read_to_string("tests/command-response.ron").unwrap(); @@ -726,7 +724,7 @@ fn command_response() { for t in tests { println!("\n\n===========================================================",); println!("Running {}", t.name); - with_ram_client("opcard", |client| { + opcard::virt::with_ram_client("opcard", |client| { let mut card = opcard::Card::new(client, opcard::Options::default()); for io in t.cmd_resp { io.run(&mut card); diff --git a/tests/crypto.rs b/tests/crypto.rs index 661ebb32..19ae01e9 100644 --- a/tests/crypto.rs +++ b/tests/crypto.rs @@ -1,5 +1,6 @@ // Copyright (C) 2022 Nitrokey GmbH // SPDX-License-Identifier: LGPL-3.0-only +#![cfg(all(feature = "virt", not(feature = "dangerous-test-real-card")))] mod card; diff --git a/tests/dos.rs b/tests/dos.rs index 0de99ef6..fd9de4ee 100644 --- a/tests/dos.rs +++ b/tests/dos.rs @@ -1,5 +1,7 @@ // Copyright (C) 2022 Nitrokey GmbH // SPDX-License-Identifier: LGPL-3.0-only +#![cfg(all(feature = "virt", not(feature = "dangerous-test-real-card")))] + use hex_literal::hex; mod card; use test_log::test; @@ -16,7 +18,6 @@ fn get_data() { options.button_available = false; with_tx_options(options.clone(), |mut tx| { let appdata = tx.application_related_data().unwrap(); - dbg!(&appdata.uif_pso_cds()); assert!(appdata.uif_pso_cds().unwrap().is_none()); assert!(appdata.uif_pso_dec().unwrap().is_none()); assert!(appdata.uif_pso_aut().unwrap().is_none()); diff --git a/tests/gpg-status.rs b/tests/gpg-status.rs index 15b00c14..6f29c8ac 100644 --- a/tests/gpg-status.rs +++ b/tests/gpg-status.rs @@ -28,7 +28,7 @@ fn gpg_card_status() { Signature PIN ....: forced\n\ Key attributes ...: rsa2048 rsa2048 rsa2048\n\ Max. PIN lengths .: 127 127 127\n\ - PIN retry counter : 3 3 3\n\ + PIN retry counter : 3 0 3\n\ Signature counter : 0\n\ KDF setting ......: off\n\ Signature key ....: \\[none\\]\n\ diff --git a/tests/verify.rs b/tests/verify.rs index 02fac0ea..11d05f66 100644 --- a/tests/verify.rs +++ b/tests/verify.rs @@ -1,5 +1,6 @@ // Copyright (C) 2022 Nitrokey GmbH // SPDX-License-Identifier: LGPL-3.0-only +#![cfg(all(feature = "virt", not(feature = "dangerous-test-real-card")))] use test_log::test; diff --git a/tests/virt/mod.rs b/tests/virt/mod.rs index fd5e55cf..afa072c9 100644 --- a/tests/virt/mod.rs +++ b/tests/virt/mod.rs @@ -17,11 +17,6 @@ use regex::{Regex, RegexSet}; #[cfg(feature = "virtual")] use stoppable_thread::spawn; -#[cfg(not(feature = "rsa"))] -use trussed::virt::with_ram_client; -#[cfg(feature = "rsa")] -use trussed_rsa_alloc::virt::with_ram_client; - const STDOUT_FILTER: &[&str] = &[ r"\[GNUPG:\] KEY_CONSIDERED [0-9A-F]{40} \d", r"\[GNUPG:\] ENCRYPTION_COMPLIANCE_MODE \d*", @@ -48,7 +43,7 @@ pub fn with_vsc R, R>(f: F) -> R { let (tx, rx) = mpsc::channel(); let handle = spawn(move |stopped| { - with_ram_client("opcard", |client| { + opcard::virt::with_ram_client("opcard", |client| { let card = opcard::Card::new(client, opcard::Options::default()); let mut virtual_card = opcard::VirtualCard::new(card); let mut result = Ok(()); @@ -216,7 +211,7 @@ pub fn gpg_status(key: KeyType, sign_count: usize) -> Vec<&'static str> { sec, third, r"maxpinlen:127:127:127:", - r"pinretry:3:3:3:", + r"pinretry:3:0:3:", signcount, r"kdf:off:", r"cafpr::::",