diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21209e46379..fc34d0d9a2d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,7 @@ jobs: run: | # trussed-core + trussed for mechanism in \ - aes256-cbc chacha8-poly1305 ed255 hmac-blake2s hmac-sha1 hmac-sha256 hmac-sha512 \ + aes256-cbc aes256-gcm chacha8-poly1305 ed255 hmac-blake2s hmac-sha1 hmac-sha256 hmac-sha512 \ p256 p384 p521 sha256 shared-secret tdes totp trng x255 do for package in trussed-core trussed @@ -68,7 +68,7 @@ jobs: done # trussed-core only for mechanism in \ - brainpoolp256r1 brainpoolp384r1 brainpoolp512r1 rsa2048 rsa3072 rsa4096 secp256k1 + brainpoolp256r1 brainpoolp384r1 brainpoolp512r1 mldsa44 rsa2048 rsa3072 rsa4096 secp256k1 do echo "trussed-core: ${mechanism}" cargo check --package trussed-core --all-targets --no-default-features --features crypto-client,${mechanism} diff --git a/CHANGELOG.md b/CHANGELOG.md index 989987a4b20..262adc1db25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 behind the `serde-extensions` feature. - Added `types::Path` re-export of `littlefs2::path::Path`. - Reduced stack usage of `Service::process`. +- Added the `Aes256Gcm` mechanism. ### Changed diff --git a/Cargo.toml b/Cargo.toml index 41ce5e852f6..764ccb84724 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ license.workspace = true repository.workspace = true [dependencies] -trussed-core = "0.2" +trussed-core = "0.2.2" # general bitflags = { version = "2.1" } @@ -45,8 +45,11 @@ zeroize = { version = "1.2", default-features = false, features = ["zeroize_deri rand_chacha = { version = "0.3.1", default-features = false } # RustCrypto +aead = { version = "0.5", default-features = false, optional = true } aes = { version = "0.8", default-features = false } +aes-gcm = { version = "0.10", default-features = false, features = ["aes"], optional = true } cbc = "0.1.2" +cipher = { version = "0.4", optional = true } blake2 = { version = "0.10", default-features = false, optional = true } chacha20 = { version = "0.9", default-features = false } chacha20poly1305 = { version = "0.10", default-features = false, features = ["reduced-round"] } @@ -116,7 +119,8 @@ default-mechanisms = [ "trng", ] aes256-cbc = ["trussed-core/aes256-cbc"] -chacha8-poly1305 = ["trussed-core/chacha8-poly1305"] +aes256-gcm = ["trussed-core/aes256-gcm", "dep:aead", "dep:aes-gcm", "dep:cipher"] +chacha8-poly1305 = ["trussed-core/chacha8-poly1305", "dep:aead", "dep:cipher"] ed255 = ["trussed-core/ed255"] x255 = ["trussed-core/x255"] hmac-blake2s = ["trussed-core/hmac-blake2s", "blake2"] diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 5640980631c..d44686628af 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -4,6 +4,12 @@ - +## [v0.2.2](https://github.com/trussed-dev/trussed/releases/tag/core-v0.2.2) (2026-05-30) + +### Added + +- Add `Mechanism::Aes256Gcm` and the `Aes256Gcm` trait behind the `aes256-gcm` feature flag. + ## [v0.2.1](https://github.com/trussed-dev/trussed/releases/tag/core-v0.2.1) (2026-05-18) ### Added diff --git a/core/Cargo.toml b/core/Cargo.toml index abbda0f5976..679df5c3a9f 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "trussed-core" -version = "0.2.1" +version = "0.2.2" description = "Core types for the trussed crate" authors.workspace = true @@ -31,6 +31,7 @@ ui-client = [] # mechanisms aes256-cbc = [] +aes256-gcm = [] brainpoolp256r1 = [] brainpoolp384r1 = [] brainpoolp512r1 = [] diff --git a/core/src/mechanisms.rs b/core/src/mechanisms.rs index 80f29471949..1b3178d1928 100644 --- a/core/src/mechanisms.rs +++ b/core/src/mechanisms.rs @@ -34,6 +34,86 @@ pub trait Aes256Cbc: CryptoClient { } } +#[cfg(feature = "aes256-gcm")] +pub trait Aes256Gcm: CryptoClient { + fn decrypt_aes256gcm<'c>( + &'c mut self, + key: KeyId, + message: &[u8], + associated_data: &[u8], + nonce: &[u8], + tag: &[u8], + ) -> ClientResult<'c, reply::Decrypt, Self> { + self.decrypt( + Mechanism::Aes256Gcm, + key, + message, + associated_data, + nonce, + tag, + ) + } + + fn encrypt_aes256gcm<'c>( + &'c mut self, + key: KeyId, + message: &[u8], + associated_data: &[u8], + nonce: Option<&[u8; 12]>, + ) -> ClientResult<'c, reply::Encrypt, Self> { + self.encrypt( + Mechanism::Aes256Gcm, + key, + message, + associated_data, + nonce.map(ShortData::from), + ) + } + + fn generate_aes256gcm_key( + &mut self, + persistence: Location, + ) -> ClientResult<'_, reply::GenerateKey, Self> { + self.generate_key( + Mechanism::Aes256Gcm, + StorageAttributes::new().set_persistence(persistence), + ) + } + + fn unwrap_key_aes256gcm<'c>( + &'c mut self, + wrapping_key: KeyId, + wrapped_key: &[u8], + associated_data: &[u8], + location: Location, + ) -> ClientResult<'c, reply::UnwrapKey, Self> { + self.unwrap_key( + Mechanism::Aes256Gcm, + wrapping_key, + Message::try_from(wrapped_key).map_err(|_| ClientError::DataTooLarge)?, + associated_data, + &[], + StorageAttributes::new().set_persistence(location), + ) + } + + fn wrap_key_aes256gcm<'c>( + &'c mut self, + wrapping_key: KeyId, + key: KeyId, + associated_data: &[u8], + nonce: Option<&[u8; 12]>, + ) -> ClientResult<'c, reply::WrapKey, Self> { + self.wrap_key( + Mechanism::Aes256Gcm, + wrapping_key, + key, + associated_data, + nonce.map(ShortData::from), + ) + } +} + #[cfg(feature = "chacha8-poly1305")] pub trait Chacha8Poly1305: CryptoClient { fn decrypt_chacha8poly1305<'c>( diff --git a/core/src/types.rs b/core/src/types.rs index e34a88037cd..7234c03c4b7 100644 --- a/core/src/types.rs +++ b/core/src/types.rs @@ -516,6 +516,8 @@ generate_mechanism! { pub enum Mechanism { #[cfg(feature = "aes256-cbc")] Aes256Cbc, + #[cfg(feature = "aes256-gcm")] + Aes256Gcm, #[cfg(feature = "chacha8-poly1305")] Chacha8Poly1305, #[cfg(feature = "ed255")] diff --git a/src/client/mechanisms.rs b/src/client/mechanisms.rs index 2aef398daa4..12f635b3c7d 100644 --- a/src/client/mechanisms.rs +++ b/src/client/mechanisms.rs @@ -6,6 +6,9 @@ pub use trussed_core::mechanisms::*; #[cfg(feature = "aes256-cbc")] impl Aes256Cbc for ClientImplementation<'_, S, E> {} +#[cfg(feature = "aes256-gcm")] +impl Aes256Gcm for ClientImplementation<'_, S, E> {} + #[cfg(feature = "chacha8-poly1305")] impl Chacha8Poly1305 for ClientImplementation<'_, S, E> {} diff --git a/src/mechanisms.rs b/src/mechanisms.rs index 4ca2baae4f2..91b3c256f63 100644 --- a/src/mechanisms.rs +++ b/src/mechanisms.rs @@ -10,12 +10,20 @@ // The question of breaking down `reply_to` into smaller, more globally understandable pieces, // should be revisited. +#[cfg(any(feature = "aes256-gcm", feature = "chacha8-poly1305"))] +mod aead; + // TODO: rename to aes256-cbc-zero-iv #[cfg(feature = "aes256-cbc")] pub struct Aes256Cbc; #[cfg(feature = "aes256-cbc")] mod aes256cbc; +#[cfg(feature = "aes256-gcm")] +pub struct Aes256Gcm; +#[cfg(feature = "aes256-gcm")] +mod aes256gcm; + #[cfg(feature = "chacha8-poly1305")] pub struct Chacha8Poly1305; #[cfg(feature = "chacha8-poly1305")] diff --git a/src/mechanisms/aead.rs b/src/mechanisms/aead.rs new file mode 100644 index 00000000000..edd43b69786 --- /dev/null +++ b/src/mechanisms/aead.rs @@ -0,0 +1,229 @@ +use core::marker::PhantomData; + +use aead::{generic_array::GenericArray, AeadCore, AeadMutInPlace}; +use cipher::{typenum::U32, ArrayLength, KeyInit, KeySizeUser, Unsigned as _}; +use rand_core::RngCore as _; +use trussed_core::{ + api::{reply, request}, + types::{EncryptedData, Mechanism, Message, ShortData}, + Error, +}; + +use crate::{key, store::keystore::Keystore}; + +pub struct Aead { + mechanism: Mechanism, + _marker: PhantomData<(T, KeyNonceSize)>, +} + +impl< + T: AeadCore + AeadMutInPlace + KeyInit + KeySizeUser, + KeyNonceSize: ArrayLength, + > Aead +{ + const KEY_LEN: usize = T::KeySize::USIZE; + const NONCE_LEN: usize = T::NonceSize::USIZE; + const TOTAL_LEN: usize = KeyNonceSize::USIZE; + + const KIND: key::Kind = key::Kind::Symmetric(Self::KEY_LEN); + const KIND_NONCE: key::Kind = key::Kind::Symmetric32Nonce(Self::NONCE_LEN); + + pub fn new(mechanism: Mechanism) -> Self { + const { + assert!(Self::KEY_LEN + Self::NONCE_LEN == Self::TOTAL_LEN); + } + Self { + mechanism, + _marker: PhantomData, + } + } + + pub fn generate_key( + &self, + keystore: &mut impl Keystore, + request: &request::GenerateKey, + ) -> Result { + let mut serialized: GenericArray = GenericArray::default(); + let entropy = &mut serialized[..Self::KEY_LEN]; + keystore.rng().fill_bytes(entropy); + + // store keys + let key_id = keystore.store_key( + request.attributes.persistence, + key::Secrecy::Secret, + Self::KIND_NONCE, + &serialized, + )?; + + Ok(reply::GenerateKey { key: key_id }) + } + + pub fn decrypt( + &self, + keystore: &mut impl Keystore, + request: &request::Decrypt, + ) -> Result { + let key = keystore.load_key(key::Secrecy::Secret, None, &request.key)?; + if key.kind != Self::KIND && key.kind != Self::KIND_NONCE { + return Err(Error::WrongKeyKind); + } + let serialized = key.material.as_slice(); + + assert!(serialized.len() == Self::TOTAL_LEN || serialized.len() == Self::KEY_LEN); + + let symmetric_key = &serialized[..Self::KEY_LEN]; + + let mut aead = T::new(&GenericArray::clone_from_slice(symmetric_key)); + + let mut plaintext = request.message.clone(); + let nonce = GenericArray::from_slice(&request.nonce); + let tag = GenericArray::from_slice(&request.tag); + + let outcome = + aead.decrypt_in_place_detached(nonce, &request.associated_data, &mut plaintext, tag); + + Ok(reply::Decrypt { + plaintext: { + if outcome.is_ok() { + Some(plaintext) + } else { + None + } + }, + }) + } + + pub fn encrypt( + &self, + keystore: &mut impl Keystore, + request: &request::Encrypt, + ) -> Result { + // load key and nonce + let secrecy = key::Secrecy::Secret; + let key_id = &request.key; + let mut key = keystore.load_key(secrecy, None, key_id)?; + if key.kind != Self::KIND && key.kind != Self::KIND_NONCE { + return Err(Error::WrongKeyKind); + } + + let serialized: &mut [u8] = key.material.as_mut(); + let symmetric_key = GenericArray::clone_from_slice(&serialized[..Self::KEY_LEN]); + let mut nonce: GenericArray = GenericArray::default(); + if let Some(n) = &request.nonce { + if n.len() == Self::NONCE_LEN { + nonce.copy_from_slice(n); + } else { + return Err(Error::MechanismParamInvalid); + } + } else if key.kind == Self::KIND { + keystore.rng().fill_bytes(&mut nonce); + } else if key.kind == Self::KIND_NONCE { + self.increment_nonce(&mut serialized[Self::KEY_LEN..])?; + nonce.copy_from_slice(&serialized[Self::KEY_LEN..]); + let location = keystore.location(secrecy, key_id).unwrap(); + keystore.overwrite_key(location, secrecy, Self::KIND_NONCE, key_id, serialized)?; + } else { + return Err(Error::WrongKeyKind); + } + + let mut aead = T::new(&symmetric_key); + + let mut ciphertext = request.message.clone(); + let tag = aead + .encrypt_in_place_detached(&nonce, &request.associated_data, &mut ciphertext) + .unwrap(); + + let nonce = ShortData::try_from(nonce.as_slice()).unwrap(); + let tag = ShortData::try_from(tag.as_slice()).unwrap(); + + Ok(reply::Encrypt { + ciphertext, + nonce, + tag, + }) + } + + pub fn wrap_key( + &self, + keystore: &mut impl Keystore, + request: &request::WrapKey, + ) -> Result { + debug!("trussed: Aead::WrapKey"); + + // TODO: need to check both secret and private keys + let serialized_key = keystore.load_key(key::Secrecy::Secret, None, &request.key)?; + + let message = Message::try_from(&*serialized_key.serialize()).unwrap(); + + let encryption_request = request::Encrypt { + mechanism: self.mechanism, + key: request.wrapping_key, + message, + associated_data: request.associated_data.clone(), + nonce: request.nonce.clone(), + }; + let encryption_reply = self.encrypt(keystore, &encryption_request)?; + + let wrapped_key = EncryptedData::from(encryption_reply); + let wrapped_key = + crate::postcard_serialize_bytes(&wrapped_key).map_err(|_| Error::CborError)?; + + Ok(reply::WrapKey { wrapped_key }) + } + + pub fn unwrap_key( + &self, + keystore: &mut impl Keystore, + request: &request::UnwrapKey, + ) -> Result { + let encrypted_data: EncryptedData = + crate::postcard_deserialize(&request.wrapped_key).map_err(|_| Error::CborError)?; + + let decryption_request = encrypted_data.decrypt( + self.mechanism, + request.wrapping_key, + request.associated_data.clone(), + ); + + let serialized_key = + if let Some(serialized_key) = self.decrypt(keystore, &decryption_request)?.plaintext { + serialized_key + } else { + return Ok(reply::UnwrapKey { key: None }); + }; + + // TODO: probably change this to returning Option too + let key::Key { + flags: _, + kind, + material, + } = key::Key::try_deserialize(&serialized_key)?; + + // TODO: need to check both secret and private keys + let key_id = keystore.store_key( + request.attributes.persistence, + // using for signing keys... we need to know + key::Secrecy::Secret, + kind, + &material, + )?; + + Ok(reply::UnwrapKey { key: Some(key_id) }) + } + + #[inline(never)] + fn increment_nonce(&self, nonce: &mut [u8]) -> Result<(), Error> { + assert_eq!(nonce.len(), Self::NONCE_LEN); + let mut carry: u16 = 1; + for digit in nonce.iter_mut() { + let x = (*digit as u16) + carry; + *digit = x as u8; + carry = x >> 8; + } + if carry == 0 { + Ok(()) + } else { + Err(Error::NonceOverflow) + } + } +} diff --git a/src/mechanisms/aes256gcm.rs b/src/mechanisms/aes256gcm.rs new file mode 100644 index 00000000000..b7724a3a34c --- /dev/null +++ b/src/mechanisms/aes256gcm.rs @@ -0,0 +1,58 @@ +use cipher::typenum::U44; +use trussed_core::{ + api::{reply, request}, + types::Mechanism, + Error, +}; + +use crate::{service::MechanismImpl, store::keystore::Keystore}; + +type KeyNonceSize = U44; +type Aead = super::aead::Aead; + +impl MechanismImpl for super::Aes256Gcm { + #[inline(never)] + fn generate_key( + &self, + keystore: &mut impl Keystore, + request: &request::GenerateKey, + ) -> Result { + Aead::new(Mechanism::Aes256Gcm).generate_key(keystore, request) + } + + #[inline(never)] + fn decrypt( + &self, + keystore: &mut impl Keystore, + request: &request::Decrypt, + ) -> Result { + Aead::new(Mechanism::Aes256Gcm).decrypt(keystore, request) + } + + #[inline(never)] + fn encrypt( + &self, + keystore: &mut impl Keystore, + request: &request::Encrypt, + ) -> Result { + Aead::new(Mechanism::Aes256Gcm).encrypt(keystore, request) + } + + #[inline(never)] + fn wrap_key( + &self, + keystore: &mut impl Keystore, + request: &request::WrapKey, + ) -> Result { + Aead::new(Mechanism::Aes256Gcm).wrap_key(keystore, request) + } + + #[inline(never)] + fn unwrap_key( + &self, + keystore: &mut impl Keystore, + request: &request::UnwrapKey, + ) -> Result { + Aead::new(Mechanism::Aes256Gcm).unwrap_key(keystore, request) + } +} diff --git a/src/mechanisms/chacha8poly1305.rs b/src/mechanisms/chacha8poly1305.rs index dbe8ec20327..f1e74684e1c 100644 --- a/src/mechanisms/chacha8poly1305.rs +++ b/src/mechanisms/chacha8poly1305.rs @@ -1,41 +1,19 @@ -use generic_array::GenericArray; -use rand_core::RngCore; -use trussed_core::types::EncryptedData; +use cipher::typenum::U44; +use trussed_core::{ + api::{reply, request}, + types::Mechanism, + Error, +}; -use crate::api::{reply, request}; -use crate::error::Error; -use crate::key; -use crate::service::MechanismImpl; -use crate::store::keystore::Keystore; -use crate::types::{Mechanism, Message, ShortData}; +use crate::{service::MechanismImpl, store::keystore::Keystore}; + +type KeyNonceSize = U44; +type Aead = super::aead::Aead; // TODO: The non-detached versions seem better. // This needs a bit of additional type gymnastics. // Maybe start a discussion on the `aead` crate's GitHub about usability concerns... -const NONCE_LEN: usize = 12; -const KEY_LEN: usize = 32; -const TOTAL_LEN: usize = KEY_LEN + NONCE_LEN; -const TAG_LEN: usize = 16; -const KIND: key::Kind = key::Kind::Symmetric(KEY_LEN); -const KIND_NONCE: key::Kind = key::Kind::Symmetric32Nonce(NONCE_LEN); - -#[inline(never)] -fn increment_nonce(nonce: &mut [u8]) -> Result<(), Error> { - assert_eq!(nonce.len(), NONCE_LEN); - let mut carry: u16 = 1; - for digit in nonce.iter_mut() { - let x = (*digit as u16) + carry; - *digit = x as u8; - carry = x >> 8; - } - if carry == 0 { - Ok(()) - } else { - Err(Error::NonceOverflow) - } -} - impl MechanismImpl for super::Chacha8Poly1305 { #[inline(never)] fn generate_key( @@ -43,24 +21,7 @@ impl MechanismImpl for super::Chacha8Poly1305 { keystore: &mut impl Keystore, request: &request::GenerateKey, ) -> Result { - use rand_core::RngCore as _; - - // 32 bytes entropy - // 12 bytes nonce - let mut serialized = [0u8; TOTAL_LEN]; - - let entropy = &mut serialized[..KEY_LEN]; - keystore.rng().fill_bytes(entropy); - - // store keys - let key_id = keystore.store_key( - request.attributes.persistence, - key::Secrecy::Secret, - KIND_NONCE, - &serialized, - )?; - - Ok(reply::GenerateKey { key: key_id }) + Aead::new(Mechanism::Chacha8Poly1305).generate_key(keystore, request) } #[inline(never)] @@ -69,39 +30,7 @@ impl MechanismImpl for super::Chacha8Poly1305 { keystore: &mut impl Keystore, request: &request::Decrypt, ) -> Result { - use chacha20poly1305::aead::{AeadMutInPlace, KeyInit}; - use chacha20poly1305::ChaCha8Poly1305; - - let key = keystore.load_key(key::Secrecy::Secret, None, &request.key)?; - if !matches!(key.kind, KIND | KIND_NONCE) { - return Err(Error::WrongKeyKind); - } - let serialized = key.material.as_slice(); - - assert!(serialized.len() == TOTAL_LEN || serialized.len() == KEY_LEN); - - let symmetric_key = &serialized[..KEY_LEN]; - - let mut aead = ChaCha8Poly1305::new(&GenericArray::clone_from_slice(symmetric_key)); - - let mut plaintext = request.message.clone(); - let nonce = GenericArray::from_slice(&request.nonce); - let tag = GenericArray::from_slice(&request.tag); - - let outcome = - aead.decrypt_in_place_detached(nonce, &request.associated_data, &mut plaintext, tag); - - // outcome.map_err(|_| Error::AeadError)?; - - Ok(reply::Decrypt { - plaintext: { - if outcome.is_ok() { - Some(plaintext) - } else { - None - } - }, - }) + Aead::new(Mechanism::Chacha8Poly1305).decrypt(keystore, request) } #[inline(never)] @@ -110,57 +39,7 @@ impl MechanismImpl for super::Chacha8Poly1305 { keystore: &mut impl Keystore, request: &request::Encrypt, ) -> Result { - use chacha20poly1305::aead::{AeadMutInPlace, KeyInit}; - use chacha20poly1305::ChaCha8Poly1305; - - // load key and nonce - let secrecy = key::Secrecy::Secret; - let key_id = &request.key; - let mut key = keystore.load_key(secrecy, None, key_id)?; - - let serialized: &mut [u8] = key.material.as_mut(); - let symmetric_key: [u8; KEY_LEN] = serialized[..KEY_LEN].try_into().unwrap(); - let mut nonce = [0; NONCE_LEN]; - match (&request.nonce, key.kind) { - (Some(n), KIND | KIND_NONCE) if n.len() == NONCE_LEN => { - nonce.copy_from_slice(n); - } - (None, KIND) => { - keystore.rng().fill_bytes(&mut nonce); - } - (None, KIND_NONCE) => { - increment_nonce(&mut serialized[KEY_LEN..])?; - nonce.copy_from_slice(&serialized[KEY_LEN..]); - let location = keystore.location(secrecy, key_id).unwrap(); - keystore.overwrite_key(location, secrecy, KIND_NONCE, key_id, serialized)?; - } - (Some(_), KIND | KIND_NONCE) => return Err(Error::MechanismParamInvalid), - _ => return Err(Error::WrongKeyKind), - } - - let mut aead = ChaCha8Poly1305::new(&GenericArray::from(symmetric_key)); - - let mut ciphertext = request.message.clone(); - let tag: [u8; TAG_LEN] = aead - .encrypt_in_place_detached( - &GenericArray::from(nonce), - &request.associated_data, - &mut ciphertext, - ) - .unwrap() - .as_slice() - .try_into() - .unwrap(); - - let nonce = ShortData::from(&nonce); - let tag = ShortData::from(&tag); - - // let ciphertext = Message::from_slice(&ciphertext).unwrap(); - Ok(reply::Encrypt { - ciphertext, - nonce, - tag, - }) + Aead::new(Mechanism::Chacha8Poly1305).encrypt(keystore, request) } #[inline(never)] @@ -169,27 +48,7 @@ impl MechanismImpl for super::Chacha8Poly1305 { keystore: &mut impl Keystore, request: &request::WrapKey, ) -> Result { - debug!("trussed: Chacha8Poly1305::WrapKey"); - - // TODO: need to check both secret and private keys - let serialized_key = keystore.load_key(key::Secrecy::Secret, None, &request.key)?; - - let message = Message::try_from(&*serialized_key.serialize()).unwrap(); - - let encryption_request = request::Encrypt { - mechanism: Mechanism::Chacha8Poly1305, - key: request.wrapping_key, - message, - associated_data: request.associated_data.clone(), - nonce: request.nonce.clone(), - }; - let encryption_reply = self.encrypt(keystore, &encryption_request)?; - - let wrapped_key = EncryptedData::from(encryption_reply); - let wrapped_key = - crate::postcard_serialize_bytes(&wrapped_key).map_err(|_| Error::CborError)?; - - Ok(reply::WrapKey { wrapped_key }) + Aead::new(Mechanism::Chacha8Poly1305).wrap_key(keystore, request) } #[inline(never)] @@ -198,38 +57,6 @@ impl MechanismImpl for super::Chacha8Poly1305 { keystore: &mut impl Keystore, request: &request::UnwrapKey, ) -> Result { - let encrypted_data: EncryptedData = - crate::postcard_deserialize(&request.wrapped_key).map_err(|_| Error::CborError)?; - - let decryption_request = encrypted_data.decrypt( - Mechanism::Chacha8Poly1305, - request.wrapping_key, - request.associated_data.clone(), - ); - - let serialized_key = - if let Some(serialized_key) = self.decrypt(keystore, &decryption_request)?.plaintext { - serialized_key - } else { - return Ok(reply::UnwrapKey { key: None }); - }; - - // TODO: probably change this to returning Option too - let key::Key { - flags: _, - kind, - material, - } = key::Key::try_deserialize(&serialized_key)?; - - // TODO: need to check both secret and private keys - let key_id = keystore.store_key( - request.attributes.persistence, - // using for signing keys... we need to know - key::Secrecy::Secret, - kind, - &material, - )?; - - Ok(reply::UnwrapKey { key: Some(key_id) }) + Aead::new(Mechanism::Chacha8Poly1305).unwrap_key(keystore, request) } } diff --git a/src/service.rs b/src/service.rs index 7764a7d07a0..03177a1bf3f 100644 --- a/src/service.rs +++ b/src/service.rs @@ -131,6 +131,8 @@ rpc_trait! { mechanisms = [ #[cfg(feature = "aes256-cbc")] Aes256Cbc, + #[cfg(feature = "aes256-gcm")] + Aes256Gcm, #[cfg(feature = "chacha8-poly1305")] Chacha8Poly1305, #[cfg(feature = "ed255")] diff --git a/src/tests.rs b/src/tests.rs index 823a8683d66..1721607d77f 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -684,154 +684,212 @@ fn agree_p521() { .signature; } -#[cfg(feature = "chacha8-poly1305")] +#[cfg(any(feature = "aes256-gcm", feature = "chacha8-poly1305"))] +const AEAD_MECHANISMS: &[trussed_core::types::Mechanism] = &[ + #[cfg(feature = "aes256-gcm")] + trussed_core::types::Mechanism::Aes256Gcm, + #[cfg(feature = "chacha8-poly1305")] + trussed_core::types::Mechanism::Chacha8Poly1305, +]; + +#[cfg(any(feature = "aes256-gcm", feature = "chacha8-poly1305"))] #[test] #[serial] fn aead_rng_nonce() { - use crate::client::mechanisms::Chacha8Poly1305; setup!(client); - let secret_key = block!(client - .generate_secret_key(32, Location::Volatile) - .expect("no client error")) - .expect("no errors") - .key; - - println!("got a key {:?}", &secret_key); - - let message = b"test message"; - let associated_data = b"solokeys.com"; - let api::reply::Encrypt { - ciphertext, - nonce, - tag, - } = block!(client - .encrypt_chacha8poly1305(secret_key, message, associated_data, None) - .expect("no client error")) - .expect("no errors"); - - let plaintext = block!(client - .decrypt_chacha8poly1305(secret_key, &ciphertext, associated_data, &nonce, &tag,) + for mechanism in AEAD_MECHANISMS { + let secret_key = block!(client + .generate_secret_key(32, Location::Volatile) + .expect("no client error")) + .expect("no errors") + .key; + + println!("got a key {:?}", &secret_key); + + let message = b"test message"; + let associated_data = b"solokeys.com"; + let api::reply::Encrypt { + ciphertext, + nonce, + tag, + } = block!(client + .encrypt(*mechanism, secret_key, message, associated_data, None) + .expect("no client error")) + .expect("no errors"); + + let plaintext = block!(client + .decrypt( + *mechanism, + secret_key, + &ciphertext, + associated_data, + &nonce, + &tag + ) + .map_err(drop) + .expect("no client error")) .map_err(drop) - .expect("no client error")) - .map_err(drop) - .expect("no errors") - .plaintext; + .expect("no errors") + .plaintext; - assert_ne!(&nonce, &[0; 12]); - assert_eq!(&message[..], plaintext.unwrap().as_ref()); + assert_ne!(&nonce, &[0; 12]); + assert_eq!(&message[..], plaintext.unwrap().as_ref()); + } } -#[cfg(feature = "chacha8-poly1305")] +#[cfg(any(feature = "aes256-gcm", feature = "chacha8-poly1305"))] #[test] #[serial] fn aead_given_nonce() { - use crate::client::mechanisms::Chacha8Poly1305; setup!(client); - let secret_key = block!(client - .generate_secret_key(32, Location::Volatile) - .expect("no client error")) - .expect("no errors") - .key; - - println!("got a key {:?}", &secret_key); - - let message = b"test message"; - let associated_data = b"solokeys.com"; - let static_nonce = b"123456789012"; - let api::reply::Encrypt { - ciphertext, - nonce, - tag, - } = block!(client - .encrypt_chacha8poly1305(secret_key, message, associated_data, Some(static_nonce)) - .expect("no client error")) - .expect("no errors"); - assert_eq!(&*nonce, static_nonce); - - let plaintext = block!(client - .decrypt_chacha8poly1305(secret_key, &ciphertext, associated_data, &nonce, &tag,) + for mechanism in AEAD_MECHANISMS { + let secret_key = block!(client + .generate_secret_key(32, Location::Volatile) + .expect("no client error")) + .expect("no errors") + .key; + + println!("got a key {:?}", &secret_key); + + let message = b"test message"; + let associated_data = b"solokeys.com"; + let static_nonce = b"123456789012"; + let api::reply::Encrypt { + ciphertext, + nonce, + tag, + } = block!(client + .encrypt( + *mechanism, + secret_key, + message, + associated_data, + Some(static_nonce.into()) + ) + .expect("no client error")) + .expect("no errors"); + assert_eq!(&*nonce, static_nonce); + + let plaintext = block!(client + .decrypt( + *mechanism, + secret_key, + &ciphertext, + associated_data, + &nonce, + &tag + ) + .map_err(drop) + .expect("no client error")) .map_err(drop) - .expect("no client error")) - .map_err(drop) - .expect("no errors") - .plaintext; + .expect("no errors") + .plaintext; - assert_eq!(&message[..], plaintext.unwrap().as_ref()); + assert_eq!(&message[..], plaintext.unwrap().as_ref()); + } } // Same as before but key generated with a nonce -#[cfg(feature = "chacha8-poly1305")] +#[cfg(any(feature = "aes256-gcm", feature = "chacha8-poly1305"))] #[test] #[serial] fn aead_given_nonce_2() { - use crate::client::mechanisms::Chacha8Poly1305; setup!(client); - let secret_key = block!(client - .generate_chacha8poly1305_key(Location::Volatile) - .expect("no client error")) - .expect("no errors") - .key; - - println!("got a key {:?}", &secret_key); - - let message = b"test message"; - let associated_data = b"solokeys.com"; - let static_nonce = b"123456789012"; - let api::reply::Encrypt { - ciphertext, - nonce, - tag, - } = block!(client - .encrypt_chacha8poly1305(secret_key, message, associated_data, Some(static_nonce)) - .expect("no client error")) - .expect("no errors"); - assert_eq!(&*nonce, static_nonce); - - let plaintext = block!(client - .decrypt_chacha8poly1305(secret_key, &ciphertext, associated_data, &nonce, &tag,) + for mechanism in AEAD_MECHANISMS { + let secret_key = block!(client + .generate_key( + *mechanism, + StorageAttributes::new().set_persistence(Location::Volatile) + ) + .expect("no client error")) + .expect("no errors") + .key; + + println!("got a key {:?}", &secret_key); + + let message = b"test message"; + let associated_data = b"solokeys.com"; + let static_nonce = b"123456789012"; + let api::reply::Encrypt { + ciphertext, + nonce, + tag, + } = block!(client + .encrypt( + *mechanism, + secret_key, + message, + associated_data, + Some(static_nonce.into()) + ) + .expect("no client error")) + .expect("no errors"); + assert_eq!(&*nonce, static_nonce); + + let plaintext = block!(client + .decrypt( + *mechanism, + secret_key, + &ciphertext, + associated_data, + &nonce, + &tag + ) + .map_err(drop) + .expect("no client error")) .map_err(drop) - .expect("no client error")) - .map_err(drop) - .expect("no errors") - .plaintext; + .expect("no errors") + .plaintext; - assert_eq!(&message[..], plaintext.unwrap().as_ref()); + assert_eq!(&message[..], plaintext.unwrap().as_ref()); + } } -#[cfg(feature = "chacha8-poly1305")] +#[cfg(any(feature = "aes256-gcm", feature = "chacha8-poly1305"))] #[test] #[serial] fn aead() { - use crate::client::mechanisms::Chacha8Poly1305; setup!(client); - let secret_key = block!(client - .generate_chacha8poly1305_key(Location::Volatile) - .expect("no client error")) - .expect("no errors") - .key; - - println!("got a key {:?}", &secret_key); - - let message = b"test message"; - let associated_data = b"solokeys.com"; - let api::reply::Encrypt { - ciphertext, - nonce, - tag, - } = block!(client - .encrypt_chacha8poly1305(secret_key, message, associated_data, None) - .expect("no client error")) - .expect("no errors"); - - let plaintext = block!(client - .decrypt_chacha8poly1305(secret_key, &ciphertext, associated_data, &nonce, &tag,) + for mechanism in AEAD_MECHANISMS { + let secret_key = block!(client + .generate_key( + *mechanism, + StorageAttributes::new().set_persistence(Location::Volatile) + ) + .expect("no client error")) + .expect("no errors") + .key; + + println!("got a key {:?}", &secret_key); + + let message = b"test message"; + let associated_data = b"solokeys.com"; + let api::reply::Encrypt { + ciphertext, + nonce, + tag, + } = block!(client + .encrypt(*mechanism, secret_key, message, associated_data, None) + .expect("no client error")) + .expect("no errors"); + + let plaintext = block!(client + .decrypt( + *mechanism, + secret_key, + &ciphertext, + associated_data, + &nonce, + &tag + ) + .map_err(drop) + .expect("no client error")) .map_err(drop) - .expect("no client error")) - .map_err(drop) - .expect("no errors") - .plaintext; + .expect("no errors") + .plaintext; - assert_eq!(&message[..], plaintext.unwrap().as_ref()); + assert_eq!(&message[..], plaintext.unwrap().as_ref()); + } } #[test]