diff --git a/Cargo.toml b/Cargo.toml index f2ee0f2..13f2b6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ serde_bytes = { version = "0.11.14", default-features = false } serde-indexed = "0.1.0" sha2 = { version = "0.10", default-features = false } trussed = "0.1" +trussed-fs-info = "0.1.0" trussed-hkdf = { version = "0.2.0" } trussed-chunked = { version = "0.1.0", optional = true } @@ -65,7 +66,7 @@ p256 = { version = "0.13.2", features = ["ecdh"] } rand = "0.8.4" sha2 = "0.10" trussed = { version = "0.1", features = ["virt"] } -trussed-staging = { version = "0.3.0", features = ["chunked", "hkdf", "virt"] } +trussed-staging = { version = "0.3.0", features = ["chunked", "hkdf", "virt", "fs-info"] } trussed-usbip = { version = "0.0.1", default-features = false, features = ["ctaphid"] } usbd-ctaphid = "0.1.0" x509-parser = "0.16.0" @@ -78,10 +79,11 @@ cbor-smol = { git = "https://github.com/sosthene-nitrokey/cbor-smol.git", rev = ctap-types = { git = "https://github.com/trussed-dev/ctap-types.git", rev = "72eb68b61e3f14957c5ab89bd22f776ac860eb62" } ctaphid-dispatch = { git = "https://github.com/trussed-dev/ctaphid-dispatch.git", rev = "57cb3317878a8593847595319aa03ef17c29ec5b" } apdu-dispatch = { git = "https://github.com/trussed-dev/apdu-dispatch.git", rev = "915fc237103fcecc29d0f0b73391f19abf6576de" } -littlefs2 = { git = "https://github.com/trussed-dev/littlefs2.git", rev = "2b45a7559ff44260c6dd693e4cb61f54ae5efc53" } -trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "b548d379dcbd67d29453d94847b7bc33ae92e673" } +trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "a055e4f79a10122c8c0c882161442e6e02f0c5c6" } +littlefs2 = { git = "https://github.com/trussed-dev/littlefs2.git", rev = "960e57d9fc0d209308c8e15dc26252bbe1ff6ba8" } trussed-chunked = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "chunked-v0.1.0" } +trussed-fs-info = { git = "https://github.com/trussed-dev/trussed-staging.git", rev = "170ab14f3bb6760399749d78e1b94e3b70106739" } trussed-hkdf = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "hkdf-v0.2.0" } -trussed-staging = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "v0.3.0" } +trussed-staging = { git = "https://github.com/trussed-dev/trussed-staging.git", rev = "170ab14f3bb6760399749d78e1b94e3b70106739" } trussed-usbip = { git = "https://github.com/Nitrokey/pc-usbip-runner.git", tag = "v0.0.1-nitrokey.1" } usbd-ctaphid = { git = "https://github.com/trussed-dev/usbd-ctaphid.git", rev = "dcff9009c3cd1ef9e5b09f8f307aca998fc9a8c8" } diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 800adbf..5adc5ca 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -10,7 +10,7 @@ cargo-fuzz = true [dependencies] ctap-types = { version = "0.2.0", features = ["arbitrary"] } libfuzzer-sys = "0.4" -trussed-staging = { version = "0.3.0", features = ["chunked", "hkdf", "virt"] } +trussed-staging = { version = "0.3.0", features = ["chunked", "hkdf", "virt", "fs-info"] } [dependencies.fido-authenticator] path = ".." @@ -24,9 +24,10 @@ bench = false [patch.crates-io] ctap-types = { git = "https://github.com/trussed-dev/ctap-types.git", rev = "72eb68b61e3f14957c5ab89bd22f776ac860eb62" } -littlefs2 = { git = "https://github.com/trussed-dev/littlefs2.git", rev = "2b45a7559ff44260c6dd693e4cb61f54ae5efc53" } -trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "b548d379dcbd67d29453d94847b7bc33ae92e673" } +trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "a055e4f79a10122c8c0c882161442e6e02f0c5c6" } +littlefs2 = { git = "https://github.com/trussed-dev/littlefs2.git", rev = "960e57d9fc0d209308c8e15dc26252bbe1ff6ba8" } trussed-chunked = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "chunked-v0.1.0" } trussed-hkdf = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "hkdf-v0.2.0" } -trussed-staging = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "v0.3.0" } cbor-smol = { git = "https://github.com/sosthene-nitrokey/cbor-smol.git", rev = "9a77dc9b528b08f531d76b44af2f5336c4ef17e0"} +trussed-fs-info = { git = "https://github.com/trussed-dev/trussed-staging.git", rev = "170ab14f3bb6760399749d78e1b94e3b70106739" } +trussed-staging = { git = "https://github.com/trussed-dev/trussed-staging.git", rev = "170ab14f3bb6760399749d78e1b94e3b70106739" } diff --git a/src/ctap2.rs b/src/ctap2.rs index 1ef0737..50c5e28 100644 --- a/src/ctap2.rs +++ b/src/ctap2.rs @@ -1,5 +1,6 @@ //! The `ctap_types::ctap2::Authenticator` implementation. +use credential_management::CredentialManagement; use ctap_types::{ ctap2::{ self, client_pin::Permissions, AttestationFormatsPreference, AttestationStatement, @@ -21,15 +22,9 @@ use trussed::{ }; use crate::{ - constants, + constants::{self, MAX_RESIDENT_CREDENTIALS_GUESSTIMATE}, credential::{self, Credential, FullCredential, Key, StrippedCredential}, - format_hex, - state::{ - self, - // // (2022-02-27): 9288 bytes - // MinCredentialHeap, - }, - Result, SigningAlgorithm, TrussedRequirements, UserPresence, + format_hex, state, Result, SigningAlgorithm, TrussedRequirements, UserPresence, }; #[allow(unused_imports)] @@ -393,21 +388,12 @@ impl Authenticator for crate::Authenti self.delete_resident_key_by_user_id(&rp_id_hash, &credential.user.id) .ok(); - let mut key_store_full = false; - - // then check the maximum number of RK credentials - if let Some(max_count) = self.config.max_resident_credential_count { - let mut cm = credential_management::CredentialManagement::new(self); - let metadata = cm.get_creds_metadata(); - let count = metadata - .existing_resident_credentials_count - .unwrap_or(max_count); - debug!("resident cred count: {} (max: {})", count, max_count); - if count >= max_count { - error!("maximum resident credential count reached"); - key_store_full = true; - } - } + let mut key_store_full = self.can_fit(serialized_credential.len()) == Some(false) + || CredentialManagement::new(self).count_credentials() + >= self + .config + .max_resident_credential_count + .unwrap_or(MAX_RESIDENT_CREDENTIALS_GUESSTIMATE); if !key_store_full { // then store key, making it resident @@ -1870,7 +1856,7 @@ impl crate::Authenticator { ); } else { info!("deleting parent {:?} as this was its last RK", &rp_path); - syscall!(self.trussed.remove_dir(Location::Internal, rp_path,)); + try_syscall!(self.trussed.remove_dir(Location::Internal, rp_path,)).ok(); } } diff --git a/src/ctap2/credential_management.rs b/src/ctap2/credential_management.rs index 527fc52..15134d3 100644 --- a/src/ctap2/credential_management.rs +++ b/src/ctap2/credential_management.rs @@ -65,14 +65,21 @@ where info!("get metadata"); let mut response: Response = Default::default(); - let max_resident_credentials = self - .config - .max_resident_credential_count - .unwrap_or(MAX_RESIDENT_CREDENTIALS_GUESSTIMATE); - response.existing_resident_credentials_count = Some(0); + let max_resident_credentials = self.estimate_remaining(); + let credential_count = self.count_credentials(); + response.existing_resident_credentials_count = Some(credential_count); response.max_possible_remaining_residential_credentials_count = - Some(max_resident_credentials); + Some(max_resident_credentials.unwrap_or_else(|| { + self.config + .max_resident_credential_count + .unwrap_or(MAX_RESIDENT_CREDENTIALS_GUESSTIMATE) + .saturating_sub(credential_count) + })); + response + } + + pub fn count_credentials(&mut self) -> u32 { let dir = PathBuf::from(b"rk"); let maybe_first_rp = syscall!(self @@ -81,7 +88,7 @@ where .entry; let first_rp = match maybe_first_rp { - None => return response, + None => return 0, Some(rp) => rp, }; @@ -98,10 +105,7 @@ where match maybe_next_rp { None => { - response.existing_resident_credentials_count = Some(num_rks); - response.max_possible_remaining_residential_credentials_count = - Some(max_resident_credentials.saturating_sub(num_rks)); - return response; + return num_rks; } Some(rp) => { last_rp = PathBuf::from(rp.file_name()); diff --git a/src/ctap2/large_blobs.rs b/src/ctap2/large_blobs.rs index 094a06e..005695e 100644 --- a/src/ctap2/large_blobs.rs +++ b/src/ctap2/large_blobs.rs @@ -1,11 +1,13 @@ use ctap_types::{sizes::LARGE_BLOB_MAX_FRAGMENT_LENGTH, Error}; use trussed::{ - client::Client, config::MAX_MESSAGE_LENGTH, - syscall, try_syscall, - types::{Bytes, Location, Mechanism, Message, PathBuf}, + try_syscall, + types::{Bytes, Location, Message, PathBuf}, }; +#[cfg(not(feature = "chunked"))] +use trussed::{syscall, types::Mechanism}; + use crate::{Result, TrussedRequirements}; const HASH_SIZE: usize = 16; @@ -151,12 +153,14 @@ type SelectedStorage = ChunkedStorage; // Basic implementation using a file in the volatile storage as a buffer based on the core Trussed // API. Maximum size for the entire large blob array: 1024 bytes. +#[cfg(not(feature = "chunked"))] struct SimpleStorage { location: Location, buffer: Message, } -impl Storage for SimpleStorage { +#[cfg(not(feature = "chunked"))] +impl Storage for SimpleStorage { fn read(client: &mut C, location: Location, offset: usize, length: usize) -> Result { let result = try_syscall!(client.read_file(location, PathBuf::from(FILENAME))); let data = if let Ok(reply) = &result { diff --git a/src/lib.rs b/src/lib.rs index e6e576a..2166238 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,12 @@ generate_macros!(); use core::time::Duration; -use trussed::{client, syscall, types::Message, Client as TrussedClient}; +use trussed::{ + client, syscall, + types::{Location, Message}, + Client as TrussedClient, +}; +use trussed_fs_info::{FsInfoClient, FsInfoReply}; use trussed_hkdf::HkdfClient; /// Re-export of `ctap-types` authenticator errors. @@ -57,6 +62,7 @@ pub trait TrussedRequirements: + client::Sha256 + client::HmacSha256 + client::Ed255 // + client::Totp + + FsInfoClient + HkdfClient + ExtensionRequirements { @@ -70,6 +76,7 @@ impl TrussedRequirements for T where + client::Sha256 + client::HmacSha256 + client::Ed255 // + client::Totp + + FsInfoClient + HkdfClient + ExtensionRequirements { @@ -266,6 +273,41 @@ where } } + fn estimate_remaining_inner(info: &FsInfoReply) -> Option { + let block_size = info.block_info.as_ref()?.size; + // 1 block for the directory, 1 for the private key, 400 bytes for a reasonnable key and metadata + let size_taken = 2 * block_size + 400; + // Remove 5 block kept as buffer + Some((info.available_space.saturating_sub(5 * block_size) / size_taken) as u32) + } + + fn estimate_remaining(&mut self) -> Option { + let info = syscall!(self.trussed.fs_info(Location::Internal)); + debug!("Got filesystem info: {info:?}"); + Self::estimate_remaining_inner(&info) + } + + fn can_fit_inner(info: &FsInfoReply, size: usize) -> Option { + let block_size = info.block_info.as_ref()?.size; + // 1 block for the rp directory, 5 block of margin, 50 bytes for a reasonnable metadata + let size_taken = 6 * block_size + size + 50; + Some(size_taken < info.available_space) + } + + /// Can a credential of size `size` be stored with safe margins + /// + /// This assumes that the key has already been generated and is stored. + fn can_fit(&mut self, size: usize) -> Option { + debug!("Can fit for {size} bytes"); + let info = syscall!(self.trussed.fs_info(Location::Internal)); + debug!("Got filesystem info: {info:?}"); + debug!( + "Available storage: {}", + Self::estimate_remaining_inner(&info) + ); + Self::can_fit_inner(&info, size) + } + fn hash(&mut self, data: &[u8]) -> [u8; 32] { let hash = syscall!(self.trussed.hash_sha256(data)).hash; hash.as_slice().try_into().expect("hash should fit")