Skip to content

Commit

Permalink
Make credential: change the path of rks to `rp_id_hash.credential_id_…
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
sosthene-nitrokey authored and robin-nitrokey committed Oct 29, 2024
1 parent c145a45 commit 5dd004a
Show file tree
Hide file tree
Showing 9 changed files with 578 additions and 256 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support CTAP 2.1
- Serialize PIN hash with `serde-bytes` ([#52][])
- Reduce the space taken by credential serializaiton ([#59][])
- Remove the per-relying party directory to save space ([#55][])

[#26]: https://github.com/solokeys/fido-authenticator/issues/26
[#28]: https://github.com/solokeys/fido-authenticator/issues/28
Expand All @@ -41,6 +42,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[#63]: https://github.com/Nitrokey/fido-authenticator/pull/63
[#52]: https://github.com/Nitrokey/fido-authenticator/issues/52
[#59]: https://github.com/Nitrokey/fido-authenticator/issues/59
[#55]: https://github.com/Nitrokey/fido-authenticator/issues/55

## [0.1.1] - 2022-08-22
- Fix bug that treated U2F payloads as APDU over APDU in NFC transport @conorpp
Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ log-warn = []
log-error = []

[dev-dependencies]
admin-app = { version = "0.1.0", features = ["migration-tests"] }
aes = "0.8.4"
cbc = { version = "0.1.2", features = ["alloc"] }
ciborium = { version = "0.2.2" }
Expand Down Expand Up @@ -80,11 +81,13 @@ x509-parser = "0.16.0"
features = ["dispatch"]

[patch.crates-io]
admin-app = { git = "https://github.com/Nitrokey/admin-app.git", tag = "v0.1.0-nitrokey.18" }
ctaphid-dispatch = { git = "https://github.com/trussed-dev/ctaphid-dispatch.git", rev = "57cb3317878a8593847595319aa03ef17c29ec5b" }
trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "046478b7a4f6e2315acf9112d98308379c2e3eee" }
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 = "fs-info-v0.1.0" }
trussed-hkdf = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "hkdf-v0.2.0" }
trussed-manage = { git = "https://github.com/trussed-dev/trussed-staging.git", tag = "manage-v0.1.0" }
trussed-staging = { git = "https://github.com/trussed-dev/trussed-staging.git", rev = "53eba84d2cd0bcacc3a7096d4b7a2490dcf6f069" }
trussed-usbip = { git = "https://github.com/Nitrokey/pc-usbip-runner.git", tag = "v0.0.1-nitrokey.5" }
usbd-ctaphid = { git = "https://github.com/trussed-dev/usbd-ctaphid.git", rev = "dcff9009c3cd1ef9e5b09f8f307aca998fc9a8c8" }
83 changes: 54 additions & 29 deletions src/ctap2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
.ok();

let mut key_store_full = self.can_fit(serialized_credential.len()) == Some(false)
|| CredentialManagement::new(self).count_credentials()
|| CredentialManagement::new(self).count_credentials()?
>= self
.config
.max_resident_credential_count
Expand Down Expand Up @@ -914,7 +914,7 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
// TODO: use custom enum of known commands
match parameters.sub_command {
// 0x1
Subcommand::GetCredsMetadata => Ok(cred_mgmt.get_creds_metadata()),
Subcommand::GetCredsMetadata => cred_mgmt.get_creds_metadata(),

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

info_now!("found {:?} applicable credentials", num_credentials);
Expand Down Expand Up @@ -1152,7 +1152,7 @@ impl<UP: UserPresence, T: TrussedRequirements> crate::Authenticator<UP, T> {
rp_id_hash: &[u8; 32],
allow_list: &Option<ctap2::get_assertion::AllowList>,
uv_performed: bool,
) -> Option<(Credential, u32)> {
) -> Result<Option<(Credential, u32)>> {
debug_now!("remaining stack size: {} bytes", msp() - 0x2000_0000);

self.state.runtime.clear_credential_cache();
Expand Down Expand Up @@ -1186,50 +1186,74 @@ impl<UP: UserPresence, T: TrussedRequirements> crate::Authenticator<UP, T> {
continue;
}

return Some((credential, 1));
return Ok(Some((credential, 1)));
}

// we don't recognize any credentials in the allowlist
return None;
return Ok(None);
}
}

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

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

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

while let Some(path) = maybe_path {
let credential_data =
syscall!(self.trussed.read_file(Location::Internal, path.clone(),)).data;
let rp_rk_dir = rp_rk_dir(rp_id_hash);
let mut maybe_entry = syscall!(self.trussed.read_dir_first_alphabetical(
Location::Internal,
PathBuf::from(RK_DIR),
Some(rp_rk_dir.clone())
))
.entry;

while let Some(entry) = maybe_entry.take() {
if !entry.path().as_ref().starts_with(rp_rk_dir.as_ref()) {
// We got past all credentials for the relevant RP
break;
}

let credential = FullCredential::deserialize(&credential_data).ok()?;
if entry.path() == &*rp_rk_dir {
debug_assert!(entry.metadata().is_dir());
error!("Migration missing");
return Err(Error::Other);
}

let credential_data = syscall!(self
.trussed
.read_file(Location::Internal, entry.path().into(),))
.data;

let credential = FullCredential::deserialize(&credential_data).map_err(|_err| {
error!("Failed to deserialize credential: {_err:?}");
Error::Other
})?;
let timestamp = credential.creation_time;
let credential = Credential::Full(credential);

if self.check_credential_applicable(&credential, false, uv_performed) {
self.state.runtime.push_credential(CachedCredential {
timestamp,
path: String::from_str(path.as_str_ref_with_trailing_nul()).ok()?,
path: String::from_str(entry.path().as_str_ref_with_trailing_nul())
.map_err(|_| Error::Other)?,
});
}

maybe_path = syscall!(self.trussed.read_dir_next())
.entry
.map(|entry| PathBuf::from(entry.path()));
maybe_entry = syscall!(self.trussed.read_dir_next()).entry;
}

let num_credentials = self.state.runtime.remaining_credentials();
let credential = self.state.runtime.pop_credential(&mut self.trussed);
credential.map(|credential| (Credential::Full(credential), num_credentials))
Ok(())
}

fn decrypt_pin_hash_and_maybe_escalate(
Expand Down Expand Up @@ -2078,11 +2102,12 @@ fn rp_rk_dir(rp_id_hash: &[u8; 32]) -> PathBuf {
}

fn rk_path(rp_id_hash: &[u8; 32], credential_id_hash: &[u8; 32]) -> PathBuf {
let mut path = rp_rk_dir(rp_id_hash);

let mut hex = [0u8; 16];
format_hex(&credential_id_hash[..8], &mut hex);
path.push(&PathBuf::try_from(&hex).unwrap());
let mut buf = [0; 33];
buf[16] = b'.';
format_hex(&rp_id_hash[..8], &mut buf[..16]);
format_hex(&credential_id_hash[..8], &mut buf[17..]);

let mut path = PathBuf::from(RK_DIR);
path.push(&PathBuf::try_from(buf.as_slice()).unwrap());
path
}
Loading

0 comments on commit 5dd004a

Please sign in to comment.