#![cfg_attr(not(test), no_std)]
#[macro_use]
extern crate delog;
generate_macros!();
pub use state::migrate;
use core::time::Duration;
use trussed_core::{
mechanisms, syscall,
types::{
KeyId, KeySerialization, Location, Mechanism, SerializedKey, Signature,
SignatureSerialization, StorageAttributes,
},
CertificateClient, CryptoClient, FilesystemClient, ManagementClient, UiClient,
};
use trussed_fs_info::{FsInfoClient, FsInfoReply};
use trussed_hkdf::HkdfClient;
pub use ctap_types::Error;
mod ctap1;
mod ctap2;
#[cfg(feature = "dispatch")]
mod dispatch;
pub mod constants;
pub mod credential;
pub mod state;
pub use ctap2::large_blobs::Config as LargeBlobsConfig;
pub type Result<T> = core::result::Result<T, Error>;
pub trait TrussedRequirements:
CertificateClient
+ CryptoClient
+ FilesystemClient
+ ManagementClient
+ UiClient
+ mechanisms::P256
+ mechanisms::Chacha8Poly1305
+ mechanisms::Aes256Cbc
+ mechanisms::Sha256
+ mechanisms::HmacSha256
+ mechanisms::Ed255
+ FsInfoClient
+ HkdfClient
+ ExtensionRequirements
{
}
impl<T> TrussedRequirements for T where
T: CertificateClient
+ CryptoClient
+ FilesystemClient
+ ManagementClient
+ UiClient
+ mechanisms::P256
+ mechanisms::Chacha8Poly1305
+ mechanisms::Aes256Cbc
+ mechanisms::Sha256
+ mechanisms::HmacSha256
+ mechanisms::Ed255
+ FsInfoClient
+ HkdfClient
+ ExtensionRequirements
{
}
#[cfg(not(feature = "chunked"))]
pub trait ExtensionRequirements {}
#[cfg(not(feature = "chunked"))]
impl<T> ExtensionRequirements for T {}
#[cfg(feature = "chunked")]
pub trait ExtensionRequirements: trussed_chunked::ChunkedClient {}
#[cfg(feature = "chunked")]
impl<T> ExtensionRequirements for T where T: trussed_chunked::ChunkedClient {}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Config {
pub max_msg_size: usize,
pub skip_up_timeout: Option<Duration>,
pub max_resident_credential_count: Option<u32>,
pub large_blobs: Option<ctap2::large_blobs::Config>,
pub nfc_transport: bool,
}
impl Config {
pub fn supports_large_blobs(&self) -> bool {
self.large_blobs.is_some()
}
}
pub struct Authenticator<UP, T>
where
UP: UserPresence,
{
trussed: T,
state: state::State,
up: UP,
config: Config,
}
fn format_hex<'a>(data: &[u8], buffer: &'a mut [u8]) -> &'a str {
const HEX_CHARS: &[u8] = b"0123456789abcdef";
assert!(data.len() * 2 >= buffer.len());
for (idx, byte) in data.iter().enumerate() {
buffer[idx * 2] = HEX_CHARS[(byte >> 4) as usize];
buffer[idx * 2 + 1] = HEX_CHARS[(byte & 0xf) as usize];
}
unsafe { core::str::from_utf8_unchecked(&buffer[0..data.len() * 2]) }
}
#[inline]
#[allow(dead_code)]
pub(crate) fn msp() -> u32 {
0x2000_0000
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(i32)]
#[non_exhaustive]
pub enum SigningAlgorithm {
Ed25519 = -8,
P256 = -7,
}
impl SigningAlgorithm {
pub fn mechanism(&self) -> Mechanism {
match self {
Self::Ed25519 => Mechanism::Ed255,
Self::P256 => Mechanism::P256,
}
}
pub fn signature_serialization(&self) -> SignatureSerialization {
match self {
Self::Ed25519 => SignatureSerialization::Raw,
Self::P256 => SignatureSerialization::Asn1Der,
}
}
pub fn generate_private_key<C: CryptoClient>(
&self,
trussed: &mut C,
location: Location,
) -> KeyId {
syscall!(trussed.generate_key(
self.mechanism(),
StorageAttributes::new().set_persistence(location)
))
.key
}
pub fn derive_public_key<C: CryptoClient>(
&self,
trussed: &mut C,
private_key: KeyId,
) -> SerializedKey {
let mechanism = self.mechanism();
let public_key = syscall!(trussed.derive_key(
mechanism,
private_key,
None,
StorageAttributes::new().set_persistence(Location::Volatile)
))
.key;
let cose_public_key =
syscall!(trussed.serialize_key(mechanism, public_key, KeySerialization::Cose))
.serialized_key;
if !syscall!(trussed.delete(public_key)).success {
error!("failed to delete credential public key");
}
cose_public_key
}
pub fn sign<C: CryptoClient>(&self, trussed: &mut C, key: KeyId, data: &[u8]) -> Signature {
syscall!(trussed.sign(self.mechanism(), key, data, self.signature_serialization()))
.signature
}
}
impl From<SigningAlgorithm> for i32 {
fn from(alg: SigningAlgorithm) -> Self {
match alg {
SigningAlgorithm::P256 => -7,
SigningAlgorithm::Ed25519 => -8,
}
}
}
impl TryFrom<i32> for SigningAlgorithm {
type Error = Error;
fn try_from(alg: i32) -> Result<Self> {
Ok(match alg {
-7 => SigningAlgorithm::P256,
-8 => SigningAlgorithm::Ed25519,
_ => return Err(Error::UnsupportedAlgorithm),
})
}
}
pub trait UserPresence: Copy {
fn user_present<T: TrussedRequirements>(
self,
trussed: &mut T,
timeout_milliseconds: u32,
) -> Result<()>;
}
#[deprecated(note = "use `Silent` directly`")]
#[doc(hidden)]
pub type SilentAuthenticator = Silent;
#[derive(Copy, Clone)]
pub struct Silent {}
impl UserPresence for Silent {
fn user_present<T: TrussedRequirements>(self, _: &mut T, _: u32) -> Result<()> {
Ok(())
}
}
#[deprecated(note = "use `Conforming` directly")]
#[doc(hidden)]
pub type NonSilentAuthenticator = Conforming;
#[derive(Copy, Clone)]
pub struct Conforming {}
impl UserPresence for Conforming {
fn user_present<T: TrussedRequirements>(
self,
trussed: &mut T,
timeout_milliseconds: u32,
) -> Result<()> {
let result = syscall!(trussed.confirm_user_present(timeout_milliseconds)).result;
result.map_err(|err| match err {
trussed_core::types::consent::Error::TimedOut => Error::UserActionTimeout,
trussed_core::types::consent::Error::Interrupted => Error::KeepaliveCancel,
_ => Error::OperationDenied,
})
}
}
impl<UP, T> Authenticator<UP, T>
where
UP: UserPresence,
T: TrussedRequirements,
{
pub fn new(trussed: T, up: UP, config: Config) -> Self {
let state = state::State::new();
Self {
trussed,
state,
up,
config,
}
}
fn estimate_remaining_inner(info: &FsInfoReply) -> Option<u32> {
let block_size = info.block_info.as_ref()?.size;
let size_taken = 2 * block_size + 400;
Some((info.available_space.saturating_sub(5 * block_size) / size_taken) as u32)
}
fn estimate_remaining(&mut self) -> Option<u32> {
let info = syscall!(self.trussed.fs_info(Location::Internal));
debug!("Got filesystem info: {info:?}");
Self::estimate_remaining_inner(&info)
}
fn can_fit_inner(info: &FsInfoReply, size: usize) -> Option<bool> {
let block_size = info.block_info.as_ref()?.size;
let size_taken = 6 * block_size + size + 50;
Some(size_taken < info.available_space)
}
fn can_fit(&mut self, size: usize) -> Option<bool> {
debug!("Can fit for {size} bytes");
let info = syscall!(self.trussed.fs_info(Location::Internal));
debug!("Got filesystem info: {info:?}");
debug!(
"Available storage: {:?}",
Self::estimate_remaining_inner(&info)
);
Self::can_fit_inner(&info, size)
}
fn hash(&mut self, data: &[u8]) -> [u8; 32] {
let hash = syscall!(self.trussed.hash_sha256(data)).hash;
hash.as_slice().try_into().expect("hash should fit")
}
fn nonce(&mut self) -> [u8; 12] {
let bytes = syscall!(self.trussed.random_bytes(12)).bytes;
bytes.as_slice().try_into().expect("hash should fit")
}
fn skip_up_check(&mut self) -> bool {
if let Some(timeout) = self.config.skip_up_timeout.take() {
let uptime = syscall!(self.trussed.uptime()).uptime;
if uptime < timeout {
info_now!("skip up check directly after boot");
return true;
}
}
false
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn hex() {
let data = [0x01, 0x02, 0xB1, 0xA1];
let buffer = &mut [0; 8];
assert_eq!(format_hex(&data, buffer), "0102b1a1");
assert_eq!(buffer, b"0102b1a1");
}
}