diff --git a/Cargo.toml b/Cargo.toml index 5b32348..8f0bd95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ subtle = { version = "2.4.1", default-features = false } trussed = { version = "0.1.0", features = ["serde-extensions"] } [dev-dependencies] +quickcheck = { version = "1.0.3", default-features = false } rand_core = { version = "0.6.4", default-features = false, features = ["getrandom"] } trussed = { version = "0.1.0", features = ["serde-extensions", "virt"] } diff --git a/src/backend.rs b/src/backend.rs index acb7337..be57859 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -22,7 +22,7 @@ use trussed::{ use crate::{ extension::{reply, AuthExtension, AuthReply, AuthRequest}, - PIN_PATH, SALT_PATH, + BACKEND_DIR, }; use data::{Key, PinData, Salt, KEY_LEN, SALT_LEN}; @@ -50,6 +50,20 @@ impl fmt::Debug for HardwareKey { /// /// This implementation stores PINs together with their retry counters on the filesystem. PINs are /// hashed with SHA-256 using a salt that is generated per PIN. +/// +/// # Filesystem Layout +/// +/// ```text +/// trussed/ +/// backend-auth/ +/// salt global salt for key derivation +/// / +/// backend-auth/ +/// pin. PIN data, can be deleted with DeletePin or DeleteAllPins +/// ``` +/// +/// The storage location can be set when creating the backend, see [`AuthBackend::new`][] and +/// [`AuthBackend::with_hw_key`][]. #[derive(Clone, Debug)] pub struct AuthBackend { location: Location, @@ -80,7 +94,7 @@ impl AuthBackend { trussed_filestore: &mut impl Filestore, rng: &mut R, ) -> Result { - let path = PathBuf::from(SALT_PATH); + let path = PathBuf::from(BACKEND_DIR).join(&PathBuf::from("salt")); trussed_filestore .read(&path, self.location) .or_else(|_| { @@ -263,7 +277,7 @@ impl ExtensionImpl for AuthBackend { Ok(reply::DeletePin.into()) } AuthRequest::DeleteAllPins(_) => { - fs.remove_dir_all(&PathBuf::from(PIN_PATH), self.location) + fs.remove_dir_all(&PathBuf::from(BACKEND_DIR), self.location) .map_err(|_| Error::WriteFailed)?; Ok(reply::DeleteAllPins.into()) } diff --git a/src/lib.rs b/src/lib.rs index 0a3cf83..86ce51d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ // Copyright (C) Nitrokey GmbH // SPDX-License-Identifier: Apache-2.0 or MIT -#![no_std] +#![cfg_attr(not(test), no_std)] #![warn( missing_debug_implementations, missing_docs, @@ -80,8 +80,7 @@ pub const MAX_PIN_LENGTH: usize = MAX_SHORT_DATA_LENGTH; /// A PIN. pub type Pin = Bytes; -const PIN_PATH: &str = "backend-auth/pin"; -const SALT_PATH: &str = "backend-auth/salt"; +const BACKEND_DIR: &str = "backend-auth"; /// The ID of a PIN within the namespace of a client. /// @@ -112,9 +111,18 @@ pub struct PinId(u8); impl PinId { fn path(&self) -> PathBuf { - let mut path = PathBuf::from(PIN_PATH); - path.push(&PathBuf::from(&self.hex())); - path + const PIN_PREFIX: &[u8] = b"/pin."; + const N: usize = BACKEND_DIR.len(); + const M: usize = PIN_PREFIX.len(); + + let mut path = [0; N + M + 2]; + let (backend_dir, rest) = path.split_at_mut(N); + let (pin, id) = rest.split_at_mut(M); + backend_dir.copy_from_slice(BACKEND_DIR.as_bytes()); + pin.copy_from_slice(PIN_PREFIX); + id.copy_from_slice(&self.hex()); + + PathBuf::from(&path) } fn hex(&self) -> [u8; 2] { @@ -137,3 +145,18 @@ impl From for u8 { id.0 } } + +#[cfg(test)] +mod tests { + use super::PinId; + use trussed::types::PathBuf; + + quickcheck::quickcheck! { + fn test_pin_path(id: u8) -> bool { + let actual = PinId(id).path(); + let expected = PathBuf::from(format!("backend-auth/pin.{id:02x}").as_str()); + println!("id: {id}, actual: {actual}, expected: {expected}"); + actual == expected + } + } +}