diff --git a/src/backend.rs b/src/backend.rs index 483d068..eebfb82 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -7,6 +7,7 @@ use core::fmt; use hkdf::Hkdf; use rand_core::{CryptoRng, RngCore}; +use salty::agreement::SecretKey; use sha2::Sha256; use trussed::{ backend::Backend, @@ -27,7 +28,7 @@ use crate::{ }; use data::{DeriveKey, PinData, Salt, CHACHA_KEY_LEN, SALT_LEN}; -use self::data::{delete_app_salt, ChachaKey}; +use self::data::{delete_app_salt, ChachaKey, DecryptedKey}; /// max accepted length for the hardware initial key material pub const MAX_HW_KEY_LEN: usize = 64; @@ -295,11 +296,15 @@ impl ExtensionImpl for AuthBackend { } AuthRequest::SetPinWithKey(request) => { let app_key = self.get_app_key(client_id, global_fs, ctx, rng)?; - let key_to_wrap = - keystore.load_key(Secrecy::Secret, Some(Kind::Symmetric(32)), &request.key)?; - let key_to_wrap = (&*key_to_wrap.material) + let key_material = keystore.load_key(Secrecy::Secret, None, &request.key)?; + let material_32: [u8; 32] = (&*key_material.material) .try_into() .map_err(|_| Error::ReadFailed)?; + let key_to_wrap = match key_material.kind { + Kind::Symmetric(32) => DecryptedKey::Chacha20Poly1305(material_32.into()), + Kind::X255 => DecryptedKey::X25519(SecretKey::from_seed(&material_32)), + _ => return Err(Error::NotFound)?, + }; PinData::reset_with_key( request.id, &request.pin, diff --git a/src/backend/data.rs b/src/backend/data.rs index b8864eb..dd734f2 100644 --- a/src/backend/data.rs +++ b/src/backend/data.rs @@ -3,7 +3,7 @@ use core::ops::Deref; -use chacha20poly1305::ChaCha8Poly1305; +use chacha20poly1305::{AeadInPlace, ChaCha8Poly1305, KeyInit}; use hmac::{Hmac, Mac}; use serde::{Deserialize, Serialize}; use serde_byte_array::ByteArray; @@ -32,7 +32,7 @@ pub(crate) type Salt = ByteArray; pub(crate) type Hash = ByteArray; pub(crate) type ChaChaTag = ByteArray; pub(crate) type ChachaKey = ByteArray; -pub(crate) type X25519Key = [u8; X25519_KEY_LEN]; +pub(crate) type X25519Key = ByteArray; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub(crate) enum Key { @@ -49,7 +49,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) } } @@ -75,9 +75,11 @@ impl<'de> Deserialize<'de> for Key { { match v.len() { CHACHA_KEY_LEN => Ok(Key::Chacha20Poly1305(ByteArray::new( + #[allow(clippy::expect_used)] v.try_into().expect("Len was just checked"), ))), ENCODED_X25519_KEY_LEN if v[X25519_KEY_LEN] == 0 => Ok(Key::X25519( + #[allow(clippy::expect_used)] v[..X25519_KEY_LEN] .try_into() .expect("Len was just checked"), @@ -110,6 +112,47 @@ impl DecryptedKey { DecryptedKey::X25519(k) => k.to_bytes(), } } + + pub fn aad(&self, id: PinId) -> Bytes<2> { + let mut aad = Bytes::new(); + #[allow(clippy::expect_used)] + aad.push(u8::from(id)) + .expect("Capacity is known to be 2 and length is known to be 0"); + match self { + DecryptedKey::Chacha20Poly1305(_) => {} + DecryptedKey::X25519(_) => + { + #[allow(clippy::expect_used)] + aad.push(0x00) + .expect("Capacity is known to be 2 and length is known to be 1") + } + } + aad + } + + pub fn encrypt(&self, id: PinId, pin_key: &ChachaKey) -> (Key, ChaChaTag) { + let aad = self.aad(id); + // 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 mut data = self.data(); + + let aead = ChaCha8Poly1305::new((&**pin_key).into()); + + #[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(); + + let key = match self { + DecryptedKey::Chacha20Poly1305(_) => Key::Chacha20Poly1305(data.into()), + DecryptedKey::X25519(_) => Key::X25519(data.into()), + }; + + (key, tag.into()) + } } /// Represent a key wrapped by the pin. @@ -270,7 +313,7 @@ impl PinData { ); KeyOrHash::Key(WrappedKeyData { - wrapped_key: Key::X25519(key_bytes), + wrapped_key: Key::X25519(key_bytes.into()), tag: tag.into(), }) } @@ -289,30 +332,21 @@ impl PinData { retries: Option, rng: &mut R, application_key: &ChachaKey, - mut key_to_wrap: ChachaKey, + key_to_wrap: DecryptedKey, ) -> Self where R: CryptoRng + RngCore, { - use chacha20poly1305::{AeadInPlace, KeyInit}; let mut salt = Salt::default(); rng.fill_bytes(salt.as_mut()); let pin_key = derive_key(id, pin, &salt, application_key); - let aead = ChaCha8Poly1305::new((&*pin_key).into()); - 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_to_wrap) - .expect("Wrapping the key should always work, length are acceptable") - .into(); + + let (wrapped_key, tag) = key_to_wrap.encrypt(id, &pin_key); Self { id, retries: retries.map(From::from), salt, - data: KeyOrHash::Key(WrappedKeyData { - wrapped_key: Key::Chacha20Poly1305(key_to_wrap), - tag: tag.into(), - }), + data: KeyOrHash::Key(WrappedKeyData { wrapped_key, tag }), } } @@ -443,8 +477,6 @@ impl<'a> PinDataMut<'a> { mut wrapped_key: Key, tag: &ChaChaTag, ) -> Option { - use chacha20poly1305::{AeadInPlace, KeyInit}; - let pin_key = derive_key(self.id, pin, &self.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 @@ -460,7 +492,7 @@ impl<'a> PinDataMut<'a> { } Key::X25519(k) => { aad_2 = [u8::from(self.data.id), 0]; - (&aad_2, *k) + (&aad_2, **k) } }; @@ -508,45 +540,13 @@ impl<'a> PinDataMut<'a> { application_key: &ChachaKey, rng: &mut R, ) { - use chacha20poly1305::{AeadInPlace, KeyInit}; let mut salt = Salt::default(); rng.fill_bytes(&mut *salt); let pin_key = derive_key(self.id, new, &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 data = match old_key { - DecryptedKey::Chacha20Poly1305(mut k) => { - #[allow(clippy::expect_used)] - let tag: [u8; CHACHA_TAG_LEN] = aead - .encrypt_in_place_detached(&nonce, &[u8::from(self.id)], &mut *k) - .expect("Wrapping the key should always work, length are acceptable") - .into(); - - KeyOrHash::Key(WrappedKeyData { - wrapped_key: Key::Chacha20Poly1305(k), - tag: tag.into(), - }) - } - DecryptedKey::X25519(k) => { - let mut data = k.to_bytes(); - #[allow(clippy::expect_used)] - let tag: [u8; CHACHA_TAG_LEN] = aead - .encrypt_in_place_detached(&nonce, &[u8::from(self.id), 0], &mut data) - .expect("Wrapping the key should always work, length are acceptable") - .into(); - - KeyOrHash::Key(WrappedKeyData { - wrapped_key: Key::X25519(data), - tag: tag.into(), - }) - } - }; - + let (wrapped_key, tag) = old_key.encrypt(self.id, &pin_key); + let data = KeyOrHash::Key(WrappedKeyData { wrapped_key, tag }); *self.data = PinData { id: self.id, retries: self.retries, @@ -628,8 +628,8 @@ fn hash(id: PinId, pin: &Pin, salt: &Salt) -> Hash { fn derive_key(id: PinId, pin: &Pin, salt: &Salt, application_key: &[u8; 32]) -> Hash { #[allow(clippy::expect_used)] - let mut hmac = Hmac::::new_from_slice(application_key) - .expect("Slice will always be of acceptable size"); + let mut hmac: Hmac = + Mac::new_from_slice(application_key).expect("Hmac is compatible with all key sizes"); hmac.update(&[u8::from(id)]); hmac.update(&[pin_len(pin)]); hmac.update(pin); @@ -646,8 +646,7 @@ fn encrypt_pin_data( 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 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 @@ -728,8 +727,8 @@ fn load_app_salt(fs: &mut S, location: Location) -> Result ChachaKey { #[allow(clippy::expect_used)] - let mut hmac = Hmac::::new_from_slice(&**application_key) - .expect("Slice will always be of acceptable size"); + let mut hmac: Hmac = + Mac::new_from_slice(&**application_key).expect("Hmac is compatible with all key sizes"); hmac.update(&**salt); hmac.update(&(info.len() as u64).to_be_bytes()); hmac.update(info);