mirror of
https://github.com/trussed-dev/trussed-auth.git
synced 2026-06-20 04:16:21 -07:00
Implement application_key derivition
This commit is contained in:
committed by
Markus Meissner
parent
e099a2ffdb
commit
e6dab308b5
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user