mirror of
https://github.com/trussed-dev/trussed-auth.git
synced 2026-06-20 04:16:21 -07:00
Split backend and extension
This patch splits the trussed-auth crate into two crates: trussed-auth only defines the AuthExtension and can be used by clients or other backends implementing the extension. trussed-auth-backend contains the AuthBackend that implements the extension using the filesystem.
This commit is contained in:
+10
-26
@@ -1,38 +1,22 @@
|
||||
# Copyright (C) Nitrokey GmbH
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
[package]
|
||||
name = "trussed-auth"
|
||||
version = "0.3.0"
|
||||
[workspace]
|
||||
members = ["backend", "extension"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
authors = ["Nitrokey GmbH <info@nitrokey.com>"]
|
||||
edition = "2021"
|
||||
repository = "https://github.com/trussed-dev/trussed-auth"
|
||||
license = "Apache-2.0 OR MIT"
|
||||
description = "Authentication extension and backend for Trussed"
|
||||
repository = "https://github.com/trussed-dev/trussed-auth"
|
||||
|
||||
[dependencies]
|
||||
chacha20poly1305 = { version = "0.10.1", default-features = false, features = ["reduced-round"] }
|
||||
hkdf = "0.12.3"
|
||||
hmac = "0.12.1"
|
||||
rand_core = "0.6.4"
|
||||
[workspace.dependencies]
|
||||
serde = { version = "1", default-features = false }
|
||||
serde-byte-array = "0.1.2"
|
||||
sha2 = { version = "0.10.6", default-features = false }
|
||||
subtle = { version = "2.4.1", default-features = false }
|
||||
trussed = { version = "0.1.0", default-features = false, features = ["serde-extensions"] }
|
||||
trussed-core = { version = "0.1.0-rc.1", features = ["serde-extensions"] }
|
||||
littlefs2-core = "0.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
quickcheck = { version = "1.0.3", default-features = false }
|
||||
rand_core = { version = "0.6.4", default-features = false, features = ["getrandom"] }
|
||||
serde_test = "1.0.176"
|
||||
trussed = { version = "0.1.0", default-features = false, features = ["clients-1", "crypto-client", "filesystem-client", "hmac-sha256", "serde-extensions", "virt"] }
|
||||
admin-app = { version = "0.1.0", features = ["migration-tests"] }
|
||||
serde_cbor = { version = "0.11.2", features = ["std"] }
|
||||
hex-literal = "0.4.1"
|
||||
|
||||
[patch.crates-io]
|
||||
trussed-auth = { path = "extension" }
|
||||
|
||||
trussed = { git = "https://github.com/trussed-dev/trussed.git", rev = "6bba8fde36d05c0227769eb63345744e87d84b2b" }
|
||||
trussed-manage = { git = "https://github.com/trussed-dev/trussed-staging.git", rev = "9355f700831c1a278c334f76382fbf98d82aedcd" }
|
||||
admin-app = { git = "https://github.com/Nitrokey/admin-app.git", branch = "ctaphid-app" }
|
||||
admin-app = { git = "https://github.com/Nitrokey/admin-app.git", tag = "v0.1.0-nitrokey.19" }
|
||||
|
||||
@@ -3,18 +3,18 @@
|
||||
|
||||
.PHONY: check
|
||||
check:
|
||||
RUSTLFAGS='-Dwarnings' cargo check --all-features --all-targets
|
||||
RUSTLFAGS='-Dwarnings' cargo check --all-features --all-targets --workspace
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
cargo clippy --all-features --all-targets -- --deny warnings
|
||||
cargo fmt -- --check
|
||||
RUSTDOCFLAGS='-Dwarnings' cargo doc --no-deps
|
||||
cargo clippy --all-features --all-targets --workspace -- --deny warnings
|
||||
cargo fmt --all -- --check
|
||||
RUSTDOCFLAGS='-Dwarnings' cargo doc --no-deps --workspace
|
||||
reuse lint
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
cargo test --all-features
|
||||
cargo test --all-features --workspace
|
||||
|
||||
.PHONY: ci
|
||||
ci: check lint test
|
||||
|
||||
@@ -5,10 +5,14 @@ SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
# trussed-auth
|
||||
|
||||
`trussed-auth` is an extension and custom backend for [Trussed][] that provides
|
||||
basic PIN handling.
|
||||
`trussed-auth` is an extension for [Trussed][] that provides basic PIN
|
||||
handling. `trussed-auth-backend` is a Trussed backend implementing that
|
||||
extension using the filesystem. Other implementations are provided by these
|
||||
backends:
|
||||
- [`trussed-se050-backend`][]
|
||||
|
||||
[Trussed]: https://github.com/trussed-dev/trussed
|
||||
[`trussed-se050-backend`]: https://github.com/Nitrokey/trussed-se050-backend
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<!--
|
||||
Copyright (C) Nitrokey GmbH
|
||||
SPDX-License-Identifier: CC0-1.0
|
||||
-->
|
||||
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## Unreleased
|
||||
|
||||
Extracted from `trussed-auth` v0.3.0.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Use serde(rename) to save space on on the size of stored credentials ([#38][])
|
||||
- Remove the `dat` intermediary directory in file storage ([#39][])
|
||||
- Use `trussed-core` and remove default features for `trussed`
|
||||
|
||||
[#38]: https://github.com/trussed-dev/trussed-auth/pull/38
|
||||
[#39]: https://github.com/trussed-dev/trussed-auth/pull/39
|
||||
@@ -0,0 +1,35 @@
|
||||
# Copyright (C) Nitrokey GmbH
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
[package]
|
||||
name = "trussed-auth-backend"
|
||||
version = "0.1.0"
|
||||
description = "Authentication backend for Trussed"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde.workspace = true
|
||||
trussed-core.workspace = true
|
||||
|
||||
chacha20poly1305 = { version = "0.10.1", default-features = false, features = ["reduced-round"] }
|
||||
hkdf = "0.12.3"
|
||||
hmac = "0.12.1"
|
||||
littlefs2-core = "0.1"
|
||||
rand_core = "0.6.4"
|
||||
serde-byte-array = "0.1.2"
|
||||
sha2 = { version = "0.10.6", default-features = false }
|
||||
subtle = { version = "2.4.1", default-features = false }
|
||||
trussed = { version = "0.1.0", default-features = false, features = ["serde-extensions"] }
|
||||
trussed-auth = { version = "0.3.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
admin-app = { version = "0.1.0", features = ["migration-tests"] }
|
||||
hex-literal = "0.4.1"
|
||||
quickcheck = { version = "1.0.3", default-features = false }
|
||||
rand_core = { version = "0.6.4", default-features = false, features = ["getrandom"] }
|
||||
serde_cbor = { version = "0.11.2", features = ["std"] }
|
||||
serde_test = "1.0.176"
|
||||
trussed = { version = "0.1.0", default-features = false, features = ["clients-1", "crypto-client", "filesystem-client", "hmac-sha256", "serde-extensions", "virt"] }
|
||||
@@ -18,7 +18,7 @@ use trussed::{
|
||||
};
|
||||
|
||||
use super::Error;
|
||||
use crate::{Pin, PinId, MAX_PIN_LENGTH};
|
||||
use trussed_auth::{Pin, PinId, MAX_PIN_LENGTH};
|
||||
|
||||
const APP_SALT_PATH: &Path = path!("application_salt");
|
||||
|
||||
@@ -1,8 +1,28 @@
|
||||
// Copyright (C) Nitrokey GmbH
|
||||
// SPDX-License-Identifier: Apache-2.0 or MIT
|
||||
|
||||
#![no_std]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
non_ascii_idents,
|
||||
trivial_casts,
|
||||
unused,
|
||||
unused_qualifications,
|
||||
clippy::expect_used,
|
||||
clippy::unwrap_used
|
||||
)]
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
//! A Trussed backend implementing the [`AuthExtension`][].
|
||||
//!
|
||||
//! [`AuthBackend`][] is an implementation of the [`AuthExtension`][] that stores PINs in the
|
||||
//! filesystem.
|
||||
|
||||
mod data;
|
||||
|
||||
pub mod migrate;
|
||||
|
||||
use core::fmt;
|
||||
|
||||
use hkdf::Hkdf;
|
||||
@@ -20,15 +40,11 @@ use trussed::{
|
||||
types::{CoreContext, Location},
|
||||
Bytes,
|
||||
};
|
||||
use trussed_auth::{reply, AuthExtension, AuthReply, AuthRequest};
|
||||
|
||||
use crate::{
|
||||
backend::data::{expand_app_key, get_app_salt},
|
||||
extension::{reply, AuthExtension, AuthReply, AuthRequest},
|
||||
BACKEND_DIR,
|
||||
};
|
||||
use data::{Key, PinData, Salt, KEY_LEN, SALT_LEN};
|
||||
use data::{delete_app_salt, expand_app_key, get_app_salt, Key, PinData, Salt, KEY_LEN, SALT_LEN};
|
||||
|
||||
use self::data::delete_app_salt;
|
||||
const BACKEND_DIR: &Path = path!("backend-auth");
|
||||
|
||||
/// max accepted length for the hardware initial key material
|
||||
pub const MAX_HW_KEY_LEN: usize = 64;
|
||||
@@ -116,7 +132,7 @@ impl AuthBackend {
|
||||
/// Creates a new `AuthBackend` with a missing hw key
|
||||
///
|
||||
/// Contrary to [`new`](Self::new) which uses a default `&[]` key, this will make operations depending on the hardware key to fail:
|
||||
/// - [`set_pin`](crate::AuthClient::set_pin) with `derive_key = true`
|
||||
/// - [`set_pin`](trussed_auth::AuthClient::set_pin) with `derive_key = true`
|
||||
/// - All operations on a pin that was created with `derive_key = true`
|
||||
pub fn with_missing_hw_key(location: Location, layout: FilesystemLayout) -> Self {
|
||||
Self {
|
||||
@@ -412,7 +428,7 @@ impl ExtensionImpl<AuthExtension> for AuthBackend {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum Error {
|
||||
enum Error {
|
||||
NotFound,
|
||||
MissingHwKey,
|
||||
ReadFailed,
|
||||
@@ -35,7 +35,7 @@ fn migrate_single(fs: &dyn DynFilesystem, path: &Path) -> Result<(), Error> {
|
||||
///
|
||||
/// ```rust
|
||||
///# use littlefs2_core::{DynFilesystem, Error, path};
|
||||
///# use trussed_auth::migrate::migrate_remove_dat;
|
||||
///# use trussed_auth_backend::migrate::migrate_remove_dat;
|
||||
///# fn test(fs: &dyn DynFilesystem) -> Result<(), Error> {
|
||||
/// migrate_remove_dat(fs, &[path!("secrets"), path!("opcard")])?;
|
||||
///# Ok(())
|
||||
@@ -11,7 +11,8 @@ mod dispatch {
|
||||
service::ServiceResources,
|
||||
types::{Bytes, Context, Location},
|
||||
};
|
||||
use trussed_auth::{AuthBackend, AuthContext, AuthExtension, MAX_HW_KEY_LEN};
|
||||
use trussed_auth::AuthExtension;
|
||||
use trussed_auth_backend::{AuthBackend, AuthContext, MAX_HW_KEY_LEN};
|
||||
|
||||
pub const BACKENDS: &[BackendId<Backend>] =
|
||||
&[BackendId::Custom(Backend::Auth), BackendId::Core];
|
||||
@@ -55,7 +56,10 @@ mod dispatch {
|
||||
impl Dispatch {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
auth: AuthBackend::new(Location::Internal, trussed_auth::FilesystemLayout::V0),
|
||||
auth: AuthBackend::new(
|
||||
Location::Internal,
|
||||
trussed_auth_backend::FilesystemLayout::V0,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +68,7 @@ mod dispatch {
|
||||
auth: AuthBackend::with_hw_key(
|
||||
Location::Internal,
|
||||
hw_key,
|
||||
trussed_auth::FilesystemLayout::V0,
|
||||
trussed_auth_backend::FilesystemLayout::V0,
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -72,7 +76,7 @@ mod dispatch {
|
||||
Self {
|
||||
auth: AuthBackend::with_missing_hw_key(
|
||||
Location::Internal,
|
||||
trussed_auth::FilesystemLayout::V0,
|
||||
trussed_auth_backend::FilesystemLayout::V0,
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -136,7 +140,8 @@ use trussed::{
|
||||
types::{Bytes, Location, Message, PathBuf},
|
||||
virt::{self, Ram},
|
||||
};
|
||||
use trussed_auth::{AuthClient as _, PinId, MAX_HW_KEY_LEN};
|
||||
use trussed_auth::{AuthClient as _, PinId};
|
||||
use trussed_auth_backend::MAX_HW_KEY_LEN;
|
||||
|
||||
use dispatch::{Backend, Dispatch, BACKENDS};
|
||||
|
||||
@@ -11,15 +11,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased][]
|
||||
|
||||
- Use serde(rename) to save space on on the size of stored credentials ([#38][])
|
||||
- Remove the `dat` intermediary directory in file storage ([#39][])
|
||||
- Use `trussed-core` and remove default features for `trussed`
|
||||
|
||||
[#38]: https://github.com/trussed-dev/trussed-auth/pull/38
|
||||
[#39]: https://github.com/trussed-dev/trussed-auth/pull/39
|
||||
|
||||
[Unreleased]: https://github.com/trussed-dev/trussed-auth/compare/v0.3.0...HEAD
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Extract `AuthBackend` into `trussed-auth-backend` crate
|
||||
- Use `trussed-core` and remove default features for `trussed`
|
||||
|
||||
## [0.3.0][] - 2024-03-22
|
||||
|
||||
[0.3.0]: https://github.com/trussed-dev/trussed-auth/releases/tag/v0.3.0
|
||||
@@ -0,0 +1,18 @@
|
||||
# Copyright (C) Nitrokey GmbH
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
[package]
|
||||
name = "trussed-auth"
|
||||
version = "0.3.0"
|
||||
description = "Authentication extension for Trussed"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde.workspace = true
|
||||
trussed-core.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
heapless-bytes = "0.3"
|
||||
@@ -1,18 +1,169 @@
|
||||
// Copyright (C) Nitrokey GmbH
|
||||
// SPDX-License-Identifier: Apache-2.0 or MIT
|
||||
|
||||
#![cfg_attr(not(test), no_std)]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
non_ascii_idents,
|
||||
trivial_casts,
|
||||
unused,
|
||||
unused_qualifications,
|
||||
clippy::expect_used,
|
||||
clippy::unwrap_used
|
||||
)]
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
//! A Trussed API extension for authentication.
|
||||
//!
|
||||
//! This crate contains an API extension for [Trussed][], [`AuthExtension`][]. The extension
|
||||
//! currently provides basic PIN handling with retry counters. Applications can access it using
|
||||
//! the [`AuthClient`][] trait.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```
|
||||
//! use heapless_bytes::Bytes;
|
||||
//! use trussed_auth::{AuthClient, PinId};
|
||||
//! use trussed_core::syscall;
|
||||
//!
|
||||
//! #[repr(u8)]
|
||||
//! enum Pin {
|
||||
//! User = 0,
|
||||
//! }
|
||||
//!
|
||||
//! impl From<Pin> for PinId {
|
||||
//! fn from(pin: Pin) -> Self {
|
||||
//! (pin as u8).into()
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! fn authenticate_user<C: AuthClient>(client: &mut C, pin: Option<&[u8]>) -> bool {
|
||||
//! if !syscall!(client.has_pin(Pin::User)).has_pin {
|
||||
//! // no PIN set
|
||||
//! return true;
|
||||
//! }
|
||||
//! let Some(pin) = pin else {
|
||||
//! // PIN is set but not provided
|
||||
//! return false;
|
||||
//! };
|
||||
//! let Ok(pin) = Bytes::from_slice(pin) else {
|
||||
//! // provided PIN is too long
|
||||
//! return false;
|
||||
//! };
|
||||
//! // check PIN
|
||||
//! syscall!(client.check_pin(Pin::User, pin)).success
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! [Trussed]: https://docs.rs/trussed
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub mod reply;
|
||||
#[allow(missing_docs)]
|
||||
pub mod request;
|
||||
|
||||
use core::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use trussed_core::{
|
||||
config::MAX_SHORT_DATA_LENGTH,
|
||||
serde_extensions::{Extension, ExtensionClient, ExtensionResult},
|
||||
types::{KeyId, Message},
|
||||
types::{Bytes, KeyId, Message, PathBuf},
|
||||
};
|
||||
|
||||
use crate::{Pin, PinId};
|
||||
/// The maximum length of a PIN.
|
||||
pub const MAX_PIN_LENGTH: usize = MAX_SHORT_DATA_LENGTH;
|
||||
|
||||
/// A PIN.
|
||||
pub type Pin = Bytes<MAX_PIN_LENGTH>;
|
||||
|
||||
/// The ID of a PIN within the namespace of a client.
|
||||
///
|
||||
/// It is recommended that applications use an enum that implements `Into<PinId>`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use trussed_auth::PinId;
|
||||
///
|
||||
/// #[repr(u8)]
|
||||
/// enum Pin {
|
||||
/// User = 0,
|
||||
/// Admin = 1,
|
||||
/// ResetCode = 2,
|
||||
/// }
|
||||
///
|
||||
/// impl From<Pin> for PinId {
|
||||
/// fn from(pin: Pin) -> Self {
|
||||
/// (pin as u8).into()
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize,
|
||||
)]
|
||||
pub struct PinId(u8);
|
||||
|
||||
/// Error obtained when trying to parse a [`PinId`][] either through [`PinId::from_path`][] or through the [`FromStr`][] implementation.
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize,
|
||||
)]
|
||||
pub struct PinIdFromStrError;
|
||||
|
||||
impl PinId {
|
||||
/// Get the path to the PIN id.
|
||||
///
|
||||
/// Path are of the form `pin.XX` where `xx` is the hexadecimal representation of the PIN number.
|
||||
pub fn path(&self) -> PathBuf {
|
||||
let mut path = [0; 6];
|
||||
path[0..4].copy_from_slice(b"pin.");
|
||||
path[4..].copy_from_slice(&self.hex());
|
||||
|
||||
// path has only ASCII characters and is not too long
|
||||
#[allow(clippy::unwrap_used)]
|
||||
PathBuf::try_from(&path).ok().unwrap()
|
||||
}
|
||||
|
||||
/// Get the hex representation of the PIN id
|
||||
pub fn hex(&self) -> [u8; 2] {
|
||||
const CHARS: &[u8; 16] = b"0123456789abcdef";
|
||||
[
|
||||
CHARS[usize::from(self.0 >> 4)],
|
||||
CHARS[usize::from(self.0 & 0xf)],
|
||||
]
|
||||
}
|
||||
|
||||
/// Parse a PinId path
|
||||
pub fn from_path(path: &str) -> Result<Self, PinIdFromStrError> {
|
||||
let path = path.strip_prefix("pin.").ok_or(PinIdFromStrError)?;
|
||||
if path.len() != 2 {
|
||||
return Err(PinIdFromStrError);
|
||||
}
|
||||
|
||||
let id = u8::from_str_radix(path, 16).map_err(|_| PinIdFromStrError)?;
|
||||
Ok(PinId(id))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for PinId {
|
||||
fn from(id: u8) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PinId> for u8 {
|
||||
fn from(id: PinId) -> Self {
|
||||
id.0
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PinId {
|
||||
type Err = PinIdFromStrError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Self::from_path(s)
|
||||
}
|
||||
}
|
||||
|
||||
/// A result returned by [`AuthClient`][].
|
||||
pub type AuthResult<'a, R, C> = ExtensionResult<'a, AuthExtension, R, C>;
|
||||
@@ -197,3 +348,21 @@ pub trait AuthClient: ExtensionClient<AuthExtension> {
|
||||
}
|
||||
|
||||
impl<C: ExtensionClient<AuthExtension>> AuthClient for C {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::PinId;
|
||||
use trussed_core::types::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn pin_id_path() {
|
||||
for i in 0..=u8::MAX {
|
||||
assert_eq!(Ok(PinId(i)), PinId::from_path(PinId(i).path().as_ref()));
|
||||
let actual = PinId(i).path();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let expected = PathBuf::try_from(format!("pin.{i:02x}").as_str()).unwrap();
|
||||
println!("id: {i}, actual: {actual}, expected: {expected}");
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
-190
@@ -1,190 +0,0 @@
|
||||
// Copyright (C) Nitrokey GmbH
|
||||
// SPDX-License-Identifier: Apache-2.0 or MIT
|
||||
|
||||
#![cfg_attr(not(test), no_std)]
|
||||
#![warn(
|
||||
missing_debug_implementations,
|
||||
missing_docs,
|
||||
non_ascii_idents,
|
||||
trivial_casts,
|
||||
unused,
|
||||
unused_qualifications,
|
||||
clippy::expect_used,
|
||||
clippy::unwrap_used
|
||||
)]
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
//! A Trussed API extension for authentication and a backend that implements it.
|
||||
//!
|
||||
//! This crate contains an API extension for [Trussed][], [`AuthExtension`][]. The extension
|
||||
//! currently provides basic PIN handling with retry counters. Applications can access it using
|
||||
//! the [`AuthClient`][] trait.
|
||||
//!
|
||||
//! This crate also contains [`AuthBackend`][], an implementation of the auth extension that stores
|
||||
//! PINs in the filesystem.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```
|
||||
//! use trussed_core::{types::Bytes, syscall};
|
||||
//! use trussed_auth::{AuthClient, PinId};
|
||||
//!
|
||||
//! #[repr(u8)]
|
||||
//! enum Pin {
|
||||
//! User = 0,
|
||||
//! }
|
||||
//!
|
||||
//! impl From<Pin> for PinId {
|
||||
//! fn from(pin: Pin) -> Self {
|
||||
//! (pin as u8).into()
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! fn authenticate_user<C: AuthClient>(client: &mut C, pin: Option<&[u8]>) -> bool {
|
||||
//! if !syscall!(client.has_pin(Pin::User)).has_pin {
|
||||
//! // no PIN set
|
||||
//! return true;
|
||||
//! }
|
||||
//! let Some(pin) = pin else {
|
||||
//! // PIN is set but not provided
|
||||
//! return false;
|
||||
//! };
|
||||
//! let Ok(pin) = Bytes::from_slice(pin) else {
|
||||
//! // provided PIN is too long
|
||||
//! return false;
|
||||
//! };
|
||||
//! // check PIN
|
||||
//! syscall!(client.check_pin(Pin::User, pin)).success
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! [Trussed]: https://docs.rs/trussed
|
||||
|
||||
mod backend;
|
||||
mod extension;
|
||||
|
||||
pub mod migrate;
|
||||
|
||||
use core::str::FromStr;
|
||||
|
||||
use littlefs2_core::{path, Path, PathBuf};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use trussed_core::{config::MAX_SHORT_DATA_LENGTH, types::Bytes};
|
||||
|
||||
pub use backend::{AuthBackend, AuthContext, FilesystemLayout, MAX_HW_KEY_LEN};
|
||||
pub use extension::{
|
||||
reply, request, AuthClient, AuthExtension, AuthReply, AuthRequest, AuthResult,
|
||||
};
|
||||
|
||||
/// The maximum length of a PIN.
|
||||
pub const MAX_PIN_LENGTH: usize = MAX_SHORT_DATA_LENGTH;
|
||||
|
||||
/// A PIN.
|
||||
pub type Pin = Bytes<MAX_PIN_LENGTH>;
|
||||
|
||||
const BACKEND_DIR: &Path = path!("backend-auth");
|
||||
|
||||
/// The ID of a PIN within the namespace of a client.
|
||||
///
|
||||
/// It is recommended that applications use an enum that implements `Into<PinId>`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use trussed_auth::PinId;
|
||||
///
|
||||
/// #[repr(u8)]
|
||||
/// enum Pin {
|
||||
/// User = 0,
|
||||
/// Admin = 1,
|
||||
/// ResetCode = 2,
|
||||
/// }
|
||||
///
|
||||
/// impl From<Pin> for PinId {
|
||||
/// fn from(pin: Pin) -> Self {
|
||||
/// (pin as u8).into()
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize,
|
||||
)]
|
||||
pub struct PinId(u8);
|
||||
|
||||
/// Error obtained when trying to parse a [`PinId`][] either through [`PinId::from_path`][] or through the [`FromStr`][] implementation.
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize,
|
||||
)]
|
||||
pub struct PinIdFromStrError;
|
||||
|
||||
impl PinId {
|
||||
/// Get the path to the PIN id.
|
||||
///
|
||||
/// Path are of the form `pin.XX` where `xx` is the hexadecimal representation of the PIN number.
|
||||
pub fn path(&self) -> PathBuf {
|
||||
let mut path = [0; 6];
|
||||
path[0..4].copy_from_slice(b"pin.");
|
||||
path[4..].copy_from_slice(&self.hex());
|
||||
|
||||
// path has only ASCII characters and is not too long
|
||||
#[allow(clippy::unwrap_used)]
|
||||
PathBuf::try_from(&path).ok().unwrap()
|
||||
}
|
||||
|
||||
/// Get the hex representation of the PIN id
|
||||
pub fn hex(&self) -> [u8; 2] {
|
||||
const CHARS: &[u8; 16] = b"0123456789abcdef";
|
||||
[
|
||||
CHARS[usize::from(self.0 >> 4)],
|
||||
CHARS[usize::from(self.0 & 0xf)],
|
||||
]
|
||||
}
|
||||
|
||||
/// Parse a PinId path
|
||||
pub fn from_path(path: &str) -> Result<Self, PinIdFromStrError> {
|
||||
let path = path.strip_prefix("pin.").ok_or(PinIdFromStrError)?;
|
||||
if path.len() != 2 {
|
||||
return Err(PinIdFromStrError);
|
||||
}
|
||||
|
||||
let id = u8::from_str_radix(path, 16).map_err(|_| PinIdFromStrError)?;
|
||||
Ok(PinId(id))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for PinId {
|
||||
fn from(id: u8) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PinId> for u8 {
|
||||
fn from(id: PinId) -> Self {
|
||||
id.0
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PinId {
|
||||
type Err = PinIdFromStrError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Self::from_path(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::PinId;
|
||||
use trussed_core::types::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn pin_id_path() {
|
||||
for i in 0..=u8::MAX {
|
||||
assert_eq!(Ok(PinId(i)), PinId::from_path(PinId(i).path().as_ref()));
|
||||
let actual = PinId(i).path();
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let expected = PathBuf::try_from(format!("pin.{i:02x}").as_str()).unwrap();
|
||||
println!("id: {i}, actual: {actual}, expected: {expected}");
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user