mirror of
https://github.com/trussed-dev/fido-authenticator.git
synced 2026-06-20 04:16:16 -07:00
Merge pull request #92 from Nitrokey/dynamic-limit
Add dynamic estimation of remaining credential space
This commit is contained in:
+6
-4
@@ -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" }
|
||||
|
||||
+5
-4
@@ -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" }
|
||||
|
||||
+10
-24
@@ -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<UP: UserPresence, T: TrussedRequirements> 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<UP: UserPresence, T: TrussedRequirements> crate::Authenticator<UP, T> {
|
||||
);
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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<C: Client> Storage<C> for SimpleStorage {
|
||||
#[cfg(not(feature = "chunked"))]
|
||||
impl<C: TrussedRequirements> Storage<C> for SimpleStorage {
|
||||
fn read(client: &mut C, location: Location, offset: usize, length: usize) -> Result<Chunk> {
|
||||
let result = try_syscall!(client.read_file(location, PathBuf::from(FILENAME)));
|
||||
let data = if let Ok(reply) = &result {
|
||||
|
||||
+43
-1
@@ -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<T> 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<u32> {
|
||||
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<u32> {
|
||||
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<bool> {
|
||||
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<bool> {
|
||||
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")
|
||||
|
||||
Reference in New Issue
Block a user