From 4918709ce5f373ad9b1ab61b59a2d6f8e8232acc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Fri, 22 Dec 2023 17:00:05 +0100 Subject: [PATCH] Implement deriving X25519 keys from pins --- src/backend.rs | 4 +- src/backend/data.rs | 113 +++++++++++++++++++++++++++++++------------- tests/backend.rs | 73 ++++++++++++++++++++-------- 3 files changed, 133 insertions(+), 57 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index d59d1a3..483d068 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -249,8 +249,8 @@ impl ExtensionImpl for AuthBackend { let key_id = keystore.store_key( Location::Volatile, Secrecy::Secret, - Kind::Symmetric(CHACHA_KEY_LEN), - &*k, + k.kind(), + &k.data(), )?; Ok(reply::GetPinKey { result: Some(key_id), diff --git a/src/backend/data.rs b/src/backend/data.rs index 9560dda..b8864eb 100644 --- a/src/backend/data.rs +++ b/src/backend/data.rs @@ -10,6 +10,7 @@ use serde_byte_array::ByteArray; use sha2::{Digest as _, Sha256}; use subtle::ConstantTimeEq as _; use trussed::{ + key, platform::{CryptoRng, RngCore}, store::filestore::Filestore, types::{Location, PathBuf}, @@ -33,7 +34,7 @@ pub(crate) type ChaChaTag = ByteArray; pub(crate) type ChachaKey = ByteArray; pub(crate) type X25519Key = [u8; X25519_KEY_LEN]; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] pub(crate) enum Key { Chacha20Poly1305(ChachaKey), X25519(X25519Key), @@ -90,6 +91,27 @@ impl<'de> Deserialize<'de> for Key { } } +pub(crate) enum DecryptedKey { + Chacha20Poly1305(ChachaKey), + X25519(salty::agreement::SecretKey), +} + +impl DecryptedKey { + pub fn kind(&self) -> key::Kind { + match self { + DecryptedKey::Chacha20Poly1305(_) => key::Kind::Symmetric(CHACHA_KEY_LEN), + DecryptedKey::X25519(_) => key::Kind::X255, + } + } + + pub fn data(&self) -> [u8; 32] { + match self { + DecryptedKey::Chacha20Poly1305(k) => (*k).into(), + DecryptedKey::X25519(k) => k.to_bytes(), + } + } +} + /// Represent a key wrapped by the pin. /// The key derivation process is as follow (pseudocode): /// @@ -348,7 +370,7 @@ pub(crate) struct PinDataMut<'a> { enum CheckResult { Validated, - Derived { k: ChachaKey, app_key: ChachaKey }, + Derived { k: DecryptedKey, app_key: ChachaKey }, Failed, } @@ -382,10 +404,7 @@ impl<'a> PinDataMut<'a> { CheckResult::Failed } } - KeyOrHash::Key(WrappedKeyData { - wrapped_key: Key::Chacha20Poly1305(wrapped_key), - tag, - }) => { + KeyOrHash::Key(WrappedKeyData { wrapped_key, tag }) => { let app_key = application_key()?; if let Some(k) = self.unwrap_key(pin, &app_key, wrapped_key, &tag) { CheckResult::Derived { k, app_key } @@ -393,13 +412,6 @@ impl<'a> PinDataMut<'a> { CheckResult::Failed } } - KeyOrHash::Key(WrappedKeyData { - wrapped_key: Key::X25519(_x25519), - tag, - }) => { - let _ = tag; - todo!() - } }; if let Some(retries) = &mut self.data.retries { if res.is_success() { @@ -428,9 +440,9 @@ impl<'a> PinDataMut<'a> { &self, pin: &Pin, application_key: &ChachaKey, - mut wrapped_key: ChachaKey, + mut wrapped_key: Key, tag: &ChaChaTag, - ) -> Option { + ) -> Option { use chacha20poly1305::{AeadInPlace, KeyInit}; let pin_key = derive_key(self.id, pin, &self.salt, application_key); @@ -438,21 +450,35 @@ impl<'a> PinDataMut<'a> { // 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(); - aead.decrypt_in_place_detached( - &nonce, - &[u8::from(self.data.id)], - &mut *wrapped_key, - (&**tag).into(), - ) - .ok() - .and(Some(wrapped_key)) + + let aad_1; + let aad_2; + let (aad, mut key_data): (&[u8], _) = match &mut wrapped_key { + Key::Chacha20Poly1305(k) => { + aad_1 = [u8::from(self.data.id)]; + (&aad_1, **k) + } + Key::X25519(k) => { + aad_2 = [u8::from(self.data.id), 0]; + (&aad_2, *k) + } + }; + + aead.decrypt_in_place_detached(&nonce, aad, &mut key_data, (&**tag).into()) + .ok()?; + match wrapped_key { + Key::Chacha20Poly1305(_) => Some(DecryptedKey::Chacha20Poly1305((key_data).into())), + Key::X25519(_) => Some(DecryptedKey::X25519( + salty::agreement::SecretKey::from_seed(&key_data), + )), + } } pub fn get_pin_key( &mut self, pin: &Pin, application_key: &ChachaKey, - ) -> Result, Error> { + ) -> Result, Error> { match self.check_or_unwrap(pin, || Ok(*application_key))? { CheckResult::Validated => Err(Error::BadPinType), CheckResult::Derived { k, .. } => Ok(Some(k)), @@ -478,7 +504,7 @@ impl<'a> PinDataMut<'a> { fn new_wrapping_pin( &mut self, new: &Pin, - mut old_key: ChachaKey, + old_key: DecryptedKey, application_key: &ChachaKey, rng: &mut R, ) { @@ -493,20 +519,39 @@ impl<'a> PinDataMut<'a> { // 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(self.id)], &mut *old_key) - .expect("Wrapping the key should always work, length are acceptable") - .into(); + 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(), + }) + } + }; *self.data = PinData { id: self.id, retries: self.retries, salt, - data: KeyOrHash::Key(WrappedKeyData { - wrapped_key: Key::Chacha20Poly1305(old_key), - tag: tag.into(), - }), + data, }; } diff --git a/tests/backend.rs b/tests/backend.rs index dff7afd..46ce412 100644 --- a/tests/backend.rs +++ b/tests/backend.rs @@ -128,7 +128,7 @@ use trussed::{ types::{Bytes, Location, Message, PathBuf}, virt::{self, Ram}, }; -use trussed_auth::{AuthClient as _, PinId, MAX_HW_KEY_LEN}; +use trussed_auth::{request::DerivedKeyMechanism, AuthClient as _, PinId, MAX_HW_KEY_LEN}; use dispatch::{Backend, Dispatch, BACKENDS}; @@ -207,7 +207,7 @@ fn basic() { let reply = syscall!(client.has_pin(Pin::User)); assert!(!reply.has_pin); - syscall!(client.set_pin(Pin::User, pin1.clone(), None, false)); + syscall!(client.set_pin(Pin::User, pin1.clone(), None, None)); let reply = syscall!(client.has_pin(Pin::User)); assert!(reply.has_pin); @@ -251,7 +251,12 @@ fn basic_wrapped() { let reply = syscall!(client.has_pin(Pin::User)); assert!(!reply.has_pin); - syscall!(client.set_pin(Pin::User, pin1.clone(), None, true)); + syscall!(client.set_pin( + Pin::User, + pin1.clone(), + None, + Some(DerivedKeyMechanism::Chacha8Poly1305) + )); let reply = syscall!(client.has_pin(Pin::User)); assert!(reply.has_pin); @@ -298,7 +303,12 @@ fn hw_key_wrapped() { let reply = syscall!(client.has_pin(Pin::User)); assert!(!reply.has_pin); - syscall!(client.set_pin(Pin::User, pin1.clone(), None, true)); + syscall!(client.set_pin( + Pin::User, + pin1.clone(), + None, + Some(DerivedKeyMechanism::Chacha8Poly1305) + )); let reply = syscall!(client.has_pin(Pin::User)); assert!(reply.has_pin); @@ -343,12 +353,18 @@ fn missing_hw_key() { let reply = syscall!(client.has_pin(Pin::User)); assert!(!reply.has_pin); - assert!(try_syscall!(client.set_pin(Pin::User, pin1.clone(), None, true)).is_err()); + assert!(try_syscall!(client.set_pin( + Pin::User, + pin1.clone(), + None, + Some(DerivedKeyMechanism::Chacha8Poly1305) + )) + .is_err()); let reply = syscall!(client.has_pin(Pin::User)); assert!(!reply.has_pin); - syscall!(client.set_pin(Pin::User, pin1.clone(), None, false)); + syscall!(client.set_pin(Pin::User, pin1.clone(), None, None)); let reply = syscall!(client.has_pin(Pin::User)); assert!(reply.has_pin); @@ -392,7 +408,12 @@ fn pin_key() { let pin1 = Bytes::from_slice(b"12345678").unwrap(); let pin2 = Bytes::from_slice(b"123456").unwrap(); - syscall!(client.set_pin(Pin::User, pin1.clone(), Some(3), true)); + syscall!(client.set_pin( + Pin::User, + pin1.clone(), + Some(3), + Some(DerivedKeyMechanism::Chacha8Poly1305) + )); assert!(syscall!(client.get_pin_key(Pin::User, pin2.clone())) .result .is_none()); @@ -441,7 +462,12 @@ fn reset_pin_key() { let pin2 = Bytes::from_slice(b"123456").unwrap(); let pin3 = Bytes::from_slice(b"1234567890").unwrap(); - syscall!(client.set_pin(Pin::User, pin1.clone(), Some(3), true)); + syscall!(client.set_pin( + Pin::User, + pin1.clone(), + Some(3), + Some(DerivedKeyMechanism::Chacha8Poly1305) + )); assert!(syscall!(client.get_pin_key(Pin::User, pin2.clone())) .result .is_none()); @@ -489,7 +515,12 @@ fn blocked_pin() { let pin1 = Bytes::from_slice(b"12345678").unwrap(); let pin2 = Bytes::from_slice(b"123456").unwrap(); - syscall!(client.set_pin(Pin::User, pin1.clone(), Some(3), false)); + syscall!(client.set_pin( + Pin::User, + pin1.clone(), + Some(3), + Some(DerivedKeyMechanism::Chacha8Poly1305) + )); let reply = syscall!(client.check_pin(Pin::User, pin1.clone())); assert!(reply.success); @@ -510,7 +541,7 @@ fn set_blocked_pin() { let pin1 = Bytes::from_slice(b"12345678").unwrap(); let pin2 = Bytes::from_slice(b"123456").unwrap(); - syscall!(client.set_pin(Pin::User, pin1.clone(), Some(1), false)); + syscall!(client.set_pin(Pin::User, pin1.clone(), Some(1), None)); let reply = syscall!(client.check_pin(Pin::User, pin1.clone())); assert!(reply.success); let reply = syscall!(client.check_pin(Pin::User, pin2.clone())); @@ -518,7 +549,7 @@ fn set_blocked_pin() { let reply = syscall!(client.check_pin(Pin::User, pin1)); assert!(!reply.success); - syscall!(client.set_pin(Pin::User, pin2.clone(), Some(1), false)); + syscall!(client.set_pin(Pin::User, pin2.clone(), Some(1), None)); let reply = syscall!(client.check_pin(Pin::User, pin2)); assert!(reply.success); }) @@ -530,7 +561,7 @@ fn empty_pin() { let pin1 = Bytes::new(); let pin2 = Bytes::from_slice(b"123456").unwrap(); - syscall!(client.set_pin(Pin::User, pin1.clone(), None, false)); + syscall!(client.set_pin(Pin::User, pin1.clone(), None, None)); let reply = syscall!(client.has_pin(Pin::User)); assert!(reply.has_pin); let reply = syscall!(client.check_pin(Pin::User, pin1.clone())); @@ -553,7 +584,7 @@ fn max_pin_length() { } }; - syscall!(client.set_pin(Pin::User, pin1.clone(), None, false)); + syscall!(client.set_pin(Pin::User, pin1.clone(), None, None)); let reply = syscall!(client.check_pin(Pin::User, pin1)); assert!(reply.success); let reply = syscall!(client.check_pin(Pin::User, pin2)); @@ -568,9 +599,9 @@ fn pin_retries() { let pin2 = Bytes::from_slice(b"123456").unwrap(); let pin3 = Bytes::from_slice(b"654321").unwrap(); - syscall!(client.set_pin(Pin::User, pin1.clone(), Some(3), false)); - syscall!(client.set_pin(Pin::Admin, pin2.clone(), Some(5), false)); - syscall!(client.set_pin(Pin::Custom, pin3.clone(), None, false)); + syscall!(client.set_pin(Pin::User, pin1.clone(), Some(3), None)); + syscall!(client.set_pin(Pin::Admin, pin2.clone(), Some(5), None)); + syscall!(client.set_pin(Pin::Custom, pin3.clone(), None, None)); let reply = syscall!(client.pin_retries(Pin::User)); assert_eq!(reply.retries, Some(3)); @@ -616,7 +647,7 @@ fn delete_pin() { run(BACKENDS, |client| { let pin = Bytes::from_slice(b"123456").unwrap(); - syscall!(client.set_pin(Pin::User, pin.clone(), None, false)); + syscall!(client.set_pin(Pin::User, pin.clone(), None, None)); let reply = syscall!(client.has_pin(Pin::User)); assert!(reply.has_pin); @@ -637,8 +668,8 @@ fn delete_all_pins() { let pin1 = Bytes::from_slice(b"123456").unwrap(); let pin2 = Bytes::from_slice(b"12345678").unwrap(); - syscall!(client.set_pin(Pin::User, pin1.clone(), None, false)); - syscall!(client.set_pin(Pin::Admin, pin2.clone(), None, false)); + syscall!(client.set_pin(Pin::User, pin1.clone(), None, None)); + syscall!(client.set_pin(Pin::Admin, pin2.clone(), None, None)); let reply = syscall!(client.has_pin(Pin::User)); assert!(reply.has_pin); @@ -726,8 +757,8 @@ fn reset_auth_data() { let pin1 = Bytes::from_slice(b"123456").unwrap(); let pin2 = Bytes::from_slice(b"12345678").unwrap(); - syscall!(client.set_pin(Pin::User, pin1.clone(), None, false)); - syscall!(client.set_pin(Pin::Admin, pin2.clone(), None, false)); + syscall!(client.set_pin(Pin::User, pin1.clone(), None, None)); + syscall!(client.set_pin(Pin::Admin, pin2.clone(), None, None)); let reply = syscall!(client.has_pin(Pin::User)); assert!(reply.has_pin);