Implement application_key derivition

This commit is contained in:
Sosthène Guédon
2023-03-03 11:13:50 +01:00
committed by Markus Meissner
parent e099a2ffdb
commit e6dab308b5
7 changed files with 184 additions and 12 deletions
+3
View File
@@ -11,6 +11,9 @@ license = "Apache-2.0 OR MIT"
description = "Authentication extension and backend for Trussed"
[dependencies]
hkdf = "0.12.3"
hmac = "0.12.1"
rand_core = "0.6.4"
serde = { version = "1", default-features = false }
serde-byte-array = "0.1.2"
sha2 = { version = "0.10.6", default-features = false }
+136 -4
View File
@@ -3,6 +3,11 @@
mod data;
use core::fmt;
use hkdf::Hkdf;
use rand_core::{CryptoRng, RngCore};
use sha2::Sha256;
use trussed::{
backend::Backend,
error::Result,
@@ -11,14 +16,34 @@ use trussed::{
service::ServiceResources,
store::filestore::Filestore,
types::{CoreContext, Location, PathBuf},
Bytes,
};
use crate::{
extension::{reply, AuthExtension, AuthReply, AuthRequest},
PIN_PATH,
PIN_PATH, SALT_PATH,
};
use data::PinData;
const MAX_HW_KEY_LEN: usize = 64;
#[derive(Clone)]
enum HardwareKey {
None,
Raw(Bytes<MAX_HW_KEY_LEN>),
Extracted(Hkdf<Sha256>),
}
impl fmt::Debug for HardwareKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => f.debug_tuple("None").finish(),
Self::Raw(_) => f.debug_tuple("Raw").field(&"[redacted]").finish(),
Self::Extracted(_) => f.debug_tuple("Raw").field(&"[redacted]").finish(),
}
}
}
/// A basic implementation of the [`AuthExtension`][].
///
/// This implementation stores PINs together with their retry counters on the filesystem. PINs are
@@ -26,24 +51,128 @@ use data::PinData;
#[derive(Clone, Debug)]
pub struct AuthBackend {
location: Location,
hw_key: HardwareKey,
}
impl AuthBackend {
/// Creates a new `AuthBackend` using the given storage location for the PINs.
pub fn new(location: Location) -> Self {
Self { location }
Self {
location,
hw_key: HardwareKey::None,
}
}
/// Creates a new `AuthBackend` with a given key.
///
/// This key is used to strengthen key generation from the pins
pub fn with_hw_key(location: Location, hw_key: Bytes<MAX_HW_KEY_LEN>) -> Self {
Self {
location,
hw_key: HardwareKey::Raw(hw_key),
}
}
fn get_salt<R: CryptoRng + RngCore>(
&self,
trussed_filestore: &mut impl Filestore,
rng: &mut R,
) -> Result<[u8; 32], Error> {
let path = PathBuf::from(SALT_PATH);
trussed_filestore
.read(&path, self.location)
.map(|d: Bytes<32>| (&**d).try_into().unwrap())
.or_else(|_| {
if trussed_filestore
.metadata(&path, self.location)
.or(Err(Error::ReadFailed))?
.is_some()
{
return Err(Error::ReadFailed);
}
let mut salt = [0; 32];
rng.fill_bytes(&mut salt);
trussed_filestore
.write(&path, self.location, &salt)
.or(Err(Error::WriteFailed))
.and(Ok(salt))
})
}
fn extract<R: CryptoRng + RngCore>(
&mut self,
trussed_filestore: &mut impl Filestore,
ikm: Option<Bytes<MAX_HW_KEY_LEN>>,
rng: &mut R,
) -> Result<&Hkdf<Sha256>, Error> {
let ikm: &[u8] = ikm.as_deref().map(|i| &**i).unwrap_or(&[]);
let salt = self.get_salt(trussed_filestore, rng)?;
let kdf = Hkdf::new(Some(&salt), ikm);
self.hw_key = HardwareKey::Extracted(kdf);
match &self.hw_key {
HardwareKey::Extracted(kdf) => Ok(kdf),
// hw_key was just set to Extracted
_ => unreachable!(),
}
}
fn expand(kdf: &Hkdf<Sha256>, client_id: &PathBuf) -> [u8; 32] {
let mut out = [0; 32];
kdf.expand(client_id.as_ref().as_bytes(), &mut out).unwrap();
out
}
fn generate_app_key<R: CryptoRng + RngCore>(
&mut self,
client_id: PathBuf,
trussed_filestore: &mut impl Filestore,
rng: &mut R,
) -> Result<[u8; 32], Error> {
Ok(match &self.hw_key {
HardwareKey::Extracted(okm) => Self::expand(okm, &client_id),
HardwareKey::Raw(hw_k) => {
let kdf = self.extract(trussed_filestore, Some(hw_k.clone()), rng)?;
Self::expand(kdf, &client_id)
}
HardwareKey::None => {
let kdf = self.extract(trussed_filestore, None, rng)?;
Self::expand(kdf, &client_id)
}
})
}
#[allow(unused)]
fn get_app_key<R: CryptoRng + RngCore>(
&mut self,
client_id: PathBuf,
trussed_filestore: &mut impl Filestore,
ctx: &mut AuthContext,
rng: &mut R,
) -> Result<[u8; 32], Error> {
if let Some(app_key) = ctx.application_key {
return Ok(app_key);
}
let app_key = self.generate_app_key(client_id, trussed_filestore, rng)?;
ctx.application_key = Some(app_key);
Ok(app_key)
}
}
/// Per-client context for [`AuthBackend`][]
#[derive(Default, Debug)]
pub struct AuthContext {
application_key: Option<[u8; 32]>,
}
impl<P: Platform> Backend<P> for AuthBackend {
type Context = ();
type Context = AuthContext;
}
impl<P: Platform> ExtensionImpl<AuthExtension, P> for AuthBackend {
fn extension_request(
&mut self,
core_ctx: &mut CoreContext,
_ctx: &mut (),
_ctx: &mut AuthContext,
request: &AuthRequest,
resources: &mut ServiceResources<P>,
) -> Result<AuthReply> {
@@ -61,6 +190,9 @@ impl<P: Platform> ExtensionImpl<AuthExtension, P> for AuthBackend {
)?;
Ok(reply::CheckPin { success }.into())
}
AuthRequest::GetPinKey(_request) => {
todo!()
}
AuthRequest::SetPin(request) => {
let mut rng = resources.rng().map_err(|_| Error::RngFailed)?;
PinData::new(request.id, &request.pin, request.retries, &mut rng)
+2
View File
@@ -30,6 +30,7 @@ impl Extension for AuthExtension {
pub enum AuthRequest {
HasPin(request::HasPin),
CheckPin(request::CheckPin),
GetPinKey(request::GetPinKey),
SetPin(request::SetPin),
DeletePin(request::DeletePin),
DeleteAllPins(request::DeleteAllPins),
@@ -41,6 +42,7 @@ pub enum AuthRequest {
pub enum AuthReply {
HasPin(reply::HasPin),
CheckPin(reply::CheckPin),
GetPinKey(reply::GetPinKey),
SetPin(reply::SetPin),
DeletePin(reply::DeletePin),
DeleteAllPins(reply::DeleteAllPins),
+17 -1
View File
@@ -2,7 +2,10 @@
// SPDX-License-Identifier: Apache-2.0 or MIT
use serde::{Deserialize, Serialize};
use trussed::error::{Error, Result};
use trussed::{
error::{Error, Result},
types::KeyId,
};
use super::AuthReply;
@@ -41,6 +44,19 @@ impl From<CheckPin> for AuthReply {
}
}
#[derive(Debug, Deserialize, Serialize)]
#[must_use]
pub struct GetPinKey {
/// None means the check failed
pub result: Option<KeyId>,
}
impl From<GetPinKey> for AuthReply {
fn from(reply: GetPinKey) -> Self {
Self::GetPinKey(reply)
}
}
impl TryFrom<AuthReply> for CheckPin {
type Error = Error;
+12
View File
@@ -29,6 +29,18 @@ impl From<CheckPin> for AuthRequest {
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct GetPinKey {
pub id: PinId,
pub pin: Pin,
}
impl From<GetPinKey> for AuthRequest {
fn from(request: GetPinKey) -> Self {
Self::GetPinKey(request)
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct SetPin {
pub id: PinId,
+2 -1
View File
@@ -69,7 +69,7 @@ use trussed::{
types::{Bytes, PathBuf},
};
pub use backend::AuthBackend;
pub use backend::{AuthBackend, AuthContext};
pub use extension::{
reply, request, AuthClient, AuthExtension, AuthReply, AuthRequest, AuthResult,
};
@@ -81,6 +81,7 @@ pub const MAX_PIN_LENGTH: usize = MAX_SHORT_DATA_LENGTH;
pub type Pin = Bytes<MAX_PIN_LENGTH>;
const PIN_PATH: &str = "backend-auth/pin";
const SALT_PATH: &str = "backend-auth/salt";
/// The ID of a PIN within the namespace of a client.
///
+12 -6
View File
@@ -11,7 +11,7 @@ mod dispatch {
service::ServiceResources,
types::{Context, Location},
};
use trussed_auth::{AuthBackend, AuthExtension};
use trussed_auth::{AuthBackend, AuthContext, AuthExtension};
pub const BACKENDS: &[BackendId<Backend>] =
&[BackendId::Custom(Backend::Auth), BackendId::Core];
@@ -47,6 +47,11 @@ mod dispatch {
auth: AuthBackend,
}
#[derive(Default)]
pub struct DispatchContext {
auth: AuthContext,
}
impl Dispatch {
pub fn new() -> Self {
Self {
@@ -57,7 +62,7 @@ mod dispatch {
impl<P: Platform> ExtensionDispatch<P> for Dispatch {
type BackendId = Backend;
type Context = ();
type Context = DispatchContext;
type ExtensionId = Extension;
fn core_request(
@@ -68,9 +73,10 @@ mod dispatch {
resources: &mut ServiceResources<P>,
) -> Result<Reply, Error> {
match backend {
Backend::Auth => self
.auth
.request(&mut ctx.core, &mut (), request, resources),
Backend::Auth => {
self.auth
.request(&mut ctx.core, &mut ctx.backends.auth, request, resources)
}
}
}
@@ -86,7 +92,7 @@ mod dispatch {
Backend::Auth => match extension {
Extension::Auth => self.auth.extension_request_serialized(
&mut ctx.core,
&mut (),
&mut ctx.backends.auth,
request,
resources,
),