mirror of
https://github.com/trussed-dev/trussed-auth.git
synced 2026-06-20 04:16:21 -07:00
Implement X25519 key generation
This commit is contained in:
@@ -15,6 +15,7 @@ chacha20poly1305 = { version = "0.10.1", default-features = false, features = ["
|
||||
hkdf = "0.12.3"
|
||||
hmac = "0.12.1"
|
||||
rand_core = "0.6.4"
|
||||
salty = { version = "0.3.0", default-features = false }
|
||||
serde = { version = "1", default-features = false }
|
||||
serde-byte-array = "0.1.2"
|
||||
sha2 = { version = "0.10.6", default-features = false }
|
||||
|
||||
+8
-6
@@ -25,7 +25,7 @@ use crate::{
|
||||
extension::{reply, AuthExtension, AuthReply, AuthRequest},
|
||||
BACKEND_DIR,
|
||||
};
|
||||
use data::{PinData, Salt, CHACHA_KEY_LEN, SALT_LEN};
|
||||
use data::{DeriveKey, PinData, Salt, CHACHA_KEY_LEN, SALT_LEN};
|
||||
|
||||
use self::data::{delete_app_salt, ChachaKey};
|
||||
|
||||
@@ -276,17 +276,19 @@ impl ExtensionImpl<AuthExtension> for AuthBackend {
|
||||
Ok(reply::ChangePin { success }.into())
|
||||
}
|
||||
AuthRequest::SetPin(request) => {
|
||||
let maybe_app_key = if request.derive_key {
|
||||
Some(self.get_app_key(client_id, global_fs, ctx, rng)?)
|
||||
} else {
|
||||
None
|
||||
let key_derivation = match request.derive_key {
|
||||
Some(key_type) => Some(DeriveKey {
|
||||
application_key: self.get_app_key(client_id, global_fs, ctx, rng)?,
|
||||
key_type,
|
||||
}),
|
||||
None => None,
|
||||
};
|
||||
PinData::new(
|
||||
request.id,
|
||||
&request.pin,
|
||||
request.retries,
|
||||
rng,
|
||||
maybe_app_key.as_ref(),
|
||||
key_derivation,
|
||||
)
|
||||
.save(fs, self.location)?;
|
||||
Ok(reply::SetPin.into())
|
||||
|
||||
+86
-22
@@ -17,7 +17,7 @@ use trussed::{
|
||||
};
|
||||
|
||||
use super::Error;
|
||||
use crate::{Pin, PinId, MAX_PIN_LENGTH};
|
||||
use crate::{request::DerivedKeyMechanism, Pin, PinId, MAX_PIN_LENGTH};
|
||||
|
||||
pub(crate) const SIZE: usize = 256;
|
||||
pub(crate) const CHACHA_TAG_LEN: usize = 16;
|
||||
@@ -31,7 +31,7 @@ pub(crate) type Salt = ByteArray<SALT_LEN>;
|
||||
pub(crate) type Hash = ByteArray<HASH_LEN>;
|
||||
pub(crate) type ChaChaTag = ByteArray<CHACHA_TAG_LEN>;
|
||||
pub(crate) type ChachaKey = ByteArray<CHACHA_KEY_LEN>;
|
||||
pub(crate) type X25519Key = ByteArray<X25519_KEY_LEN>;
|
||||
pub(crate) type X25519Key = [u8; X25519_KEY_LEN];
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) enum Key {
|
||||
@@ -48,7 +48,7 @@ impl Serialize for Key {
|
||||
Key::Chacha20Poly1305(k) => serializer.serialize_bytes(&**k),
|
||||
Key::X25519(k) => {
|
||||
let mut encoded = [0; ENCODED_X25519_KEY_LEN];
|
||||
encoded[0..X25519_KEY_LEN].copy_from_slice(&**k);
|
||||
encoded[0..X25519_KEY_LEN].copy_from_slice(&*k);
|
||||
serializer.serialize_bytes(&encoded)
|
||||
}
|
||||
}
|
||||
@@ -76,9 +76,11 @@ impl<'de> Deserialize<'de> for Key {
|
||||
CHACHA_KEY_LEN => Ok(Key::Chacha20Poly1305(ByteArray::new(
|
||||
v.try_into().expect("Len was just checked"),
|
||||
))),
|
||||
ENCODED_X25519_KEY_LEN => Ok(Key::X25519(ByteArray::new(
|
||||
v.try_into().expect("Len was just checked"),
|
||||
))),
|
||||
ENCODED_X25519_KEY_LEN if v[X25519_KEY_LEN] == 0 => Ok(Key::X25519(
|
||||
v[..X25519_KEY_LEN]
|
||||
.try_into()
|
||||
.expect("Len was just checked"),
|
||||
)),
|
||||
_ => Err(E::invalid_length(v.len(), &self)),
|
||||
}
|
||||
}
|
||||
@@ -188,6 +190,12 @@ pub(crate) struct PinData {
|
||||
data: KeyOrHash,
|
||||
}
|
||||
|
||||
/// Information required to derive a key
|
||||
pub(crate) struct DeriveKey {
|
||||
pub(crate) application_key: ChachaKey,
|
||||
pub(crate) key_type: DerivedKeyMechanism,
|
||||
}
|
||||
|
||||
impl PinData {
|
||||
/// An application_key of `None` means that the pin should only be salted/hashed
|
||||
/// `Some` means that it should instead be used to wrap a 32 bytes encryption key
|
||||
@@ -196,35 +204,55 @@ impl PinData {
|
||||
pin: &Pin,
|
||||
retries: Option<u8>,
|
||||
rng: &mut R,
|
||||
application_key: Option<&ChachaKey>,
|
||||
derive_parameter: Option<DeriveKey>,
|
||||
) -> Self
|
||||
where
|
||||
R: CryptoRng + RngCore,
|
||||
{
|
||||
let mut salt = Salt::default();
|
||||
rng.fill_bytes(salt.as_mut());
|
||||
let data = application_key
|
||||
.map(|k| {
|
||||
use chacha20poly1305::{AeadInPlace, KeyInit};
|
||||
let data = match derive_parameter {
|
||||
None => KeyOrHash::Hash(hash(id, pin, &salt)),
|
||||
Some(DeriveKey {
|
||||
application_key,
|
||||
key_type: DerivedKeyMechanism::Chacha8Poly1305,
|
||||
}) => {
|
||||
let mut key = ChachaKey::default();
|
||||
rng.fill_bytes(&mut *key);
|
||||
let pin_key = derive_key(id, pin, &salt, k);
|
||||
let aead = ChaCha8Poly1305::new((&*pin_key).into());
|
||||
// The pin key is only ever used to once to wrap a key. Nonce reuse is not a concern
|
||||
// Because the salt is also used in the key derivation process, PIN reuse across PINs will still lead to different keys
|
||||
let nonce = Default::default();
|
||||
#[allow(clippy::expect_used)]
|
||||
let tag: [u8; CHACHA_TAG_LEN] = aead
|
||||
.encrypt_in_place_detached(&nonce, &[u8::from(id)], &mut *key)
|
||||
.expect("Wrapping the key should always work, length are acceptable")
|
||||
.into();
|
||||
let tag = encrypt_pin_data(id, pin, &salt, &application_key, &mut *key, None);
|
||||
|
||||
KeyOrHash::Key(WrappedKeyData {
|
||||
wrapped_key: Key::Chacha20Poly1305(key),
|
||||
tag: tag.into(),
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| KeyOrHash::Hash(hash(id, pin, &salt)));
|
||||
}
|
||||
Some(DeriveKey {
|
||||
application_key,
|
||||
key_type: DerivedKeyMechanism::X25519,
|
||||
}) => {
|
||||
use salty::agreement::SecretKey;
|
||||
let mut seed: [u8; 32] = Default::default();
|
||||
rng.fill_bytes(&mut seed);
|
||||
let key = SecretKey::from_seed(&seed);
|
||||
|
||||
let mut key_bytes = key.to_bytes();
|
||||
|
||||
// X25519 keys have a dedicated AAD to avoid key confusion
|
||||
let tag = encrypt_pin_data(
|
||||
id,
|
||||
pin,
|
||||
&salt,
|
||||
&application_key,
|
||||
&mut key_bytes,
|
||||
Some([0x00]),
|
||||
);
|
||||
|
||||
KeyOrHash::Key(WrappedKeyData {
|
||||
wrapped_key: Key::X25519(key_bytes),
|
||||
tag: tag.into(),
|
||||
})
|
||||
}
|
||||
};
|
||||
Self {
|
||||
id,
|
||||
retries: retries.map(From::from),
|
||||
@@ -565,6 +593,42 @@ fn derive_key(id: PinId, pin: &Pin, salt: &Salt, application_key: &[u8; 32]) ->
|
||||
tmp.into()
|
||||
}
|
||||
|
||||
fn encrypt_pin_data(
|
||||
id: PinId,
|
||||
pin: &Pin,
|
||||
salt: &Salt,
|
||||
application_key: &[u8; 32],
|
||||
data: &mut [u8],
|
||||
aad: Option<[u8; 1]>,
|
||||
) -> [u8; CHACHA_TAG_LEN] {
|
||||
use chacha20poly1305::{AeadInPlace, KeyInit};
|
||||
let pin_key = derive_key(id, pin, &salt, application_key);
|
||||
let aead = ChaCha8Poly1305::new((&*pin_key).into());
|
||||
// The pin key is only ever used to once to wrap a key. Nonce reuse is not a concern
|
||||
// Because the salt is also used in the key derivation process, PIN reuse across PINs will still lead to different keys
|
||||
let nonce = Default::default();
|
||||
|
||||
let sup_1;
|
||||
let sup_2;
|
||||
|
||||
let aad: &[u8] = match aad {
|
||||
Some([aad_byte]) => {
|
||||
sup_1 = [u8::from(id), aad_byte];
|
||||
&sup_1
|
||||
}
|
||||
None => {
|
||||
sup_2 = [u8::from(id)];
|
||||
&sup_2
|
||||
}
|
||||
};
|
||||
#[allow(clippy::expect_used)]
|
||||
let tag: [u8; CHACHA_TAG_LEN] = aead
|
||||
.encrypt_in_place_detached(&nonce, aad, &mut *data)
|
||||
.expect("Wrapping the key should always work, length are acceptable")
|
||||
.into();
|
||||
tag
|
||||
}
|
||||
|
||||
fn pin_len(pin: &Pin) -> u8 {
|
||||
const _: () = assert!(MAX_PIN_LENGTH <= u8::MAX as usize);
|
||||
pin.len() as u8
|
||||
|
||||
+3
-1
@@ -14,6 +14,8 @@ use trussed::{
|
||||
|
||||
use crate::{Pin, PinId};
|
||||
|
||||
use self::request::DerivedKeyMechanism;
|
||||
|
||||
/// A result returned by [`AuthClient`][].
|
||||
pub type AuthResult<'a, R, C> = ExtensionResult<'a, AuthExtension, R, C>;
|
||||
|
||||
@@ -116,7 +118,7 @@ pub trait AuthClient: ExtensionClient<AuthExtension> {
|
||||
id: I,
|
||||
pin: Pin,
|
||||
retries: Option<u8>,
|
||||
derive_key: bool,
|
||||
derive_key: Option<DerivedKeyMechanism>,
|
||||
) -> AuthResult<'_, reply::SetPin, Self> {
|
||||
self.extension(request::SetPin {
|
||||
id: id.into(),
|
||||
|
||||
@@ -53,7 +53,7 @@ impl From<GetApplicationKey> for AuthRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize, Copy, Clone)]
|
||||
pub enum DerivedKeyMechanism {
|
||||
Chacha8Poly1305,
|
||||
X25519,
|
||||
@@ -65,7 +65,7 @@ pub struct SetPin {
|
||||
pub pin: Pin,
|
||||
pub retries: Option<u8>,
|
||||
/// If `Some`, the PIN can be used to wrap/unwrap a PIN key
|
||||
pub derive_key: bool,
|
||||
pub derive_key: Option<DerivedKeyMechanism>,
|
||||
}
|
||||
|
||||
impl From<SetPin> for AuthRequest {
|
||||
|
||||
Reference in New Issue
Block a user