Implement X25519 key generation

This commit is contained in:
Sosthène Guédon
2024-03-27 15:42:16 +01:00
parent e3e02eebaf
commit f2bee120b4
5 changed files with 100 additions and 31 deletions
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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(),
+2 -2
View File
@@ -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 {