From 09e07ccd98a1d927289f47810770a2d5d84e6630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Mon, 20 Mar 2023 11:05:12 +0100 Subject: [PATCH] Encrypt keys --- Cargo.toml | 2 +- src/command/data.rs | 14 +- src/command/gen.rs | 103 ++++----- src/command/private_key_template.rs | 75 +++--- src/command/pso.rs | 60 +++-- src/error.rs | 2 + src/state.rs | 344 ++++++++++++++++++++++------ src/types.rs | 8 + 8 files changed, 408 insertions(+), 200 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 695d2128..40261a3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,7 +85,7 @@ 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" , rev = "56848118cefa74eaa4f33e93d79930d7e66717ee" } +trussed = { git = "https://github.com/Nitrokey/trussed" , rev = "fc4758a0ee3b23b3f358e94dec461ace843eecc5" } 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" } diff --git a/src/command/data.rs b/src/command/data.rs index 4449d254..b12acb60 100644 --- a/src/command/data.rs +++ b/src/command/data.rs @@ -849,12 +849,9 @@ fn get_arbitrary_user_enc_do( ctx: LoadedContext<'_, R, T>, obj: ArbitraryDO, ) -> Result<(), Status> { - if !ctx.state.volatile.other_verified() { + let Some(k) = ctx.state.volatile.other_verified_kek() else { return Err(Status::SecurityStatusNotSatisfied); - } - // Unwrap cannnot fail becaus of above check - #[allow(clippy::unwrap_used)] - let k = ctx.state.volatile.user_kek().unwrap(); + }; get_arbitrary_enc_do(ctx, obj, k) } @@ -1269,12 +1266,9 @@ fn put_arbitrary_user_enc_do( ctx: LoadedContext<'_, R, T>, obj: ArbitraryDO, ) -> Result<(), Status> { - if !ctx.state.volatile.other_verified() { + let Some(k) =ctx.state.volatile.other_verified_kek() else { return Err(Status::SecurityStatusNotSatisfied); - } - // Unwrap cannnot fail becaus of above check - #[allow(clippy::unwrap_used)] - let k = ctx.state.volatile.user_kek().unwrap(); + }; put_arbitrary_enc_do(ctx, obj, k) } 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..f97c15c9 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, 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 5641da49..01da2be5 100644 --- a/src/state.rs +++ b/src/state.rs @@ -36,6 +36,10 @@ 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"; + macro_rules! enum_u8 { ( $(#[$outer:meta])* @@ -324,16 +328,22 @@ impl<'a> LoadedState<'a> { 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; - #[allow(clippy::expect_used)] - let user_key = syscall!(client.unwrap_key( + 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 - .expect("Key backup should not fail to unwrap"); + .ok_or_else(|| { + error!("Failed to unwrap backup user key"); + Error::Internal + })?; Ok(user_key) } @@ -345,16 +355,22 @@ impl<'a> LoadedState<'a> { ) -> Result { let user_wrapped = syscall!(client.read_file(storage, PathBuf::from(RC_USER_KEY_BACKUP))).data; - #[allow(clippy::expect_used)] - let user_key = syscall!(client.unwrap_key( + 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 - .expect("Key backup should not fail to unwrap"); + .ok_or_else(|| { + error!("Failed to unwrap backup key from rc"); + Error::Internal + })?; Ok(user_key) } @@ -419,6 +435,55 @@ impl<'a> LoadedState<'a> { 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_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! { @@ -445,8 +510,11 @@ 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, @@ -503,10 +571,26 @@ 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 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, @@ -844,42 +928,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 { @@ -903,20 +957,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(()) } @@ -944,19 +1003,63 @@ 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, +} + +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] + .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() { @@ -985,10 +1088,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()) } _ => {} } @@ -996,44 +1099,86 @@ 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) { - if let Some(k) = take(self).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_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()), _ => {} }; } @@ -1087,12 +1232,83 @@ 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_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) + } + + /// 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)]