mirror of
https://github.com/trussed-dev/fido-authenticator.git
synced 2026-06-20 04:16:16 -07:00
tests: Inspect filesystem after test runs
This commit is contained in:
+69
-6
@@ -1,5 +1,6 @@
|
||||
#![cfg(feature = "dispatch")]
|
||||
|
||||
pub mod fs;
|
||||
pub mod virt;
|
||||
pub mod webauthn;
|
||||
|
||||
@@ -12,7 +13,8 @@ use ciborium::Value;
|
||||
use exhaustive::Exhaustive;
|
||||
use hex_literal::hex;
|
||||
|
||||
use virt::{Ctap2, Ctap2Error};
|
||||
use fs::list_fs;
|
||||
use virt::{Ctap2, Ctap2Error, Options};
|
||||
use webauthn::{
|
||||
AttStmtFormat, ClientPin, CredentialManagement, CredentialManagementParams, ExtensionsInput,
|
||||
GetAssertion, GetAssertionOptions, GetInfo, GetNextAssertion, KeyAgreementKey, MakeCredential,
|
||||
@@ -51,7 +53,15 @@ fn test_ping() {
|
||||
|
||||
#[test]
|
||||
fn test_get_info() {
|
||||
virt::run_ctap2(|device| {
|
||||
let options = Options {
|
||||
inspect_ifs: Some(Box::new(|ifs| {
|
||||
let mut files = list_fs(ifs);
|
||||
files.remove_standard();
|
||||
files.assert_empty();
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
virt::run_ctap2_with_options(options, |device| {
|
||||
let reply = device.exec(GetInfo).unwrap();
|
||||
assert!(reply.versions.contains(&"FIDO_2_0".to_owned()));
|
||||
assert!(reply.versions.contains(&"FIDO_2_1".to_owned()));
|
||||
@@ -93,7 +103,16 @@ fn set_pin(
|
||||
#[test]
|
||||
fn test_set_pin() {
|
||||
let key_agreement_key = KeyAgreementKey::generate();
|
||||
virt::run_ctap2(|device| {
|
||||
let options = Options {
|
||||
inspect_ifs: Some(Box::new(|ifs| {
|
||||
let mut files = list_fs(ifs);
|
||||
files.remove_standard();
|
||||
files.remove_state();
|
||||
files.assert_empty();
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
virt::run_ctap2_with_options(options, |device| {
|
||||
let reply = device.exec(GetInfo).unwrap();
|
||||
let options = reply.options.unwrap();
|
||||
assert_eq!(options.get("clientPin"), Some(&Value::from(false)));
|
||||
@@ -576,7 +595,27 @@ impl Test for TestMakeCredential {
|
||||
// TODO: client data
|
||||
let client_data_hash = b"";
|
||||
|
||||
virt::run_ctap2(|device| {
|
||||
let is_rk = self
|
||||
.options
|
||||
.and_then(|options| options.rk)
|
||||
.unwrap_or_default();
|
||||
let is_successful = self.expected_error().is_none();
|
||||
let options = Options {
|
||||
inspect_ifs: Some(Box::new(move |ifs| {
|
||||
let mut files = list_fs(ifs);
|
||||
files.remove_standard();
|
||||
files.try_remove_state();
|
||||
let n = files.try_remove_keys();
|
||||
assert!(n <= 2, "n: {n}, files: {files:?}");
|
||||
if is_rk && is_successful {
|
||||
assert_eq!(files.try_remove_rks(), 1, "{files:?}");
|
||||
}
|
||||
files.assert_empty();
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
virt::run_ctap2_with_options(options, |device| {
|
||||
let mut pin_auth = None;
|
||||
match &self.pin_auth {
|
||||
PinAuth::NoPin => {}
|
||||
@@ -850,7 +889,19 @@ fn run_test_get_next_assertion(device: &Ctap2) {
|
||||
|
||||
#[test]
|
||||
fn test_get_next_assertion() {
|
||||
virt::run_ctap2(|device| {
|
||||
let options = Options {
|
||||
inspect_ifs: Some(Box::new(move |ifs| {
|
||||
let mut files = list_fs(ifs);
|
||||
files.remove_standard();
|
||||
files.remove_state();
|
||||
assert_eq!(files.try_remove_keys(), 4);
|
||||
assert_eq!(files.try_remove_rks(), 3);
|
||||
files.assert_empty();
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
virt::run_ctap2_with_options(options, |device| {
|
||||
run_test_get_next_assertion(&device);
|
||||
});
|
||||
}
|
||||
@@ -858,7 +909,19 @@ fn test_get_next_assertion() {
|
||||
#[test]
|
||||
fn test_get_next_assertion_multi_rp() {
|
||||
let client_data_hash = b"";
|
||||
virt::run_ctap2(|device| {
|
||||
let options = Options {
|
||||
inspect_ifs: Some(Box::new(move |ifs| {
|
||||
let mut files = list_fs(ifs);
|
||||
files.remove_standard();
|
||||
files.remove_state();
|
||||
assert_eq!(files.try_remove_keys(), 10);
|
||||
assert_eq!(files.try_remove_rks(), 9);
|
||||
files.assert_empty();
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
virt::run_ctap2_with_options(options, |device| {
|
||||
let pub_key_cred_params = vec![PubKeyCredParam::new("public-key", -7)];
|
||||
for rp in ["test.com", "something.dev", "else.foobar"] {
|
||||
for user in [b"john.doe", b"jane.doe"] {
|
||||
|
||||
+95
-6
@@ -1,6 +1,7 @@
|
||||
#![cfg(feature = "dispatch")]
|
||||
|
||||
pub mod authenticator;
|
||||
pub mod fs;
|
||||
pub mod virt;
|
||||
pub mod webauthn;
|
||||
|
||||
@@ -9,6 +10,7 @@ use std::collections::BTreeSet;
|
||||
use littlefs2::path::PathBuf;
|
||||
|
||||
use authenticator::{Authenticator, Pin};
|
||||
use fs::list_fs;
|
||||
use virt::{Ctap2Error, Options};
|
||||
use webauthn::{CredentialData, PubKeyCredDescriptor, Rp, User};
|
||||
|
||||
@@ -112,7 +114,19 @@ fn generate_user(i: u8) -> User {
|
||||
|
||||
#[test]
|
||||
fn test_list_credentials() {
|
||||
virt::run_ctap2(|device| {
|
||||
let options = Options {
|
||||
inspect_ifs: Some(Box::new(move |ifs| {
|
||||
let mut files = list_fs(ifs);
|
||||
files.remove_standard();
|
||||
files.remove_state();
|
||||
assert_eq!(files.try_remove_keys(), 11);
|
||||
assert_eq!(files.try_remove_rks(), 10);
|
||||
files.assert_empty();
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
virt::run_ctap2_with_options(options, |device| {
|
||||
let authenticator = Authenticator::new(device).set_pin(b"123456");
|
||||
let mut cred_mgmt = CredMgmt::new(authenticator);
|
||||
for i in 0..10 {
|
||||
@@ -127,7 +141,19 @@ fn test_list_credentials() {
|
||||
|
||||
#[test]
|
||||
fn test_list_credentials_multi() {
|
||||
virt::run_ctap2(|device| {
|
||||
let options = Options {
|
||||
inspect_ifs: Some(Box::new(move |ifs| {
|
||||
let mut files = list_fs(ifs);
|
||||
files.remove_standard();
|
||||
files.remove_state();
|
||||
assert_eq!(files.try_remove_keys(), 11);
|
||||
assert_eq!(files.try_remove_rks(), 10);
|
||||
files.assert_empty();
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
virt::run_ctap2_with_options(options, |device| {
|
||||
let authenticator = Authenticator::new(device).set_pin(b"123456");
|
||||
let mut cred_mgmt = CredMgmt::new(authenticator);
|
||||
for (i, n) in [1, 3, 1, 3, 2].into_iter().enumerate() {
|
||||
@@ -144,7 +170,19 @@ fn test_list_credentials_multi() {
|
||||
|
||||
#[test]
|
||||
fn test_list_credentials_delete() {
|
||||
virt::run_ctap2(|device| {
|
||||
let options = Options {
|
||||
inspect_ifs: Some(Box::new(move |ifs| {
|
||||
let mut files = list_fs(ifs);
|
||||
files.remove_standard();
|
||||
files.remove_state();
|
||||
assert_eq!(files.try_remove_keys(), 9);
|
||||
assert_eq!(files.try_remove_rks(), 8);
|
||||
files.assert_empty();
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
virt::run_ctap2_with_options(options, |device| {
|
||||
let authenticator = Authenticator::new(device).set_pin(b"123456");
|
||||
let mut cred_mgmt = CredMgmt::new(authenticator);
|
||||
for (i, n) in [1, 3, 1, 3, 2].into_iter().enumerate() {
|
||||
@@ -164,9 +202,52 @@ fn test_list_credentials_delete() {
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_credentials_delete_all() {
|
||||
let options = Options {
|
||||
inspect_ifs: Some(Box::new(move |ifs| {
|
||||
let mut files = list_fs(ifs);
|
||||
files.remove_standard();
|
||||
files.remove_state();
|
||||
assert_eq!(files.try_remove_keys(), 1);
|
||||
files.remove_empty_dir("fido/dat/rk");
|
||||
files.assert_empty();
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
virt::run_ctap2_with_options(options, |device| {
|
||||
let authenticator = Authenticator::new(device).set_pin(b"123456");
|
||||
let mut cred_mgmt = CredMgmt::new(authenticator);
|
||||
for (i, n) in [1, 3, 1, 3, 2].into_iter().enumerate() {
|
||||
let rp = generate_rp(i);
|
||||
for j in 0..n {
|
||||
let user = generate_user(j);
|
||||
cred_mgmt.make_credential(rp.clone(), user).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0..10 {
|
||||
cred_mgmt.delete_credential_at(0).unwrap();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_credentials_update_user() {
|
||||
virt::run_ctap2(|device| {
|
||||
let options = Options {
|
||||
inspect_ifs: Some(Box::new(move |ifs| {
|
||||
let mut files = list_fs(ifs);
|
||||
files.remove_standard();
|
||||
files.remove_state();
|
||||
assert_eq!(files.try_remove_keys(), 11);
|
||||
assert_eq!(files.try_remove_rks(), 10);
|
||||
files.assert_empty();
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
virt::run_ctap2_with_options(options, |device| {
|
||||
let authenticator = Authenticator::new(device).set_pin(b"123456");
|
||||
let mut cred_mgmt = CredMgmt::new(authenticator);
|
||||
for (i, n) in [1, 3, 1, 3, 2].into_iter().enumerate() {
|
||||
@@ -256,13 +337,17 @@ fn test_max_credential_count() {
|
||||
fn test_filesystem_full() {
|
||||
let mut options = Options {
|
||||
max_resident_credential_count: Some(10),
|
||||
inspect_ifs: Some(Box::new(|ifs| {
|
||||
let blocks = ifs.available_blocks().unwrap();
|
||||
assert!(blocks < 5, "{blocks}");
|
||||
assert!(blocks > 1, "{blocks}");
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
for i in 0..80 {
|
||||
let path = PathBuf::try_from(format!("/test/{i}").as_str()).unwrap();
|
||||
options.files.push((path, vec![0; 512]));
|
||||
}
|
||||
// TODO: inspect filesystem after run and check remaining blocks
|
||||
virt::run_ctap2_with_options(options, |device| {
|
||||
let mut authenticator = Authenticator::new(device).set_pin(b"123456");
|
||||
let metadata = authenticator.credentials_metadata();
|
||||
@@ -305,13 +390,17 @@ fn test_filesystem_full() {
|
||||
fn test_filesystem_full_update_user() {
|
||||
let mut options = Options {
|
||||
max_resident_credential_count: Some(10),
|
||||
inspect_ifs: Some(Box::new(|ifs| {
|
||||
let blocks = ifs.available_blocks().unwrap();
|
||||
assert!(blocks < 5, "{blocks}");
|
||||
assert!(blocks > 1, "{blocks}");
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
for i in 0..80 {
|
||||
let path = PathBuf::try_from(format!("/test/{i}").as_str()).unwrap();
|
||||
options.files.push((path, vec![0; 512]));
|
||||
}
|
||||
// TODO: inspect filesystem after run and check remaining blocks
|
||||
virt::run_ctap2_with_options(options, |device| {
|
||||
let authenticator = Authenticator::new(device).set_pin(b"123456");
|
||||
let mut cred_mgmt = CredMgmt::new(authenticator);
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use littlefs2_core::{path, DynFilesystem, FileType, Path};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Entry {
|
||||
File,
|
||||
EmptyDir,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct Entries(pub BTreeMap<String, Entry>);
|
||||
|
||||
impl Entries {
|
||||
pub fn remove_standard(&mut self) {
|
||||
self.remove_file("fido/sec/00");
|
||||
self.remove_file("fido/x5c/00");
|
||||
self.remove_file("trussed/dat/rng-state.bin");
|
||||
}
|
||||
|
||||
pub fn remove_state(&mut self) {
|
||||
self.remove_file("fido/dat/persistent-state.cbor");
|
||||
}
|
||||
|
||||
pub fn try_remove_state(&mut self) {
|
||||
self.0.remove("fido/dat/persistent-state.cbor");
|
||||
}
|
||||
|
||||
pub fn try_remove_keys(&mut self) -> usize {
|
||||
self.try_remove_dir("fido/sec")
|
||||
}
|
||||
|
||||
pub fn try_remove_rks(&mut self) -> usize {
|
||||
let n = self.0.len();
|
||||
self.0.retain(|path, _| {
|
||||
let (start, _) = path.rsplit_once('/').unwrap();
|
||||
let start = start.rsplit_once('/').map(|(start, _)| start);
|
||||
start != Some("fido/dat/rk")
|
||||
});
|
||||
n - self.0.len()
|
||||
}
|
||||
|
||||
pub fn try_remove_dir(&mut self, dir: &str) -> usize {
|
||||
let n = self.0.len();
|
||||
self.0.retain(|path, _| {
|
||||
let (start, _) = path.rsplit_once('/').unwrap();
|
||||
start != dir
|
||||
});
|
||||
n - self.0.len()
|
||||
}
|
||||
|
||||
pub fn remove_file(&mut self, path: &str) {
|
||||
let entry = self.0.remove(path);
|
||||
assert_eq!(entry, Some(Entry::File), "{path}");
|
||||
}
|
||||
|
||||
pub fn remove_empty_dir(&mut self, path: &str) {
|
||||
let entry = self.0.remove(path);
|
||||
assert_eq!(entry, Some(Entry::EmptyDir), "{path}");
|
||||
}
|
||||
|
||||
pub fn assert_empty(&self) {
|
||||
assert_eq!(self.0, Default::default());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn list_fs(fs: &dyn DynFilesystem) -> Entries {
|
||||
fn list_dir(fs: &dyn DynFilesystem, dir: &Path, files: &mut BTreeMap<String, Entry>) -> usize {
|
||||
fs.read_dir_and_then(dir, &mut |iter| {
|
||||
let mut child_count = 0;
|
||||
for entry in iter {
|
||||
let entry = entry.unwrap();
|
||||
if entry.file_name().as_str() == "." || entry.file_name().as_str() == ".." {
|
||||
continue;
|
||||
}
|
||||
child_count += 1;
|
||||
match entry.file_type() {
|
||||
FileType::File => {
|
||||
files.insert(entry.path().as_str().to_owned(), Entry::File);
|
||||
}
|
||||
FileType::Dir => {
|
||||
let n = list_dir(fs, entry.path(), files);
|
||||
if n == 0 {
|
||||
files.insert(entry.path().as_str().to_owned(), Entry::EmptyDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(child_count)
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
let mut entries = BTreeMap::new();
|
||||
list_dir(fs, path!(""), &mut entries);
|
||||
Entries(entries)
|
||||
}
|
||||
+57
-40
@@ -4,6 +4,7 @@ use std::{
|
||||
borrow::Cow,
|
||||
cell::RefCell,
|
||||
fmt::{self, Debug, Formatter},
|
||||
ops::Deref as _,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Once,
|
||||
@@ -20,7 +21,7 @@ use ctaphid::{
|
||||
};
|
||||
use ctaphid_dispatch::{Channel, Dispatch, Requester};
|
||||
use fido_authenticator::{Authenticator, Config, Conforming};
|
||||
use littlefs2::{path, path::PathBuf};
|
||||
use littlefs2::{object_safe::DynFilesystem, path, path::PathBuf};
|
||||
use rand::{
|
||||
distributions::{Distribution, Uniform},
|
||||
RngCore as _,
|
||||
@@ -62,45 +63,53 @@ where
|
||||
let mut files = options.files;
|
||||
files.push((path!("fido/x5c/00").into(), ATTESTATION_CERT.into()));
|
||||
files.push((path!("fido/sec/00").into(), ATTESTATION_KEY.into()));
|
||||
with_client(&files, |client| {
|
||||
let mut authenticator = Authenticator::new(
|
||||
client,
|
||||
Conforming {},
|
||||
Config {
|
||||
max_msg_size: 0,
|
||||
skip_up_timeout: None,
|
||||
max_resident_credential_count: options.max_resident_credential_count,
|
||||
large_blobs: None,
|
||||
nfc_transport: false,
|
||||
},
|
||||
);
|
||||
with_client(
|
||||
&files,
|
||||
|client| {
|
||||
let mut authenticator = Authenticator::new(
|
||||
client,
|
||||
Conforming {},
|
||||
Config {
|
||||
max_msg_size: 0,
|
||||
skip_up_timeout: None,
|
||||
max_resident_credential_count: options.max_resident_credential_count,
|
||||
large_blobs: None,
|
||||
nfc_transport: false,
|
||||
},
|
||||
);
|
||||
|
||||
let channel = Channel::new();
|
||||
let (rq, rp) = channel.split().unwrap();
|
||||
let channel = Channel::new();
|
||||
let (rq, rp) = channel.split().unwrap();
|
||||
|
||||
thread::scope(|s| {
|
||||
let stop = Arc::new(AtomicBool::new(false));
|
||||
let poller_stop = stop.clone();
|
||||
let poller = s.spawn(move || {
|
||||
let mut dispatch = Dispatch::new(rp);
|
||||
while !poller_stop.load(Ordering::Relaxed) {
|
||||
dispatch.poll(&mut [&mut authenticator]);
|
||||
thread::sleep(Duration::from_millis(1));
|
||||
}
|
||||
});
|
||||
thread::scope(|s| {
|
||||
let stop = Arc::new(AtomicBool::new(false));
|
||||
let poller_stop = stop.clone();
|
||||
let poller = s.spawn(move || {
|
||||
let mut dispatch = Dispatch::new(rp);
|
||||
while !poller_stop.load(Ordering::Relaxed) {
|
||||
dispatch.poll(&mut [&mut authenticator]);
|
||||
thread::sleep(Duration::from_millis(1));
|
||||
}
|
||||
});
|
||||
|
||||
let runner = s.spawn(move || {
|
||||
let device = Device::new(rq);
|
||||
let device = ctaphid::Device::new(device, DeviceInfo).unwrap();
|
||||
f(device)
|
||||
});
|
||||
let runner = s.spawn(move || {
|
||||
let device = Device::new(rq);
|
||||
let device = ctaphid::Device::new(device, DeviceInfo).unwrap();
|
||||
f(device)
|
||||
});
|
||||
|
||||
let result = runner.join();
|
||||
stop.store(true, Ordering::Relaxed);
|
||||
poller.join().unwrap();
|
||||
result.unwrap()
|
||||
})
|
||||
})
|
||||
let result = runner.join();
|
||||
stop.store(true, Ordering::Relaxed);
|
||||
poller.join().unwrap();
|
||||
result.unwrap()
|
||||
})
|
||||
},
|
||||
|ifs| {
|
||||
if let Some(inspect_ifs) = options.inspect_ifs {
|
||||
inspect_ifs(ifs);
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn run_ctap2<F, T>(f: F) -> T
|
||||
@@ -119,10 +128,13 @@ where
|
||||
run_ctaphid_with_options(options, |device| f(Ctap2(device)))
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub type InspectFsFn = Box<dyn Fn(&dyn DynFilesystem)>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Options {
|
||||
pub files: Vec<(PathBuf, Vec<u8>)>,
|
||||
pub max_resident_credential_count: Option<u32>,
|
||||
pub inspect_ifs: Option<InspectFsFn>,
|
||||
}
|
||||
|
||||
pub struct Ctap2<'a>(ctaphid::Device<Device<'a>>);
|
||||
@@ -235,9 +247,10 @@ impl HidDevice for Device<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn with_client<F, T>(files: &[(PathBuf, Vec<u8>)], f: F) -> T
|
||||
fn with_client<F, F2, T>(files: &[(PathBuf, Vec<u8>)], f: F, inspect_ifs: F2) -> T
|
||||
where
|
||||
F: FnOnce(Client<Ram>) -> T,
|
||||
F2: FnOnce(&dyn DynFilesystem),
|
||||
{
|
||||
virt::with_platform(Ram::default(), |mut platform| {
|
||||
// virt always uses the same seed -- request some random bytes to reach a somewhat random
|
||||
@@ -257,7 +270,7 @@ where
|
||||
ifs.write(path, content).unwrap();
|
||||
}
|
||||
|
||||
platform.run_client_with_backends(
|
||||
let result = platform.run_client_with_backends(
|
||||
"fido",
|
||||
Dispatcher::default(),
|
||||
&[
|
||||
@@ -265,6 +278,10 @@ where
|
||||
BackendId::Core,
|
||||
],
|
||||
f,
|
||||
)
|
||||
);
|
||||
|
||||
inspect_ifs(ifs.deref());
|
||||
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user