Skip to content

Commit b096896

Browse files
Make credential: change the path of rks to rp_id_hash.credential_id_hash from rp_id_hash/credential_id_hash
The goal is to make credential storage more efficient, by making use of littlefs's ability to inline file contents into the directory metadata when the file is small.
1 parent abad0f1 commit b096896

File tree

10 files changed

+569
-260
lines changed

10 files changed

+569
-260
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2424
- Implement PIN token permissions ([#63][])
2525
- Implement UpdateUserInformation subcommand for CredentialManagement
2626
- Support CTAP 2.1
27+
- Remove the per-relying party directory to save spcae ([#55][])
2728

2829
[#26]: https://github.com/solokeys/fido-authenticator/issues/26
2930
[#28]: https://github.com/solokeys/fido-authenticator/issues/28
@@ -37,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3738
[#61]: https://github.com/Nitrokey/fido-authenticator/pull/61
3839
[#62]: https://github.com/Nitrokey/fido-authenticator/pull/62
3940
[#63]: https://github.com/Nitrokey/fido-authenticator/pull/63
41+
[#55]: https://github.com/Nitrokey/fido-authenticator/issues/55
4042

4143
## [0.1.1] - 2022-08-22
4244
- Fix bug that treated U2F payloads as APDU over APDU in NFC transport @conorpp

Cargo.toml

+6-4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ sha2 = { version = "0.10", default-features = false }
2525
trussed = "0.1"
2626
trussed-hkdf = { version = "0.1.0" }
2727
trussed-staging = { version = "0.1.0", default-features = false, optional = true }
28+
littlefs2 = { version = "0.4.0" }
2829

2930
apdu-dispatch = { version = "0.1", optional = true }
3031
ctaphid-dispatch = { version = "0.1", optional = true }
@@ -50,6 +51,7 @@ env_logger = "0.11.0"
5051
rand = "0.8.4"
5152
trussed = { version = "0.1", features = ["virt"] }
5253
trussed-usbip = { version = "0.0.1", default-features = false, features = ["ctaphid"] }
54+
trussed-staging = { version = "0.1.0", features = ["migration-tests"] }
5355
usbd-ctaphid = "0.1.0"
5456

5557
[package.metadata.docs.rs]
@@ -59,10 +61,10 @@ features = ["dispatch"]
5961
ctap-types = { git = "https://github.com/trussed-dev/ctap-types.git", rev = "4846817d9cd44604121680a19d46f3264973a3ce" }
6062
ctaphid-dispatch = { git = "https://github.com/trussed-dev/ctaphid-dispatch.git", rev = "57cb3317878a8593847595319aa03ef17c29ec5b" }
6163
apdu-dispatch = { git = "https://github.com/trussed-dev/apdu-dispatch.git", rev = "915fc237103fcecc29d0f0b73391f19abf6576de" }
62-
littlefs2 = { git = "https://github.com/trussed-dev/littlefs2.git", rev = "ebd27e49ca321089d01d8c9b169c4aeb58ceeeca" }
63-
serde-indexed = { git = "https://github.com/sosthene-nitrokey/serde-indexed.git", rev = "5005d23cb4ee8622e62188ea0f9466146f851f0d" }
64-
trussed = { git = "https://github.com/Nitrokey/trussed.git", tag = "v0.1.0-nitrokey.18" }
64+
littlefs2 = { git = "https://github.com/sosthene-nitrokey/littlefs2.git", rev = "99b1a9832c46c9097e73ca1fa43e119026e2068f" }
65+
trussed = { git = "https://github.com/Nitrokey/trussed.git", rev = "dadeda9a1fe94911196223232bcabff2cc746b7e" }
66+
trussed-staging = { git = "https://github.com/trussed-dev/trussed-staging", rev = "93801d3b4aefe469806a213e7a49aa540059abb1" }
6567
trussed-hkdf = { git = "https://github.com/Nitrokey/trussed-hkdf-backend.git", tag = "v0.1.0" }
66-
trussed-staging = { git = "https://github.com/Nitrokey/trussed-staging", tag = "v0.1.0-nitrokey-hmac256p256.3" }
6768
trussed-usbip = { git = "https://github.com/Nitrokey/pc-usbip-runner.git", tag = "v0.0.1-nitrokey.1" }
6869
usbd-ctaphid = { git = "https://github.com/Nitrokey/usbd-ctaphid.git", tag = "v0.1.0-nitrokey.2" }
70+
serde-indexed = { git = "https://github.com/sosthene-nitrokey/serde-indexed.git", rev = "5005d23cb4ee8622e62188ea0f9466146f851f0d" }

examples/usbip.rs

+11-12
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,18 @@ impl trussed_usbip::Apps<'static, VirtClient, CoreOnly> for FidoApp {
2929
#[cfg(feature = "chunked")]
3030
max_size: 4096,
3131
});
32+
let mut fido = fido_authenticator::Authenticator::new(
33+
builder.build("fido", &[BackendId::Core]),
34+
fido_authenticator::Conforming {},
35+
fido_authenticator::Config {
36+
max_msg_size: usbd_ctaphid::constants::MESSAGE_SIZE,
37+
skip_up_timeout: None,
38+
max_resident_credential_count: Some(10),
39+
large_blobs: large_blogs,
40+
},
41+
);
3242

33-
FidoApp {
34-
fido: fido_authenticator::Authenticator::new(
35-
builder.build("fido", &[BackendId::Core]),
36-
fido_authenticator::Conforming {},
37-
fido_authenticator::Config {
38-
max_msg_size: usbd_ctaphid::constants::MESSAGE_SIZE,
39-
skip_up_timeout: None,
40-
max_resident_credential_count: Some(10),
41-
large_blobs: large_blogs,
42-
},
43-
),
44-
}
43+
FidoApp { fido }
4544
}
4645

4746
fn with_ctaphid_apps<T>(

src/ctap2.rs

+54-29
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
397397
// then check the maximum number of RK credentials
398398
if let Some(max_count) = self.config.max_resident_credential_count {
399399
let mut cm = credential_management::CredentialManagement::new(self);
400-
let metadata = cm.get_creds_metadata();
400+
let metadata = cm.get_creds_metadata()?;
401401
let count = metadata
402402
.existing_resident_credentials_count
403403
.unwrap_or(max_count);
@@ -945,7 +945,7 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
945945
let sub_parameters = &parameters.sub_command_params;
946946
match parameters.sub_command {
947947
// 0x1
948-
Subcommand::GetCredsMetadata => Ok(cred_mgmt.get_creds_metadata()),
948+
Subcommand::GetCredsMetadata => cred_mgmt.get_creds_metadata(),
949949

950950
// 0x2
951951
Subcommand::EnumerateRpsBegin => cred_mgmt.first_relying_party(),
@@ -1040,7 +1040,7 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
10401040
// If no allowList is passed, credential is None and the retrieved credentials
10411041
// are stored in state.runtime.credential_heap
10421042
let (credential, num_credentials) = self
1043-
.prepare_credentials(&rp_id_hash, &parameters.allow_list, uv_performed)
1043+
.prepare_credentials(&rp_id_hash, &parameters.allow_list, uv_performed)?
10441044
.ok_or(Error::NoCredentials)?;
10451045

10461046
info_now!("found {:?} applicable credentials", num_credentials);
@@ -1180,7 +1180,7 @@ impl<UP: UserPresence, T: TrussedRequirements> crate::Authenticator<UP, T> {
11801180
rp_id_hash: &Bytes<32>,
11811181
allow_list: &Option<ctap2::get_assertion::AllowList>,
11821182
uv_performed: bool,
1183-
) -> Option<(Credential, u32)> {
1183+
) -> Result<Option<(Credential, u32)>> {
11841184
debug_now!("remaining stack size: {} bytes", msp() - 0x2000_0000);
11851185

11861186
self.state.runtime.clear_credential_cache();
@@ -1214,50 +1214,74 @@ impl<UP: UserPresence, T: TrussedRequirements> crate::Authenticator<UP, T> {
12141214
continue;
12151215
}
12161216

1217-
return Some((credential, 1));
1217+
return Ok(Some((credential, 1)));
12181218
}
12191219

12201220
// we don't recognize any credentials in the allowlist
1221-
return None;
1221+
return Ok(None);
12221222
}
12231223
}
12241224

12251225
// we are only dealing with discoverable credentials.
12261226
debug_now!("Allowlist not passed, fetching RKs");
1227+
self.prepare_cache(rp_id_hash, uv_performed)?;
12271228

1228-
let mut maybe_path =
1229-
syscall!(self
1230-
.trussed
1231-
.read_dir_first(Location::Internal, rp_rk_dir(rp_id_hash), None,))
1232-
.entry
1233-
.map(|entry| PathBuf::from(entry.path()));
1229+
let num_credentials = self.state.runtime.remaining_credentials();
1230+
let credential = self.state.runtime.pop_credential(&mut self.trussed);
1231+
Ok(credential.map(|credential| (Credential::Full(credential), num_credentials)))
1232+
}
12341233

1234+
/// Populate the cache with the RP credentials.
1235+
///
1236+
/// Returns true if legacy credentials are present and therefore prepare_cache_legacy should be called too
1237+
#[inline(never)]
1238+
fn prepare_cache(&mut self, rp_id_hash: &Bytes<32>, uv_performed: bool) -> Result<()> {
12351239
use crate::state::CachedCredential;
12361240
use core::str::FromStr;
12371241

1238-
while let Some(path) = maybe_path {
1239-
let credential_data =
1240-
syscall!(self.trussed.read_file(Location::Internal, path.clone(),)).data;
1242+
let rp_rk_dir = rp_rk_dir(rp_id_hash);
1243+
let mut maybe_entry = syscall!(self.trussed.read_dir_first_alphabetical(
1244+
Location::Internal,
1245+
PathBuf::from(b"rk"),
1246+
Some(rp_rk_dir.clone())
1247+
))
1248+
.entry;
1249+
1250+
while let Some(entry) = maybe_entry.take() {
1251+
if !entry.path().as_ref().starts_with(rp_rk_dir.as_ref()) {
1252+
// We got past all credentials for the relevant RP
1253+
break;
1254+
}
12411255

1242-
let credential = FullCredential::deserialize(&credential_data).ok()?;
1256+
if entry.path() == &*rp_rk_dir {
1257+
debug_assert!(entry.metadata().is_dir());
1258+
error!("Migration missing");
1259+
return Err(Error::Other);
1260+
}
1261+
1262+
let credential_data = syscall!(self
1263+
.trussed
1264+
.read_file(Location::Internal, entry.path().into(),))
1265+
.data;
1266+
1267+
let credential = FullCredential::deserialize(&credential_data).map_err(|_err| {
1268+
error!("Failed to deserialize credential: {_err:?}");
1269+
Error::Other
1270+
})?;
12431271
let timestamp = credential.creation_time;
12441272
let credential = Credential::Full(credential);
12451273

12461274
if self.check_credential_applicable(&credential, false, uv_performed) {
12471275
self.state.runtime.push_credential(CachedCredential {
12481276
timestamp,
1249-
path: String::from_str(path.as_str_ref_with_trailing_nul()).ok()?,
1277+
path: String::from_str(entry.path().as_str_ref_with_trailing_nul())
1278+
.map_err(|_| Error::Other)?,
12501279
});
12511280
}
12521281

1253-
maybe_path = syscall!(self.trussed.read_dir_next())
1254-
.entry
1255-
.map(|entry| PathBuf::from(entry.path()));
1282+
maybe_entry = syscall!(self.trussed.read_dir_next()).entry;
12561283
}
1257-
1258-
let num_credentials = self.state.runtime.remaining_credentials();
1259-
let credential = self.state.runtime.pop_credential(&mut self.trussed);
1260-
credential.map(|credential| (Credential::Full(credential), num_credentials))
1284+
Ok(())
12611285
}
12621286

12631287
fn decrypt_pin_hash_and_maybe_escalate(
@@ -2001,11 +2025,12 @@ fn rp_rk_dir(rp_id_hash: &Bytes<32>) -> PathBuf {
20012025
}
20022026

20032027
fn rk_path(rp_id_hash: &Bytes<32>, credential_id_hash: &Bytes<32>) -> PathBuf {
2004-
let mut path = rp_rk_dir(rp_id_hash);
2005-
2006-
let mut hex = [0u8; 16];
2007-
format_hex(&credential_id_hash[..8], &mut hex);
2008-
path.push(&PathBuf::from(&hex));
2028+
let mut buf = [0; 33];
2029+
buf[16] = b'.';
2030+
format_hex(&rp_id_hash[..8], &mut buf[..16]);
2031+
format_hex(&credential_id_hash[..8], &mut buf[17..]);
20092032

2033+
let mut path = PathBuf::from("rk");
2034+
path.push(&PathBuf::from(buf.as_slice()));
20102035
path
20112036
}

0 commit comments

Comments
 (0)