diff --git a/Cargo.toml b/Cargo.toml index 29207dbb..446af7ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ trussed = "0.1.0" trussed-rsa-alloc = { version = "0.1.0", optional = true } serde_repr = "0.1" hex-literal = "0.3.4" -trussed-auth = "0.1.0" +trussed-auth = "0.2.1" # optional dependencies apdu-dispatch = { version = "0.1", optional = true } @@ -85,8 +85,8 @@ log-error = [] interchange = { git = "https://github.com/trussed-dev/interchange", rev = "fe5633466640e1e9a8c06d9b5dd1d0af08c272af" } 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"} +trussed = { git = "https://github.com/Nitrokey/trussed" , tag = "v0.1.0-nitrokey.9" } +trussed-auth = { git = "https://github.com/trussed-dev/trussed-auth.git", tag= "v0.2.1"} trussed-rsa-alloc = { git = "https://github.com/Nitrokey/trussed-rsa-backend", rev = "311d2366f99cc300b03d61e7f6a0a07abd3e8700" } [package.metadata.docs.rs] diff --git a/src/command.rs b/src/command.rs index 1ca7170e..8141ccfc 100644 --- a/src/command.rs +++ b/src/command.rs @@ -553,7 +553,7 @@ fn reset_retry_conter( } fn reset_retry_conter_with_p3( - ctx: LoadedContext<'_, R, T>, + mut ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { if ctx.data.len() < MIN_LENGTH_USER_PIN || ctx.data.len() > MAX_PIN_LENGTH { warn!( @@ -568,13 +568,7 @@ fn reset_retry_conter_with_p3( } ctx.state - .persistent - .set_pin( - ctx.backend.client_mut(), - ctx.options.storage, - ctx.data, - Password::Pw1, - ) + .reset_user_code_with_pw3(ctx.backend.client_mut(), ctx.options.storage, ctx.data) .map_err(|_err| { error!("Failed to change PIN: {_err}"); Status::UnspecifiedNonpersistentExecutionError @@ -601,7 +595,7 @@ fn reset_retry_conter_with_code let res = ctx .state .check_pin(ctx.backend.client_mut(), old, Password::ResetCode); - match res { + let rc_key = match res { Err(Error::InvalidPin) => { return Err(Status::RemainingRetries( ctx.state @@ -613,8 +607,8 @@ fn reset_retry_conter_with_code error!("Failed to check reset code: {_err:?}"); return Err(Status::UnspecifiedNonpersistentExecutionError); } - Ok(_reset_kek) => {} - } + Ok(rc_key) => rc_key, + }; if new.len() > MAX_PIN_LENGTH || new.len() < MIN_LENGTH_USER_PIN { warn!("Attempt to set resetting code with invalid length"); @@ -622,17 +616,13 @@ fn reset_retry_conter_with_code } ctx.state - .persistent - .set_pin( - ctx.backend.client_mut(), - ctx.options.storage, - new, - Password::Pw1, - ) + .reset_user_code_with_rc(ctx.backend.client_mut(), ctx.options.storage, new, rc_key) .map_err(|_err| { error!("Failed to change PIN: {_err:?}"); Status::UnspecifiedNonpersistentExecutionError - }) + })?; + syscall!(ctx.backend.client_mut().delete(rc_key)); + Ok(()) } // § 7.2.5 diff --git a/src/command/data.rs b/src/command/data.rs index c4df8004..b8277af2 100644 --- a/src/command/data.rs +++ b/src/command/data.rs @@ -6,10 +6,13 @@ use hex_literal::hex; use iso7816::Status; use trussed::{ syscall, try_syscall, - types::{KeySerialization, Mechanism}, + types::{KeyId, KeySerialization, Mechanism}, }; use trussed_auth::AuthClient; +const CHACHA_NONCE_SIZE: usize = 12; +const CHACHA_TAG_SIZE: usize = 16; + use crate::{ card::{Context, LoadedContext, Options}, command::{GetDataMode, Password, PutDataMode, Tag}, @@ -375,8 +378,12 @@ impl GetDataObject { Self::KdfDo => get_arbitrary_do(context, ArbitraryDO::KdfDo)?, Self::PrivateUse1 => get_arbitrary_do(context, ArbitraryDO::PrivateUse1)?, Self::PrivateUse2 => get_arbitrary_do(context, ArbitraryDO::PrivateUse2)?, - Self::PrivateUse3 => get_arbitrary_do(context, ArbitraryDO::PrivateUse3)?, - Self::PrivateUse4 => get_arbitrary_do(context, ArbitraryDO::PrivateUse4)?, + Self::PrivateUse3 => { + get_arbitrary_user_enc_do(context.load_state()?, ArbitraryDO::PrivateUse3)? + } + Self::PrivateUse4 => { + get_arbitrary_admin_enc_do(context.load_state()?, ArbitraryDO::PrivateUse4)? + } Self::CardHolderCertificate => cardholder_cert(context)?, Self::SecureMessagingCertificate => return Err(Status::SecureMessagingNotSupported), Self::CardHolderRelatedData @@ -811,6 +818,54 @@ fn get_arbitrary_do( ctx.reply.expand(&data) } +fn get_arbitrary_enc_do( + mut ctx: LoadedContext<'_, R, T>, + obj: ArbitraryDO, + key: KeyId, +) -> Result<(), Status> { + let data = obj + .load(ctx.backend.client_mut(), ctx.options.storage) + .map_err(|_| Status::UnspecifiedNonpersistentExecutionError)?; + if data.is_empty() { + return Ok(()); + } + let (data, tag) = data.split_at(data.len() - CHACHA_TAG_SIZE); + let (data, nonce) = data.split_at(data.len() - CHACHA_NONCE_SIZE); + let decrypted = syscall!(ctx.backend.client_mut().decrypt( + Mechanism::Chacha8Poly1305, + key, + data, + &[], + nonce, + tag + )) + .plaintext + .ok_or(Status::UnspecifiedNonpersistentExecutionError)?; + ctx.reply.expand(&decrypted) +} + +/// Get an arbitrary DO encrypted with the user key +fn get_arbitrary_user_enc_do( + ctx: LoadedContext<'_, R, T>, + obj: ArbitraryDO, +) -> Result<(), Status> { + let Some(k) = ctx.state.volatile.other_verified_kek() else { + return Err(Status::SecurityStatusNotSatisfied); + }; + get_arbitrary_enc_do(ctx, obj, k) +} + +/// Get an arbitrary DO encrypted with the admin key +fn get_arbitrary_admin_enc_do( + ctx: LoadedContext<'_, R, T>, + obj: ArbitraryDO, +) -> Result<(), Status> { + let Some(k) = ctx.state.volatile.admin_kek() else { + return Err(Status::SecurityStatusNotSatisfied); + }; + get_arbitrary_enc_do(ctx, obj, k) +} + // § 7.2.8 pub fn put_data( mut context: Context<'_, R, T>, @@ -889,7 +944,7 @@ enum_subset! { impl PutDataObject { fn write_perm(&self) -> PermissionRequirement { match self { - Self::PrivateUse2 | Self::PrivateUse4 => PermissionRequirement::User, + Self::PrivateUse1 | Self::PrivateUse3 => PermissionRequirement::User, _ => PermissionRequirement::Admin, } } @@ -901,8 +956,12 @@ impl PutDataObject { match self { Self::PrivateUse1 => put_arbitrary_do(ctx, ArbitraryDO::PrivateUse1)?, Self::PrivateUse2 => put_arbitrary_do(ctx, ArbitraryDO::PrivateUse2)?, - Self::PrivateUse3 => put_arbitrary_do(ctx, ArbitraryDO::PrivateUse3)?, - Self::PrivateUse4 => put_arbitrary_do(ctx, ArbitraryDO::PrivateUse4)?, + Self::PrivateUse3 => { + put_arbitrary_user_enc_do(ctx.load_state()?, ArbitraryDO::PrivateUse3)? + } + Self::PrivateUse4 => { + put_arbitrary_admin_enc_do(ctx.load_state()?, ArbitraryDO::PrivateUse4)? + } Self::LoginData => put_arbitrary_do(ctx, ArbitraryDO::LoginData)?, Self::ExtendedHeaderList => { super::private_key_template::put_private_key_template(ctx.load_state()?)? @@ -954,7 +1013,7 @@ fn put_cardholder_cert( const AES256_KEY_LEN: usize = 32; fn put_enc_dec_key( - ctx: LoadedContext<'_, R, T>, + mut ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { if ctx.data.len() != AES256_KEY_LEN { warn!( @@ -976,23 +1035,18 @@ fn put_enc_dec_key( })? .key; - let old_key = ctx - .state - .persistent - .set_aes_key_id(Some(new_key), ctx.backend.client_mut(), ctx.options.storage) + ctx.state + .set_aes_key(new_key, ctx.backend.client_mut(), ctx.options.storage) .map_err(|_err| { error!("Failed to set new key: {_err:?}"); Status::UnspecifiedNonpersistentExecutionError })?; - if let Some(old_key) = old_key { - syscall!(ctx.backend.client_mut().delete(old_key)); - } Ok(()) } fn put_resetting_code( - ctx: LoadedContext<'_, R, T>, + mut ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { if ctx.data.is_empty() { info!("Removing resetting code"); @@ -1014,13 +1068,7 @@ fn put_resetting_code( } ctx.state - .persistent - .set_pin( - ctx.backend.client_mut(), - ctx.options.storage, - ctx.data, - Password::ResetCode, - ) + .set_reset_code(ctx.backend.client_mut(), ctx.options.storage, ctx.data) .map_err(|_err| { error!("Failed to change resetting code: {_err}"); Status::UnspecifiedNonpersistentExecutionError @@ -1200,6 +1248,56 @@ fn put_alg_attributes_aut( .map_err(|_| Status::UnspecifiedNonpersistentExecutionError) } +fn put_arbitrary_admin_enc_do( + ctx: LoadedContext<'_, R, T>, + obj: ArbitraryDO, +) -> Result<(), Status> { + let Some(k) = ctx.state.volatile.admin_kek() else { + return Err(Status::SecurityStatusNotSatisfied); + }; + put_arbitrary_enc_do(ctx, obj, k) +} +fn put_arbitrary_user_enc_do( + ctx: LoadedContext<'_, R, T>, + obj: ArbitraryDO, +) -> Result<(), Status> { + let Some(k) =ctx.state.volatile.other_verified_kek() else { + return Err(Status::SecurityStatusNotSatisfied); + }; + put_arbitrary_enc_do(ctx, obj, k) +} + +fn put_arbitrary_enc_do( + ctx: LoadedContext<'_, R, T>, + obj: ArbitraryDO, + key: KeyId, +) -> Result<(), Status> { + if ctx.data.len() + CHACHA_NONCE_SIZE + CHACHA_TAG_SIZE > MAX_GENERIC_LENGTH { + return Err(Status::WrongLength); + } + let mut encrypted = syscall!(ctx.backend.client_mut().encrypt( + Mechanism::Chacha8Poly1305, + key, + ctx.data, + &[], + None + )); + encrypted + .ciphertext + .extend_from_slice(&encrypted.nonce) + .map_err(|_| Status::NotEnoughMemory)?; + encrypted + .ciphertext + .extend_from_slice(&encrypted.tag) + .map_err(|_| Status::NotEnoughMemory)?; + obj.save( + ctx.backend.client_mut(), + ctx.options.storage, + &encrypted.ciphertext, + ) + .map_err(|_| Status::UnspecifiedPersistentExecutionError) +} + fn put_arbitrary_do( ctx: Context<'_, R, T>, obj: ArbitraryDO, diff --git a/src/command/gen.rs b/src/command/gen.rs index fa3df7fc..f64d8257 100644 --- a/src/command/gen.rs +++ b/src/command/gen.rs @@ -3,14 +3,13 @@ use hex_literal::hex; use iso7816::Status; +use trussed::try_syscall; 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; use crate::types::*; -use crate::utils::InspectErr; const KEYGEN_DO_TAG: &[u8] = &hex!("7f49"); @@ -103,14 +102,14 @@ pub fn aut( #[cfg(feature = "rsa")] fn gen_rsa_key( - ctx: LoadedContext<'_, R, T>, + mut ctx: LoadedContext<'_, R, T>, key: KeyType, mechanism: Mechanism, ) -> Result<(), Status> { let client = ctx.backend.client_mut(); let key_id = try_syscall!(client.generate_key( mechanism, - StorageAttributes::new().set_persistence(ctx.options.storage) + StorageAttributes::default().set_persistence(Location::Volatile) )) .map_err(|_err| { error!("Failed to generate key: {_err:?}"); @@ -118,61 +117,64 @@ fn gen_rsa_key( })? .key; - if let Some((old_key, _)) = ctx - .state - .persistent - .set_key_id( + let pubkey = try_syscall!(client.derive_key( + mechanism, + key_id, + None, + StorageAttributes::default().set_persistence(ctx.options.storage) + )) + .map_err(|_err| { + warn!("Failed to derive_ke: {_err:?}"); + Status::UnspecifiedNonpersistentExecutionError + })? + .key; + ctx.state + .set_key( key, - Some((key_id, KeyOrigin::Generated)), + Some((key_id, (pubkey, KeyOrigin::Generated))), client, ctx.options.storage, ) - .map_err(|_| Status::UnspecifiedNonpersistentExecutionError)? - { - // Deletion is not a fatal error - try_syscall!(client.delete(old_key)) - .inspect_err_stable(|_err| { - error!("Failed to delete old key: {_err:?}"); - }) - .ok(); - } - read_rsa_key(ctx, key_id, mechanism) + .map_err(|_| Status::UnspecifiedNonpersistentExecutionError)?; + read_rsa_key(ctx, pubkey, mechanism) } fn gen_ec_key( - ctx: LoadedContext<'_, R, T>, + mut ctx: LoadedContext<'_, R, T>, key: KeyType, curve: CurveAlgo, ) -> Result<(), Status> { let client = ctx.backend.client_mut(); let key_id = try_syscall!(client.generate_key( curve.mechanism(), - StorageAttributes::new().set_persistence(ctx.options.storage) + StorageAttributes::default().set_persistence(Location::Volatile) )) .map_err(|_err| { error!("Failed to generate key: {_err:?}"); Status::UnspecifiedNonpersistentExecutionError })? .key; - if let Some((old_key, _)) = ctx - .state - .persistent - .set_key_id( + + let pubkey = try_syscall!(client.derive_key( + curve.mechanism(), + key_id, + None, + StorageAttributes::default().set_persistence(ctx.options.storage) + )) + .map_err(|_err| { + warn!("Failed to derive_ke: {_err:?}"); + Status::UnspecifiedNonpersistentExecutionError + })? + .key; + ctx.state + .set_key( key, - Some((key_id, KeyOrigin::Generated)), + Some((key_id, (pubkey, KeyOrigin::Generated))), client, ctx.options.storage, ) - .map_err(|_| Status::UnspecifiedNonpersistentExecutionError)? - { - // Deletion is not a fatal error - try_syscall!(client.delete(old_key)) - .inspect_err_stable(|_err| { - error!("Failed to delete old key: {_err:?}"); - }) - .ok(); - } - read_ec_key(ctx, key_id, curve) + .map_err(|_| Status::UnspecifiedNonpersistentExecutionError)?; + read_ec_key(ctx, pubkey, curve) } pub fn read_sign( @@ -181,7 +183,7 @@ pub fn read_sign( let key_id = ctx .state .persistent - .key_id(KeyType::Sign) + .public_key_id(KeyType::Sign) .ok_or(Status::KeyReferenceNotFound)?; let algo = ctx.state.persistent.sign_alg(); @@ -200,7 +202,7 @@ pub fn read_dec( let key_id = ctx .state .persistent - .key_id(KeyType::Dec) + .public_key_id(KeyType::Dec) .ok_or(Status::KeyReferenceNotFound)?; let algo = ctx.state.persistent.dec_alg(); @@ -225,7 +227,7 @@ pub fn read_aut( let key_id = ctx .state .persistent - .key_id(KeyType::Aut) + .public_key_id(KeyType::Aut) .ok_or(Status::KeyReferenceNotFound)?; let algo = ctx.state.persistent.aut_alg(); @@ -265,26 +267,17 @@ fn serialize_25519( fn read_ec_key( mut ctx: LoadedContext<'_, R, T>, - key_id: KeyId, + public_key: KeyId, curve: CurveAlgo, ) -> Result<(), Status> { let client = ctx.backend.client_mut(); - let public_key = syscall!(client.derive_key( - curve.mechanism(), - key_id, - None, - StorageAttributes::new().set_persistence(Location::Volatile) - )) - .key; let serialized = try_syscall!(client.serialize_key(curve.mechanism(), public_key, KeySerialization::Raw)) .map_err(|_err| { error!("Failed to serialize public key: {_err:?}"); - syscall!(client.delete(public_key)); Status::UnspecifiedNonpersistentExecutionError })? .serialized_key; - syscall!(client.delete(public_key)); ctx.reply.expand(KEYGEN_DO_TAG)?; let offset = ctx.reply.len(); serialize_pub(curve, ctx.lend(), &serialized)?; @@ -294,17 +287,10 @@ fn read_ec_key( #[cfg(feature = "rsa")] fn read_rsa_key( mut ctx: LoadedContext<'_, R, T>, - key_id: KeyId, + public_key: KeyId, mechanism: Mechanism, ) -> Result<(), Status> { let client = ctx.backend.client_mut(); - let public_key = syscall!(client.derive_key( - mechanism, - key_id, - None, - StorageAttributes::new().set_persistence(Location::Volatile) - )) - .key; ctx.reply.expand(KEYGEN_DO_TAG)?; let offset = ctx.reply.len(); @@ -312,14 +298,12 @@ fn read_rsa_key( try_syscall!(client.serialize_key(mechanism, public_key, KeySerialization::RsaParts)) .map_err(|_err| { error!("Failed to serialize public key N: {_err:?}"); - syscall!(client.delete(public_key)); Status::UnspecifiedNonpersistentExecutionError })? .serialized_key; let parsed_pubkey_data: RsaPublicParts = trussed::postcard_deserialize(&pubkey_data).map_err(|_err| { error!("Failed to deserialize public key"); - syscall!(client.delete(public_key)); Status::UnspecifiedNonpersistentExecutionError })?; ctx.reply.expand(&[0x81])?; @@ -332,7 +316,6 @@ fn read_rsa_key( ctx.reply.prepend_len(offset)?; - syscall!(client.delete(public_key)); Ok(()) } diff --git a/src/command/private_key_template.rs b/src/command/private_key_template.rs index 96ab559e..a977b8c3 100644 --- a/src/command/private_key_template.rs +++ b/src/command/private_key_template.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only use iso7816::Status; -use trussed::types::{KeyId, KeySerialization, Mechanism}; -use trussed::{syscall, try_syscall}; +use trussed::try_syscall; +use trussed::types::{KeyId, KeySerialization, Location, Mechanism, StorageAttributes}; use trussed_auth::AuthClient; use crate::card::LoadedContext; @@ -48,11 +48,9 @@ pub fn put_sign( SignatureAlgorithm::Rsa3072 => put_rsa(ctx.lend(), Mechanism::Rsa3072Pkcs1v15)?, SignatureAlgorithm::Rsa4096 => put_rsa(ctx.lend(), Mechanism::Rsa4096Pkcs1v15)?, } - .map(|key_id| (key_id, KeyOrigin::Imported)); - let old_key_id = ctx - .state - .persistent - .set_key_id( + .map(|(private_key, pubkey)| (private_key, (pubkey, KeyOrigin::Imported))); + ctx.state + .set_key( KeyType::Sign, key_id, ctx.backend.client_mut(), @@ -62,9 +60,6 @@ pub fn put_sign( error!("Failed to store new key: {_err:?}"); Status::UnspecifiedNonpersistentExecutionError })?; - if let Some((k, _)) = old_key_id { - syscall!(ctx.backend.client_mut().delete(k)); - } Ok(()) } @@ -79,11 +74,9 @@ pub fn put_dec( DecryptionAlgorithm::Rsa3072 => put_rsa(ctx.lend(), Mechanism::Rsa3072Pkcs1v15)?, DecryptionAlgorithm::Rsa4096 => put_rsa(ctx.lend(), Mechanism::Rsa4096Pkcs1v15)?, } - .map(|key_id| (key_id, KeyOrigin::Imported)); - let old_key_id = ctx - .state - .persistent - .set_key_id( + .map(|(private_key, pubkey)| (private_key, (pubkey, KeyOrigin::Imported))); + ctx.state + .set_key( KeyType::Dec, key_id, ctx.backend.client_mut(), @@ -93,9 +86,6 @@ pub fn put_dec( error!("Failed to store new key: {_err:?}"); Status::UnspecifiedNonpersistentExecutionError })?; - if let Some((k, _)) = old_key_id { - syscall!(ctx.backend.client_mut().delete(k)); - } Ok(()) } @@ -110,11 +100,9 @@ pub fn put_aut( AuthenticationAlgorithm::Rsa3072 => put_rsa(ctx.lend(), Mechanism::Rsa3072Pkcs1v15)?, AuthenticationAlgorithm::Rsa4096 => put_rsa(ctx.lend(), Mechanism::Rsa4096Pkcs1v15)?, } - .map(|key_id| (key_id, KeyOrigin::Imported)); - let old_key_id = ctx - .state - .persistent - .set_key_id( + .map(|(private_key, public_key)| (private_key, (public_key, KeyOrigin::Imported))); + ctx.state + .set_key( KeyType::Aut, key_id, ctx.backend.client_mut(), @@ -124,16 +112,13 @@ pub fn put_aut( error!("Failed to store new key: {_err:?}"); Status::UnspecifiedNonpersistentExecutionError })?; - if let Some((k, _)) = old_key_id { - syscall!(ctx.backend.client_mut().delete(k)); - } Ok(()) } fn put_ec( ctx: LoadedContext<'_, R, T>, curve: CurveAlgo, -) -> Result, Status> { +) -> Result, Status> { debug!("Importing key for algo {curve:?}"); let private_key_data = get_do( &[PRIVATE_KEY_TEMPLATE_DO, CONCATENATION_KEY_DATA_DO], @@ -165,7 +150,7 @@ fn put_ec( let key = try_syscall!(ctx.backend.client_mut().unsafe_inject_key( curve.mechanism(), message, - ctx.options.storage, + Location::Volatile, KeySerialization::Raw )) .map_err(|_err| { @@ -173,7 +158,19 @@ fn put_ec( Status::UnspecifiedNonpersistentExecutionError })? .key; - Ok(Some(key)) + + let pubkey = try_syscall!(ctx.backend.client_mut().derive_key( + curve.mechanism(), + key, + None, + StorageAttributes::default().set_persistence(ctx.options.storage) + )) + .map_err(|_err| { + warn!("Failed to derive_ke: {_err:?}"); + Status::UnspecifiedNonpersistentExecutionError + })? + .key; + Ok(Some((key, pubkey))) } #[cfg(feature = "rsa")] @@ -210,7 +207,7 @@ fn parse_rsa_template(data: &[u8]) -> Option { fn put_rsa( ctx: LoadedContext<'_, R, T>, mechanism: Mechanism, -) -> Result, Status> { +) -> Result, Status> { use trussed::{postcard_serialize_bytes, types::SerializedKey}; let key_data = parse_rsa_template(ctx.data).ok_or_else(|| { @@ -225,7 +222,7 @@ fn put_rsa( let key = try_syscall!(ctx.backend.client_mut().unsafe_inject_key( mechanism, &key_message, - ctx.options.storage, + Location::Volatile, KeySerialization::RsaParts )) .map_err(|_err| { @@ -233,13 +230,25 @@ fn put_rsa( Status::UnspecifiedNonpersistentExecutionError })? .key; - Ok(Some(key)) + + let pubkey = try_syscall!(ctx.backend.client_mut().derive_key( + mechanism, + key, + None, + StorageAttributes::default().set_persistence(ctx.options.storage) + )) + .map_err(|_err| { + warn!("Failed to derive_ke: {_err:?}"); + Status::UnspecifiedNonpersistentExecutionError + })? + .key; + Ok(Some((key, pubkey))) } #[cfg(not(feature = "rsa"))] fn put_rsa( _ctx: LoadedContext<'_, R, T>, _mechanism: Mechanism, -) -> Result, Status> { +) -> Result, Status> { Err(Status::FunctionNotSupported) } diff --git a/src/command/pso.rs b/src/command/pso.rs index c64a9942..ce35b476 100644 --- a/src/command/pso.rs +++ b/src/command/pso.rs @@ -43,39 +43,37 @@ fn prompt_uif( 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() { - warn!("Attempt to sign without PW1 verified"); - return Err(Status::SecurityStatusNotSatisfied); - } + let key_id = + ctx.state + .volatile + .key_id(ctx.backend.client_mut(), KeyType::Sign, ctx.options.storage)?; check_uif(ctx.lend(), KeyType::Sign)?; - if !ctx.state.persistent.pw1_valid_multiple() { - ctx.state.volatile.clear_sign(ctx.backend.client_mut()) - } - ctx.state + let sign_result = ctx + .state .persistent .increment_sign_count(ctx.backend.client_mut(), ctx.options.storage) .map_err(|_err| { error!("Failed to increment sign count"); Status::UnspecifiedPersistentExecutionError - })?; - - match ctx.state.persistent.sign_alg() { - SignatureAlgorithm::Ed255 => sign_ec(ctx, key_id, Mechanism::Ed255), - SignatureAlgorithm::EcDsaP256 => { - if ctx.data.len() != 32 { - return Err(Status::ConditionsOfUseNotSatisfied); + }) + .and_then(|_| match ctx.state.persistent.sign_alg() { + SignatureAlgorithm::Ed255 => sign_ec(ctx.lend(), key_id, Mechanism::Ed255), + SignatureAlgorithm::EcDsaP256 => { + if ctx.data.len() != 32 { + return Err(Status::ConditionsOfUseNotSatisfied); + } + sign_ec(ctx.lend(), key_id, Mechanism::P256Prehashed) } - sign_ec(ctx, key_id, Mechanism::P256Prehashed) - } - SignatureAlgorithm::Rsa2048 => sign_rsa(ctx, key_id, Mechanism::Rsa2048Pkcs1v15), - SignatureAlgorithm::Rsa3072 => sign_rsa(ctx, key_id, Mechanism::Rsa3072Pkcs1v15), - SignatureAlgorithm::Rsa4096 => sign_rsa(ctx, key_id, Mechanism::Rsa4096Pkcs1v15), + SignatureAlgorithm::Rsa2048 => sign_rsa(ctx.lend(), key_id, Mechanism::Rsa2048Pkcs1v15), + SignatureAlgorithm::Rsa3072 => sign_rsa(ctx.lend(), key_id, Mechanism::Rsa3072Pkcs1v15), + SignatureAlgorithm::Rsa4096 => sign_rsa(ctx.lend(), key_id, Mechanism::Rsa4096Pkcs1v15), + }); + + if !ctx.state.persistent.pw1_valid_multiple() { + ctx.state.volatile.clear_sign(ctx.backend.client_mut()) } + sign_result } fn sign_ec( @@ -160,10 +158,9 @@ fn int_aut_key_mecha_uif( } Ok(( - ctx.state.persistent.key_id(key_type).ok_or_else(|| { - warn!("Attempt to INTERNAL AUTHENTICATE without a key set"); - Status::KeyReferenceNotFound - })?, + ctx.state + .volatile + .key_id(ctx.backend.client_mut(), key_type, ctx.options.storage)?, mechanism, ctx.state.persistent.uif(key_type).is_enabled(), key_kind, @@ -221,10 +218,9 @@ fn decipher_key_mecha_uif( }; Ok(( - ctx.state.persistent.key_id(key_type).ok_or_else(|| { - warn!("Attempt to decrypt without a key set"); - Status::KeyReferenceNotFound - })?, + ctx.state + .volatile + .key_id(ctx.backend.client_mut(), key_type, ctx.options.storage)?, mechanism, ctx.state.persistent.uif(key_type).is_enabled(), key_kind, @@ -367,10 +363,14 @@ fn decrypt_ec( fn decipher_aes( mut ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { - let key_id = ctx.state.persistent.aes_key().ok_or_else(|| { - warn!("Attempt to decipher with AES and no key set"); - Status::ConditionsOfUseNotSatisfied - })?; + let key_id = ctx + .state + .volatile + .aes_key_id(ctx.backend.client_mut(), ctx.options.storage) + .map_err(|_err| { + warn!("Failed to load aes key: {:?}", _err); + Status::ConditionsOfUseNotSatisfied + })?; if (ctx.data.len() - 1) % 16 != 0 { warn!("Attempt to decipher with AES with length not a multiple of block size"); @@ -396,15 +396,14 @@ fn decipher_aes( pub fn encipher( mut ctx: LoadedContext<'_, R, T>, ) -> Result<(), Status> { - if !ctx.state.volatile.other_verified() { - warn!("Attempt to encipher without PW1 verified"); - return Err(Status::SecurityStatusNotSatisfied); - } - - let key_id = ctx.state.persistent.aes_key().ok_or_else(|| { - warn!("Attempt to decipher with AES and no key set"); - Status::ConditionsOfUseNotSatisfied - })?; + let key_id = ctx + .state + .volatile + .aes_key_id(ctx.backend.client_mut(), ctx.options.storage) + .map_err(|_err| { + warn!("Failed to load aes key: {:?}", _err); + Status::ConditionsOfUseNotSatisfied + })?; if ctx.data.len() % 16 != 0 { warn!("Attempt to encipher with AES with length not a multiple of block size"); diff --git a/src/error.rs b/src/error.rs index cc81924d..4f55d63e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -8,6 +8,7 @@ pub enum Error { InvalidPin, BadRequest, UserInteraction, + Internal, } impl core::fmt::Display for Error { @@ -18,6 +19,7 @@ impl core::fmt::Display for Error { Error::InvalidPin => "Failed PIN authentication", Error::BadRequest => "Request data invalid", Error::UserInteraction => "Failed to get user presence", + Error::Internal => "Internal error", }; f.write_str(to_write) } diff --git a/src/state.rs b/src/state.rs index c1cfbf97..82e573c6 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,7 +1,7 @@ // Copyright (C) 2022 Nitrokey GmbH // SPDX-License-Identifier: LGPL-3.0-only -use core::mem::{swap, take}; +use core::mem::take; use heapless_bytes::Bytes; use hex_literal::hex; @@ -11,7 +11,7 @@ use serde_repr::{Deserialize_repr, Serialize_repr}; use trussed::api::reply::Metadata; use trussed::config::MAX_MESSAGE_LENGTH; -use trussed::types::{KeyId, Location, PathBuf}; +use trussed::types::{KeyId, Location, Mechanism, PathBuf, StorageAttributes}; use trussed::{syscall, try_syscall}; use trussed_auth::AuthClient; @@ -36,6 +36,11 @@ pub const MAX_GENERIC_LENGTH: usize = MAX_MESSAGE_LENGTH; /// Big endian encoding of [MAX_GENERIC_LENGTH](MAX_GENERIC_LENGTH) pub const MAX_GENERIC_LENGTH_BE: [u8; 2] = (MAX_GENERIC_LENGTH as u16).to_be_bytes(); +pub const SIGNING_KEY_PATH: &str = "signing_key.bin"; +pub const DEC_KEY_PATH: &str = "conf_key.bin"; +pub const AUTH_KEY_PATH: &str = "auth_key.bin"; +pub const AES_KEY_PATH: &str = "aes_key.bin"; + macro_rules! enum_u8 { ( $(#[$outer:meta])* @@ -299,6 +304,7 @@ impl<'a> LoadedState<'a> { .set_pin_len(client, storage, pin.len(), pin_id)?; Ok(()) } + pub fn check_pin( &mut self, client: &mut T, @@ -314,6 +320,191 @@ impl<'a> LoadedState<'a> { .result .ok_or(Error::InvalidPin) } + + fn get_user_key( + &mut self, + client: &mut T, + storage: Location, + ) -> Result { + let admin_key = self.volatile.admin_kek().ok_or(Error::InvalidPin)?; + let user_wrapped = + syscall!(client.read_file(storage, PathBuf::from(ADMIN_USER_KEY_BACKUP))).data; + let user_key = try_syscall!(client.unwrap_key( + Mechanism::Chacha8Poly1305, + admin_key, + user_wrapped, + ADMIN_USER_KEY_BACKUP.as_bytes(), + StorageAttributes::new().set_persistence(Location::Volatile) + )) + .map_err(|_err| { + error!("Failed to unwrap backup user key: {:?}", _err); + Error::Internal + })? + .key + .ok_or_else(|| { + error!("Failed to unwrap backup user key"); + Error::Internal + })?; + Ok(user_key) + } + + fn get_user_key_from_rc( + &mut self, + client: &mut T, + storage: Location, + rc_key: KeyId, + ) -> Result { + let user_wrapped = + syscall!(client.read_file(storage, PathBuf::from(RC_USER_KEY_BACKUP))).data; + let user_key = try_syscall!(client.unwrap_key( + Mechanism::Chacha8Poly1305, + rc_key, + user_wrapped, + RC_USER_KEY_BACKUP.as_bytes(), + StorageAttributes::new().set_persistence(Location::Volatile) + )) + .map_err(|_err| { + error!("Failed to unwrap backup key from rc: {:?}", _err); + Error::Internal + })? + .key + .ok_or_else(|| { + error!("Failed to unwrap backup key from rc"); + Error::Internal + })?; + Ok(user_key) + } + + pub fn reset_user_code_with_pw3( + &mut self, + client: &mut T, + storage: Location, + new_value: &[u8], + ) -> Result<(), Error> { + let user_key = self.get_user_key(client, storage)?; + let new_pin = Bytes::from_slice(new_value).map_err(|_| Error::InvalidPin)?; + syscall!(client.set_pin_with_key(Password::Pw1, new_pin, Some(3), user_key)); + syscall!(client.delete(user_key)); + Ok(()) + } + + pub fn reset_user_code_with_rc( + &mut self, + client: &mut T, + storage: Location, + new_value: &[u8], + rc_key: KeyId, + ) -> Result<(), Error> { + let user_key = self.get_user_key_from_rc(client, storage, rc_key)?; + let new_pin = Bytes::from_slice(new_value).map_err(|_| Error::InvalidPin)?; + syscall!(client.set_pin_with_key(Password::Pw1, new_pin, Some(3), user_key)); + syscall!(client.delete(user_key)); + Ok(()) + } + + pub fn set_reset_code( + &mut self, + client: &mut T, + storage: Location, + new_value: &[u8], + ) -> Result<(), Error> { + let new_pin = Bytes::from_slice(new_value).map_err(|_| Error::InvalidPin)?; + syscall!(client.set_pin(Password::ResetCode, new_pin.clone(), Some(3), true)); + self.persistent + .set_pin_len(client, storage, new_pin.len(), Password::ResetCode)?; + #[allow(clippy::expect_used)] + let rc_key = syscall!(client.get_pin_key(Password::ResetCode, new_pin)) + .result + .expect("New pin should not fail"); + + let user_key = self.get_user_key(client, storage)?; + let wrapped_user_key = syscall!(client.wrap_key( + Mechanism::Chacha8Poly1305, + rc_key, + user_key, + RC_USER_KEY_BACKUP.as_bytes() + )) + .wrapped_key; + syscall!(client.write_file( + storage, + PathBuf::from(RC_USER_KEY_BACKUP), + wrapped_user_key, + None + )); + syscall!(client.delete(user_key)); + syscall!(client.delete(rc_key)); + + Ok(()) + } + + pub fn set_aes_key( + &mut self, + new: KeyId, + client: &mut T, + storage: Location, + ) -> Result<(), Error> { + self.volatile.user.0.clear_aes_cached(client); + let user_kek = self.get_user_key(client, storage)?; + syscall!(client.wrap_key_to_file( + Mechanism::Chacha8Poly1305, + user_kek, + new, + PathBuf::from(AES_KEY_PATH), + storage, + AES_KEY_PATH.as_bytes() + )); + syscall!(client.delete(new)); + Ok(()) + } + + /// New contains (private key, (public key, KeyOrigin)) + pub fn set_key( + &mut self, + ty: KeyType, + new: Option<(KeyId, (KeyId, KeyOrigin))>, + client: &mut T, + storage: Location, + ) -> Result<(), Error> { + let path_str = ty.path(); + let origin = self.persistent.key_data_mut(ty); + let path = PathBuf::from(path_str); + + let (new_id, new_origin) = match (new, origin.is_some()) { + (None, true) => { + *origin = None; + self.persistent.save(client, storage)?; + try_syscall!(client.remove_file(storage, path)).ok(); + return Ok(()); + } + (None, false) => return Ok(()), + + // In this case we want to avoid storing old information with a new key, or vice-versa + (Some((new_id, new_origin)), true) => { + *origin = None; + self.persistent.save(client, storage)?; + (new_id, new_origin) + } + (Some((new_id, new_origin)), false) => (new_id, new_origin), + }; + + self.volatile.user.0.clear_cached(client, ty); + + let user_kek = self.get_user_key(client, storage)?; + + syscall!(client.wrap_key_to_file( + Mechanism::Chacha8Poly1305, + user_kek, + new_id, + path, + storage, + path_str.as_bytes() + )); + syscall!(client.delete(new_id)); + syscall!(client.delete(user_kek)); + *self.persistent.key_data_mut(ty) = Some(new_origin); + self.persistent.save(client, storage)?; + Ok(()) + } } enum_u8! { @@ -340,10 +531,12 @@ pub struct Persistent { user_pin_len: u8, admin_pin_len: u8, reset_code_pin_len: Option, + /// (public_key, origin) signing_key: Option<(KeyId, KeyOrigin)>, + /// (public_key, origin) confidentiality_key: Option<(KeyId, KeyOrigin)>, + /// (public_key, origin) aut_key: Option<(KeyId, KeyOrigin)>, - aes_key: Option, sign_alg: SignatureAlgorithm, dec_alg: DecryptionAlgorithm, aut_alg: AuthenticationAlgorithm, @@ -360,8 +553,14 @@ pub struct Persistent { uif_aut: Uif, } +/// User pin key wrapped by the resetting code key +const RC_USER_KEY_BACKUP: &str = "rc-user-pin-key.bin"; +/// User pin key wrapped by the admin key +const ADMIN_USER_KEY_BACKUP: &str = "admin-user-pin-key.bin"; + impl Persistent { const FILENAME: &'static str = "persistent-state.cbor"; + // § 4.3 const MAX_RETRIES: u8 = 3; @@ -379,7 +578,6 @@ impl Persistent { signing_key: None, confidentiality_key: None, aut_key: None, - aes_key: None, sign_alg: SignatureAlgorithm::default(), dec_alg: DecryptionAlgorithm::default(), aut_alg: AuthenticationAlgorithm::default(), @@ -392,27 +590,67 @@ impl Persistent { } } + pub fn public_key_id(&self, ty: KeyType) -> Option { + match ty { + KeyType::Sign => self.signing_key.map(|(pubkey, _)| pubkey), + KeyType::Aut => self.aut_key.map(|(pubkey, _)| pubkey), + KeyType::Dec => self.confidentiality_key.map(|(pubkey, _)| pubkey), + } + } + fn path() -> PathBuf { PathBuf::from(Self::FILENAME) } - fn init_pins(client: &mut T) -> Result<(), Error> { + fn key_data_mut(&mut self, ty: KeyType) -> &mut Option<(KeyId, KeyOrigin)> { + match ty { + KeyType::Sign => &mut self.signing_key, + KeyType::Aut => &mut self.aut_key, + KeyType::Dec => &mut self.confidentiality_key, + } + } + + fn init_pins( + client: &mut T, + location: Location, + ) -> 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, + default_user_pin.clone(), Some(Self::MAX_RETRIES), true, )); syscall!(client.set_pin( Password::Pw3, - default_admin_pin, + default_admin_pin.clone(), Some(Self::MAX_RETRIES), true, )); + #[allow(clippy::expect_used)] + let user_key = syscall!(client.get_pin_key(Password::Pw1, default_user_pin)) + .result + .expect("Default pin should work after initialization"); + #[allow(clippy::expect_used)] + let admin_key = syscall!(client.get_pin_key(Password::Pw3, default_admin_pin)) + .result + .expect("Default pin should work after initialization"); + + let backup = syscall!(client.wrap_key( + Mechanism::Chacha8Poly1305, + admin_key, + user_key, + ADMIN_USER_KEY_BACKUP.as_bytes() + )) + .wrapped_key; + syscall!(client.write_file(location, PathBuf::from(ADMIN_USER_KEY_BACKUP), backup, None)); + + // Clean up memory + syscall!(client.delete(user_key)); + syscall!(client.delete(admin_key)); Ok(()) } pub fn load( @@ -425,7 +663,7 @@ impl Persistent { Error::Loading }) } else { - Self::init_pins(client)?; + Self::init_pins(client, storage)?; Ok(Self::default()) } } @@ -474,18 +712,6 @@ impl Persistent { self.reset_code_pin_len.map(Into::into) } - pub fn set_pin( - &mut self, - client: &mut T, - storage: Location, - new_value: &[u8], - password: Password, - ) -> Result<(), Error> { - 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 change_pin( &mut self, client: &mut T, @@ -721,57 +947,12 @@ impl Persistent { self.save(client, storage) } - pub fn key_id(&self, ty: KeyType) -> Option { - match ty { - KeyType::Sign => self.signing_key, - KeyType::Dec => self.confidentiality_key, - KeyType::Aut => self.aut_key, - } - .map(|(key_id, _)| key_id) - } - pub fn key_origin(&self, ty: KeyType) -> Option { match ty { - KeyType::Sign => self.signing_key, - KeyType::Dec => self.confidentiality_key, - KeyType::Aut => self.aut_key, + KeyType::Sign => self.signing_key.map(|(_pubkey, origin)| origin), + KeyType::Dec => self.confidentiality_key.map(|(_pubkey, origin)| origin), + KeyType::Aut => self.aut_key.map(|(_pubkey, origin)| origin), } - .map(|(_, origin)| origin) - } - - /// If the key id was already set, return the old key_id - pub fn set_key_id( - &mut self, - ty: KeyType, - mut new: Option<(KeyId, KeyOrigin)>, - client: &mut impl trussed::Client, - storage: Location, - ) -> Result, Error> { - match ty { - KeyType::Sign => { - self.sign_count = 0; - swap(&mut self.signing_key, &mut new) - } - KeyType::Dec => swap(&mut self.confidentiality_key, &mut new), - KeyType::Aut => swap(&mut self.aut_key, &mut new), - } - self.save(client, storage)?; - Ok(new) - } - - pub fn aes_key(&self) -> &Option { - &self.aes_key - } - - pub fn set_aes_key_id( - &mut self, - mut new: Option, - client: &mut impl trussed::Client, - storage: Location, - ) -> Result, Error> { - swap(&mut self.aes_key, &mut new); - self.save(client, storage)?; - Ok(new) } pub fn delete_key( @@ -780,20 +961,25 @@ impl Persistent { client: &mut impl trussed::Client, storage: Location, ) -> Result<(), Error> { - let key = match ty { - KeyType::Sign => self.signing_key.take(), - KeyType::Dec => self.confidentiality_key.take(), - KeyType::Aut => self.aut_key.take(), + let (key, path) = match ty { + KeyType::Sign => (self.signing_key.take(), SIGNING_KEY_PATH), + KeyType::Dec => (self.confidentiality_key.take(), DEC_KEY_PATH), + KeyType::Aut => (self.aut_key.take(), AUTH_KEY_PATH), }; - if let Some((key_id, _)) = key { + if let Some((pubkey, _)) = key { + self.fingerprints.key_part_mut(ty).copy_from_slice(&[0; 20]); + self.keygen_dates.key_part_mut(ty).copy_from_slice(&[0; 4]); self.save(client, storage)?; - try_syscall!(client.delete(key_id)).map_err(|_err| { + try_syscall!(client.remove_file(storage, PathBuf::from(path))).map_err(|_err| { error!("Failed to delete key {_err:?}"); Error::Saving })?; - self.fingerprints.key_part_mut(ty).copy_from_slice(&[0; 20]); - self.keygen_dates.key_part_mut(ty).copy_from_slice(&[0; 4]); + try_syscall!(client.delete(pubkey)) + .map_err(|_err| { + error!("Failed to delete public key: {:?} (ignored)", _err); + }) + .ok(); } Ok(()) } @@ -821,19 +1007,64 @@ impl Default for KeyRefs { } } +/// Since keys are stored encrypted, cache them to not have to decrypt them again +#[derive(Debug, Default, Clone, PartialEq, Eq)] +struct UserKeys { + sign: Option, + dec: Option, + aut: Option, + aes: Option, +} + +impl UserKeys { + // Replace self with an empty cache to avoid the drop check + fn take(&mut self) -> Self { + take(self) + } + + fn clear(&mut self, client: &mut impl trussed::Client) { + for k in [&mut self.sign, &mut self.dec, &mut self.aut, &mut self.aes] + .into_iter() + .flat_map(Option::take) + { + syscall!(client.delete(k)); + } + } +} + +/// Check for memory leaks +impl Drop for UserKeys { + fn drop(&mut self) { + if matches!((self.sign, self.dec, self.aut), (None, None, None)) { + return; + } + + #[cfg(all(debug_assertions, feature = "std"))] + if !std::thread::panicking() { + panic!("User dropped with keys still in volatile storage {self:?}"); + } + + error!( + "Error: User dropped with keys still in volatile storage: {:?}", + self + ); + } +} + #[derive(Debug, Default, Clone, PartialEq, Eq)] enum UserVerifiedInner { #[default] None, - Other(KeyId), - Sign(KeyId), + Other(KeyId, UserKeys), + Sign(KeyId, UserKeys), #[allow(unused)] - OtherAndSign(KeyId), + OtherAndSign(KeyId, UserKeys), } #[derive(Debug, Default, Clone, PartialEq, Eq)] struct UserVerified(UserVerifiedInner); +/// Check for memory leaks impl Drop for UserVerified { fn drop(&mut self) { if self.0.user_kek().is_none() { @@ -862,10 +1093,10 @@ impl UserVerified { impl UserVerifiedInner { fn verify_sign(&mut self, k: KeyId) { match self { - Self::None => *self = Self::Sign(k), - Self::Other(old_k) => { + Self::None => *self = Self::Sign(k, UserKeys::default()), + Self::Other(old_k, cache) => { debug_assert_eq!(*old_k, k); - *self = Self::OtherAndSign(k) + *self = Self::OtherAndSign(k, cache.take()) } _ => {} } @@ -873,45 +1104,96 @@ impl UserVerifiedInner { fn verify_other(&mut self, k: KeyId) { match self { - Self::None => *self = Self::Other(k), - Self::Sign(old_k) => { + Self::None => *self = Self::Other(k, UserKeys::default()), + Self::Sign(old_k, cache) => { debug_assert_eq!(*old_k, k); - *self = Self::OtherAndSign(k) + *self = Self::OtherAndSign(k, cache.take()) } _ => {} } } fn sign_verified(&self) -> bool { - matches!(self, Self::Sign(_) | Self::OtherAndSign(_)) + matches!(self, Self::Sign(_, _) | Self::OtherAndSign(_, _)) } fn other_verified(&self) -> bool { - matches!(self, Self::Other(_) | Self::OtherAndSign(_)) + matches!(self, Self::Other(_, _) | Self::OtherAndSign(_, _)) + } + fn other_verified_kek(&self) -> Option { + match self { + Self::Other(k, _) | Self::OtherAndSign(k, _) => Some(*k), + _ => None, + } } fn user_kek(&self) -> Option { match self { - Self::Other(k) | Self::Sign(k) | Self::OtherAndSign(k) => Some(*k), + 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() { + match self.take() { + Self::Other(k, mut cache) + | Self::Sign(k, mut cache) + | Self::OtherAndSign(k, mut cache) => { + syscall!(client.delete(k)); + cache.clear(client); + } + _ => (), + } + } + + // Replace self with an empty cache to avoid the drop check + fn take(&mut self) -> Self { + take(self) + } + + fn cache_mut(&mut self) -> Option<&mut UserKeys> { + match self { + Self::None => None, + Self::Other(_, cache) => Some(cache), + Self::Sign(_, cache) => Some(cache), + Self::OtherAndSign(_, cache) => Some(cache), + } + } + + fn clear_cached(&mut self, client: &mut impl trussed::Client, ty: KeyType) { + let Some(cache) = self.cache_mut() else { + return; + }; + + let key = match ty { + KeyType::Sign => cache.sign.take(), + KeyType::Dec => cache.dec.take(), + KeyType::Aut => cache.aut.take(), + }; + + if let Some(k) = key { + syscall!(client.delete(k)); + } + } + + fn clear_aes_cached(&mut self, client: &mut impl trussed::Client) { + let Some(cache) = self.cache_mut() else { + return; + }; + + if let Some(k) = cache.aes { 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), + Self::Sign(_k, _cache) => self.clear(client), + Self::OtherAndSign(k, cache) => *self = Self::Other(*k, cache.take()), _ => {} }; } 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), + Self::Other(_k, _cache) => self.clear(client), + Self::OtherAndSign(k, cache) => *self = Self::Sign(*k, cache.take()), _ => {} }; } @@ -965,12 +1247,98 @@ impl Volatile { syscall!(client.delete(k)); } } + + fn load_or_get_key( + client: &mut impl trussed::Client, + user_kek: KeyId, + opt_key: &mut Option, + path: &'static str, + storage: Location, + ) -> Result { + if let Some(k) = opt_key { + return Ok(*k); + } + + let unwrapped_key = try_syscall!(client.unwrap_key_from_file( + Mechanism::Chacha8Poly1305, + user_kek, + PathBuf::from(path), + storage, + Location::Volatile, + path.as_bytes() + )) + .map_err(|_err| { + error!("Failed to load key: {:?}", _err); + Status::UnspecifiedPersistentExecutionError + })? + .key + .ok_or_else(|| { + error!("Failed to decrypt key"); + Status::UnspecifiedPersistentExecutionError + })?; + *opt_key = Some(unwrapped_key); + + Ok(unwrapped_key) + } + + pub fn aes_key_id( + &mut self, + client: &mut impl trussed::Client, + storage: Location, + ) -> Result { + match &mut self.user.0 { + UserVerifiedInner::None | UserVerifiedInner::Sign(_, _) => { + Err(Status::ConditionsOfUseNotSatisfied) + } + UserVerifiedInner::Other(user_kek, cache) + | UserVerifiedInner::OtherAndSign(user_kek, cache) => { + Self::load_or_get_key(client, *user_kek, &mut cache.aes, AES_KEY_PATH, storage) + } + } + } + /// Returns the requested key + pub fn key_id( + &mut self, + client: &mut impl trussed::Client, + key: KeyType, + storage: Location, + ) -> Result { + match (&mut self.user.0, key) { + (UserVerifiedInner::None, _) => Err(Status::SecurityStatusNotSatisfied), + ( + UserVerifiedInner::Sign(user_kek, cache) + | UserVerifiedInner::OtherAndSign(user_kek, cache), + KeyType::Sign, + ) => Self::load_or_get_key( + client, + *user_kek, + &mut cache.sign, + SIGNING_KEY_PATH, + storage, + ), + ( + UserVerifiedInner::Other(user_kek, cache) + | UserVerifiedInner::OtherAndSign(user_kek, cache), + KeyType::Aut, + ) => Self::load_or_get_key(client, *user_kek, &mut cache.aut, AUTH_KEY_PATH, storage), + ( + UserVerifiedInner::Other(user_kek, cache) + | UserVerifiedInner::OtherAndSign(user_kek, cache), + KeyType::Dec, + ) => Self::load_or_get_key(client, *user_kek, &mut cache.dec, DEC_KEY_PATH, storage), + _ => Err(Status::SecurityStatusNotSatisfied), + } + } + pub fn sign_verified(&self) -> bool { self.user.0.sign_verified() } pub fn other_verified(&self) -> bool { self.user.0.other_verified() } + pub fn other_verified_kek(&self) -> Option { + self.user.0.other_verified_kek() + } pub fn user_kek(&self) -> Option { self.user.0.user_kek() } diff --git a/src/types.rs b/src/types.rs index 25d928ab..44dcbbfd 100644 --- a/src/types.rs +++ b/src/types.rs @@ -287,6 +287,14 @@ impl KeyType { Err(Status::IncorrectDataParameter) } } + + pub fn path(&self) -> &'static str { + match self { + KeyType::Sign => crate::state::SIGNING_KEY_PATH, + KeyType::Aut => crate::state::AUTH_KEY_PATH, + KeyType::Dec => crate::state::DEC_KEY_PATH, + } + } } #[derive(Clone, Debug, Eq, PartialEq, Copy, Deserialize_repr, Serialize_repr, Default)] diff --git a/tests/card/mod.rs b/tests/card/mod.rs index dfdee7d7..6707c994 100644 --- a/tests/card/mod.rs +++ b/tests/card/mod.rs @@ -45,6 +45,15 @@ impl Card { let tx = openpgp.transaction().expect("failed to create transaction"); f(tx) } + pub fn with_many_tx( + &mut self, + fs: impl IntoIterator)>, + ) { + for f in fs { + self.with_tx(f); + self.0.lock().unwrap().reset(); + } + } pub fn reset(&self) { self.0.lock().unwrap().reset(); @@ -139,6 +148,11 @@ 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 with_many_tx(fs: impl IntoIterator)>) { + with_card(move |mut card| card.with_many_tx(fs)) +} + #[cfg(not(feature = "dangerous-test-real-card"))] pub fn error_to_retries(err: Result<(), openpgp_card::Error>) -> Option { match err { diff --git a/tests/dos.rs b/tests/dos.rs index fd9de4ee..067b83ac 100644 --- a/tests/dos.rs +++ b/tests/dos.rs @@ -6,11 +6,14 @@ use hex_literal::hex; mod card; use test_log::test; -use card::with_tx_options; +use card::{with_many_tx, with_tx_options}; use opcard::Options; -use openpgp_card::card_do::{ApplicationIdentifier, HistoricalBytes, Lang, Sex, TouchPolicy}; +use openpgp_card::{ + card_do::{ApplicationIdentifier, HistoricalBytes, Lang, Sex, TouchPolicy}, + OpenPgpTransaction, +}; #[test] fn get_data() { @@ -131,3 +134,87 @@ fn get_data() { } }); } + +#[test] +fn arbitrary() { + with_many_tx([|mut tx: OpenPgpTransaction<'_>| { + assert_eq!(tx.private_use_do(1).unwrap(), b""); + assert_eq!(tx.private_use_do(2).unwrap(), b""); + assert!(tx.private_use_do(3).is_err()); + assert!(tx.private_use_do(4).is_err()); + tx.verify_pw3(b"12345678").unwrap(); + assert!(tx.private_use_do(3).is_err()); + assert_eq!(tx.private_use_do(4).unwrap(), b""); + tx.set_private_use_do(2, b"private use 2".to_vec()).unwrap(); + assert_eq!(tx.private_use_do(2).unwrap(), b"private use 2"); + tx.set_private_use_do(4, b"private use 4".to_vec()).unwrap(); + assert_eq!(tx.private_use_do(4).unwrap(), b"private use 4"); + + // Check that password change doesn't prevent reading + tx.change_pw3(b"12345678", b"new admin pin").unwrap(); + tx.verify_pw3(b"new admin pin").unwrap(); + assert_eq!(tx.private_use_do(2).unwrap(), b"private use 2"); + assert_eq!(tx.private_use_do(4).unwrap(), b"private use 4"); + }]); + with_many_tx([ + |mut tx: OpenPgpTransaction<'_>| { + assert_eq!(tx.private_use_do(1).unwrap(), b""); + assert_eq!(tx.private_use_do(2).unwrap(), b""); + assert!(tx.private_use_do(3).is_err()); + assert!(tx.private_use_do(4).is_err()); + tx.verify_pw1_user(b"123456").unwrap(); + assert_eq!(tx.private_use_do(3).unwrap(), b""); + assert!(tx.private_use_do(4).is_err()); + tx.set_private_use_do(1, b"private use 1".to_vec()).unwrap(); + assert_eq!(tx.private_use_do(1).unwrap(), b"private use 1"); + tx.set_private_use_do(3, b"private use 3".to_vec()).unwrap(); + assert_eq!(tx.private_use_do(3).unwrap(), b"private use 3"); + + // Check that password change doesn't prevent reading + tx.change_pw1(b"123456", b"new user pin").unwrap(); + tx.verify_pw1_user(b"new user pin").unwrap(); + assert_eq!(tx.private_use_do(1).unwrap(), b"private use 1"); + assert_eq!(tx.private_use_do(3).unwrap(), b"private use 3"); + + // Check that password reset code use doesn't prevent reading + + tx.verify_pw3(b"12345678").unwrap(); + tx.reset_retry_counter_pw1(b"pin from PW3", None).unwrap(); + tx.set_resetting_code(b"reseting code").unwrap(); + }, + |mut tx: OpenPgpTransaction<'_>| { + tx.verify_pw1_user(b"pin from PW3").unwrap(); + assert_eq!(tx.private_use_do(1).unwrap(), b"private use 1"); + assert_eq!(tx.private_use_do(3).unwrap(), b"private use 3"); + }, + |mut tx: OpenPgpTransaction<'_>| { + tx.reset_retry_counter_pw1(b"pin from RC", Some(b"reseting code")) + .unwrap(); + tx.verify_pw1_user(b"pin from RC").unwrap(); + assert_eq!(tx.private_use_do(1).unwrap(), b"private use 1"); + assert_eq!(tx.private_use_do(3).unwrap(), b"private use 3"); + + tx.change_pw3(b"12345678", b"changed admin pin").unwrap(); + tx.verify_pw3(b"changed admin pin").unwrap(); + tx.reset_retry_counter_pw1(b"pin from changed PW3", None) + .unwrap(); + tx.set_resetting_code(b"reseting code with changed PW3") + .unwrap(); + }, + |mut tx: OpenPgpTransaction<'_>| { + tx.verify_pw1_user(b"pin from changed PW3").unwrap(); + assert_eq!(tx.private_use_do(1).unwrap(), b"private use 1"); + assert_eq!(tx.private_use_do(3).unwrap(), b"private use 3"); + }, + |mut tx: OpenPgpTransaction<'_>| { + tx.reset_retry_counter_pw1( + b"pin from RC with changed PW3", + Some(b"reseting code with changed PW3"), + ) + .unwrap(); + tx.verify_pw1_user(b"pin from RC with changed PW3").unwrap(); + assert_eq!(tx.private_use_do(1).unwrap(), b"private use 1"); + assert_eq!(tx.private_use_do(3).unwrap(), b"private use 3"); + }, + ]); +}