From 3416146a1647aaf948b3b8260ab33facc2fe2cc1 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Tue, 20 Sep 2022 22:26:52 +0200 Subject: [PATCH 01/12] feat!: support multiple recipient COSE operations --- Cargo.toml | 21 +- src/common/test_helper.rs | 151 +++++++---- src/error/mod.rs | 25 +- src/lib.rs | 6 +- src/token/mod.rs | 518 +++++++++++++++++++++++++++---------- tests/integration_tests.rs | 12 +- 6 files changed, 524 insertions(+), 209 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d5df21a..48dd2e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "dcaf" description = "An implementation of the ACE-OAuth framework" -version = "0.3.1" +version = "0.4.0" edition = "2021" authors = ["Falko Galperin "] rust-version = "1.58" @@ -19,16 +19,17 @@ std = ["serde/std", "ciborium/std", "serde_bytes/std", "erased-serde/std", "deri [dependencies] serde = { version = "1.0", default-features = false, features = ["derive"] } -ciborium = { version = "^0.2.0", default-features = false } -ciborium-io = { version = "^0.2.0", default-features = false } -coset = { version = "^0.3.2", default-features = false } -serde_bytes = { version = "^0.11.7", default-features = false, features = ["alloc"] } -erased-serde = { version = "^0.3.22", default-features = false, features = ["alloc"] } -derive_builder = { version = "^0.11.2", default-features = false } +ciborium = { version = "^0.2", default-features = false } +ciborium-io = { version = "^0.2", default-features = false } +coset = { version = "^0.3", default-features = false } +serde_bytes = { version = "^0.11", default-features = false, features = ["alloc"] } +erased-serde = { version = "^0.3", default-features = false, features = ["alloc"] } +derive_builder = { version = "^0.11", default-features = false } strum = { version = "^0.24", default-features = false, features = ["derive"] } strum_macros = { version = "^0.24", default-features = false } -enumflags2 = { version = "^0.7.5", default-features = false } +enumflags2 = { version = "^0.7", default-features = false } +rand = { version = "^0.8", default-features = false } [dev-dependencies] -hex = { version = "^0.4.3" } -base64 = { version = "^0.13.0" } +hex = { version = "^0.4" } +base64 = { version = "^0.13" } diff --git a/src/common/test_helper.rs b/src/common/test_helper.rs index 0bf83f7..717aaed 100644 --- a/src/common/test_helper.rs +++ b/src/common/test_helper.rs @@ -12,15 +12,13 @@ //! Contains a few helper functions intended purely for tests. //! Not intended to be used outside of this crate. -use crate::common::cbor_map::ToCborMap; -use crate::error::CoseCipherError; -use crate::token::CoseCipherCommon; -use crate::{CoseEncrypt0Cipher, CoseMac0Cipher, CoseSign1Cipher}; -use ciborium::value::Value; use core::convert::identity; use core::fmt::Debug; + +use ciborium::value::Value; +use coset::{CoseKey, CoseKeyBuilder, Header, Label, ProtectedHeader}; use coset::iana::Algorithm; -use coset::{Header, Label}; +use rand::{CryptoRng, RngCore}; #[cfg(not(feature = "std"))] use { @@ -29,6 +27,11 @@ use { alloc::vec::Vec, }; +use crate::{CoseEncryptCipher, CoseMacCipher, CoseSignCipher}; +use crate::common::cbor_map::ToCborMap; +use crate::error::CoseCipherError; +use crate::token::{MultipleEncryptCipher, MultipleSignCipher}; + /// Helper function for tests which ensures that [`value`] serializes to the hexadecimal bytestring /// [expected_hex] and deserializes back to [`value`]. /// @@ -81,18 +84,12 @@ where } } -/// Used to implement a basic [`CipherProvider`] for tests (obviously not secure in any way). +/// Used to implement a basic `CipherProvider` for tests (obviously not secure in any way). #[derive(Copy, Clone)] pub(crate) struct FakeCrypto {} -impl CoseCipherCommon for FakeCrypto { - type Error = String; - - fn header( - &self, - unprotected_header: &mut Header, - protected_header: &mut Header, - ) -> Result<(), CoseCipherError> { +impl FakeCrypto { + fn set_headers_common(key: &[u8; 5], unprotected_header: &mut Header, protected_header: &mut Header, rng: RNG) -> Result<(), CoseCipherError> { // We have to later verify these headers really are used. if let Some(label) = unprotected_header .rest @@ -110,78 +107,132 @@ impl CoseCipherCommon for FakeCrypto { } } -/// Implements basic operations from the [`CoseEncrypt0Cipher`] trait without actually using any +struct Key([u8; 5]); + +impl AsRef for Key { + fn as_ref(&self) -> &CoseKey { + &CoseKeyBuilder::new_symmetric_key(self.0.to_vec()).build() + } +} + +impl TryFrom> for Key { + type Error = String; + + fn try_from(value: Vec) -> Result { + let key: [u8; 5] = value.try_into().map_err(|_| "Invalid input size")?; + Ok(Key(key)) + } +} + +impl From for Vec { + fn from(k: Key) -> Self { + k.0.to_vec() + } +} + +/// Implements basic operations from the [`CoseEncryptCipher`] trait without actually using any /// "real" cryptography. /// This is purely to be used for testing and obviously offers no security at all. -impl CoseEncrypt0Cipher for FakeCrypto { - fn encrypt(&mut self, data: &[u8], aad: &[u8]) -> Vec { - // We simply put AAD behind the data and call it a day. +impl CoseEncryptCipher for FakeCrypto { + type EncryptKey = Key; + type DecryptKey = Self::EncryptKey; + type Error = String; + + fn encrypt(key: &Self::EncryptKey, plaintext: &[u8], aad: &[u8], protected_header: &Header, unprotected_header: &Header) -> Vec { + // We put the key before and the AAD behind the data. + // Again, this obviously isn't secure in any sane definition of the word. let mut result: Vec = vec![]; - result.append(&mut data.to_vec()); + result.append(&mut key.0.to_vec()); + result.append(&mut plaintext.to_vec()); result.append(&mut aad.to_vec()); result } - fn decrypt( - &mut self, - data: &[u8], - aad: &[u8], - ) -> Result, CoseCipherError> { - // Now we just split off the AAD we previously put at the end of the data. + fn decrypt(key: &Self::DecryptKey, ciphertext: &[u8], aad: &[u8], unprotected_header: &Header, protected_header: &ProtectedHeader) -> Result, CoseCipherError> { + // Now we just split off the AAD and key we previously put at the end of the data. // We return an error if it does not match. - if data.len() < aad.len() { + if ciphertext.len() < (aad.len() + key.0.len()) { return Err(CoseCipherError::Other( - "Encrypted data must be at least as long as AAD!".to_string(), + "Encrypted data has invalid length!".to_string(), )); } - let mut result: Vec = data.to_vec(); - let aad_result = result.split_off(data.len() - aad.len()); - if aad == aad_result { - Ok(result) + let mut result: Vec = ciphertext.to_vec(); + let aad_result = result.split_off(ciphertext.len() + key.0.len()); + let plaintext = result.split_off(key.0.len()); + if aad == aad_result && key.0 == result.as_slice() { + Ok(plaintext) } else { Err(CoseCipherError::Other("AADs don't match!".to_string())) } } + + fn set_headers(key: &Self::EncryptKey, unprotected_header: &mut Header, protected_header: &mut Header, rng: RNG) -> Result<(), CoseCipherError> { + Self::set_headers_common(&key.0, unprotected_header, protected_header, rng) + } } /// Implements basic operations from the [`CoseSign1Cipher`] trait without actually using any /// "real" cryptography. /// This is purely to be used for testing and obviously offers no security at all. -impl CoseSign1Cipher for FakeCrypto { - fn generate_signature(&mut self, data: &[u8]) -> Vec { - data.to_vec() +impl CoseSignCipher for FakeCrypto { + type SignKey = Key; + type VerifyKey = Self::SignKey; + type Error = String; + + fn sign(key: &Self::SignKey, target: &[u8], unprotected_header: &Header, protected_header: &Header) -> Vec { + // We simply append the key behind the data. + let mut signature = target.to_vec(); + signature.append(&mut key.0.to_vec()); + signature } - fn verify_signature( - &mut self, - sig: &[u8], - data: &[u8], - ) -> Result<(), CoseCipherError> { - if sig == self.generate_signature(data) { + fn verify(key: &Self::VerifyKey, signature: &[u8], signed_data: &[u8], unprotected_header: &Header, protected_header: &ProtectedHeader, unprotected_signature_header: Option<&Header>, protected_signature_header: Option<&ProtectedHeader>) -> Result<(), CoseCipherError> { + if signature == Self::sign(key, signed_data, unprotected_header, &protected_header.header) { Ok(()) } else { Err(CoseCipherError::VerificationFailure) } } + + fn set_headers(key: &Self::SignKey, unprotected_header: &mut Header, protected_header: &mut Header, rng: RNG) -> Result<(), CoseCipherError> { + Self::set_headers_common(&key.0, unprotected_header, protected_header, rng) + } } /// Implements basic operations from the [`CoseMac0Cipher`] trait without actually using any /// "real" cryptography. /// This is purely to be used for testing and obviously offers no security at all. -impl CoseMac0Cipher for FakeCrypto { - fn generate_tag(&mut self, target: &[u8]) -> Vec { - target.to_vec() +impl CoseMacCipher for FakeCrypto { + type ComputeKey = Key; + type VerifyKey = Self::ComputeKey; + type Error = String; + + fn compute(key: &Self::ComputeKey, target: &[u8], unprotected_header: &Header, protected_header: &Header) -> Vec { + // We simply append the key behind the data. + let mut tag = target.to_vec(); + tag.append(&mut key.0.to_vec()); + tag } - fn verify_tag( - &mut self, - tag: &[u8], - maced_data: &[u8], - ) -> Result<(), CoseCipherError> { - if tag == self.generate_tag(maced_data) { + fn verify(key: &Self::VerifyKey, tag: &[u8], maced_data: &[u8], unprotected_header: &Header, protected_header: &ProtectedHeader) -> Result<(), CoseCipherError> { + if tag == Self::compute(key, maced_data, unprotected_header, &protected_header.header) { Ok(()) } else { Err(CoseCipherError::VerificationFailure) } } + + fn set_headers(key: &Self::ComputeKey, unprotected_header: &mut Header, protected_header: &mut Header, rng: RNG) -> Result<(), CoseCipherError> { + Self::set_headers_common(&key.0, unprotected_header, protected_header, rng) + } } + +impl MultipleEncryptCipher for FakeCrypto { + fn generate_cek(rng: &mut RNG) -> Self::EncryptKey { + let mut key = [0; 5]; + rng.fill_bytes(&mut key); + Key(key) + } +} + +impl MultipleSignCipher for FakeCrypto {} \ No newline at end of file diff --git a/src/error/mod.rs b/src/error/mod.rs index df54030..412cfb6 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -11,22 +11,21 @@ //! Contains error types used across this crate. +use core::any::type_name; +use core::fmt::{Display, Formatter}; + +use ciborium::value::Value; +use coset::{CoseError, Label}; +use strum_macros::IntoStaticStr; + #[cfg(feature = "std")] use {std::marker::PhantomData, std::num::TryFromIntError}; - #[cfg(not(feature = "std"))] use { alloc::format, alloc::string::String, alloc::string::ToString, core::num::TryFromIntError, derive_builder::export::core::marker::PhantomData, }; -use core::any::type_name; -use core::fmt::{Display, Formatter}; - -use ciborium::value::Value; -use coset::{CoseError, Label}; -use strum_macros::IntoStaticStr; - /// Error type used when the parameter of the type `T` couldn't be /// converted into [`expected_type`](WrongSourceTypeError::expected_type) because the received /// type was [`actual_type`](WrongSourceTypeError::actual_type) instead. @@ -440,6 +439,14 @@ where /// [`CoseEncrypt0`](coset::CoseEncrypt0), [`CoseSign1`](coset::CoseSign1), /// nor [`CoseMac0`](coset::CoseMac0). UnknownCoseStructure, + /// No matching key was found in the list of COSE_Recipient structures. + /// This means that the given Key Encryption Key could not be used to decrypt any of the + /// recipients, which means no Content Encryption Key could be extracted. + NoMatchingKey, + /// Multiple matching keys were found in the list of COSE_Recipient structures. + /// This means that the given Key Encryption Key could be used to decrypt multiple of the + /// recipients, which means the token is malformed. + MultipleMatchingKeys } impl Display for AccessTokenError @@ -454,6 +461,8 @@ where f, "input is either invalid or none of CoseEncrypt0, CoseSign1 nor CoseMac0" ), + AccessTokenError::NoMatchingKey => write!(f, "given KEK doesn't match any recipient"), + AccessTokenError::MultipleMatchingKeys => write!(f, "given KEK matches multiple recipients") } } } diff --git a/src/lib.rs b/src/lib.rs index f94287a..e584a6b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,7 +98,7 @@ //! # impl CoseCipherCommon for FakeCipher { //! # type Error = String; //! # -//! # fn header(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { +//! # fn set_headers(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { //! # Ok(()) //! # } //! # } @@ -232,8 +232,8 @@ pub use endpoints::token_req::{ }; #[doc(inline)] pub use token::{ - decrypt_access_token, encrypt_access_token, get_token_headers, sign_access_token, - verify_access_token, CoseCipherCommon, CoseEncrypt0Cipher, CoseMac0Cipher, CoseSign1Cipher, + CoseEncryptCipher, CoseMacCipher, CoseSignCipher, + decrypt_access_token, encrypt_access_token, get_token_headers, sign_access_token, verify_access_token, }; pub mod common; diff --git a/src/token/mod.rs b/src/token/mod.rs index f23ddd2..6cb7d10 100644 --- a/src/token/mod.rs +++ b/src/token/mod.rs @@ -40,7 +40,7 @@ //! # impl CoseCipherCommon for FakeCrypto { //! # type Error = String; //! # -//! # fn header(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { +//! # fn set_headers(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { //! # // We have to later verify these headers really are used. //! # if let Some(label) = unprotected_header.rest.iter().find(|x| x.0 == Label::Int(47)) { //! # return Err(CoseCipherError::existing_header_label(&label.0)); @@ -85,79 +85,75 @@ #[cfg(not(feature = "std"))] use alloc::vec::Vec; - -use crate::common::cbor_values::ByteString; use core::fmt::{Debug, Display}; + +use ciborium::value::Value; use coset::cwt::ClaimsSet; -use coset::{ - CborSerializable, CoseEncrypt0, CoseEncrypt0Builder, CoseMac0, CoseSign1, CoseSign1Builder, - Header, HeaderBuilder, ProtectedHeader, -}; +use coset::{AsCborValue, CborSerializable, CoseEncrypt, CoseEncrypt0, CoseEncrypt0Builder, CoseEncryptBuilder, CoseKey, CoseMac0, CoseRecipient, CoseRecipientBuilder, CoseSign, CoseSign1, CoseSign1Builder, CoseSignatureBuilder, CoseSignBuilder, EncryptionContext, Header, HeaderBuilder, ProtectedHeader}; +use rand::{CryptoRng, RngCore}; +use crate::common::cbor_values::ByteString; use crate::error::{AccessTokenError, CoseCipherError}; #[cfg(test)] mod tests; -/// Provides common operations necessary for other COSE cipher types to function. -/// -/// This needs to be implemented if [`CoseEncrypt0Cipher`], [`CoseSign1Cipher`], or -/// [`CoseMac0Cipher`] is to be implemented as well. -/// -/// See the documentation of [`header`](CoseCipherCommon::header) for an example. -pub trait CoseCipherCommon { - /// Error type that this cipher uses in [`Result`]s returned by cryptographic operations. - type Error: Display + Debug; +macro_rules! add_common_cipher_functionality { + [$a:ty] => { + /// Error type that this cipher uses in [`Result`]s returned by cryptographic operations. + type Error: Display + Debug; - /// Sets headers specific to this cipher by adding new header fields to the given - /// `unprotected_header` and `protected_header`. - /// - /// Before actually changing the headers, it will be verified that none of the header fields - /// that are about to be set are already set, so as not to overwrite them. In such a - /// case, an error is returned. - /// - /// This will usually not be called by users of `dcaf-rs`, but instead by access methods - /// such as [`encrypt_access_token`], which will later pass it to [`coset`]'s methods. - /// - /// # Errors - /// - When the fields that this method would set on the given headers are already set. - /// - /// # Example - /// Let's say our cipher needs to set the content type to - /// [`Cbor`](coset::iana::CoapContentFormat::Cbor) (in the unprotected header) - /// and the algorithm to [`HMAC_256_256`](coset::iana::Algorithm::HMAC_256_256) - /// (in the protected header). Our implementation would first need to verify that these - /// header fields haven't already been set, then actually set them, so an implementation - /// of this function might look like the following: - /// ``` - /// # use ciborium::value::Value; - /// # use coset::{ContentType, Header, Label, RegisteredLabel}; - /// # use coset::iana::Algorithm; - /// # use dcaf::CoseCipherCommon; - /// # use dcaf::error::CoseCipherError; - /// # struct FakeCipher {} - /// # impl CoseCipherCommon for FakeCipher { - /// # // This should of course be an actual error type, not just a String. - /// # type Error = String; - /// - /// fn header(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { - /// if unprotected_header.content_type.is_some() { - /// return Err(CoseCipherError::existing_header("content_type")); - /// } - /// if protected_header.alg.is_some() { - /// return Err(CoseCipherError::existing_header("alg")); - /// } - /// unprotected_header.content_type = Some(ContentType::Assigned(coset::iana::CoapContentFormat::Cbor)); - /// protected_header.alg = Some(coset::Algorithm::Assigned(Algorithm::HMAC_256_256)); - /// Ok(()) - /// } - /// # } - /// ``` - fn header( - &self, - unprotected_header: &mut Header, - protected_header: &mut Header, - ) -> Result<(), CoseCipherError>; + /// Sets headers specific to this cipher by adding new header fields to the given + /// `unprotected_header` and `protected_header`. + /// + /// Before actually changing the headers, it will be verified that none of the header fields + /// that are about to be set are already set, so as not to overwrite them. In such a + /// case, an error is returned. + /// + /// This will usually not be called by users of `dcaf-rs`, but instead by access methods + /// such as [`encrypt_access_token`], which will later pass it to [`coset`]'s methods. + /// + /// # Errors + /// - When the fields that this method would set on the given headers are already set. + /// + /// # Example + /// Let's say our cipher needs to set the content type to + /// [`Cbor`](coset::iana::CoapContentFormat::Cbor) (in the unprotected header) + /// and the algorithm to [`HMAC_256_256`](coset::iana::Algorithm::HMAC_256_256) + /// (in the protected header). Our implementation would first need to verify that these + /// header fields haven't already been set, then actually set them, so an implementation + /// of this function might look like the following: + /// ``` + /// # use ciborium::value::Value; + /// # use coset::{ContentType, Header, Label, RegisteredLabel}; + /// # use coset::iana::Algorithm; + /// # use dcaf::CoseCipherCommon; + /// # use dcaf::error::CoseCipherError; + /// # struct FakeCipher {} + /// # impl CoseCipherCommon for FakeCipher { + /// # // This should of course be an actual error type, not just a String. + /// # type Error = String; + /// + /// fn set_headers(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { + /// if unprotected_header.content_type.is_some() { + /// return Err(CoseCipherError::existing_header("content_type")); + /// } + /// if protected_header.alg.is_some() { + /// return Err(CoseCipherError::existing_header("alg")); + /// } + /// unprotected_header.content_type = Some(ContentType::Assigned(coset::iana::CoapContentFormat::Cbor)); + /// protected_header.alg = Some(coset::Algorithm::Assigned(Algorithm::HMAC_256_256)); + /// Ok(()) + /// } + /// # } + /// ``` + fn set_headers( + key: &$a, + unprotected_header: &mut Header, + protected_header: &mut Header, + rng: RNG + ) -> Result<(), CoseCipherError>; + } } /// Provides basic operations for encrypting and decrypting COSE structures. @@ -177,16 +173,16 @@ pub trait CoseCipherCommon { /// and implementing `decrypt` by verifying that the AAD matches (same warning applies): /// ``` /// # use coset::Header; -/// # use dcaf::{CoseCipherCommon, CoseEncrypt0Cipher}; +/// # use dcaf::{CoseCipherCommon, CoseEncryptCipher}; /// # use dcaf::error::CoseCipherError; /// # struct FakeCrypto {}; /// # impl CoseCipherCommon for FakeCrypto { /// # type Error = String; -/// # fn header(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { +/// # fn set_headers(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { /// # unimplemented!() /// # } /// # } -/// impl CoseEncrypt0Cipher for FakeCrypto { +/// impl CoseEncryptCipher for FakeCrypto { /// fn encrypt(&mut self, data: &[u8], aad: &[u8]) -> Vec { /// // We simply put AAD behind the data and call it a day. /// let mut result: Vec = Vec::new(); @@ -218,11 +214,19 @@ pub trait CoseCipherCommon { /// assert_eq!(cipher.decrypt(&encrypted, &aad)?, data); /// # Ok::<(), CoseCipherError>(()) /// ``` -pub trait CoseEncrypt0Cipher: CoseCipherCommon { +pub trait CoseEncryptCipher { + type EncryptKey: AsRef + Into>; + type DecryptKey: AsRef + TryFrom>; /// Encrypts the given `plaintext` and `aad`, returning the result. /// /// For an example, view the documentation of [`CoseEncrypt0Cipher`]. - fn encrypt(&mut self, plaintext: &[u8], aad: &[u8]) -> Vec; + fn encrypt( + key: &Self::EncryptKey, + plaintext: &[u8], + aad: &[u8], + protected_header: &Header, + unprotected_header: &Header, + ) -> Vec; /// Decrypts the given `ciphertext` and `aad`, returning the result. /// @@ -231,10 +235,18 @@ pub trait CoseEncrypt0Cipher: CoseCipherCommon { /// # Errors /// If the `ciphertext` and `aad` are invalid, i.e., can't be decrypted. fn decrypt( - &mut self, + key: &Self::DecryptKey, ciphertext: &[u8], aad: &[u8], + unprotected_header: &Header, + protected_header: &ProtectedHeader, ) -> Result, CoseCipherError>; + + add_common_cipher_functionality![Self::EncryptKey]; +} + +pub trait MultipleEncryptCipher: CoseEncryptCipher { + fn generate_cek(rng: &mut RNG) -> Self::EncryptKey; } /// Provides basic operations for signing and verifying COSE structures. @@ -258,7 +270,7 @@ pub trait CoseEncrypt0Cipher: CoseCipherCommon { /// # struct FakeSigner {}; /// # impl CoseCipherCommon for FakeSigner { /// # type Error = String; -/// # fn header(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { +/// # fn set_headers(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { /// # unimplemented!() /// # } /// # } @@ -281,11 +293,19 @@ pub trait CoseEncrypt0Cipher: CoseCipherCommon { /// assert!(signer.verify_signature(&signature, &vec![0xDC, 0xAF]).is_ok()); /// assert!(signer.verify_signature(&signature, &vec![0xDE, 0xAD]).is_err()); /// ``` -pub trait CoseSign1Cipher: CoseCipherCommon { +pub trait CoseSignCipher { + type SignKey: AsRef; + type VerifyKey: AsRef; + /// Cryptographically signs the given `target` value and returns the signature. /// /// For an example, see the documentation of [`CoseSign1Cipher`]. - fn generate_signature(&mut self, target: &[u8]) -> Vec; + fn sign( + key: &Self::SignKey, + target: &[u8], + unprotected_header: &Header, + protected_header: &Header, + ) -> Vec; /// Verifies the `signature` of the `signed_data`. /// @@ -293,13 +313,21 @@ pub trait CoseSign1Cipher: CoseCipherCommon { /// /// # Errors /// If the `signature` is invalid or does not belong to the `signed_data`. - fn verify_signature( - &mut self, + fn verify( + key: &Self::VerifyKey, signature: &[u8], signed_data: &[u8], + unprotected_header: &Header, + protected_header: &ProtectedHeader, + unprotected_signature_header: Option<&Header>, + protected_signature_header: Option<&ProtectedHeader>, ) -> Result<(), CoseCipherError>; + + add_common_cipher_functionality![Self::SignKey]; } +pub trait MultipleSignCipher: CoseSignCipher {} + /// Provides basic operations for generating and verifying MAC tags for COSE structures. /// /// This trait is currently not used by any access token function. @@ -309,21 +337,21 @@ pub trait CoseSign1Cipher: CoseCipherCommon { /// (which you **clearly should not do**, this is just for illustrative purposes): /// ``` /// # use coset::Header; -/// # use dcaf::{CoseCipherCommon, CoseMac0Cipher, CoseSign1Cipher}; +/// # use dcaf::{CoseCipherCommon, CoseMacCipher, CoseSign1Cipher}; /// # use dcaf::error::CoseCipherError; /// # struct FakeTagger {}; /// # impl CoseCipherCommon for FakeTagger { /// # type Error = String; -/// # fn header(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { +/// # fn set_headers(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { /// # unimplemented!() /// # } /// # } -/// impl CoseMac0Cipher for FakeTagger { -/// fn generate_tag(&mut self, target: &[u8]) -> Vec { +/// impl CoseMacCipher for FakeTagger { +/// fn compute(&mut self, target: &[u8]) -> Vec { /// target.to_vec() /// } /// -/// fn verify_tag(&mut self, tag: &[u8], signed_data: &[u8]) -> Result<(), CoseCipherError> { +/// fn verify(&mut self, tag: &[u8], signed_data: &[u8]) -> Result<(), CoseCipherError> { /// if tag != self.generate_tag(signed_data) { /// Err(CoseCipherError::VerificationFailure) /// } else { @@ -337,11 +365,19 @@ pub trait CoseSign1Cipher: CoseCipherCommon { /// assert!(tagger.verify_tag(&tag, &vec![0xDC, 0xAF]).is_ok()); /// assert!(tagger.verify_tag(&tag, &vec![0xDE, 0xAD]).is_err()); /// ``` -pub trait CoseMac0Cipher: CoseCipherCommon { +pub trait CoseMacCipher { + type ComputeKey: AsRef; + type VerifyKey: AsRef; + /// Generates a MAC tag for the given `target` and returns it. /// /// For an example, see the documentation of [`CoseMac0Cipher`]. - fn generate_tag(&mut self, target: &[u8]) -> Vec; + fn compute( + key: &Self::ComputeKey, + target: &[u8], + unprotected_header: &Header, + protected_header: &Header, + ) -> Vec; /// Verifies the `tag` of the `maced_data`. /// @@ -349,29 +385,34 @@ pub trait CoseMac0Cipher: CoseCipherCommon { /// /// # Errors /// If the `tag` is invalid or does not belong to the `maced_data`. - fn verify_tag( - &mut self, + fn verify( + key: &Self::VerifyKey, tag: &[u8], maced_data: &[u8], + unprotected_header: &Header, + protected_header: &ProtectedHeader, ) -> Result<(), CoseCipherError>; + + add_common_cipher_functionality![Self::ComputeKey]; } +pub trait MultipleMacCipher: CoseMacCipher {} + /// Creates new headers if `unprotected_header` or `protected_header` is `None`, respectively, /// and passes them to the `cipher`'s `header` function, returning the mutated result. -fn prepare_headers( - unprotected_header: Option
, - protected_header: Option
, - cipher: &T, -) -> Result<(Header, Header), AccessTokenError> -where - T: CoseCipherCommon, -{ - let mut unprotected = unprotected_header.unwrap_or_else(|| HeaderBuilder::new().build()); - let mut protected = protected_header.unwrap_or_else(|| HeaderBuilder::new().build()); - cipher - .header(&mut unprotected, &mut protected) - .map_err(AccessTokenError::from_cose_cipher_error)?; - Ok((unprotected, protected)) +/// Arguments: key (expr), unprotected (ident), protected (ident), rng (expr) cipher (type) +macro_rules! prepare_headers { + ($key:expr, $unprotected:ident, $protected:ident, $rng:expr, $t:ty) => {{ + let mut unprotected = $unprotected.unwrap_or_else(|| HeaderBuilder::new().build()); + let mut protected = $protected.unwrap_or_else(|| HeaderBuilder::new().build()); + if let Err(e) = <$t>::set_headers($key, &mut unprotected, &mut protected, $rng) + .map_err(AccessTokenError::from_cose_cipher_error) + { + Err(e) + } else { + Ok((unprotected, protected)) + } + }}; } /// Encrypts the given `claims` with the given headers and `aad` using `cipher` for cryptography, @@ -389,17 +430,17 @@ where /// # use coset::cwt::ClaimsSetBuilder; /// # use coset::Header; /// # use coset::iana::CwtClaimName; -/// # use dcaf::{ToCborMap, CoseCipherCommon, CoseEncrypt0Cipher, decrypt_access_token, encrypt_access_token, sign_access_token, verify_access_token}; +/// # use dcaf::{ToCborMap, CoseCipherCommon, CoseEncryptCipher, decrypt_access_token, encrypt_access_token, sign_access_token, verify_access_token}; /// # use dcaf::common::cbor_values::{ByteString, ProofOfPossessionKey}; /// # use dcaf::error::{AccessTokenError, CoseCipherError}; /// # struct FakeCrypto {}; /// # impl CoseCipherCommon for FakeCrypto { /// # type Error = String; -/// # fn header(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { +/// # fn set_headers(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { /// # Ok(()) /// # } /// # } -/// # impl CoseEncrypt0Cipher for FakeCrypto { +/// # impl CoseEncryptCipher for FakeCrypto { /// # fn encrypt(&mut self, data: &[u8], aad: &[u8]) -> Vec { /// # let mut result: Vec = Vec::new(); /// # result.append(&mut data.to_vec()); @@ -431,30 +472,76 @@ where /// assert_eq!(decrypt_access_token(&token, &mut cipher, None)?, claims); /// # Ok::<(), AccessTokenError>(()) /// ``` -pub fn encrypt_access_token( +pub fn encrypt_access_token( + key: T::EncryptKey, claims: ClaimsSet, - cipher: &mut T, - aad: Option<&[u8]>, + external_aad: Option<&[u8]>, unprotected_header: Option
, protected_header: Option
, + mut rng: RNG, ) -> Result> where - T: CoseEncrypt0Cipher, + T: CoseEncryptCipher, + RNG: RngCore + CryptoRng, { - let (unprotected, protected) = prepare_headers(unprotected_header, protected_header, cipher)?; + let (unprotected, protected) = + prepare_headers!(&key, unprotected_header, protected_header, &mut rng, T)?; CoseEncrypt0Builder::new() - .unprotected(unprotected) - .protected(protected) + .unprotected(unprotected.clone()) + .protected(protected.clone()) .create_ciphertext( &claims.to_vec().map_err(AccessTokenError::from_cose_error)?[..], - aad.unwrap_or(&[0; 0]), - |payload, aad| cipher.encrypt(payload, aad), + external_aad.unwrap_or(&[0; 0]), + |payload, aad| T::encrypt(&key, payload, aad, &unprotected, &protected), ) .build() .to_vec() .map_err(AccessTokenError::from_cose_error) } +/// Encrypts the given `claims` with the given headers and `aad` using `cipher` for cryptography, +/// returning the token as a serialized bytestring of the [`CoseEncrypt0`] structure. +/// +/// # Errors +/// TODO +/// +/// # Panics +/// TODO +pub fn encrypt_access_token_multiple( + keys: Vec<&T::EncryptKey>, + claims: ClaimsSet, + external_aad: Option<&[u8]>, + unprotected_header: Option
, + protected_header: Option
, + mut rng: RNG, +) -> Result> +where + T: MultipleEncryptCipher, + RNG: CryptoRng + RngCore, +{ + let key = T::generate_cek(&mut rng); + let (unprotected, protected) = prepare_headers!(&key, unprotected_header, protected_header, &mut rng, T)?; + let mut builder = CoseEncryptBuilder::new() + .unprotected(unprotected.clone()) + .protected(protected.clone()) + .create_ciphertext( + &claims.to_vec().map_err(AccessTokenError::from_cose_error)?[..], + external_aad.unwrap_or(&[0; 0]), + |payload, aad| T::encrypt(&key, payload, aad, &protected, &unprotected), + ); + let serialized_key = key.into(); + for rec_key in keys { + let (rec_unprotected, rec_protected) = prepare_headers!(rec_key, None, None, &mut rng, T)?; + builder = builder.add_recipient(CoseRecipientBuilder::new().protected(rec_protected.clone()).unprotected(rec_unprotected.clone()).create_ciphertext( + // TODO: What should AAD be here? + EncryptionContext::EncRecipient, &serialized_key, &[0; 0], |payload, aad| T::encrypt(rec_key, payload, aad, &rec_protected, &rec_unprotected) + ).build()); + } + builder.build() + .to_vec() + .map_err(AccessTokenError::from_cose_error) +} + /// Signs the given `claims` with the given headers and `aad` using `cipher` for cryptography, /// returning the token as a serialized bytestring of the [`CoseSign1`] structure. /// @@ -476,7 +563,7 @@ where /// # struct FakeSigner {}; /// # impl CoseCipherCommon for FakeSigner { /// # type Error = String; -/// # fn header(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { +/// # fn set_headers(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { /// # Ok(()) /// # } /// # } @@ -503,27 +590,66 @@ where /// assert!(verify_access_token(&token, &mut cipher, None).is_ok()); /// # Ok::<(), AccessTokenError>(()) /// ``` -pub fn sign_access_token( +pub fn sign_access_token( + key: &T::SignKey, claims: ClaimsSet, - cipher: &mut T, - aad: Option<&[u8]>, + external_aad: Option<&[u8]>, unprotected_header: Option
, protected_header: Option
, + mut rng: RNG, ) -> Result> where - T: CoseSign1Cipher, + T: CoseSignCipher, + RNG: RngCore + CryptoRng, { - let (unprotected, protected) = prepare_headers(unprotected_header, protected_header, cipher)?; + let (unprotected, protected) = + prepare_headers!(key, unprotected_header, protected_header, &mut rng, T)?; CoseSign1Builder::new() - .unprotected(unprotected) - .protected(protected) + .unprotected(unprotected.clone()) + .protected(protected.clone()) .payload(claims.to_vec().map_err(AccessTokenError::from_cose_error)?) - .create_signature(aad.unwrap_or(&[0; 0]), |x| cipher.generate_signature(x)) + .create_signature(external_aad.unwrap_or(&[0; 0]), |x| { + T::sign(key, x, &unprotected, &protected) + }) .build() .to_vec() .map_err(AccessTokenError::from_cose_error) } +/// TODO. +/// # Errors +/// TODO. +pub fn sign_access_token_multiple( + keys: Vec<&T::SignKey>, + claims: ClaimsSet, + external_aad: Option<&[u8]>, + unprotected_header: Option
, + protected_header: Option
, + mut rng: RNG, +) -> Result> + where + T: MultipleSignCipher, + RNG: RngCore + CryptoRng, +{ + let (unprotected, protected) = (unprotected_header.unwrap_or_else(|| HeaderBuilder::default().build()), protected_header.unwrap_or_else(|| HeaderBuilder::default().build())); + let mut builder = CoseSignBuilder::new() + .unprotected(unprotected.clone()) + .protected(protected.clone()) + .payload(claims.to_vec().map_err(AccessTokenError::from_cose_error)?); + + for key in keys { + let (rec_unprotected, rec_protected) = prepare_headers!(key, None, None, &mut rng, T)?; + let signature = CoseSignatureBuilder::new().unprotected(rec_unprotected.clone()).protected(rec_protected.clone()).build(); + builder = builder.add_created_signature(signature, external_aad.unwrap_or(&[0; 0]), |x| { + T::sign(key, x, &unprotected, &protected) + }); + } + + builder.build() + .to_vec() + .map_err(AccessTokenError::from_cose_error) +} + /// Returns the headers of the given signed ([`CoseSign1`]), MAC tagged ([`CoseMac0`]), /// or encrypted ([`CoseEncrypt0`]) access token. /// @@ -553,13 +679,27 @@ where /// ``` #[must_use] pub fn get_token_headers(token: &ByteString) -> Option<(Header, ProtectedHeader)> { - CoseSign1::from_slice(token.as_slice()) - .map(|x| (x.unprotected, x.protected)) - .or_else(|_| { - CoseEncrypt0::from_slice(token.as_slice()).map(|x| (x.unprotected, x.protected)) - }) - .or_else(|_| CoseMac0::from_slice(token.as_slice()).map(|x| (x.unprotected, x.protected))) - .ok() + let value: Option = ciborium::de::from_reader(token.as_slice()).ok(); + // All of COSE_Encrypt(0), COSE_Sign(1), COSE_Mac(0) are an array with headers first + match value { + Some(Value::Array(x)) => { + let mut iter = x.into_iter(); + let unprotected = iter + .next() + .map(Header::from_cbor_value) + .and_then(Result::ok); + let protected = iter + .next() + .map(ProtectedHeader::from_cbor_value) + .and_then(Result::ok); + if let (Some(u), Some(p)) = (unprotected, protected) { + Some((u, p)) + } else { + None + } + } + Some(_) | None => None, + } } /// Verifies the given `token` and `aad` using `verifier` for cryptography, @@ -576,21 +716,79 @@ pub fn get_token_headers(token: &ByteString) -> Option<(Header, ProtectedHeader) /// - When there's a verification error coming from the `verifier` /// (e.g., if the `token`'s data does not match its signature). pub fn verify_access_token( + key: &T::VerifyKey, token: &ByteString, - cipher: &mut T, aad: Option<&[u8]>, ) -> Result<(), AccessTokenError> where - T: CoseSign1Cipher, + T: CoseSignCipher, { let sign = CoseSign1::from_slice(token.as_slice()).map_err(AccessTokenError::CoseError)?; + let (unprotected, protected) = + get_token_headers(token).ok_or(AccessTokenError::UnknownCoseStructure)?; // TODO: Verify protected headers sign.verify_signature(aad.unwrap_or(&[0; 0]), |signature, signed_data| { - cipher.verify_signature(signature, signed_data) + T::verify( + key, + signature, + signed_data, + &unprotected, + &protected, + None, + None, + ) }) .map_err(AccessTokenError::from_cose_cipher_error) } +/// TODO. +/// # Errors +/// TODO. +/// # Panics +/// TODO. +pub fn verify_access_token_multiple( + key: &T::VerifyKey, + kid: &[u8], + token: &ByteString, + aad: Option<&[u8]>, +) -> Result<(), AccessTokenError> +where + T: CoseSignCipher, +{ + let sign = CoseSign::from_slice(token.as_slice()).map_err(AccessTokenError::CoseError)?; + let (unprotected, protected) = + get_token_headers(token).ok_or(AccessTokenError::UnknownCoseStructure)?; + let matching = sign + .signatures + .iter() + .enumerate() + .filter(|(_, s)| s.unprotected.key_id == kid || s.protected.header.key_id == kid) + .map(|(i, _)| i); + // We iterate over each signature whose kid matches until it completes successfully. + // TODO: However: https://www.rfc-editor.org/rfc/rfc9052.html#section-4.1-3 + for index in matching { + // TODO: Verify protected headers + if let Ok(()) = + sign.verify_signature(index, aad.unwrap_or(&[0; 0]), |signature, signed_data| { + T::verify( + key, + signature, + signed_data, + &unprotected, + &protected, + Some(&sign.signatures[index].unprotected), + Some(&sign.signatures[index].protected), + ) + }) + { + return Ok(()); + } + } + Err(AccessTokenError::from_cose_cipher_error( + CoseCipherError::VerificationFailure, + )) +} + /// Decrypts the given `token` and `aad` using `cipher` for cryptography, /// returning the decrypted `ClaimsSet`. /// @@ -604,19 +802,73 @@ where /// - When the deserialized and decrypted [`CoseEncrypt0`] structure does not contain a valid /// [`ClaimsSet`]. pub fn decrypt_access_token( + key: &T::DecryptKey, token: &ByteString, - cipher: &mut T, aad: Option<&[u8]>, ) -> Result> where - T: CoseEncrypt0Cipher, + T: CoseEncryptCipher, { let encrypt = CoseEncrypt0::from_slice(token.as_slice()).map_err(AccessTokenError::from_cose_error)?; + let (unprotected, protected) = + get_token_headers(token).ok_or(AccessTokenError::UnknownCoseStructure)?; + // TODO: Verify protected header let result = encrypt .decrypt(aad.unwrap_or(&[0; 0]), |ciphertext, aad| { - cipher.decrypt(ciphertext, aad) + T::decrypt(key, ciphertext, aad, &unprotected, &protected) }) .map_err(AccessTokenError::from_cose_cipher_error)?; ClaimsSet::from_slice(result.as_slice()).map_err(AccessTokenError::from_cose_error) } + +/// TODO. +/// # Errors +/// TODO. +pub fn decrypt_access_token_multiple( + kek: &K::DecryptKey, + token: &ByteString, + external_aad: Option<&[u8]>, +) -> Result> +where + K: CoseEncryptCipher, + C: CoseEncryptCipher, +{ + let encrypt = + CoseEncrypt::from_slice(token.as_slice()).map_err(AccessTokenError::from_cose_error)?; + let (unprotected, protected) = + get_token_headers(token).ok_or(AccessTokenError::UnknownCoseStructure)?; + let aad = external_aad.unwrap_or(&[0; 0]); + let kek_id = kek.as_ref().key_id.as_slice(); + // One of the recipient structures should contain CEK encrypted with our KEK. + let recipients = encrypt + .recipients + .iter() + .filter(|x| x.unprotected.key_id == kek_id || x.protected.header.key_id == kek_id); + let mut content_keys = recipients.filter_map(|r| { + r.decrypt(EncryptionContext::EncRecipient, aad, |ciphertext, aad| { + K::decrypt(kek, ciphertext, aad, &r.unprotected, &r.protected) + }) + .ok() + }); + // Our CEK must be contained exactly once. + if let Some(content_key) = content_keys.next() { + if content_keys.next().is_none() { + let target_key = C::DecryptKey::try_from(content_key).map_err(|_| { + AccessTokenError::from_cose_cipher_error(CoseCipherError::DecryptionFailure) + })?; + // TODO: Verify protected header + let result = encrypt + .decrypt(aad, |ciphertext, aad| { + C::decrypt(&target_key, ciphertext, aad, &unprotected, &protected) + }) + .map_err(AccessTokenError::from_cose_cipher_error)?; + ClaimsSet::from_slice(result.as_slice()).map_err(AccessTokenError::from_cose_error) + } else { + // TODO: Implement strict mode, where this is prohibited, otherwise allow it + Err(AccessTokenError::MultipleMatchingKeys) + } + } else { + Err(AccessTokenError::NoMatchingKey) + } +} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 0b7b170..902aeb9 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -9,11 +9,15 @@ * SPDX-License-Identifier: MIT OR Apache-2.0 */ +use std::fmt::Debug; + use ciborium::value::Value; +use coset::{CoseKeyBuilder, Header, HeaderBuilder, Label}; use coset::cwt::{ClaimsSetBuilder, Timestamp}; -use coset::iana::EllipticCurve::P_256; use coset::iana::{Algorithm, CwtClaimName}; -use coset::{CoseKeyBuilder, Header, HeaderBuilder, Label}; +use coset::iana::EllipticCurve::P_256; + +use dcaf::{CoseSign1Cipher, sign_access_token}; use dcaf::common::cbor_map::ToCborMap; use dcaf::common::cbor_values::ProofOfPossessionKey::PlainCoseKey; use dcaf::common::scope::TextEncodedScope; @@ -24,8 +28,6 @@ use dcaf::endpoints::token_req::{ }; use dcaf::error::CoseCipherError; use dcaf::token::CoseCipherCommon; -use dcaf::{sign_access_token, CoseSign1Cipher}; -use std::fmt::Debug; fn example_headers() -> (Header, Header) { let unprotected_header = HeaderBuilder::new() @@ -47,7 +49,7 @@ pub(crate) struct FakeCrypto {} impl CoseCipherCommon for FakeCrypto { type Error = String; - fn header( + fn set_headers( &self, unprotected_header: &mut Header, protected_header: &mut Header, From 534342f16b4deec188b93a665d70b65472a170a4 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Sun, 16 Oct 2022 19:59:35 +0200 Subject: [PATCH 02/12] refactor: change `AsRef` bound to `ToCoseKey` --- src/token/mod.rs | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/src/token/mod.rs b/src/token/mod.rs index 6cb7d10..912fe47 100644 --- a/src/token/mod.rs +++ b/src/token/mod.rs @@ -88,8 +88,8 @@ use alloc::vec::Vec; use core::fmt::{Debug, Display}; use ciborium::value::Value; -use coset::cwt::ClaimsSet; use coset::{AsCborValue, CborSerializable, CoseEncrypt, CoseEncrypt0, CoseEncrypt0Builder, CoseEncryptBuilder, CoseKey, CoseMac0, CoseRecipient, CoseRecipientBuilder, CoseSign, CoseSign1, CoseSign1Builder, CoseSignatureBuilder, CoseSignBuilder, EncryptionContext, Header, HeaderBuilder, ProtectedHeader}; +use coset::cwt::ClaimsSet; use rand::{CryptoRng, RngCore}; use crate::common::cbor_values::ByteString; @@ -98,6 +98,10 @@ use crate::error::{AccessTokenError, CoseCipherError}; #[cfg(test)] mod tests; +pub trait ToCoseKey { + fn to_cose_key(&self) -> CoseKey; +} + macro_rules! add_common_cipher_functionality { [$a:ty] => { /// Error type that this cipher uses in [`Result`]s returned by cryptographic operations. @@ -215,8 +219,8 @@ macro_rules! add_common_cipher_functionality { /// # Ok::<(), CoseCipherError>(()) /// ``` pub trait CoseEncryptCipher { - type EncryptKey: AsRef + Into>; - type DecryptKey: AsRef + TryFrom>; + type EncryptKey: ToCoseKey + Into>; + type DecryptKey: ToCoseKey + TryFrom>; /// Encrypts the given `plaintext` and `aad`, returning the result. /// /// For an example, view the documentation of [`CoseEncrypt0Cipher`]. @@ -294,8 +298,8 @@ pub trait MultipleEncryptCipher: CoseEncryptCipher { /// assert!(signer.verify_signature(&signature, &vec![0xDE, 0xAD]).is_err()); /// ``` pub trait CoseSignCipher { - type SignKey: AsRef; - type VerifyKey: AsRef; + type SignKey: ToCoseKey; + type VerifyKey: ToCoseKey; /// Cryptographically signs the given `target` value and returns the signature. /// @@ -366,8 +370,8 @@ pub trait MultipleSignCipher: CoseSignCipher {} /// assert!(tagger.verify_tag(&tag, &vec![0xDE, 0xAD]).is_err()); /// ``` pub trait CoseMacCipher { - type ComputeKey: AsRef; - type VerifyKey: AsRef; + type ComputeKey: ToCoseKey; + type VerifyKey: ToCoseKey; /// Generates a MAC tag for the given `target` and returns it. /// @@ -529,7 +533,7 @@ where external_aad.unwrap_or(&[0; 0]), |payload, aad| T::encrypt(&key, payload, aad, &protected, &unprotected), ); - let serialized_key = key.into(); + let serialized_key: Vec = key.into(); for rec_key in keys { let (rec_unprotected, rec_protected) = prepare_headers!(rec_key, None, None, &mut rng, T)?; builder = builder.add_recipient(CoseRecipientBuilder::new().protected(rec_protected.clone()).unprotected(rec_unprotected.clone()).create_ciphertext( @@ -684,13 +688,13 @@ pub fn get_token_headers(token: &ByteString) -> Option<(Header, ProtectedHeader) match value { Some(Value::Array(x)) => { let mut iter = x.into_iter(); - let unprotected = iter + let protected = iter .next() - .map(Header::from_cbor_value) + .map(ProtectedHeader::from_cbor_bstr) .and_then(Result::ok); - let protected = iter + let unprotected = iter .next() - .map(ProtectedHeader::from_cbor_value) + .map(Header::from_cbor_value) .and_then(Result::ok); if let (Some(u), Some(p)) = (unprotected, protected) { Some((u, p)) @@ -804,7 +808,7 @@ where pub fn decrypt_access_token( key: &T::DecryptKey, token: &ByteString, - aad: Option<&[u8]>, + external_aad: Option<&[u8]>, ) -> Result> where T: CoseEncryptCipher, @@ -815,7 +819,7 @@ where get_token_headers(token).ok_or(AccessTokenError::UnknownCoseStructure)?; // TODO: Verify protected header let result = encrypt - .decrypt(aad.unwrap_or(&[0; 0]), |ciphertext, aad| { + .decrypt(external_aad.unwrap_or(&[0; 0]), |ciphertext, aad| { T::decrypt(key, ciphertext, aad, &unprotected, &protected) }) .map_err(AccessTokenError::from_cose_cipher_error)?; @@ -839,7 +843,9 @@ where let (unprotected, protected) = get_token_headers(token).ok_or(AccessTokenError::UnknownCoseStructure)?; let aad = external_aad.unwrap_or(&[0; 0]); - let kek_id = kek.as_ref().key_id.as_slice(); + // FIXME: below line does not compile + let cose_kek: CoseKey = kek.to_cose_key(); + let kek_id = cose_kek.key_id.as_slice(); // One of the recipient structures should contain CEK encrypted with our KEK. let recipients = encrypt .recipients @@ -849,7 +855,7 @@ where r.decrypt(EncryptionContext::EncRecipient, aad, |ciphertext, aad| { K::decrypt(kek, ciphertext, aad, &r.unprotected, &r.protected) }) - .ok() + .ok() }); // Our CEK must be contained exactly once. if let Some(content_key) = content_keys.next() { From 2cc15f2ef27f2bfbeba7bac9fd4d60d95210c446 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Sun, 16 Oct 2022 20:00:05 +0200 Subject: [PATCH 03/12] test: fix failing token tests (except doctests) --- src/common/test_helper.rs | 227 +++++++++++++++++++++++++++++-------- src/error/mod.rs | 84 ++++++++++++-- src/token/mod.rs | 63 +++++----- src/token/tests.rs | 80 ++++++------- tests/integration_tests.rs | 179 ++++++++++++++++++++++------- 5 files changed, 463 insertions(+), 170 deletions(-) diff --git a/src/common/test_helper.rs b/src/common/test_helper.rs index 717aaed..d63335b 100644 --- a/src/common/test_helper.rs +++ b/src/common/test_helper.rs @@ -13,12 +13,12 @@ //! Not intended to be used outside of this crate. use core::convert::identity; -use core::fmt::Debug; +use core::fmt::{Debug, Display}; use ciborium::value::Value; use coset::{CoseKey, CoseKeyBuilder, Header, Label, ProtectedHeader}; use coset::iana::Algorithm; -use rand::{CryptoRng, RngCore}; +use rand::{CryptoRng, Error, RngCore}; #[cfg(not(feature = "std"))] use { @@ -29,8 +29,8 @@ use { use crate::{CoseEncryptCipher, CoseMacCipher, CoseSignCipher}; use crate::common::cbor_map::ToCborMap; -use crate::error::CoseCipherError; -use crate::token::{MultipleEncryptCipher, MultipleSignCipher}; +use crate::error::{AccessTokenError, CoseCipherError, MultipleCoseError}; +use crate::token::{MultipleEncryptCipher, MultipleSignCipher, ToCoseKey}; /// Helper function for tests which ensures that [`value`] serializes to the hexadecimal bytestring /// [expected_hex] and deserializes back to [`value`]. @@ -89,7 +89,11 @@ where pub(crate) struct FakeCrypto {} impl FakeCrypto { - fn set_headers_common(key: &[u8; 5], unprotected_header: &mut Header, protected_header: &mut Header, rng: RNG) -> Result<(), CoseCipherError> { + fn set_headers_common( + key: &FakeKey, + unprotected_header: &mut Header, + protected_header: &mut Header, + ) -> Result<(), CoseCipherError> { // We have to later verify these headers really are used. if let Some(label) = unprotected_header .rest @@ -101,32 +105,47 @@ impl FakeCrypto { if protected_header.alg != None { return Err(CoseCipherError::existing_header("alg")); } + if !protected_header.key_id.is_empty() { + return Err(CoseCipherError::existing_header("key_id")); + } unprotected_header.rest.push((Label::Int(47), Value::Null)); protected_header.alg = Some(coset::Algorithm::Assigned(Algorithm::Direct)); + protected_header.key_id = key.kid.to_vec(); Ok(()) } } -struct Key([u8; 5]); +#[derive(Clone)] +pub(crate) struct FakeKey { + key: [u8; 5], + kid: [u8; 2], +} -impl AsRef for Key { - fn as_ref(&self) -> &CoseKey { - &CoseKeyBuilder::new_symmetric_key(self.0.to_vec()).build() +impl ToCoseKey for FakeKey { + fn to_cose_key(&self) -> CoseKey { + CoseKeyBuilder::new_symmetric_key(self.key.to_vec()) + .key_id(self.kid.to_vec()) + .build() } } -impl TryFrom> for Key { +impl TryFrom> for FakeKey { type Error = String; - fn try_from(value: Vec) -> Result { + // Should be 5 bytes of key + 2 bytes of kid + fn try_from(mut value: Vec) -> Result { + let kid_value = value.split_off(5); let key: [u8; 5] = value.try_into().map_err(|_| "Invalid input size")?; - Ok(Key(key)) + let kid: [u8; 2] = kid_value.try_into().map_err(|_| "Invalid input size")?; + Ok(FakeKey { key, kid }) } } -impl From for Vec { - fn from(k: Key) -> Self { - k.0.to_vec() +impl From for Vec { + fn from(k: FakeKey) -> Self { + let mut key = k.key.to_vec(); + key.append(&mut k.kid.to_vec()); + key } } @@ -134,40 +153,60 @@ impl From for Vec { /// "real" cryptography. /// This is purely to be used for testing and obviously offers no security at all. impl CoseEncryptCipher for FakeCrypto { - type EncryptKey = Key; + type EncryptKey = FakeKey; type DecryptKey = Self::EncryptKey; type Error = String; - fn encrypt(key: &Self::EncryptKey, plaintext: &[u8], aad: &[u8], protected_header: &Header, unprotected_header: &Header) -> Vec { - // We put the key before and the AAD behind the data. + fn encrypt( + key: &Self::EncryptKey, + plaintext: &[u8], + aad: &[u8], + protected_header: &Header, + unprotected_header: &Header, + ) -> Vec { + // We put the key and the AAD before the data. // Again, this obviously isn't secure in any sane definition of the word. - let mut result: Vec = vec![]; - result.append(&mut key.0.to_vec()); - result.append(&mut plaintext.to_vec()); + let mut result: Vec = key.key.to_vec(); result.append(&mut aad.to_vec()); + result.append(&mut plaintext.to_vec()); result } - fn decrypt(key: &Self::DecryptKey, ciphertext: &[u8], aad: &[u8], unprotected_header: &Header, protected_header: &ProtectedHeader) -> Result, CoseCipherError> { + fn decrypt( + key: &Self::DecryptKey, + ciphertext: &[u8], + aad: &[u8], + unprotected_header: &Header, + protected_header: &ProtectedHeader, + ) -> Result, CoseCipherError> { // Now we just split off the AAD and key we previously put at the end of the data. // We return an error if it does not match. - if ciphertext.len() < (aad.len() + key.0.len()) { + if key.kid.to_vec() != protected_header.header.key_id { + // Mismatching key + return Err(CoseCipherError::DecryptionFailure); + } + if ciphertext.len() < (aad.len() + key.key.len()) { return Err(CoseCipherError::Other( "Encrypted data has invalid length!".to_string(), )); } let mut result: Vec = ciphertext.to_vec(); - let aad_result = result.split_off(ciphertext.len() + key.0.len()); - let plaintext = result.split_off(key.0.len()); - if aad == aad_result && key.0 == result.as_slice() { + let plaintext = result.split_off(aad.len() + key.key.len()); + let aad_result = result.split_off(key.key.len()); + if aad == aad_result && key.key == result.as_slice() { Ok(plaintext) } else { - Err(CoseCipherError::Other("AADs don't match!".to_string())) + Err(CoseCipherError::DecryptionFailure) } } - fn set_headers(key: &Self::EncryptKey, unprotected_header: &mut Header, protected_header: &mut Header, rng: RNG) -> Result<(), CoseCipherError> { - Self::set_headers_common(&key.0, unprotected_header, protected_header, rng) + fn set_headers( + key: &Self::EncryptKey, + unprotected_header: &mut Header, + protected_header: &mut Header, + rng: RNG, + ) -> Result<(), CoseCipherError> { + Self::set_headers_common(key, unprotected_header, protected_header) } } @@ -175,27 +214,52 @@ impl CoseEncryptCipher for FakeCrypto { /// "real" cryptography. /// This is purely to be used for testing and obviously offers no security at all. impl CoseSignCipher for FakeCrypto { - type SignKey = Key; + type SignKey = FakeKey; type VerifyKey = Self::SignKey; type Error = String; - fn sign(key: &Self::SignKey, target: &[u8], unprotected_header: &Header, protected_header: &Header) -> Vec { + fn sign( + key: &Self::SignKey, + target: &[u8], + unprotected_header: &Header, + protected_header: &Header, + ) -> Vec { // We simply append the key behind the data. let mut signature = target.to_vec(); - signature.append(&mut key.0.to_vec()); + signature.append(&mut key.key.to_vec()); signature } - fn verify(key: &Self::VerifyKey, signature: &[u8], signed_data: &[u8], unprotected_header: &Header, protected_header: &ProtectedHeader, unprotected_signature_header: Option<&Header>, protected_signature_header: Option<&ProtectedHeader>) -> Result<(), CoseCipherError> { - if signature == Self::sign(key, signed_data, unprotected_header, &protected_header.header) { + fn verify( + key: &Self::VerifyKey, + signature: &[u8], + signed_data: &[u8], + unprotected_header: &Header, + protected_header: &ProtectedHeader, + unprotected_signature_header: Option<&Header>, + protected_signature_header: Option<&ProtectedHeader>, + ) -> Result<(), CoseCipherError> { + let matching_kid = if let Some(protected) = protected_signature_header { + protected.header.key_id == key.kid + } else { + protected_header.header.key_id == key.kid + }; + let signed_again = Self::sign(key, signed_data, unprotected_header, &protected_header.header); + if matching_kid && signed_again == signature + { Ok(()) } else { Err(CoseCipherError::VerificationFailure) } } - fn set_headers(key: &Self::SignKey, unprotected_header: &mut Header, protected_header: &mut Header, rng: RNG) -> Result<(), CoseCipherError> { - Self::set_headers_common(&key.0, unprotected_header, protected_header, rng) + fn set_headers( + key: &Self::SignKey, + unprotected_header: &mut Header, + protected_header: &mut Header, + rng: RNG, + ) -> Result<(), CoseCipherError> { + Self::set_headers_common(&key, unprotected_header, protected_header) } } @@ -203,36 +267,109 @@ impl CoseSignCipher for FakeCrypto { /// "real" cryptography. /// This is purely to be used for testing and obviously offers no security at all. impl CoseMacCipher for FakeCrypto { - type ComputeKey = Key; + type ComputeKey = FakeKey; type VerifyKey = Self::ComputeKey; type Error = String; - fn compute(key: &Self::ComputeKey, target: &[u8], unprotected_header: &Header, protected_header: &Header) -> Vec { + fn compute( + key: &Self::ComputeKey, + target: &[u8], + unprotected_header: &Header, + protected_header: &Header, + ) -> Vec { // We simply append the key behind the data. let mut tag = target.to_vec(); - tag.append(&mut key.0.to_vec()); + tag.append(&mut key.key.to_vec()); tag } - fn verify(key: &Self::VerifyKey, tag: &[u8], maced_data: &[u8], unprotected_header: &Header, protected_header: &ProtectedHeader) -> Result<(), CoseCipherError> { - if tag == Self::compute(key, maced_data, unprotected_header, &protected_header.header) { + fn verify( + key: &Self::VerifyKey, + tag: &[u8], + maced_data: &[u8], + unprotected_header: &Header, + protected_header: &ProtectedHeader, + ) -> Result<(), CoseCipherError> { + if protected_header.header.key_id == key.kid + && tag + == Self::compute( + key, + maced_data, + unprotected_header, + &protected_header.header, + ) + { Ok(()) } else { Err(CoseCipherError::VerificationFailure) } } - fn set_headers(key: &Self::ComputeKey, unprotected_header: &mut Header, protected_header: &mut Header, rng: RNG) -> Result<(), CoseCipherError> { - Self::set_headers_common(&key.0, unprotected_header, protected_header, rng) + fn set_headers( + key: &Self::ComputeKey, + unprotected_header: &mut Header, + protected_header: &mut Header, + rng: RNG, + ) -> Result<(), CoseCipherError> { + Self::set_headers_common(&key, unprotected_header, protected_header) } } impl MultipleEncryptCipher for FakeCrypto { fn generate_cek(rng: &mut RNG) -> Self::EncryptKey { let mut key = [0; 5]; + let mut kid = [0; 2]; rng.fill_bytes(&mut key); - Key(key) + rng.fill_bytes(&mut kid); + FakeKey { key, kid } + } +} + +impl MultipleSignCipher for FakeCrypto {} + +#[derive(Clone, Copy)] +pub(crate) struct FakeRng; + +impl RngCore for FakeRng { + fn next_u32(&mut self) -> u32 { + 0 + } + + fn next_u64(&mut self) -> u64 { + 0 + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + dest.fill(0) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + Ok(dest.fill(0)) } } -impl MultipleSignCipher for FakeCrypto {} \ No newline at end of file +impl CryptoRng for FakeRng {} + +// Makes the tests easier later on, as we use String as the error type in there. +impl From>> for CoseCipherError where C: Display, K: Display { + fn from(x: CoseCipherError>) -> Self { + match x { + CoseCipherError::HeaderAlreadySet { existing_header_name } => CoseCipherError::HeaderAlreadySet { existing_header_name }, + CoseCipherError::VerificationFailure => CoseCipherError::VerificationFailure, + CoseCipherError::DecryptionFailure => CoseCipherError::DecryptionFailure, + CoseCipherError::Other(x) => CoseCipherError::Other(x.to_string()) + } + } +} + +impl From>> for AccessTokenError where C: Display, K: Display { + fn from(x: AccessTokenError>) -> Self { + match x { + AccessTokenError::CoseError(x) => AccessTokenError::CoseError(x), + AccessTokenError::CoseCipherError(x) => AccessTokenError::CoseCipherError(CoseCipherError::from(x)), + AccessTokenError::UnknownCoseStructure => AccessTokenError::UnknownCoseStructure, + AccessTokenError::NoMatchingKey => AccessTokenError::NoMatchingKey, + AccessTokenError::MultipleMatchingKeys => AccessTokenError::MultipleMatchingKeys + } + } +} \ No newline at end of file diff --git a/src/error/mod.rs b/src/error/mod.rs index 412cfb6..91a252f 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -299,6 +299,29 @@ where pub fn other_error(other: T) -> CoseCipherError { CoseCipherError::Other(other) } + + // TODO: Maybe there's a better way to do the below, parts of this are redundant and duplicated. + pub(crate) fn from_kek_error(error: CoseCipherError) -> CoseCipherError> { + match error { + CoseCipherError::Other(x) => CoseCipherError::Other(MultipleCoseError::KekError(x)), + CoseCipherError::HeaderAlreadySet { existing_header_name } => CoseCipherError::HeaderAlreadySet { + existing_header_name + }, + CoseCipherError::VerificationFailure => CoseCipherError::VerificationFailure, + CoseCipherError::DecryptionFailure => CoseCipherError::DecryptionFailure + } + } + + pub(crate) fn from_cek_error(error: CoseCipherError) -> CoseCipherError> { + match error { + CoseCipherError::Other(x) => CoseCipherError::Other(MultipleCoseError::CekError(x)), + CoseCipherError::HeaderAlreadySet { existing_header_name } => CoseCipherError::HeaderAlreadySet { + existing_header_name + }, + CoseCipherError::VerificationFailure => CoseCipherError::VerificationFailure, + CoseCipherError::DecryptionFailure => CoseCipherError::DecryptionFailure + } + } } impl Display for CoseCipherError @@ -320,6 +343,21 @@ where } } +#[derive(Debug)] +pub enum MultipleCoseError where K: Display, C: Display { + KekError(K), + CekError(C) +} + +impl Display for MultipleCoseError where K: Display, C: Display { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + MultipleCoseError::KekError(k) => k.fmt(f), + MultipleCoseError::CekError(c) => c.fmt(f) + } + } +} + /// Error type when a [`Value`] can't be converted to a Scope. /// /// This can be because it isn't a scope, or because the scope is invalid. @@ -467,22 +505,44 @@ where } } -impl AccessTokenError -where - T: Display, -{ - /// Creates a new [`AccessTokenError`] of variant [`CoseError`](AccessTokenError::CoseError) - /// with the given `error`. +impl From> for AccessTokenError where T: Display { #[must_use] - pub fn from_cose_error(error: CoseError) -> AccessTokenError { - AccessTokenError::CoseError(error) + fn from(error: CoseCipherError) -> Self { + AccessTokenError::CoseCipherError(error) } +} - /// Creates a new [`AccessTokenError`] of variant - /// [`CoseCipherError`](AccessTokenError::CoseCipherError) with the given `error`. +impl From for AccessTokenError where T: Display { #[must_use] - pub fn from_cose_cipher_error(error: CoseCipherError) -> AccessTokenError { - AccessTokenError::CoseCipherError(error) + fn from(error: CoseError) -> Self { + AccessTokenError::CoseError(error) + } +} + +impl AccessTokenError +where + T: Display { + + // TODO: Again, as in CoseCipherError, maybe there's a better way to do the below. + + pub(crate) fn from_kek_error(error: AccessTokenError) -> AccessTokenError> { + match error { + AccessTokenError::CoseCipherError(x) => AccessTokenError::CoseCipherError(CoseCipherError::from_kek_error(x)), + AccessTokenError::CoseError(x) => AccessTokenError::CoseError(x), + AccessTokenError::UnknownCoseStructure => AccessTokenError::UnknownCoseStructure, + AccessTokenError::NoMatchingKey => AccessTokenError::NoMatchingKey, + AccessTokenError::MultipleMatchingKeys => AccessTokenError::MultipleMatchingKeys, + } + } + + pub(crate) fn from_cek_error(error: AccessTokenError) -> AccessTokenError> { + match error { + AccessTokenError::CoseCipherError(x) => AccessTokenError::CoseCipherError(CoseCipherError::from_cek_error(x)), + AccessTokenError::CoseError(x) => AccessTokenError::CoseError(x), + AccessTokenError::UnknownCoseStructure => AccessTokenError::UnknownCoseStructure, + AccessTokenError::NoMatchingKey => AccessTokenError::NoMatchingKey, + AccessTokenError::MultipleMatchingKeys => AccessTokenError::MultipleMatchingKeys, + } } } diff --git a/src/token/mod.rs b/src/token/mod.rs index 912fe47..d74ca21 100644 --- a/src/token/mod.rs +++ b/src/token/mod.rs @@ -85,7 +85,7 @@ #[cfg(not(feature = "std"))] use alloc::vec::Vec; -use core::fmt::{Debug, Display}; +use core::fmt::{Debug, Display, Formatter, Pointer}; use ciborium::value::Value; use coset::{AsCborValue, CborSerializable, CoseEncrypt, CoseEncrypt0, CoseEncrypt0Builder, CoseEncryptBuilder, CoseKey, CoseMac0, CoseRecipient, CoseRecipientBuilder, CoseSign, CoseSign1, CoseSign1Builder, CoseSignatureBuilder, CoseSignBuilder, EncryptionContext, Header, HeaderBuilder, ProtectedHeader}; @@ -93,7 +93,7 @@ use coset::cwt::ClaimsSet; use rand::{CryptoRng, RngCore}; use crate::common::cbor_values::ByteString; -use crate::error::{AccessTokenError, CoseCipherError}; +use crate::error::{AccessTokenError, CoseCipherError, MultipleCoseError}; #[cfg(test)] mod tests; @@ -410,7 +410,6 @@ macro_rules! prepare_headers { let mut unprotected = $unprotected.unwrap_or_else(|| HeaderBuilder::new().build()); let mut protected = $protected.unwrap_or_else(|| HeaderBuilder::new().build()); if let Err(e) = <$t>::set_headers($key, &mut unprotected, &mut protected, $rng) - .map_err(AccessTokenError::from_cose_cipher_error) { Err(e) } else { @@ -494,13 +493,13 @@ where .unprotected(unprotected.clone()) .protected(protected.clone()) .create_ciphertext( - &claims.to_vec().map_err(AccessTokenError::from_cose_error)?[..], + &claims.to_vec()?[..], external_aad.unwrap_or(&[0; 0]), |payload, aad| T::encrypt(&key, payload, aad, &unprotected, &protected), ) .build() .to_vec() - .map_err(AccessTokenError::from_cose_error) + .map_err(AccessTokenError::from) } /// Encrypts the given `claims` with the given headers and `aad` using `cipher` for cryptography, @@ -529,7 +528,7 @@ where .unprotected(unprotected.clone()) .protected(protected.clone()) .create_ciphertext( - &claims.to_vec().map_err(AccessTokenError::from_cose_error)?[..], + &claims.to_vec()?[..], external_aad.unwrap_or(&[0; 0]), |payload, aad| T::encrypt(&key, payload, aad, &protected, &unprotected), ); @@ -543,7 +542,7 @@ where } builder.build() .to_vec() - .map_err(AccessTokenError::from_cose_error) + .map_err(AccessTokenError::from) } /// Signs the given `claims` with the given headers and `aad` using `cipher` for cryptography, @@ -611,13 +610,13 @@ where CoseSign1Builder::new() .unprotected(unprotected.clone()) .protected(protected.clone()) - .payload(claims.to_vec().map_err(AccessTokenError::from_cose_error)?) + .payload(claims.to_vec()?) .create_signature(external_aad.unwrap_or(&[0; 0]), |x| { T::sign(key, x, &unprotected, &protected) }) .build() .to_vec() - .map_err(AccessTokenError::from_cose_error) + .map_err(AccessTokenError::from) } /// TODO. @@ -639,7 +638,7 @@ pub fn sign_access_token_multiple( let mut builder = CoseSignBuilder::new() .unprotected(unprotected.clone()) .protected(protected.clone()) - .payload(claims.to_vec().map_err(AccessTokenError::from_cose_error)?); + .payload(claims.to_vec().map_err(AccessTokenError::from)?); for key in keys { let (rec_unprotected, rec_protected) = prepare_headers!(key, None, None, &mut rng, T)?; @@ -651,7 +650,7 @@ pub fn sign_access_token_multiple( builder.build() .to_vec() - .map_err(AccessTokenError::from_cose_error) + .map_err(AccessTokenError::from) } /// Returns the headers of the given signed ([`CoseSign1`]), MAC tagged ([`CoseMac0`]), @@ -742,7 +741,7 @@ where None, ) }) - .map_err(AccessTokenError::from_cose_cipher_error) + .map_err(AccessTokenError::from) } /// TODO. @@ -752,7 +751,6 @@ where /// TODO. pub fn verify_access_token_multiple( key: &T::VerifyKey, - kid: &[u8], token: &ByteString, aad: Option<&[u8]>, ) -> Result<(), AccessTokenError> @@ -760,6 +758,7 @@ where T: CoseSignCipher, { let sign = CoseSign::from_slice(token.as_slice()).map_err(AccessTokenError::CoseError)?; + let kid = key.to_cose_key().key_id; let (unprotected, protected) = get_token_headers(token).ok_or(AccessTokenError::UnknownCoseStructure)?; let matching = sign @@ -768,9 +767,11 @@ where .enumerate() .filter(|(_, s)| s.unprotected.key_id == kid || s.protected.header.key_id == kid) .map(|(i, _)| i); + let mut matching_kid = false; // We iterate over each signature whose kid matches until it completes successfully. // TODO: However: https://www.rfc-editor.org/rfc/rfc9052.html#section-4.1-3 for index in matching { + matching_kid = true; // TODO: Verify protected headers if let Ok(()) = sign.verify_signature(index, aad.unwrap_or(&[0; 0]), |signature, signed_data| { @@ -788,9 +789,13 @@ where return Ok(()); } } - Err(AccessTokenError::from_cose_cipher_error( - CoseCipherError::VerificationFailure, - )) + if matching_kid { + Err(AccessTokenError::from( + CoseCipherError::VerificationFailure, + )) + } else { + Err(AccessTokenError::NoMatchingKey) + } } /// Decrypts the given `token` and `aad` using `cipher` for cryptography, @@ -814,16 +819,15 @@ where T: CoseEncryptCipher, { let encrypt = - CoseEncrypt0::from_slice(token.as_slice()).map_err(AccessTokenError::from_cose_error)?; + CoseEncrypt0::from_slice(token.as_slice()).map_err(AccessTokenError::from)?; let (unprotected, protected) = get_token_headers(token).ok_or(AccessTokenError::UnknownCoseStructure)?; // TODO: Verify protected header let result = encrypt .decrypt(external_aad.unwrap_or(&[0; 0]), |ciphertext, aad| { T::decrypt(key, ciphertext, aad, &unprotected, &protected) - }) - .map_err(AccessTokenError::from_cose_cipher_error)?; - ClaimsSet::from_slice(result.as_slice()).map_err(AccessTokenError::from_cose_error) + })?; + ClaimsSet::from_slice(result.as_slice()).map_err(AccessTokenError::from) } /// TODO. @@ -833,17 +837,16 @@ pub fn decrypt_access_token_multiple( kek: &K::DecryptKey, token: &ByteString, external_aad: Option<&[u8]>, -) -> Result> +) -> Result>> where K: CoseEncryptCipher, C: CoseEncryptCipher, { let encrypt = - CoseEncrypt::from_slice(token.as_slice()).map_err(AccessTokenError::from_cose_error)?; + CoseEncrypt::from_slice(token.as_slice()).map_err(AccessTokenError::from)?; let (unprotected, protected) = get_token_headers(token).ok_or(AccessTokenError::UnknownCoseStructure)?; let aad = external_aad.unwrap_or(&[0; 0]); - // FIXME: below line does not compile let cose_kek: CoseKey = kek.to_cose_key(); let kek_id = cose_kek.key_id.as_slice(); // One of the recipient structures should contain CEK encrypted with our KEK. @@ -851,25 +854,25 @@ where .recipients .iter() .filter(|x| x.unprotected.key_id == kek_id || x.protected.header.key_id == kek_id); - let mut content_keys = recipients.filter_map(|r| { - r.decrypt(EncryptionContext::EncRecipient, aad, |ciphertext, aad| { + let mut content_keys = recipients.map(|r| { + r.decrypt(EncryptionContext::EncRecipient, &[0; 0], |ciphertext, aad| { K::decrypt(kek, ciphertext, aad, &r.unprotected, &r.protected) }) - .ok() }); // Our CEK must be contained exactly once. - if let Some(content_key) = content_keys.next() { + if let Some(content_key_result) = content_keys.next() { if content_keys.next().is_none() { + let content_key = content_key_result.map_err(CoseCipherError::from_kek_error)?; let target_key = C::DecryptKey::try_from(content_key).map_err(|_| { - AccessTokenError::from_cose_cipher_error(CoseCipherError::DecryptionFailure) + CoseCipherError::DecryptionFailure })?; // TODO: Verify protected header let result = encrypt .decrypt(aad, |ciphertext, aad| { C::decrypt(&target_key, ciphertext, aad, &unprotected, &protected) }) - .map_err(AccessTokenError::from_cose_cipher_error)?; - ClaimsSet::from_slice(result.as_slice()).map_err(AccessTokenError::from_cose_error) + .map_err(CoseCipherError::from_cek_error)?; + ClaimsSet::from_slice(result.as_slice()).map_err(AccessTokenError::from) } else { // TODO: Implement strict mode, where this is prohibited, otherwise allow it Err(AccessTokenError::MultipleMatchingKeys) diff --git a/src/token/tests.rs b/src/token/tests.rs index ea5698c..c5c66fa 100644 --- a/src/token/tests.rs +++ b/src/token/tests.rs @@ -13,22 +13,18 @@ use alloc::vec; use ciborium::value::Value; -use coset::cwt::ClaimsSetBuilder; -use coset::iana::{Algorithm, CwtClaimName}; use coset::{AsCborValue, CoseKey, CoseKeyBuilder, CoseMac0Builder, HeaderBuilder}; +use coset::cwt::ClaimsSetBuilder; +use coset::iana::{Algorithm, CoapContentFormat, CwtClaimName}; -use crate::common::test_helper::FakeCrypto; +use crate::common::test_helper::{FakeCrypto, FakeKey, FakeRng}; use crate::error::CoseCipherError; use super::*; -fn example_key() -> CoseKey { - CoseKeyBuilder::new_symmetric_key(vec![ - 0x84, 0x9b, 0x57, 0x86, 0x45, 0x7c, 0x14, 0x91, 0xbe, 0x3a, 0x76, 0xdc, 0xea, 0x6c, 0x42, - 0x71, 0x08, - ]) - .key_id(vec![0x84, 0x9b, 0x57, 0x86, 0x45, 0x7c]) - .build() +/// Generates a test key with content `[1,2,3,4,5]` and key id `[0xDC, 0xAF]`. +fn example_key() -> FakeKey { + FakeKey::try_from(vec![1, 2, 3, 4, 5, 0xDC, 0xAF]).expect("invalid test key") } fn example_headers() -> (Header, Header) { @@ -37,7 +33,7 @@ fn example_headers() -> (Header, Header) { 0x63, 0x68, 0x98, 0x99, 0x4F, 0xF0, 0xEC, 0x7B, 0xFC, 0xF6, 0xD3, 0xF9, 0x5B, ]) .build(); - let protected_header = HeaderBuilder::new().key_id(example_key().key_id).build(); + let protected_header = HeaderBuilder::new().content_format(CoapContentFormat::Cbor).build(); (unprotected_header, protected_header) } @@ -50,12 +46,12 @@ fn example_invalid_headers() -> (Header, Header) { } fn example_aad() -> Vec { - vec![0x01, 0x02, 0x03, 0x04, 0x05] + vec![0x10, 0x12, 0x13, 0x14, 0x15] } fn example_claims( key: CoseKey, -) -> Result::Error>> { +) -> Result::Error>> { Ok(ClaimsSetBuilder::new() .claim( CwtClaimName::Cnf, @@ -102,7 +98,8 @@ fn assert_header_is_part_of(subset: &Header, superset: &Header) { } #[test] -fn test_get_headers_enc() -> Result<(), AccessTokenError<::Error>> { +fn test_get_headers_enc() -> Result<(), AccessTokenError<::Error>> +{ let (unprotected_header, protected_header) = example_headers(); let enc_test = CoseEncrypt0Builder::new() .unprotected(unprotected_header.clone()) @@ -120,8 +117,7 @@ fn test_get_headers_enc() -> Result<(), AccessTokenError< Result<(), AccessTokenError<::Error>> -{ +fn test_get_headers_sign() -> Result<(), AccessTokenError<::Error>> { let (unprotected_header, protected_header) = example_headers(); let sign_test = CoseSign1Builder::new() .unprotected(unprotected_header.clone()) @@ -139,7 +135,7 @@ fn test_get_headers_sign() -> Result<(), AccessTokenError< Result<(), AccessTokenError<::Error>> { +fn test_get_headers_mac() -> Result<(), AccessTokenError<::Error>> { let (unprotected_header, protected_header) = example_headers(); let mac_test = CoseMac0Builder::new() .unprotected(unprotected_header.clone()) @@ -171,49 +167,52 @@ fn test_get_headers_invalid() { } #[test] -fn test_encrypt_decrypt() -> Result<(), AccessTokenError<::Error>> { +fn test_encrypt_decrypt() -> Result<(), AccessTokenError<::Error>> { let mut crypto = FakeCrypto {}; let key = example_key(); let (unprotected_header, protected_header) = example_headers(); - let claims = example_claims(key)?; + let claims = example_claims(key.to_cose_key())?; let aad = example_aad(); - let encrypted = encrypt_access_token( + let mut rng = FakeRng; + let encrypted = encrypt_access_token::( + key.clone(), claims.clone(), - &mut crypto, Some(&aad), Some(unprotected_header.clone()), Some(protected_header.clone()), + rng, )?; let (unprotected, protected) = get_token_headers(&encrypted).unwrap(); assert_header_is_part_of(&unprotected_header, &unprotected); assert_header_is_part_of(&protected_header, &protected.header); assert_eq!( - decrypt_access_token(&encrypted, &mut crypto, Some(&aad))?, + decrypt_access_token::(&key, &encrypted, Some(&aad))?, claims ); Ok(()) } #[test] -fn test_encrypt_decrypt_invalid_header( -) -> Result<(), AccessTokenError<::Error>> { +fn test_encrypt_decrypt_invalid_header() -> Result<(), AccessTokenError<::Error>> { let mut crypto = FakeCrypto {}; let key = example_key(); let (unprotected_header, protected_header) = example_headers(); let (unprotected_invalid, protected_invalid) = example_invalid_headers(); - let claims = example_claims(key)?; + let claims = example_claims(key.to_cose_key())?; let aad = example_aad(); - let encrypted = encrypt_access_token( + let rng = FakeRng; + let encrypted = encrypt_access_token::( + key.clone(), claims.clone(), - &mut crypto, Some(&aad), Some(unprotected_invalid), Some(protected_header), + rng, ); assert!(encrypted.err().map_or(false, |x| { if let AccessTokenError::CoseCipherError(CoseCipherError::HeaderAlreadySet { - existing_header_name, - }) = x + existing_header_name, + }) = x { existing_header_name == "47" } else { @@ -221,18 +220,19 @@ fn test_encrypt_decrypt_invalid_header( } })); - let encrypted = encrypt_access_token( - claims, - &mut crypto, + let encrypted = encrypt_access_token::( + key.clone(), + claims.clone(), Some(&aad), Some(unprotected_header), Some(protected_invalid), + rng, ); assert!(encrypted.is_err()); assert!(encrypted.err().map_or(false, |x| { if let AccessTokenError::CoseCipherError(CoseCipherError::HeaderAlreadySet { - existing_header_name, - }) = x + existing_header_name, + }) = x { existing_header_name == "alg" } else { @@ -244,18 +244,20 @@ fn test_encrypt_decrypt_invalid_header( } #[test] -fn test_sign_verify() -> Result<(), AccessTokenError<::Error>> { +fn test_sign_verify() -> Result<(), AccessTokenError<::Error>> { let mut crypto = FakeCrypto {}; let key = example_key(); let (unprotected_header, protected_header) = example_headers(); - let claims = example_claims(key)?; + let claims = example_claims(key.to_cose_key())?; let aad = example_aad(); - let signed = sign_access_token( + let rng = FakeRng; + let signed = sign_access_token::( + &key, claims, - &mut crypto, Some(&aad), Some(unprotected_header.clone()), Some(protected_header.clone()), + rng, )?; #[cfg(feature = "std")] @@ -264,6 +266,6 @@ fn test_sign_verify() -> Result<(), AccessTokenError<(&key, &signed, Some(&aad))?; Ok(()) } diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 902aeb9..cc2c6cf 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -9,15 +9,14 @@ * SPDX-License-Identifier: MIT OR Apache-2.0 */ -use std::fmt::Debug; - use ciborium::value::Value; -use coset::{CoseKeyBuilder, Header, HeaderBuilder, Label}; +use coset::{CborSerializable, CoseKey, CoseKeyBuilder, Header, HeaderBuilder, iana, KeyType, Label, ProtectedHeader}; use coset::cwt::{ClaimsSetBuilder, Timestamp}; use coset::iana::{Algorithm, CwtClaimName}; use coset::iana::EllipticCurve::P_256; +use rand::{CryptoRng, Error, RngCore}; -use dcaf::{CoseSign1Cipher, sign_access_token}; +use dcaf::{CoseSignCipher, sign_access_token}; use dcaf::common::cbor_map::ToCborMap; use dcaf::common::cbor_values::ProofOfPossessionKey::PlainCoseKey; use dcaf::common::scope::TextEncodedScope; @@ -27,7 +26,78 @@ use dcaf::endpoints::token_req::{ TokenType, }; use dcaf::error::CoseCipherError; -use dcaf::token::CoseCipherCommon; +use dcaf::token::ToCoseKey; + +#[derive(Clone)] +pub(crate) struct EC2P256Key { + x: Vec, + y: Vec, +} + +impl ToCoseKey for EC2P256Key { + fn to_cose_key(&self) -> CoseKey { + CoseKeyBuilder::new_ec2_pub_key( + P_256, + self.x.to_vec(), + self.y.to_vec(), + ) + .build() + } +} + +impl TryFrom> for EC2P256Key { + type Error = String; + + fn try_from(value: Vec) -> Result { + let key = CoseKey::from_slice(value.as_slice()).map_err(|x| x.to_string())?; + assert_eq!(key.kty, KeyType::Assigned(iana::KeyType::EC2)); + assert_eq!(get_param(Label::Int(iana::Ec2KeyParameter::Crv as i64), &key.params), Some(Value::from(P_256 as u64))); + + if let Some(Value::Bytes(x)) = get_param(Label::Int(iana::Ec2KeyParameter::X as i64), &key.params) { + if let Some(Value::Bytes(y)) = get_param(Label::Int(iana::Ec2KeyParameter::Y as i64), &key.params) { + return Ok(EC2P256Key { + x, + y, + }) + } + } + return Err("x and y must be present in key as bytes".to_string()); + + fn get_param(label: Label, params: &Vec<(Label, Value)>) -> Option { + let mut iter = params.iter().filter(|x| x.0 == label); + iter.map(|x| x.1.clone()).next() + } + } +} + +impl From for Vec { + fn from(k: EC2P256Key) -> Self { + k.to_cose_key().to_vec().expect("couldn't serialize key") + } +} + +#[derive(Clone, Copy)] +pub(crate) struct FakeRng; + +impl RngCore for FakeRng { + fn next_u32(&mut self) -> u32 { + 0 + } + + fn next_u64(&mut self) -> u64 { + 0 + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + dest.fill(0) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + Ok(dest.fill(0)) + } +} + +impl CryptoRng for FakeRng {} fn example_headers() -> (Header, Header) { let unprotected_header = HeaderBuilder::new() @@ -40,19 +110,61 @@ fn example_headers() -> (Header, Header) { } fn example_aad() -> Vec { - vec![0x01, 0x02, 0x03, 0x04, 0x05] + vec![0x10, 0x12, 0x13, 0x14, 0x15] } #[derive(Copy, Clone)] pub(crate) struct FakeCrypto {} -impl CoseCipherCommon for FakeCrypto { +/// Implements basic operations from the [`CoseSign1Cipher`] trait without actually using any +/// "real" cryptography. +/// This is purely to be used for testing and obviously offers no security at all. +impl CoseSignCipher for FakeCrypto { + type SignKey = EC2P256Key; + type VerifyKey = Self::SignKey; type Error = String; - fn set_headers( - &self, + fn sign( + key: &Self::SignKey, + target: &[u8], + unprotected_header: &Header, + protected_header: &Header, + ) -> Vec { + // We simply append the key behind the data. + let mut signature = target.to_vec(); + signature.append(&mut key.x.to_vec()); + signature.append(&mut key.y.to_vec()); + signature + } + + fn verify( + key: &Self::VerifyKey, + signature: &[u8], + signed_data: &[u8], + unprotected_header: &Header, + protected_header: &ProtectedHeader, + unprotected_signature_header: Option<&Header>, + protected_signature_header: Option<&ProtectedHeader>, + ) -> Result<(), CoseCipherError> { + if signature + == Self::sign( + key, + signed_data, + unprotected_header, + &protected_header.header, + ) + { + Ok(()) + } else { + Err(CoseCipherError::VerificationFailure) + } + } + + fn set_headers( + key: &Self::SignKey, unprotected_header: &mut Header, protected_header: &mut Header, + rng: RNG, ) -> Result<(), CoseCipherError> { // We have to later verify these headers really are used. if let Some(label) = unprotected_header @@ -71,27 +183,6 @@ impl CoseCipherCommon for FakeCrypto { } } -/// Implements basic operations from the [`CoseSign1Cipher`] trait without actually using any -/// "real" cryptography. -/// This is purely to be used for testing and obviously offers no security at all. -impl CoseSign1Cipher for FakeCrypto { - fn generate_signature(&mut self, data: &[u8]) -> Vec { - data.to_vec() - } - - fn verify_signature( - &mut self, - sig: &[u8], - data: &[u8], - ) -> Result<(), CoseCipherError> { - if sig != self.generate_signature(data) { - Err(CoseCipherError::VerificationFailure) - } else { - Ok(()) - } - } -} - /// We assume the following scenario here: /// 1. The client tries to access a protected resource. Since it's still unauthorized, /// this is an Unauthorized Resource Request message. The RS replies with an error response @@ -111,14 +202,13 @@ fn test_scenario() -> Result<(), String> { let scope = TextEncodedScope::try_from("first second").map_err(|x| x.to_string())?; assert!(scope.elements().eq(["first", "second"])); // Taken from RFC 8747, section 3.2. - let key = CoseKeyBuilder::new_ec2_pub_key( - P_256, - hex::decode("d7cc072de2205bdc1537a543d53c60a6acb62eccd890c7fa27c9e354089bbe13") + let key = EC2P256Key { + x: hex::decode("d7cc072de2205bdc1537a543d53c60a6acb62eccd890c7fa27c9e354089bbe13") .map_err(|x| x.to_string())?, - hex::decode("f95e1d4b851a2cc80fff87d8e23f22afb725d535e515d020731e79a3b4e47120") + y: hex::decode("f95e1d4b851a2cc80fff87d8e23f22afb725d535e515d020731e79a3b4e47120") .map_err(|x| x.to_string())?, - ) - .build(); + }; + let (unprotected_headers, protected_headers) = example_headers(); let mut crypto = FakeCrypto {}; let aad = example_aad(); @@ -139,27 +229,28 @@ fn test_scenario() -> Result<(), String> { .client_nonce(nonce) .ace_profile() .client_id(client_id) - .req_cnf(key.clone()) + .req_cnf(PlainCoseKey(key.to_cose_key())) .build() .map_err(|x| x.to_string())?; let result = pseudo_send_receive(request.clone())?; assert_eq!(request, result); let expires_in: u32 = 3600; - let token = sign_access_token( + let rng = FakeRng; + let token = sign_access_token::( + &key, ClaimsSetBuilder::new() .audience(resource_server.to_string()) .issuer(auth_server.to_string()) .issued_at(Timestamp::WholeSeconds(47)) - .claim(CwtClaimName::Cnf, PlainCoseKey(key).to_ciborium_value()) + .claim(CwtClaimName::Cnf, PlainCoseKey(key.to_cose_key()).to_ciborium_value()) .build(), // TODO: Proper headers - &mut crypto, Some(aad.as_slice()), Some(unprotected_headers), Some(protected_headers), - ) - .map_err(|x| x.to_string())?; + rng, + ).map_err(|x| x.to_string())?; let response = AccessTokenResponse::builder() .access_token(token) .ace_profile(AceProfile::CoapDtls) @@ -183,8 +274,8 @@ fn test_scenario() -> Result<(), String> { } fn pseudo_send_receive(input: T) -> Result -where - T: ToCborMap + Debug + PartialEq + Clone, + where + T: ToCborMap + PartialEq + Clone, { let mut serialized: Vec = Vec::new(); input From 9a9f7593e9b3d62cb779bee822695425af2f4db6 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Mon, 17 Oct 2022 22:01:26 +0200 Subject: [PATCH 04/12] test: add tests for new COSE multi-cipher operations --- src/token/tests.rs | 130 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 110 insertions(+), 20 deletions(-) diff --git a/src/token/tests.rs b/src/token/tests.rs index c5c66fa..b888483 100644 --- a/src/token/tests.rs +++ b/src/token/tests.rs @@ -23,10 +23,15 @@ use crate::error::CoseCipherError; use super::*; /// Generates a test key with content `[1,2,3,4,5]` and key id `[0xDC, 0xAF]`. -fn example_key() -> FakeKey { +fn example_key_one() -> FakeKey { FakeKey::try_from(vec![1, 2, 3, 4, 5, 0xDC, 0xAF]).expect("invalid test key") } +/// Generates a test key with content `[10, 9, 8, 7, 6]` and key id `[0xCA, 0xFE]`. +fn example_key_two() -> FakeKey { + FakeKey::try_from(vec![10, 9, 8, 7, 6, 0xCA, 0xFE]).expect("invalid test key") +} + fn example_headers() -> (Header, Header) { let unprotected_header = HeaderBuilder::new() .iv(vec![ @@ -55,8 +60,7 @@ fn example_claims( Ok(ClaimsSetBuilder::new() .claim( CwtClaimName::Cnf, - key.to_cbor_value() - .map_err(AccessTokenError::from_cose_error)?, + key.to_cbor_value()?, ) .build()) } @@ -105,8 +109,7 @@ fn test_get_headers_enc() -> Result<(), AccessTokenError< Result<(), AccessTokenError< Result<(), AccessTokenError< Result<(), AccessTokenError<::Error>> { - let mut crypto = FakeCrypto {}; - let key = example_key(); + let key = example_key_one(); let (unprotected_header, protected_header) = example_headers(); let claims = example_claims(key.to_cose_key())?; let aad = example_aad(); - let mut rng = FakeRng; + let rng = FakeRng; let encrypted = encrypt_access_token::( key.clone(), claims.clone(), @@ -182,7 +182,7 @@ fn test_encrypt_decrypt() -> Result<(), AccessTokenError< Result<(), AccessTokenError< Result<(), AccessTokenError<::Error>> { + const AUDIENCE: &str = "example_aud"; + let (unprotected_header, protected_header) = example_headers(); + let key1 = example_key_one(); + let key2 = example_key_two(); + let invalid_key1 = FakeKey::try_from(vec![0, 0, 0, 0, 0, 0, 0]).expect("invalid test key"); + let invalid_key2 = FakeKey::try_from(vec![0, 0, 0, 0, 0, 0xDC, 0xAF]).expect("invalid test key"); + let rng = FakeRng; + let aad = example_aad(); + // Using example_claims doesn't make sense, since they contain a cnf for the key, + // but we don't know the CEK at this point. + let claims = ClaimsSetBuilder::new().audience(AUDIENCE.to_string()).build(); + let encrypted = encrypt_access_token_multiple::( + vec![&key1, &key2], + claims.clone(), + Some(&aad), + Some(unprotected_header.clone()), + Some(protected_header.clone()), + rng + )?; + let (unprotected, protected) = get_token_headers(&encrypted).expect("invalid headers"); + assert_header_is_part_of(&unprotected_header, &unprotected); + assert_header_is_part_of(&protected_header, &protected.header); + for key in vec![key1, key2] { + assert_eq!( + &decrypt_access_token_multiple::(&key, &encrypted, Some(&aad))?, + &claims + ); + } + let failed = decrypt_access_token_multiple::(&invalid_key1, &encrypted, Some(&aad)); + assert!(failed.err().filter(|x| matches!(x, AccessTokenError::NoMatchingKey)).is_some()); + let failed = decrypt_access_token_multiple::(&invalid_key2, &encrypted, Some(&aad)); + dbg!(&failed); + assert!(failed.err().filter(|x| matches!(x, AccessTokenError::CoseCipherError(CoseCipherError::DecryptionFailure))).is_some()); + Ok(()) +} + +#[test] +fn test_encrypt_decrypt_match_multiple() -> Result<(), AccessTokenError<::Error>> { + let (unprotected_header, protected_header) = example_headers(); + let key1 = example_key_one(); + let rng = FakeRng; + let aad = example_aad(); + let claims = ClaimsSetBuilder::new().build(); + let encrypted = encrypt_access_token_multiple::( + vec![&key1, &key1], + claims, + Some(&aad), + Some(unprotected_header.clone()), + Some(protected_header.clone()), + rng + )?; + let (unprotected, protected) = get_token_headers(&encrypted).expect("invalid headers"); + assert_header_is_part_of(&unprotected_header, &unprotected); + assert_header_is_part_of(&protected_header, &protected.header); + // In the future, this should only be an error in "strict mode". + assert!(decrypt_access_token_multiple::(&key1, &encrypted, Some(&aad)).err().filter(|x| matches!(x, AccessTokenError::MultipleMatchingKeys)).is_some()); + Ok(()) +} + #[test] fn test_encrypt_decrypt_invalid_header() -> Result<(), AccessTokenError<::Error>> { - let mut crypto = FakeCrypto {}; - let key = example_key(); + let key = example_key_one(); let (unprotected_header, protected_header) = example_headers(); let (unprotected_invalid, protected_invalid) = example_invalid_headers(); let claims = example_claims(key.to_cose_key())?; @@ -221,8 +281,8 @@ fn test_encrypt_decrypt_invalid_header() -> Result<(), AccessTokenError<( - key.clone(), - claims.clone(), + key, + claims, Some(&aad), Some(unprotected_header), Some(protected_invalid), @@ -245,8 +305,7 @@ fn test_encrypt_decrypt_invalid_header() -> Result<(), AccessTokenError< Result<(), AccessTokenError<::Error>> { - let mut crypto = FakeCrypto {}; - let key = example_key(); + let key = example_key_one(); let (unprotected_header, protected_header) = example_headers(); let claims = example_claims(key.to_cose_key())?; let aad = example_aad(); @@ -263,9 +322,40 @@ fn test_sign_verify() -> Result<(), AccessTokenError<::UnknownCoseStructure)?; assert_header_is_part_of(&unprotected_header, &unprotected); assert_header_is_part_of(&protected_header, &protected.header); verify_access_token::(&key, &signed, Some(&aad))?; Ok(()) } + +#[test] +fn test_sign_verify_multiple() -> Result<(), AccessTokenError<::Error>> { + const AUDIENCE: &str = "example_aud"; + let key1 = example_key_one(); + let key2 = example_key_two(); + let invalid_key1 = FakeKey::try_from(vec![0, 0, 0, 0, 0, 0, 0]).expect("invalid test key"); + let invalid_key2 = FakeKey::try_from(vec![0, 0, 0, 0, 0, 0xDC, 0xAF]).expect("invalid test key"); + let (unprotected_header, protected_header) = example_headers(); + let claims = ClaimsSetBuilder::new().audience(AUDIENCE.to_string()).build(); + let aad = example_aad(); + let rng = FakeRng; + let signed = sign_access_token_multiple::( + vec![&key1, &key2], + claims, + Some(&aad), + Some(unprotected_header.clone()), + Some(protected_header.clone()), + rng + )?; + let (unprotected, protected) = + get_token_headers(&signed).ok_or(AccessTokenError::::UnknownCoseStructure)?; + assert_header_is_part_of(&unprotected_header, &unprotected); + assert_header_is_part_of(&protected_header, &protected.header); + for key in vec![key1, key2] { + verify_access_token_multiple::(&key, &signed, Some(&aad))?; + } + assert!(verify_access_token_multiple::(&invalid_key1, &signed, Some(&aad)).err().filter(|x| matches!(x, AccessTokenError::NoMatchingKey)).is_some()); + assert!(verify_access_token_multiple::(&invalid_key2, &signed, Some(&aad)).err().filter(|x| matches!(x, AccessTokenError::CoseCipherError(CoseCipherError::VerificationFailure))).is_some()); + Ok(()) +} \ No newline at end of file From 4db5fea2f5ffb3c73a82627f7f62be8d797c0e02 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Mon, 24 Oct 2022 15:16:06 +0200 Subject: [PATCH 05/12] docs: update documentation for cipher rewrite --- CHANGELOG.md | 16 +- src/common/test_helper.rs | 11 +- src/error/mod.rs | 34 +- src/lib.rs | 198 +++++++++--- src/token/mod.rs | 621 +++++++++++++++++++------------------ src/token/tests.rs | 6 +- tests/integration_tests.rs | 6 +- 7 files changed, 514 insertions(+), 378 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d9afbb..17ce0d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,14 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -This release mainly deals with the newly released RFCs and fixes `no_std` support. +This release mainly adds support for multiple token recipients, deals with the newly released RFCs, +and fixes `no_std` support. ### Added - The `CoapOscore` profile has been added as an `AceProfile`. +- Support for multiple token recipients has been added. Specifically, the following new methods have been added: + - `encrypt_access_token_multiple` / `decrypt_access_token_multiple`: Creates a new access token encoded as a + `CoseEncrypt` rather than a `CoseEncrypt0`. The user passes in a vector of keys on encryption, these will then be + used as Key Encryption Keys. The Content Encryption Key is generated by the `MultipleEncryptCipher` required by + the function. On decryption, the correct recipient structure will be identified by the key ID of the passed-in key. + - `sign_access_token_multiple` / `decrypt_access_token_multiple`: Creates a new access token encoded as a `CoseSign` + rather than a `CoseSign1`. The user passes in a vector of keys when signing, and a recipient will be created + for each key. When verifying, the correct recipient structure will be identified by the key ID of the passed-in key. ### Changed +- The ciphers' API has been majorly changed. As a result, the API for the token functions has changed as well. + Users no longer need to pass in an instance of the cipher, they only need to specify the type parameter, as the + cipher's methods no longer need `self` as a parameter. Additionally, users now need to pass in the `key` for the + corresponding operation, whereas the type of the key is defined in the cipher. For more information, read the + documentation of `CoseEncryptCipher`, `CoseSignCipher`, or `CoseMacCipher`, as well as of the token functions. - The documentation has been updated to refer to the recently released RFCs instead of the now outdated internet drafts. ### Fixed diff --git a/src/common/test_helper.rs b/src/common/test_helper.rs index d63335b..2403359 100644 --- a/src/common/test_helper.rs +++ b/src/common/test_helper.rs @@ -106,7 +106,7 @@ impl FakeCrypto { return Err(CoseCipherError::existing_header("alg")); } if !protected_header.key_id.is_empty() { - return Err(CoseCipherError::existing_header("key_id")); + return Err(CoseCipherError::existing_header("kid")); } unprotected_header.rest.push((Label::Int(47), Value::Null)); protected_header.alg = Some(coset::Algorithm::Assigned(Algorithm::Direct)); @@ -340,11 +340,12 @@ impl RngCore for FakeRng { } fn fill_bytes(&mut self, dest: &mut [u8]) { - dest.fill(0) + dest.fill(0); } fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - Ok(dest.fill(0)) + dest.fill(0); + Ok(()) } } @@ -368,8 +369,8 @@ impl From>> for AccessTokenError< AccessTokenError::CoseError(x) => AccessTokenError::CoseError(x), AccessTokenError::CoseCipherError(x) => AccessTokenError::CoseCipherError(CoseCipherError::from(x)), AccessTokenError::UnknownCoseStructure => AccessTokenError::UnknownCoseStructure, - AccessTokenError::NoMatchingKey => AccessTokenError::NoMatchingKey, - AccessTokenError::MultipleMatchingKeys => AccessTokenError::MultipleMatchingKeys + AccessTokenError::NoMatchingRecipient => AccessTokenError::NoMatchingRecipient, + AccessTokenError::MultipleMatchingRecipients => AccessTokenError::MultipleMatchingRecipients } } } \ No newline at end of file diff --git a/src/error/mod.rs b/src/error/mod.rs index 91a252f..0a64c3d 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -237,8 +237,8 @@ impl Display for InvalidAifEncodedScopeError { } } -/// Error type used when a [`CoseEncrypt0Cipher`](crate::CoseEncrypt0Cipher), -/// [`CoseSign1Cipher`](crate::CoseSign1Cipher), or [`CoseMac0Cipher`](crate::CoseMac0Cipher). +/// Error type used when a [`CoseEncryptCipher`](crate::CoseEncryptCipher), +/// [`CoseSignCipher`](crate::CoseSignCipher), or [`CoseMacCipher`](crate::CoseMacCipher). /// fails to perform an operation. /// /// `T` is the type of the nested error represented by the [`Other`](CoseCipherError::Other) variant. @@ -343,10 +343,17 @@ where } } +/// Error type used when a token for multiple recipients (i.e., `CoseEncrypt`) is decrypted. +/// +/// In that case, the recipients may be encrypted with a different cipher (`K`) than the +/// actual content (`C`); hence, this error type differentiates between the two. #[derive(Debug)] pub enum MultipleCoseError where K: Display, C: Display { + /// Used when an error occurred in the Key Encryption Key's cipher. KekError(K), - CekError(C) + + /// Used when an error occurred in the Content Encryption Key's cipher. + CekError(C), } impl Display for MultipleCoseError where K: Display, C: Display { @@ -477,14 +484,14 @@ where /// [`CoseEncrypt0`](coset::CoseEncrypt0), [`CoseSign1`](coset::CoseSign1), /// nor [`CoseMac0`](coset::CoseMac0). UnknownCoseStructure, - /// No matching key was found in the list of COSE_Recipient structures. + /// No matching recipient was found in the list of COSE_Recipient structures. /// This means that the given Key Encryption Key could not be used to decrypt any of the /// recipients, which means no Content Encryption Key could be extracted. - NoMatchingKey, - /// Multiple matching keys were found in the list of COSE_Recipient structures. + NoMatchingRecipient, + /// Multiple matching recipients were found in the list of COSE_Recipient structures. /// This means that the given Key Encryption Key could be used to decrypt multiple of the /// recipients, which means the token is malformed. - MultipleMatchingKeys + MultipleMatchingRecipients } impl Display for AccessTokenError @@ -499,8 +506,8 @@ where f, "input is either invalid or none of CoseEncrypt0, CoseSign1 nor CoseMac0" ), - AccessTokenError::NoMatchingKey => write!(f, "given KEK doesn't match any recipient"), - AccessTokenError::MultipleMatchingKeys => write!(f, "given KEK matches multiple recipients") + AccessTokenError::NoMatchingRecipient => write!(f, "given KEK doesn't match any recipient"), + AccessTokenError::MultipleMatchingRecipients => write!(f, "given KEK matches multiple recipients") } } } @@ -519,6 +526,7 @@ impl From for AccessTokenError where T: Display { } } +#[allow(dead_code)] impl AccessTokenError where T: Display { @@ -530,8 +538,8 @@ where AccessTokenError::CoseCipherError(x) => AccessTokenError::CoseCipherError(CoseCipherError::from_kek_error(x)), AccessTokenError::CoseError(x) => AccessTokenError::CoseError(x), AccessTokenError::UnknownCoseStructure => AccessTokenError::UnknownCoseStructure, - AccessTokenError::NoMatchingKey => AccessTokenError::NoMatchingKey, - AccessTokenError::MultipleMatchingKeys => AccessTokenError::MultipleMatchingKeys, + AccessTokenError::NoMatchingRecipient => AccessTokenError::NoMatchingRecipient, + AccessTokenError::MultipleMatchingRecipients => AccessTokenError::MultipleMatchingRecipients, } } @@ -540,8 +548,8 @@ where AccessTokenError::CoseCipherError(x) => AccessTokenError::CoseCipherError(CoseCipherError::from_cek_error(x)), AccessTokenError::CoseError(x) => AccessTokenError::CoseError(x), AccessTokenError::UnknownCoseStructure => AccessTokenError::UnknownCoseStructure, - AccessTokenError::NoMatchingKey => AccessTokenError::NoMatchingKey, - AccessTokenError::MultipleMatchingKeys => AccessTokenError::MultipleMatchingKeys, + AccessTokenError::NoMatchingRecipient => AccessTokenError::NoMatchingRecipient, + AccessTokenError::MultipleMatchingRecipients => AccessTokenError::MultipleMatchingRecipients, } } } diff --git a/src/lib.rs b/src/lib.rs index e584a6b..0d85339 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,7 +18,7 @@ //! as well as the possibility to create COSE encrypted/signed access tokens //! (as described in the standard) along with decryption/verification functions. //! Implementations of the cryptographic functions must be provided by the user by implementing -//! [`CoseEncrypt0Cipher`] or [`CoseSign1Cipher`]. +//! [`CoseEncryptCipher`] or [`CoseSignCipher`]. //! //! Note that actually transmitting the serialized values (e.g., via CoAP) or providing more complex //! features not mentioned in the ACE-OAuth RFC (e.g., a permission management system for @@ -84,50 +84,142 @@ //! ## Access Tokens //! Following up from the previous example, let's assume we now want to create a signed //! access token containing the existing `key`, as well as claims about the audience and issuer -//! of the token, using an existing `cipher`[^cipher]: +//! of the token, using an existing cipher of type `FakeCrypto`[^cipher]: //! ``` //! # use ciborium::value::Value; +//! # use coset::{AsCborValue, CoseKey, CoseKeyBuilder, Header, Label, ProtectedHeader}; +//! # use coset::cwt::{ClaimsSetBuilder, Timestamp}; +//! # use coset::iana::{Algorithm, CwtClaimName}; +//! # use rand::{CryptoRng, RngCore}; +//! # use dcaf::{ToCborMap, sign_access_token, verify_access_token, CoseSignCipher}; +//! # use dcaf::common::cbor_values::{ByteString, ProofOfPossessionKey}; +//! # use dcaf::common::cbor_values::ProofOfPossessionKey::PlainCoseKey; //! # use dcaf::error::{AccessTokenError, CoseCipherError}; -//! # use dcaf::{CoseCipherCommon, CoseSign1Cipher, ProofOfPossessionKey}; -//! use dcaf::{ToCborMap, sign_access_token, verify_access_token}; -//! use coset::cwt::ClaimsSetBuilder; -//! use coset::Header; -//! use coset::iana::CwtClaimName; -//! -//! # struct FakeCipher {}; -//! # impl CoseCipherCommon for FakeCipher { -//! # type Error = String; +//! # use dcaf::token::ToCoseKey; +//! +//! #[derive(Clone)] +//! # pub(crate) struct FakeKey { +//! # key: [u8; 5], +//! # kid: [u8; 2], +//! # } +//! # +//! # impl ToCoseKey for FakeKey { +//! # fn to_cose_key(&self) -> CoseKey { +//! # CoseKeyBuilder::new_symmetric_key(self.key.to_vec()) +//! # .key_id(self.kid.to_vec()) +//! # .build() +//! # } +//! # } +//! # +//! # struct FakeCrypto {} +//! # +//! # #[derive(Clone, Copy)] +//! # pub(crate) struct FakeRng; +//! # +//! # impl RngCore for FakeRng { +//! # fn next_u32(&mut self) -> u32 { +//! # 0 +//! # } +//! # +//! # fn next_u64(&mut self) -> u64 { +//! # 0 +//! # } +//! # +//! # fn fill_bytes(&mut self, dest: &mut [u8]) { +//! # dest.fill(0); +//! # } //! # -//! # fn set_headers(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { +//! # fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { +//! # dest.fill(0); //! # Ok(()) //! # } //! # } -//! # impl CoseSign1Cipher for FakeCipher { -//! # fn generate_signature(&mut self, target: &[u8]) -> Vec { -//! # target.to_vec() +//! # +//! # impl CryptoRng for FakeRng {} +//! # +//! # /// Implements basic operations from the [`CoseSignCipher`] trait without actually using any +//! # /// "real" cryptography. +//! # /// This is purely to be used for testing and obviously offers no security at all. +//! # impl CoseSignCipher for FakeCrypto { +//! # type Error = String; +//! # type SignKey = FakeKey; +//! # type VerifyKey = Self::SignKey; +//! # +//! # fn set_headers( +//! # key: &FakeKey, +//! # unprotected_header: &mut Header, +//! # protected_header: &mut Header, +//! # rng: RNG +//! # ) -> Result<(), CoseCipherError> { +//! # if let Some(label) = unprotected_header +//! # .rest +//! # .iter() +//! # .find(|x| x.0 == Label::Int(47)) +//! # { +//! # return Err(CoseCipherError::existing_header_label(&label.0)); +//! # } +//! # if protected_header.alg != None { +//! # return Err(CoseCipherError::existing_header("alg")); +//! # } +//! # if !protected_header.key_id.is_empty() { +//! # return Err(CoseCipherError::existing_header("key_id")); +//! # } +//! # unprotected_header.rest.push((Label::Int(47), Value::Null)); +//! # protected_header.alg = Some(coset::Algorithm::Assigned(Algorithm::Direct)); +//! # protected_header.key_id = key.kid.to_vec(); +//! # Ok(()) //! # } -//! # fn verify_signature(&mut self, signature: &[u8], signed_data: &[u8]) -> Result<(), CoseCipherError> { -//! # if signature.to_vec() != self.generate_signature(signed_data) { -//! # Err(CoseCipherError::VerificationFailure) +//! # fn sign( +//! # key: &Self::SignKey, +//! # target: &[u8], +//! # unprotected_header: &Header, +//! # protected_header: &Header, +//! # ) -> Vec { +//! # // We simply append the key behind the data. +//! # let mut signature = target.to_vec(); +//! # signature.append(&mut key.key.to_vec()); +//! # signature +//! # } +//! # +//! # fn verify( +//! # key: &Self::VerifyKey, +//! # signature: &[u8], +//! # signed_data: &[u8], +//! # unprotected_header: &Header, +//! # protected_header: &ProtectedHeader, +//! # unprotected_signature_header: Option<&Header>, +//! # protected_signature_header: Option<&ProtectedHeader>, +//! # ) -> Result<(), CoseCipherError> { +//! # let matching_kid = if let Some(protected) = protected_signature_header { +//! # protected.header.key_id == key.kid //! # } else { +//! # protected_header.header.key_id == key.kid +//! # }; +//! # let signed_again = Self::sign(key, signed_data, unprotected_header, &protected_header.header); +//! # if matching_kid && signed_again == signature +//! # { //! # Ok(()) +//! # } else { +//! # Err(CoseCipherError::VerificationFailure) //! # } //! # } //! # } -//! # let key = ProofOfPossessionKey::KeyId(vec![0xDC, 0xAF]); -//! # let mut cipher = FakeCipher {}; +//! +//! let rng = FakeRng; +//! let key = FakeKey { key: [1,2,3,4,5], kid: [0xDC, 0xAF]}; +//! let cose_key: CoseKey = key.to_cose_key(); //! let claims = ClaimsSetBuilder::new() -//! .audience("valve242".to_string()) -//! .claim(CwtClaimName::Cnf, key.to_ciborium_value()) -//! .claim(CwtClaimName::Scope, Value::Text("read".to_string())) -//! .build(); -//! let token = sign_access_token(claims, &mut cipher, None, None, None)?; -//! assert!(verify_access_token(&token, &mut cipher, None).is_ok()); +//! .audience(String::from("coaps://rs.example.com")) +//! .issuer(String::from("coaps://as.example.com")) +//! .claim(CwtClaimName::Cnf, cose_key.to_cbor_value()?) +//! .build(); +//! let token = sign_access_token::(&key, claims, None, None, None, rng)?; +//! assert!(verify_access_token::(&key, &token, None).is_ok()); //! # Ok::<(), AccessTokenError>(()) //! ``` //! //! [^cipher]: Note that we are deliberately omitting details about the implementation of the -//! `cipher` here, since such implementations won't be in scope of this crate. +//! `cipher` here, since such implementations won't be in the scope of this crate. //! //! # Provided Data Models //! @@ -153,12 +245,13 @@ //! In order to create access tokens, you can use either [`encrypt_access_token`] or //! [`sign_access_token`], depending on whether you want the access token to be wrapped in a //! `COSE_Encrypt0` or `COSE_Sign1` structure. Support for a combination of both is planned for the -//! future. +//! future. In case you want to create a token intended for multiple recipients (each with their +//! own key), you can use [`encrypt_access_token_multiple`] or [`sign_access_token_multiple`]. //! //! Both functions take a [`ClaimsSet`](coset::cwt::ClaimsSet) containing the claims that -//! shall be part of the access token, a cipher implementing the cryptographic operations -//! (explained further below), as well as optional `aad` (additional authenticated data) -//! and un-/protected headers. +//! shall be part of the access token, a key used to encrypt or sign the token, +//! optional `aad` (additional authenticated data), un-/protected headers and a cipher (explained +//! further below) identified by type parameter `T`. //! Note that if the headers you pass in set fields which the cipher wants to set as well, //! the function will fail with a //! [`HeaderAlreadySet`](crate::error::CoseCipherError::HeaderAlreadySet) error. @@ -167,9 +260,12 @@ //! # Verifying / decrypting Access Tokens //! In order to verify or decrypt existing access tokens represented as [`ByteString`]s, //! use [`verify_access_token`] or [`decrypt_access_token`] respectively. +//! In case the token was created for multiple recipients (each with their own key), +//! use [`verify_access_token_multiple`] or [`decrypt_access_token_multiple`]. //! -//! Both functions take the access token, a `cipher` for the cryptographic operations and an -//! optional `aad` (additional authenticated data). +//! Both functions take the access token, a `key` used to decrypt or verify, optional `aad` +//! (additional authenticated data) and a cipher implementing cryptographic operations identified +//! by type parameter `T`. //! //! [`decrypt_access_token`] will return a result containing the decrypted //! [`ClaimsSet`](coset::cwt::ClaimsSet). @@ -177,34 +273,37 @@ //! was successfully verified---an [`Err`](Result::Err) would indicate failure. //! //! # Extracting Headers from an Access Token -//! Regardless of whether token was signed, encrypted, or MAC-tagged, you can extract its +//! Regardless of whether a token was signed, encrypted, or MAC-tagged, you can extract its //! headers using [`get_token_headers`], which will return an option containing both //! unprotected and protected headers (or which will be [`None`](Option::None) in case -//! the token is invalid or neither a `COSE_Sign1`, `COSE_Encrypt0`, or `COSE_Mac0` structure). +//! the token is invalid). //! //! # COSE Cipher //! As mentioned before, cryptographic functions are outside the scope of this crate. //! For this reason, the various COSE cipher traits exist; namely, -//! [`CoseEncrypt0Cipher`], [`CoseSign1Cipher`], and [`CoseMac0Cipher`], each implementing +//! [`CoseEncryptCipher`], [`CoseSignCipher`], and [`CoseMacCipher`], each implementing //! a corresponding COSE operation as specified in sections 4, 5, and 6 of //! [RFC 8152](https://www.rfc-editor.org/rfc/rfc8152). +//! There are also the traits [`MultipleEncryptCipher`], [`MultipleSignCipher`], and +//! [`MultipleMacCipher`], which are used for creating tokens intended for multiple recipients. //! -//! Note that these ciphers *don't* need to wrap their results in e.g. a `Cose_Encrypt0` structure, -//! this part is already handled using this library (which uses [`coset`])---only the -//! cryptographic algorithms themselves need to be implemented (e.g. step 4 of -//! "how to decrypt a message" in [section 5.3 of RFC 8152](https://www.rfc-editor.org/rfc/rfc8152#section-5.3)). +//! Note that these ciphers *don't* need to wrap their results in, e.g., +//! a `Cose_Encrypt0` structure, as this part is already handled by this library +//! (which uses [`coset`])---only the cryptographic algorithms themselves need to be implemented +//! (e.g., step 4 of "how to decrypt a message" in [section 5.3 of RFC 8152](https://www.rfc-editor.org/rfc/rfc8152#section-5.3)). //! -//! When implementing any of the specific COSE ciphers, you'll also need to implement the -//! [`CoseCipherCommon`] trait, which can be used to set headers specific to your COSE cipher -//! (e.g. the used algorithm). +//! When implementing any of the specific COSE ciphers, you'll also need to specify the type +//! of the key (which must be convertible to a `CoseKey`) and implement a method which sets +//! headers for the token, for example, the used algorithm, the key ID, an IV, and so on. #![deny(rustdoc::broken_intra_doc_links, clippy::pedantic)] #![warn(missing_docs, rustdoc::missing_crate_level_docs)] // These ones are a little too eager #![allow( - clippy::doc_markdown, - clippy::module_name_repetitions, - clippy::wildcard_imports +clippy::doc_markdown, +clippy::module_name_repetitions, +clippy::wildcard_imports, +clippy::type_complexity )] #![cfg_attr(not(feature = "std"), no_std)] #[macro_use] @@ -233,7 +332,12 @@ pub use endpoints::token_req::{ #[doc(inline)] pub use token::{ CoseEncryptCipher, CoseMacCipher, CoseSignCipher, - decrypt_access_token, encrypt_access_token, get_token_headers, sign_access_token, verify_access_token, + decrypt_access_token, decrypt_access_token_multiple, encrypt_access_token, + encrypt_access_token_multiple, + get_token_headers, MultipleEncryptCipher, + MultipleMacCipher, MultipleSignCipher, + sign_access_token, sign_access_token_multiple, + verify_access_token, verify_access_token_multiple, }; pub mod common; diff --git a/src/token/mod.rs b/src/token/mod.rs index d74ca21..40d9874 100644 --- a/src/token/mod.rs +++ b/src/token/mod.rs @@ -13,82 +13,161 @@ //! [signing](sign_access_token) and [verifying](verify_access_token) access tokens. //! //! **NOTE: The APIs in this module are experimental and likely to change in the future!** -//! This is due to the COSE support being very basic right now (e.g. only `CoseEncrypt0` instead of -//! `CoseEncrypt`) and due to the APIs needing to be "battle-tested" in active use. -//! Builders will also most likely be added as well due to a lot of optional arguments present -//! in the functions at the moment. +//! This is because we plan to move much of the code here to the [coset](https://docs.rs/coset/) +//! library, since much of this just builds on COSE functionality and isn't ACE-OAuth specific. //! //! In order to use any of these methods, you will need to provide a cipher which handles -//! the cryptographic operations by implementing both [`CoseCipherCommon`] (which sets -//! necessary headers) and either [`CoseEncrypt0Cipher`], [`CoseMac0Cipher`] or [`CoseSign1Cipher`], -//! depending on the intended operation. See the respective traits for details. +//! the cryptographic operations by implementingeither [`CoseEncryptCipher`], +//! [`CoseMacCipher`] or [`CoseSignCipher`], depending on the intended operation. +//! If you plan to support `CoseEncrypt` or `CoseSign` rather than just `CoseEncrypt0` or +//! `CoseSign1` (i.e., if you have multiple recipients with separate keys), you will also need to +//! implement [`MultipleEncryptCipher`] or [`MultipleSignCipher`]. +//! See the respective traits for details. //! //! # Example -//! The following shows how to create and sign an access token (assuming a cipher implementing -//! both [`CoseSign1Cipher`] and [`CoseCipherCommon`] exists in variable `cipher`): +//! The following shows how to create and sign an access token (assuming a cipher named +//! `FakeCrypto` which implements [`CoseSignCipher`] exists.): //! ``` +//! # // TODO: There's really too much hidden code here. Should be heavily refactored once we have +//! # // crypto implementations available. Same goes for crate-level docs. //! # use ciborium::value::Value; -//! # use coset::{Header, Label}; +//! # use coset::{AsCborValue, CoseKey, CoseKeyBuilder, Header, Label, ProtectedHeader}; //! # use coset::cwt::{ClaimsSetBuilder, Timestamp}; //! # use coset::iana::{Algorithm, CwtClaimName}; -//! # use dcaf::{ToCborMap, CoseCipherCommon, CoseSign1Cipher, sign_access_token, verify_access_token}; +//! # use rand::{CryptoRng, RngCore}; +//! # use dcaf::{ToCborMap, sign_access_token, verify_access_token, CoseSignCipher}; //! # use dcaf::common::cbor_values::{ByteString, ProofOfPossessionKey}; //! # use dcaf::common::cbor_values::ProofOfPossessionKey::PlainCoseKey; //! # use dcaf::error::{AccessTokenError, CoseCipherError}; +//! # use dcaf::token::ToCoseKey; +//! +//! #[derive(Clone)] +//! # pub(crate) struct FakeKey { +//! # key: [u8; 5], +//! # kid: [u8; 2], +//! # } +//! # +//! # impl ToCoseKey for FakeKey { +//! # fn to_cose_key(&self) -> CoseKey { +//! # CoseKeyBuilder::new_symmetric_key(self.key.to_vec()) +//! # .key_id(self.kid.to_vec()) +//! # .build() +//! # } +//! # } +//! # //! # struct FakeCrypto {} //! # -//! # impl CoseCipherCommon for FakeCrypto { +//! # #[derive(Clone, Copy)] +//! # pub(crate) struct FakeRng; +//! # +//! # impl RngCore for FakeRng { +//! # fn next_u32(&mut self) -> u32 { +//! # 0 +//! # } +//! # +//! # fn next_u64(&mut self) -> u64 { +//! # 0 +//! # } +//! # +//! # fn fill_bytes(&mut self, dest: &mut [u8]) { +//! # dest.fill(0); +//! # } +//! # +//! # fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { +//! # dest.fill(0); +//! # Ok(()) +//! # } +//! # } +//! # +//! # impl CryptoRng for FakeRng {} +//! # +//! # /// Implements basic operations from the [`CoseSignCipher`] trait without actually using any +//! # /// "real" cryptography. +//! # /// This is purely to be used for testing and obviously offers no security at all. +//! # impl CoseSignCipher for FakeCrypto { //! # type Error = String; +//! # type SignKey = FakeKey; +//! # type VerifyKey = Self::SignKey; //! # -//! # fn set_headers(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { -//! # // We have to later verify these headers really are used. -//! # if let Some(label) = unprotected_header.rest.iter().find(|x| x.0 == Label::Int(47)) { +//! # fn set_headers( +//! # key: &FakeKey, +//! # unprotected_header: &mut Header, +//! # protected_header: &mut Header, +//! # rng: RNG +//! # ) -> Result<(), CoseCipherError> { +//! # if let Some(label) = unprotected_header +//! # .rest +//! # .iter() +//! # .find(|x| x.0 == Label::Int(47)) +//! # { //! # return Err(CoseCipherError::existing_header_label(&label.0)); //! # } //! # if protected_header.alg != None { //! # return Err(CoseCipherError::existing_header("alg")); //! # } +//! # if !protected_header.key_id.is_empty() { +//! # return Err(CoseCipherError::existing_header("key_id")); +//! # } //! # unprotected_header.rest.push((Label::Int(47), Value::Null)); //! # protected_header.alg = Some(coset::Algorithm::Assigned(Algorithm::Direct)); +//! # protected_header.key_id = key.kid.to_vec(); //! # Ok(()) //! # } -//! # } -//! # -//! # /// Implements basic operations from the [`CoseSign1Cipher`] trait without actually using any -//! # /// "real" cryptography. -//! # /// This is purely to be used for testing and obviously offers no security at all. -//! # impl CoseSign1Cipher for FakeCrypto { -//! # fn generate_signature(&mut self, data: &[u8]) -> Vec { -//! # data.to_vec() +//! # fn sign( +//! # key: &Self::SignKey, +//! # target: &[u8], +//! # unprotected_header: &Header, +//! # protected_header: &Header, +//! # ) -> Vec { +//! # // We simply append the key behind the data. +//! # let mut signature = target.to_vec(); +//! # signature.append(&mut key.key.to_vec()); +//! # signature //! # } //! # -//! # fn verify_signature(&mut self, sig: &[u8], data: &[u8]) -> Result<(), CoseCipherError> { -//! # if sig != self.generate_signature(data) { -//! # Err(CoseCipherError::VerificationFailure) +//! # fn verify( +//! # key: &Self::VerifyKey, +//! # signature: &[u8], +//! # signed_data: &[u8], +//! # unprotected_header: &Header, +//! # protected_header: &ProtectedHeader, +//! # unprotected_signature_header: Option<&Header>, +//! # protected_signature_header: Option<&ProtectedHeader>, +//! # ) -> Result<(), CoseCipherError> { +//! # let matching_kid = if let Some(protected) = protected_signature_header { +//! # protected.header.key_id == key.kid //! # } else { +//! # protected_header.header.key_id == key.kid +//! # }; +//! # let signed_again = Self::sign(key, signed_data, unprotected_header, &protected_header.header); +//! # if matching_kid && signed_again == signature +//! # { //! # Ok(()) +//! # } else { +//! # Err(CoseCipherError::VerificationFailure) //! # } //! # } //! # } //! -//! # let mut cipher = FakeCrypto {}; -//! let key = ProofOfPossessionKey::KeyId(vec![0xDC, 0xAF]); +//! let rng = FakeRng; +//! let key = FakeKey { key: [1,2,3,4,5], kid: [0xDC, 0xAF]}; +//! let cose_key: CoseKey = key.to_cose_key(); //! let claims = ClaimsSetBuilder::new() //! .audience(String::from("coaps://rs.example.com")) //! .issuer(String::from("coaps://as.example.com")) -//! .claim(CwtClaimName::Cnf, key.to_ciborium_value()) +//! .claim(CwtClaimName::Cnf, cose_key.to_cbor_value()?) //! .build(); -//! let token = sign_access_token(claims, &mut cipher, None, None, None)?; -//! assert!(verify_access_token(&token, &mut cipher, None).is_ok()); +//! let token = sign_access_token::(&key, claims, None, None, None, rng)?; +//! assert!(verify_access_token::(&key, &token, None).is_ok()); //! # Ok::<(), AccessTokenError>(()) //! ``` #[cfg(not(feature = "std"))] use alloc::vec::Vec; -use core::fmt::{Debug, Display, Formatter, Pointer}; +use core::fmt::{Debug, Display}; use ciborium::value::Value; -use coset::{AsCborValue, CborSerializable, CoseEncrypt, CoseEncrypt0, CoseEncrypt0Builder, CoseEncryptBuilder, CoseKey, CoseMac0, CoseRecipient, CoseRecipientBuilder, CoseSign, CoseSign1, CoseSign1Builder, CoseSignatureBuilder, CoseSignBuilder, EncryptionContext, Header, HeaderBuilder, ProtectedHeader}; +use coset::{AsCborValue, CborSerializable, CoseEncrypt, CoseEncrypt0, CoseEncrypt0Builder, CoseEncryptBuilder, CoseKey, CoseRecipientBuilder, CoseSign, CoseSign1, CoseSign1Builder, CoseSignatureBuilder, CoseSignBuilder, EncryptionContext, Header, HeaderBuilder, ProtectedHeader}; use coset::cwt::ClaimsSet; use rand::{CryptoRng, RngCore}; @@ -98,10 +177,18 @@ use crate::error::{AccessTokenError, CoseCipherError, MultipleCoseError}; #[cfg(test)] mod tests; +/// Trait for keys which can be converted to [CoseKey]s from a reference of the original type. pub trait ToCoseKey { + /// Converts a reference of itself to a [CoseKey]. + /// + /// Note that this may lead to fields of the key being copied, + /// as we merely pass a reference in, even though [CoseKey] is not associated with any lifetime. fn to_cose_key(&self) -> CoseKey; } +// TODO: Examples in here are currently either not run or do not exist because they require too much +// setup (see crate-level docs). This should be fixed once we have cipher implementations. + macro_rules! add_common_cipher_functionality { [$a:ty] => { /// Error type that this cipher uses in [`Result`]s returned by cryptographic operations. @@ -110,12 +197,15 @@ macro_rules! add_common_cipher_functionality { /// Sets headers specific to this cipher by adding new header fields to the given /// `unprotected_header` and `protected_header`. /// + /// The given `key` may be used to extract information for the headers (e.g., the key ID) + /// and `rng` may be used to generate random values for the headers (e.g., an IV). + /// /// Before actually changing the headers, it will be verified that none of the header fields /// that are about to be set are already set, so as not to overwrite them. In such a /// case, an error is returned. /// /// This will usually not be called by users of `dcaf-rs`, but instead by access methods - /// such as [`encrypt_access_token`], which will later pass it to [`coset`]'s methods. + /// such as [`encrypt_access_token`], which will later pass it to [coset]'s methods. /// /// # Errors /// - When the fields that this method would set on the given headers are already set. @@ -123,33 +213,27 @@ macro_rules! add_common_cipher_functionality { /// # Example /// Let's say our cipher needs to set the content type to /// [`Cbor`](coset::iana::CoapContentFormat::Cbor) (in the unprotected header) - /// and the algorithm to [`HMAC_256_256`](coset::iana::Algorithm::HMAC_256_256) - /// (in the protected header). Our implementation would first need to verify that these + /// and the key ID to the ID of the passed in `key` (in the protected header). + /// Our implementation would first need to verify that these /// header fields haven't already been set, then actually set them, so an implementation /// of this function might look like the following: - /// ``` - /// # use ciborium::value::Value; - /// # use coset::{ContentType, Header, Label, RegisteredLabel}; - /// # use coset::iana::Algorithm; - /// # use dcaf::CoseCipherCommon; - /// # use dcaf::error::CoseCipherError; - /// # struct FakeCipher {} - /// # impl CoseCipherCommon for FakeCipher { - /// # // This should of course be an actual error type, not just a String. - /// # type Error = String; - /// - /// fn set_headers(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { + /// ```ignore + /// fn set_headers( + /// key: &FakeKey, + /// unprotected_header: &mut Header, + /// protected_header: &mut Header, + /// rng: RNG + /// ) -> Result<(), CoseCipherError> { /// if unprotected_header.content_type.is_some() { /// return Err(CoseCipherError::existing_header("content_type")); /// } - /// if protected_header.alg.is_some() { - /// return Err(CoseCipherError::existing_header("alg")); + /// if !protected_header.key_id.is_empty() { + /// return Err(CoseCipherError::existing_header("kid")); /// } /// unprotected_header.content_type = Some(ContentType::Assigned(coset::iana::CoapContentFormat::Cbor)); - /// protected_header.alg = Some(coset::Algorithm::Assigned(Algorithm::HMAC_256_256)); + /// protected_header.key_id = key.kid.to_vec(); /// Ok(()) /// } - /// # } /// ``` fn set_headers( key: &$a, @@ -162,68 +246,24 @@ macro_rules! add_common_cipher_functionality { /// Provides basic operations for encrypting and decrypting COSE structures. /// -/// This will be used by [`encrypt_access_token`] and [`decrypt_access_token`] to apply the +/// This will be used by [`encrypt_access_token`] and [`decrypt_access_token`] (as well as the +/// variants for multiple recipients: [`encrypt_access_token_multiple`] +/// and [`decrypt_access_token_multiple`]) to apply the /// corresponding cryptographic operations to the constructed token bytestring. -/// Since [`CoseCipherCommon`] also needs to be implemented, the -/// [`headers` method](CoseCipherCommon::header) can be used to set parameters this cipher requires -/// to be set. If you need to operate on other fields in the token than just the claims, -/// you can use the data type behind this trait for that. -/// The methods provided in this trait accept `&mut self` in case the structure behind it needs to -/// modify internal fields during any cryptographic operation. -/// -/// # Example -/// For example, to simply implement the encryption operation as appending the `aad` to the -/// `plaintext` (which you **clearly should not do**, this is just for illustrative purposes), -/// and implementing `decrypt` by verifying that the AAD matches (same warning applies): -/// ``` -/// # use coset::Header; -/// # use dcaf::{CoseCipherCommon, CoseEncryptCipher}; -/// # use dcaf::error::CoseCipherError; -/// # struct FakeCrypto {}; -/// # impl CoseCipherCommon for FakeCrypto { -/// # type Error = String; -/// # fn set_headers(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { -/// # unimplemented!() -/// # } -/// # } -/// impl CoseEncryptCipher for FakeCrypto { -/// fn encrypt(&mut self, data: &[u8], aad: &[u8]) -> Vec { -/// // We simply put AAD behind the data and call it a day. -/// let mut result: Vec = Vec::new(); -/// result.append(&mut data.to_vec()); -/// result.append(&mut aad.to_vec()); -/// result -/// } -/// -/// fn decrypt(&mut self, ciphertext: &[u8], aad: &[u8]) -> Result, CoseCipherError> { -/// // Now we just split off the AAD we previously put at the end of the data. -/// // We return an error if it does not match. -/// if ciphertext.len() < aad.len() { -/// return Err(CoseCipherError::Other("Encrypted data must be at least as long as AAD!".to_string())); -/// } -/// let mut result: Vec = ciphertext.to_vec(); -/// let aad_result = result.split_off(ciphertext.len() - aad.len()); -/// if aad != aad_result { -/// Err(CoseCipherError::DecryptionFailure) -/// } else { -/// Ok(result) -/// } -/// } -/// } -/// -/// let mut cipher = FakeCrypto{}; -/// let data = vec![0xDC, 0xAF]; -/// let aad = vec![42]; -/// let encrypted = cipher.encrypt(&data, &aad); -/// assert_eq!(cipher.decrypt(&encrypted, &aad)?, data); -/// # Ok::<(), CoseCipherError>(()) -/// ``` +/// The [`set_headers` method](CoseEncryptCipher::set_headers) can be used to set parameters this +/// cipher requires to be set. pub trait CoseEncryptCipher { + /// Type of the encryption key. Needs to be serializable to a vector of bytes in case + /// [`encrypt_access_token_multiple`] is used, in which we need to serialize the + /// Key Encryption Keys. type EncryptKey: ToCoseKey + Into>; + + /// Type of the decryption key. Needs to be deserializable from a vector of bytes in case + /// [`decrypt_access_token_multiple`] is used, in which we need to deserialize the + /// Key Encryption Keys. type DecryptKey: ToCoseKey + TryFrom>; - /// Encrypts the given `plaintext` and `aad`, returning the result. - /// - /// For an example, view the documentation of [`CoseEncrypt0Cipher`]. + + /// Encrypts the `plaintext` and `aad` with the given `key`, returning the result. fn encrypt( key: &Self::EncryptKey, plaintext: &[u8], @@ -232,9 +272,7 @@ pub trait CoseEncryptCipher { unprotected_header: &Header, ) -> Vec; - /// Decrypts the given `ciphertext` and `aad`, returning the result. - /// - /// For an example, view the documentation of [`CoseEncrypt0Cipher`]. + /// Decrypts the `ciphertext` and `aad` with the given `key`, returning the result. /// /// # Errors /// If the `ciphertext` and `aad` are invalid, i.e., can't be decrypted. @@ -249,61 +287,34 @@ pub trait CoseEncryptCipher { add_common_cipher_functionality![Self::EncryptKey]; } +/// Intended for ciphers which encrypt for multiple recipients. +/// For this purpose, a method must be provided which generates the Content Encryption Key. +/// +/// If these recipients each use different key types, you can use an enum to represent them. pub trait MultipleEncryptCipher: CoseEncryptCipher { + /// Randomly generates a new Content Encryption Key (CEK) using the given `rng`. + /// The content of the `CoseEncrypt` will then be encrypted with the key, while each recipient + /// will be encrypted with a corresponding Key Encryption Key (KEK) provided by the caller + /// of [`encrypt_access_token_multiple`]. fn generate_cek(rng: &mut RNG) -> Self::EncryptKey; } /// Provides basic operations for signing and verifying COSE structures. /// -/// This will be used by [`sign_access_token`] and [`verify_access_token`] to apply the +/// This will be used by [`sign_access_token`] and [`verify_access_token`] (as well as the +/// equivalents for multiple recipients: [`sign_access_token_multiple`] and +/// [`verify_access_token_multiple`]) to apply the /// corresponding cryptographic operations to the constructed token bytestring. -/// Since [`CoseCipherCommon`] also needs to be implemented, the -/// [`headers` method](CoseCipherCommon::header) can be used to set parameters this cipher requires -/// to be set. If you need to operate on other fields in the token than just the claims, -/// you can use the data type behind this trait for that. -/// The methods provided in this trait accept `&mut self` in case the structure behind it needs to -/// modify internal fields during any cryptographic operation. -/// -/// # Example -/// For example, to simply implement the signing operation as the identity function -/// (which you **clearly should not do**, this is just for illustrative purposes): -/// ``` -/// # use coset::Header; -/// # use dcaf::{CoseCipherCommon, CoseSign1Cipher}; -/// # use dcaf::error::CoseCipherError; -/// # struct FakeSigner {}; -/// # impl CoseCipherCommon for FakeSigner { -/// # type Error = String; -/// # fn set_headers(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { -/// # unimplemented!() -/// # } -/// # } -/// impl CoseSign1Cipher for FakeSigner { -/// fn generate_signature(&mut self, target: &[u8]) -> Vec { -/// target.to_vec() -/// } -/// -/// fn verify_signature(&mut self, signature: &[u8], signed_data: &[u8]) -> Result<(), CoseCipherError> { -/// if signature != self.generate_signature(signed_data) { -/// Err(CoseCipherError::VerificationFailure) -/// } else { -/// Ok(()) -/// } -/// } -/// } -/// -/// let mut signer = FakeSigner {}; -/// let signature = signer.generate_signature(&vec![0xDC, 0xAF]); -/// assert!(signer.verify_signature(&signature, &vec![0xDC, 0xAF]).is_ok()); -/// assert!(signer.verify_signature(&signature, &vec![0xDE, 0xAD]).is_err()); -/// ``` +/// The [`set_headers` method](CoseSignCipher::set_headers) can be used to set parameters +/// this cipher requires to be set. pub trait CoseSignCipher { + /// Type of the key used to create signatures. type SignKey: ToCoseKey; + + /// Type of the key used to verify signatures. type VerifyKey: ToCoseKey; - /// Cryptographically signs the given `target` value and returns the signature. - /// - /// For an example, see the documentation of [`CoseSign1Cipher`]. + /// Cryptographically signs the `target` value with the `key` and returns the signature. fn sign( key: &Self::SignKey, target: &[u8], @@ -311,9 +322,14 @@ pub trait CoseSignCipher { protected_header: &Header, ) -> Vec; - /// Verifies the `signature` of the `signed_data`. + /// Verifies the `signature` of the `signed_data` with the `key`. /// - /// For an example, see the documentation of [`CoseSign1Cipher`]. + /// Note that, for single recipients (i.e., `CoseSign1`), + /// `unprotected_signature_header` and `protected_signature_header` will be `None`. + /// For multiple recipients (i.e., `CoseSign`), `unprotected_signature_header` and + /// `protected_signature_header` will be the headers of the individual signature for this + /// recipient, whereas `unprotected_header` and `protected_header` will be the headers + /// of the `CoseSign` structure as a whole. /// /// # Errors /// If the `signature` is invalid or does not belong to the `signed_data`. @@ -330,52 +346,22 @@ pub trait CoseSignCipher { add_common_cipher_functionality![Self::SignKey]; } +/// Marker trait intended for ciphers which create signatures for multiple recipients. +/// +/// If these recipients each use different key types, you can use an enum to represent them. pub trait MultipleSignCipher: CoseSignCipher {} /// Provides basic operations for generating and verifying MAC tags for COSE structures. /// /// This trait is currently not used by any access token function. -/// -/// # Example -/// For example, to simply implement the signing operation as the identity function -/// (which you **clearly should not do**, this is just for illustrative purposes): -/// ``` -/// # use coset::Header; -/// # use dcaf::{CoseCipherCommon, CoseMacCipher, CoseSign1Cipher}; -/// # use dcaf::error::CoseCipherError; -/// # struct FakeTagger {}; -/// # impl CoseCipherCommon for FakeTagger { -/// # type Error = String; -/// # fn set_headers(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { -/// # unimplemented!() -/// # } -/// # } -/// impl CoseMacCipher for FakeTagger { -/// fn compute(&mut self, target: &[u8]) -> Vec { -/// target.to_vec() -/// } -/// -/// fn verify(&mut self, tag: &[u8], signed_data: &[u8]) -> Result<(), CoseCipherError> { -/// if tag != self.generate_tag(signed_data) { -/// Err(CoseCipherError::VerificationFailure) -/// } else { -/// Ok(()) -/// } -/// } -/// } -/// -/// let mut tagger = FakeTagger {}; -/// let tag = tagger.generate_tag(&vec![0xDC, 0xAF]); -/// assert!(tagger.verify_tag(&tag, &vec![0xDC, 0xAF]).is_ok()); -/// assert!(tagger.verify_tag(&tag, &vec![0xDE, 0xAD]).is_err()); -/// ``` pub trait CoseMacCipher { + /// Type of the key used to compute MAC tags. type ComputeKey: ToCoseKey; + + /// Type of the key used to verify MAC tags. type VerifyKey: ToCoseKey; - /// Generates a MAC tag for the given `target` and returns it. - /// - /// For an example, see the documentation of [`CoseMac0Cipher`]. + /// Generates a MAC tag for the given `target` with the given `key` and returns it. fn compute( key: &Self::ComputeKey, target: &[u8], @@ -383,9 +369,7 @@ pub trait CoseMacCipher { protected_header: &Header, ) -> Vec; - /// Verifies the `tag` of the `maced_data`. - /// - /// For an example, see the documentation of [`CoseMac0Cipher`]. + /// Verifies the `tag` of the `maced_data` with the `key`. /// /// # Errors /// If the `tag` is invalid or does not belong to the `maced_data`. @@ -400,11 +384,14 @@ pub trait CoseMacCipher { add_common_cipher_functionality![Self::ComputeKey]; } +/// Marker trait intended for ciphers which create MAC tags for multiple recipients. +/// +/// If these recipients each use different key types, you can use an enum to represent them. pub trait MultipleMacCipher: CoseMacCipher {} /// Creates new headers if `unprotected_header` or `protected_header` is `None`, respectively, /// and passes them to the `cipher`'s `header` function, returning the mutated result. -/// Arguments: key (expr), unprotected (ident), protected (ident), rng (expr) cipher (type) +/// Arguments: key (expr), unprotected (ident), protected (ident), rng (expr), cipher (type) macro_rules! prepare_headers { ($key:expr, $unprotected:ident, $protected:ident, $rng:expr, $t:ty) => {{ let mut unprotected = $unprotected.unwrap_or_else(|| HeaderBuilder::new().build()); @@ -418,62 +405,31 @@ macro_rules! prepare_headers { }}; } -/// Encrypts the given `claims` with the given headers and `aad` using `cipher` for cryptography, -/// returning the token as a serialized bytestring of the [`CoseEncrypt0`] structure. +/// Encrypts the given `claims` with the given headers and `aad` using the `key` and the cipher +/// given by type parameter `T`, returning the token as a serialized bytestring of +/// the [`CoseEncrypt0`] structure. +/// +/// Note that this method will create a token intended for a single recipient. +/// If you wish to create a token for more than one recipient, use +/// [`encrypt_access_token_multiple`]. /// /// # Errors /// - When there's a [`CoseError`](coset::CoseError) while serializing the given `claims` to CBOR. /// - When there's a [`CoseError`](coset::CoseError) while serializing the [`CoseEncrypt0`] structure. +/// - When the given headers conflict with the headers set by the cipher `T`. /// /// # Example -/// For example, assuming we have a [`CoseEncrypt0Cipher`] in `cipher`, -/// have a [`ProofOfPossessionKey`](crate::common::cbor_values::ProofOfPossessionKey) +/// For example, assuming we have a [`CoseEncryptCipher`] in `FakeCrypto`, a random number generator +/// in `rng`, a [`ProofOfPossessionKey`](crate::common::cbor_values::ProofOfPossessionKey) /// in `key` and want to associate this key with the access token we are about to create and encrypt: -/// ``` -/// # use coset::cwt::ClaimsSetBuilder; -/// # use coset::Header; -/// # use coset::iana::CwtClaimName; -/// # use dcaf::{ToCborMap, CoseCipherCommon, CoseEncryptCipher, decrypt_access_token, encrypt_access_token, sign_access_token, verify_access_token}; -/// # use dcaf::common::cbor_values::{ByteString, ProofOfPossessionKey}; -/// # use dcaf::error::{AccessTokenError, CoseCipherError}; -/// # struct FakeCrypto {}; -/// # impl CoseCipherCommon for FakeCrypto { -/// # type Error = String; -/// # fn set_headers(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { -/// # Ok(()) -/// # } -/// # } -/// # impl CoseEncryptCipher for FakeCrypto { -/// # fn encrypt(&mut self, data: &[u8], aad: &[u8]) -> Vec { -/// # let mut result: Vec = Vec::new(); -/// # result.append(&mut data.to_vec()); -/// # result.append(&mut aad.to_vec()); -/// # result -/// # } -/// # -/// # fn decrypt(&mut self, ciphertext: &[u8], aad: &[u8]) -> Result, CoseCipherError> { -/// # if ciphertext.len() < aad.len() { -/// # return Err(CoseCipherError::Other("Encrypted data must be at least as long as AAD!".to_string())); -/// # } -/// # let mut result: Vec = ciphertext.to_vec(); -/// # let aad_result = result.split_off(ciphertext.len() - aad.len()); -/// # if aad != aad_result { -/// # Err(CoseCipherError::DecryptionFailure) -/// # } else { -/// # Ok(result) -/// # } -/// # } -/// # } -/// # let mut cipher = FakeCrypto{}; -/// # let key = ProofOfPossessionKey::KeyId(vec![0xDC, 0xAF]); +/// ```ignore /// let claims = ClaimsSetBuilder::new() /// .audience(String::from("coaps://rs.example.com")) /// .issuer(String::from("coaps://as.example.com")) -/// .claim(CwtClaimName::Cnf, key.to_ciborium_value()) +/// .claim(CwtClaimName::Cnf, key.to_cose_key().to_cbor_value()?) /// .build(); -/// let token: ByteString = encrypt_access_token(claims.clone(), &mut cipher, None, None, None)?; -/// assert_eq!(decrypt_access_token(&token, &mut cipher, None)?, claims); -/// # Ok::<(), AccessTokenError>(()) +/// let token: ByteString = encrypt_access_token::(&key, claims.clone(), None, None, None, rng)?; +/// assert_eq!(decrypt_access_token::(&key, &token, None)?, claims); /// ``` pub fn encrypt_access_token( key: T::EncryptKey, @@ -502,14 +458,31 @@ where .map_err(AccessTokenError::from) } -/// Encrypts the given `claims` with the given headers and `aad` using `cipher` for cryptography, -/// returning the token as a serialized bytestring of the [`CoseEncrypt0`] structure. +/// Encrypts the given `claims` with the given headers and `aad` for each recipient by using the +/// `keys` with the cipher given by type parameter `T`, +/// returning the token as a serialized bytestring of the [`CoseEncrypt`] structure. +/// +/// Note that the given `keys` must each have an associated `kid` (key ID) field when converted +/// to COSE keys, as the recipients inside the [`CoseEncrypt`] are identified in this way. +/// +/// The Content Encryption Key (used to encrypt the actual claims) is randomly generated by the +/// given cipher in `T`, whereas the given `keys` are used as Key Encryption Keys, that is, +/// they encrypt the Content Encryption Key for each recipient. /// /// # Errors -/// TODO +/// - When there's a [`CoseError`](coset::CoseError) while serializing the given `claims` to CBOR. +/// - When there's a [`CoseError`](coset::CoseError) while serializing the [`CoseEncrypt`] structure. +/// - When the given headers conflict with the headers set by the cipher `T`. /// -/// # Panics -/// TODO +/// # Example +/// For example, assuming we have a [`MultipleEncryptCipher`] in `FakeCrypto`, a random number +/// generator in `rng`, and some `claims`, we can then create a token encrypted for two recipients +/// (with keys `key1` and `key2`, respectively) as follows: +/// ```ignore +/// let encrypted = encrypt_access_token_multiple::( +/// vec![&key1, &key2], claims.clone(), None, None, None rng +/// )?; +/// ``` pub fn encrypt_access_token_multiple( keys: Vec<&T::EncryptKey>, claims: ClaimsSet, @@ -545,53 +518,31 @@ where .map_err(AccessTokenError::from) } -/// Signs the given `claims` with the given headers and `aad` using `cipher` for cryptography, -/// returning the token as a serialized bytestring of the [`CoseSign1`] structure. +/// Signs the given `claims` with the given headers and `aad` using the `key` and the cipher +/// given by type parameter `T`, returning the token as a serialized bytestring of +/// the [`CoseSign1`] structure. +/// +/// Note that this method will create a token intended for a single recipient. +/// If you wish to create a token for more than one recipient, use +/// [`sign_access_token_multiple`]. /// /// # Errors /// - When there's a [`CoseError`](coset::CoseError) while serializing the given `claims` to CBOR. /// - When there's a [`CoseError`](coset::CoseError) while serializing the [`CoseSign1`] structure. +/// - When the given headers conflict with the headers set by the cipher `T`. /// /// # Example -/// For example, assuming we have a [`CoseSign1Cipher`] in `cipher`, -/// have a [`ProofOfPossessionKey`](crate::common::cbor_values::ProofOfPossessionKey) +/// For example, assuming we have a [`CoseSignCipher`] in `FakeCrypto`, a random number generator +/// in `rng`, a [`ProofOfPossessionKey`](crate::common::cbor_values::ProofOfPossessionKey) /// in `key` and want to associate this key with the access token we are about to create and sign: -/// ``` -/// # use coset::cwt::ClaimsSetBuilder; -/// # use coset::Header; -/// # use coset::iana::CwtClaimName; -/// # use dcaf::{ToCborMap, CoseCipherCommon, CoseSign1Cipher, encrypt_access_token, sign_access_token, verify_access_token}; -/// # use dcaf::common::cbor_values::{ByteString, ProofOfPossessionKey}; -/// # use dcaf::error::{AccessTokenError, CoseCipherError}; -/// # struct FakeSigner {}; -/// # impl CoseCipherCommon for FakeSigner { -/// # type Error = String; -/// # fn set_headers(&self, unprotected_header: &mut Header, protected_header: &mut Header) -> Result<(), CoseCipherError> { -/// # Ok(()) -/// # } -/// # } -/// # impl CoseSign1Cipher for FakeSigner { -/// # fn generate_signature(&mut self, target: &[u8]) -> Vec { -/// # target.to_vec() -/// # } -/// # fn verify_signature(&mut self, signature: &[u8], signed_data: &[u8]) -> Result<(), CoseCipherError> { -/// # if signature != self.generate_signature(signed_data) { -/// # Err(CoseCipherError::VerificationFailure) -/// # } else { -/// # Ok(()) -/// # } -/// # } -/// # } -/// # let mut cipher = FakeSigner {}; -/// # let key = ProofOfPossessionKey::KeyId(vec![0xDC, 0xAF]); +/// ```ignore /// let claims = ClaimsSetBuilder::new() /// .audience(String::from("coaps://rs.example.com")) /// .issuer(String::from("coaps://as.example.com")) -/// .claim(CwtClaimName::Cnf, key.to_ciborium_value()) +/// .claim(CwtClaimName::Cnf, key.to_cose_key().to_cbor_value()?) /// .build(); -/// let token: ByteString = sign_access_token(claims, &mut cipher, None, None, None)?; -/// assert!(verify_access_token(&token, &mut cipher, None).is_ok()); -/// # Ok::<(), AccessTokenError>(()) +/// let token: ByteString = sign_access_token::(&key, claims, None, None, None, rng)?; +/// assert!(verify_access_token::(&key, &token, None).is_ok()); /// ``` pub fn sign_access_token( key: &T::SignKey, @@ -619,9 +570,31 @@ where .map_err(AccessTokenError::from) } -/// TODO. +/// Signs the given `claims` with the given headers and `aad` for each recipient by using the `keys` +/// with the cipher given by type parameter `T`, returning the token as a serialized bytestring of +/// the [`CoseSign`] structure. +/// +/// For each key in `keys`, another signature will be added, created with that respective key. +/// The given headers will be used for the [`CoseSign`] structure as a whole, not for each +/// individual signature. +/// /// # Errors -/// TODO. +/// - When there's a [`CoseError`](coset::CoseError) while serializing the given `claims` to CBOR. +/// - When there's a [`CoseError`](coset::CoseError) while serializing the [`CoseSign`] structure. +/// - When the given headers conflict with the headers set by the cipher `T`. +/// +/// # Example +/// For example, assuming we have a [`MultipleSignCipher`] in `FakeCrypto`, +/// a random number generator in `rng`, and some `claims`, we can then create a token +/// with signatures for two recipients (with keys `key1` and `key2`, respectively) as follows: +/// ```ignore +/// let signed = sign_access_token_multiple::( +/// vec![&key1, &key2], +/// claims, +/// None, None, None, +/// rng +/// )?; +/// ``` pub fn sign_access_token_multiple( keys: Vec<&T::SignKey>, claims: ClaimsSet, @@ -653,11 +626,11 @@ pub fn sign_access_token_multiple( .map_err(AccessTokenError::from) } -/// Returns the headers of the given signed ([`CoseSign1`]), MAC tagged ([`CoseMac0`]), -/// or encrypted ([`CoseEncrypt0`]) access token. +/// Returns the headers of the given signed ([`CoseSign1`] / [`CoseSign`]), +/// MAC tagged (`CoseMac0` / `CoseMac`), or encrypted ([`CoseEncrypt0`] / [`CoseEncrypt`]) +/// access token. /// -/// When the given `token` is neither a [`CoseEncrypt0`], [`CoseSign1`], nor a [`CoseMac0`] -/// structure, `None` is returned. +/// When the given `token` is none of those structures mentioned above, `None` is returned. /// /// # Example /// For example, say you have an access token saved in `token` and want to look at its headers: @@ -705,8 +678,12 @@ pub fn get_token_headers(token: &ByteString) -> Option<(Header, ProtectedHeader) } } -/// Verifies the given `token` and `aad` using `verifier` for cryptography, -/// returning an error in case it could not be verified. +/// Verifies the given `token` and `aad` with the `key` using the cipher +/// given by type parameter `T`, returning an error in case it could not be verified. +/// +/// This method should be used when the given `token` is a [`CoseSign1`] rather than +/// [`CoseSign`] (i.e., if it is intended for a single recipient). In case the token is an +/// instance of the latter, use [`verify_access_token_multiple`] instead. /// /// NOTE: Protected headers are not verified as of now. /// @@ -716,7 +693,7 @@ pub fn get_token_headers(token: &ByteString) -> Option<(Header, ProtectedHeader) /// - When there's a [`CoseError`](coset::CoseError) while deserializing the given `token` /// to a [`CoseSign1`] structure /// (e.g., if it's not in fact a [`CoseSign1`] structure but rather something else). -/// - When there's a verification error coming from the `verifier` +/// - When there's a verification error coming from the cipher `T` /// (e.g., if the `token`'s data does not match its signature). pub fn verify_access_token( key: &T::VerifyKey, @@ -744,11 +721,21 @@ where .map_err(AccessTokenError::from) } -/// TODO. +/// Verifies the given `token` and `aad` with the `key` using the cipher +/// given by type parameter `T`, returning an error in case it could not be verified. +/// +/// This method should be used when the given `token` is a [`CoseSign`] rather than +/// [`CoseSign1`] (i.e., if it is intended for a multiple recipients). In case the token is an +/// instance of the latter, use [`verify_access_token`] instead. +/// +/// NOTE: Protected headers are not verified as of now. +/// /// # Errors -/// TODO. -/// # Panics -/// TODO. +/// - When there's a [`CoseError`](coset::CoseError) while deserializing the given `token` +/// to a [`CoseSign`] structure +/// (e.g., if it's not in fact a [`CoseSign`] structure but rather something else). +/// - When there's a verification error coming from the cipher `T` +/// (e.g., if the `token`'s data does not match its signature). pub fn verify_access_token_multiple( key: &T::VerifyKey, token: &ByteString, @@ -794,12 +781,16 @@ where CoseCipherError::VerificationFailure, )) } else { - Err(AccessTokenError::NoMatchingKey) + Err(AccessTokenError::NoMatchingRecipient) } } -/// Decrypts the given `token` and `aad` using `cipher` for cryptography, -/// returning the decrypted `ClaimsSet`. +/// Decrypts the given `token` and `aad` using the `key` and the cipher given by type parameter `T`, +/// returning the decrypted [`ClaimsSet`]. +/// +/// This method should be used when the given `token` is a [`CoseEncrypt0`] rather than +/// [`CoseEncrypt`] (i.e., if it is intended for a single recipient). In case the token is an +/// instance of the latter, use [`decrypt_access_token_multiple`] instead. /// /// For an example, see the documentation of [`encrypt_access_token`]. /// @@ -807,7 +798,7 @@ where /// - When there's a [`CoseError`](coset::CoseError) while deserializing /// the given `token` to a [`CoseEncrypt0`] structure /// (e.g., if it's not in fact a [`CoseEncrypt0`] structure but rather something else). -/// - When there's a decryption error coming from the `cipher`. +/// - When there's a decryption error coming from the cipher given by `T`. /// - When the deserialized and decrypted [`CoseEncrypt0`] structure does not contain a valid /// [`ClaimsSet`]. pub fn decrypt_access_token( @@ -830,9 +821,25 @@ where ClaimsSet::from_slice(result.as_slice()).map_err(AccessTokenError::from) } -/// TODO. +/// Decrypts the given `token` and `aad` using the Key Encryption Key `kek` and the cipher given +/// by type parameter `T`, returning the decrypted [`ClaimsSet`]. +/// +/// Note that the given `kek` must have an associated `kid` (key ID) field when converted +/// to a COSE key, as the recipient inside the [`CoseEncrypt`] is identified in this way. +/// +/// This method should be used when the given `token` is a [`CoseEncrypt`] rather than +/// [`CoseEncrypt0`] (i.e., if it is intended for multiple recipients). In case the token is an +/// instance of the latter, use [`decrypt_access_token`] instead. +/// /// # Errors -/// TODO. +/// - When there's a [`CoseError`](coset::CoseError) while deserializing +/// the given `token` to a [`CoseEncrypt`] structure +/// (e.g., if it's not in fact a [`CoseEncrypt`] structure but rather something else). +/// - When there's a decryption error coming from the cipher given by `T`. +/// - When the deserialized and decrypted [`CoseEncrypt`] structure does not contain a valid +/// [`ClaimsSet`]. +/// - When the [`CoseEncrypt`] contains either multiple matching recipients or none at all for +/// the given `kek`. pub fn decrypt_access_token_multiple( kek: &K::DecryptKey, token: &ByteString, @@ -875,9 +882,9 @@ where ClaimsSet::from_slice(result.as_slice()).map_err(AccessTokenError::from) } else { // TODO: Implement strict mode, where this is prohibited, otherwise allow it - Err(AccessTokenError::MultipleMatchingKeys) + Err(AccessTokenError::MultipleMatchingRecipients) } } else { - Err(AccessTokenError::NoMatchingKey) + Err(AccessTokenError::NoMatchingRecipient) } } diff --git a/src/token/tests.rs b/src/token/tests.rs index b888483..b110c3f 100644 --- a/src/token/tests.rs +++ b/src/token/tests.rs @@ -223,7 +223,7 @@ fn test_encrypt_decrypt_multiple() -> Result<(), AccessTokenError<(&invalid_key1, &encrypted, Some(&aad)); - assert!(failed.err().filter(|x| matches!(x, AccessTokenError::NoMatchingKey)).is_some()); + assert!(failed.err().filter(|x| matches!(x, AccessTokenError::NoMatchingRecipient)).is_some()); let failed = decrypt_access_token_multiple::(&invalid_key2, &encrypted, Some(&aad)); dbg!(&failed); assert!(failed.err().filter(|x| matches!(x, AccessTokenError::CoseCipherError(CoseCipherError::DecryptionFailure))).is_some()); @@ -249,7 +249,7 @@ fn test_encrypt_decrypt_match_multiple() -> Result<(), AccessTokenError<(&key1, &encrypted, Some(&aad)).err().filter(|x| matches!(x, AccessTokenError::MultipleMatchingKeys)).is_some()); + assert!(decrypt_access_token_multiple::(&key1, &encrypted, Some(&aad)).err().filter(|x| matches!(x, AccessTokenError::MultipleMatchingRecipients)).is_some()); Ok(()) } @@ -355,7 +355,7 @@ fn test_sign_verify_multiple() -> Result<(), AccessTokenError<(&key, &signed, Some(&aad))?; } - assert!(verify_access_token_multiple::(&invalid_key1, &signed, Some(&aad)).err().filter(|x| matches!(x, AccessTokenError::NoMatchingKey)).is_some()); + assert!(verify_access_token_multiple::(&invalid_key1, &signed, Some(&aad)).err().filter(|x| matches!(x, AccessTokenError::NoMatchingRecipient)).is_some()); assert!(verify_access_token_multiple::(&invalid_key2, &signed, Some(&aad)).err().filter(|x| matches!(x, AccessTokenError::CoseCipherError(CoseCipherError::VerificationFailure))).is_some()); Ok(()) } \ No newline at end of file diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index cc2c6cf..c4db67e 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -16,7 +16,7 @@ use coset::iana::{Algorithm, CwtClaimName}; use coset::iana::EllipticCurve::P_256; use rand::{CryptoRng, Error, RngCore}; -use dcaf::{CoseSignCipher, sign_access_token}; +use dcaf::{CoseSignCipher, sign_access_token, verify_access_token}; use dcaf::common::cbor_map::ToCborMap; use dcaf::common::cbor_values::ProofOfPossessionKey::PlainCoseKey; use dcaf::common::scope::TextEncodedScope; @@ -26,7 +26,7 @@ use dcaf::endpoints::token_req::{ TokenType, }; use dcaf::error::CoseCipherError; -use dcaf::token::ToCoseKey; +use dcaf::token::{ToCoseKey, verify_access_token_multiple}; #[derive(Clone)] pub(crate) struct EC2P256Key { @@ -262,6 +262,8 @@ fn test_scenario() -> Result<(), String> { let result = pseudo_send_receive(response.clone())?; assert_eq!(response, result); + verify_access_token::(&key, &response.access_token, Some(aad.as_slice())).map_err(|x| x.to_string())?; + let error = ErrorResponse::builder() .error(ErrorCode::InvalidRequest) .description("You sent an invalid request.") From 60427da5326c27766f68cd57fd7409c2a45142eb Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Mon, 24 Oct 2022 15:19:12 +0200 Subject: [PATCH 06/12] style: reformat code --- src/common/test_helper.rs | 56 ++++++++++++------ src/error/mod.rs | 90 +++++++++++++++++++--------- src/lib.rs | 18 +++--- src/token/mod.rs | 97 ++++++++++++++++-------------- src/token/tests.rs | 117 ++++++++++++++++++++++++++----------- tests/integration_tests.rs | 61 ++++++++++--------- 6 files changed, 280 insertions(+), 159 deletions(-) diff --git a/src/common/test_helper.rs b/src/common/test_helper.rs index 2403359..969ba0c 100644 --- a/src/common/test_helper.rs +++ b/src/common/test_helper.rs @@ -16,8 +16,8 @@ use core::convert::identity; use core::fmt::{Debug, Display}; use ciborium::value::Value; -use coset::{CoseKey, CoseKeyBuilder, Header, Label, ProtectedHeader}; use coset::iana::Algorithm; +use coset::{CoseKey, CoseKeyBuilder, Header, Label, ProtectedHeader}; use rand::{CryptoRng, Error, RngCore}; #[cfg(not(feature = "std"))] @@ -27,10 +27,10 @@ use { alloc::vec::Vec, }; -use crate::{CoseEncryptCipher, CoseMacCipher, CoseSignCipher}; use crate::common::cbor_map::ToCborMap; use crate::error::{AccessTokenError, CoseCipherError, MultipleCoseError}; use crate::token::{MultipleEncryptCipher, MultipleSignCipher, ToCoseKey}; +use crate::{CoseEncryptCipher, CoseMacCipher, CoseSignCipher}; /// Helper function for tests which ensures that [`value`] serializes to the hexadecimal bytestring /// [expected_hex] and deserializes back to [`value`]. @@ -244,9 +244,13 @@ impl CoseSignCipher for FakeCrypto { } else { protected_header.header.key_id == key.kid }; - let signed_again = Self::sign(key, signed_data, unprotected_header, &protected_header.header); - if matching_kid && signed_again == signature - { + let signed_again = Self::sign( + key, + signed_data, + unprotected_header, + &protected_header.header, + ); + if matching_kid && signed_again == signature { Ok(()) } else { Err(CoseCipherError::VerificationFailure) @@ -292,12 +296,12 @@ impl CoseMacCipher for FakeCrypto { ) -> Result<(), CoseCipherError> { if protected_header.header.key_id == key.kid && tag - == Self::compute( - key, - maced_data, - unprotected_header, - &protected_header.header, - ) + == Self::compute( + key, + maced_data, + unprotected_header, + &protected_header.header, + ) { Ok(()) } else { @@ -352,25 +356,41 @@ impl RngCore for FakeRng { impl CryptoRng for FakeRng {} // Makes the tests easier later on, as we use String as the error type in there. -impl From>> for CoseCipherError where C: Display, K: Display { +impl From>> for CoseCipherError +where + C: Display, + K: Display, +{ fn from(x: CoseCipherError>) -> Self { match x { - CoseCipherError::HeaderAlreadySet { existing_header_name } => CoseCipherError::HeaderAlreadySet { existing_header_name }, + CoseCipherError::HeaderAlreadySet { + existing_header_name, + } => CoseCipherError::HeaderAlreadySet { + existing_header_name, + }, CoseCipherError::VerificationFailure => CoseCipherError::VerificationFailure, CoseCipherError::DecryptionFailure => CoseCipherError::DecryptionFailure, - CoseCipherError::Other(x) => CoseCipherError::Other(x.to_string()) + CoseCipherError::Other(x) => CoseCipherError::Other(x.to_string()), } } } -impl From>> for AccessTokenError where C: Display, K: Display { +impl From>> for AccessTokenError +where + C: Display, + K: Display, +{ fn from(x: AccessTokenError>) -> Self { match x { AccessTokenError::CoseError(x) => AccessTokenError::CoseError(x), - AccessTokenError::CoseCipherError(x) => AccessTokenError::CoseCipherError(CoseCipherError::from(x)), + AccessTokenError::CoseCipherError(x) => { + AccessTokenError::CoseCipherError(CoseCipherError::from(x)) + } AccessTokenError::UnknownCoseStructure => AccessTokenError::UnknownCoseStructure, AccessTokenError::NoMatchingRecipient => AccessTokenError::NoMatchingRecipient, - AccessTokenError::MultipleMatchingRecipients => AccessTokenError::MultipleMatchingRecipients + AccessTokenError::MultipleMatchingRecipients => { + AccessTokenError::MultipleMatchingRecipients + } } } -} \ No newline at end of file +} diff --git a/src/error/mod.rs b/src/error/mod.rs index 0a64c3d..a521866 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -18,13 +18,13 @@ use ciborium::value::Value; use coset::{CoseError, Label}; use strum_macros::IntoStaticStr; -#[cfg(feature = "std")] -use {std::marker::PhantomData, std::num::TryFromIntError}; #[cfg(not(feature = "std"))] use { alloc::format, alloc::string::String, alloc::string::ToString, core::num::TryFromIntError, derive_builder::export::core::marker::PhantomData, }; +#[cfg(feature = "std")] +use {std::marker::PhantomData, std::num::TryFromIntError}; /// Error type used when the parameter of the type `T` couldn't be /// converted into [`expected_type`](WrongSourceTypeError::expected_type) because the received @@ -301,25 +301,33 @@ where } // TODO: Maybe there's a better way to do the below, parts of this are redundant and duplicated. - pub(crate) fn from_kek_error(error: CoseCipherError) -> CoseCipherError> { + pub(crate) fn from_kek_error( + error: CoseCipherError, + ) -> CoseCipherError> { match error { CoseCipherError::Other(x) => CoseCipherError::Other(MultipleCoseError::KekError(x)), - CoseCipherError::HeaderAlreadySet { existing_header_name } => CoseCipherError::HeaderAlreadySet { - existing_header_name + CoseCipherError::HeaderAlreadySet { + existing_header_name, + } => CoseCipherError::HeaderAlreadySet { + existing_header_name, }, CoseCipherError::VerificationFailure => CoseCipherError::VerificationFailure, - CoseCipherError::DecryptionFailure => CoseCipherError::DecryptionFailure + CoseCipherError::DecryptionFailure => CoseCipherError::DecryptionFailure, } } - pub(crate) fn from_cek_error(error: CoseCipherError) -> CoseCipherError> { + pub(crate) fn from_cek_error( + error: CoseCipherError, + ) -> CoseCipherError> { match error { CoseCipherError::Other(x) => CoseCipherError::Other(MultipleCoseError::CekError(x)), - CoseCipherError::HeaderAlreadySet { existing_header_name } => CoseCipherError::HeaderAlreadySet { - existing_header_name + CoseCipherError::HeaderAlreadySet { + existing_header_name, + } => CoseCipherError::HeaderAlreadySet { + existing_header_name, }, CoseCipherError::VerificationFailure => CoseCipherError::VerificationFailure, - CoseCipherError::DecryptionFailure => CoseCipherError::DecryptionFailure + CoseCipherError::DecryptionFailure => CoseCipherError::DecryptionFailure, } } } @@ -348,7 +356,11 @@ where /// In that case, the recipients may be encrypted with a different cipher (`K`) than the /// actual content (`C`); hence, this error type differentiates between the two. #[derive(Debug)] -pub enum MultipleCoseError where K: Display, C: Display { +pub enum MultipleCoseError +where + K: Display, + C: Display, +{ /// Used when an error occurred in the Key Encryption Key's cipher. KekError(K), @@ -356,11 +368,15 @@ pub enum MultipleCoseError where K: Display, C: Display { CekError(C), } -impl Display for MultipleCoseError where K: Display, C: Display { +impl Display for MultipleCoseError +where + K: Display, + C: Display, +{ fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { match self { MultipleCoseError::KekError(k) => k.fmt(f), - MultipleCoseError::CekError(c) => c.fmt(f) + MultipleCoseError::CekError(c) => c.fmt(f), } } } @@ -491,7 +507,7 @@ where /// Multiple matching recipients were found in the list of COSE_Recipient structures. /// This means that the given Key Encryption Key could be used to decrypt multiple of the /// recipients, which means the token is malformed. - MultipleMatchingRecipients + MultipleMatchingRecipients, } impl Display for AccessTokenError @@ -506,20 +522,30 @@ where f, "input is either invalid or none of CoseEncrypt0, CoseSign1 nor CoseMac0" ), - AccessTokenError::NoMatchingRecipient => write!(f, "given KEK doesn't match any recipient"), - AccessTokenError::MultipleMatchingRecipients => write!(f, "given KEK matches multiple recipients") + AccessTokenError::NoMatchingRecipient => { + write!(f, "given KEK doesn't match any recipient") + } + AccessTokenError::MultipleMatchingRecipients => { + write!(f, "given KEK matches multiple recipients") + } } } } -impl From> for AccessTokenError where T: Display { +impl From> for AccessTokenError +where + T: Display, +{ #[must_use] fn from(error: CoseCipherError) -> Self { AccessTokenError::CoseCipherError(error) } } -impl From for AccessTokenError where T: Display { +impl From for AccessTokenError +where + T: Display, +{ #[must_use] fn from(error: CoseError) -> Self { AccessTokenError::CoseError(error) @@ -529,27 +555,39 @@ impl From for AccessTokenError where T: Display { #[allow(dead_code)] impl AccessTokenError where - T: Display { - + T: Display, +{ // TODO: Again, as in CoseCipherError, maybe there's a better way to do the below. - pub(crate) fn from_kek_error(error: AccessTokenError) -> AccessTokenError> { + pub(crate) fn from_kek_error( + error: AccessTokenError, + ) -> AccessTokenError> { match error { - AccessTokenError::CoseCipherError(x) => AccessTokenError::CoseCipherError(CoseCipherError::from_kek_error(x)), + AccessTokenError::CoseCipherError(x) => { + AccessTokenError::CoseCipherError(CoseCipherError::from_kek_error(x)) + } AccessTokenError::CoseError(x) => AccessTokenError::CoseError(x), AccessTokenError::UnknownCoseStructure => AccessTokenError::UnknownCoseStructure, AccessTokenError::NoMatchingRecipient => AccessTokenError::NoMatchingRecipient, - AccessTokenError::MultipleMatchingRecipients => AccessTokenError::MultipleMatchingRecipients, + AccessTokenError::MultipleMatchingRecipients => { + AccessTokenError::MultipleMatchingRecipients + } } } - pub(crate) fn from_cek_error(error: AccessTokenError) -> AccessTokenError> { + pub(crate) fn from_cek_error( + error: AccessTokenError, + ) -> AccessTokenError> { match error { - AccessTokenError::CoseCipherError(x) => AccessTokenError::CoseCipherError(CoseCipherError::from_cek_error(x)), + AccessTokenError::CoseCipherError(x) => { + AccessTokenError::CoseCipherError(CoseCipherError::from_cek_error(x)) + } AccessTokenError::CoseError(x) => AccessTokenError::CoseError(x), AccessTokenError::UnknownCoseStructure => AccessTokenError::UnknownCoseStructure, AccessTokenError::NoMatchingRecipient => AccessTokenError::NoMatchingRecipient, - AccessTokenError::MultipleMatchingRecipients => AccessTokenError::MultipleMatchingRecipients, + AccessTokenError::MultipleMatchingRecipients => { + AccessTokenError::MultipleMatchingRecipients + } } } } diff --git a/src/lib.rs b/src/lib.rs index 0d85339..a188e95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -300,10 +300,10 @@ #![warn(missing_docs, rustdoc::missing_crate_level_docs)] // These ones are a little too eager #![allow( -clippy::doc_markdown, -clippy::module_name_repetitions, -clippy::wildcard_imports, -clippy::type_complexity + clippy::doc_markdown, + clippy::module_name_repetitions, + clippy::wildcard_imports, + clippy::type_complexity )] #![cfg_attr(not(feature = "std"), no_std)] #[macro_use] @@ -331,13 +331,11 @@ pub use endpoints::token_req::{ }; #[doc(inline)] pub use token::{ - CoseEncryptCipher, CoseMacCipher, CoseSignCipher, decrypt_access_token, decrypt_access_token_multiple, encrypt_access_token, - encrypt_access_token_multiple, - get_token_headers, MultipleEncryptCipher, - MultipleMacCipher, MultipleSignCipher, - sign_access_token, sign_access_token_multiple, - verify_access_token, verify_access_token_multiple, + encrypt_access_token_multiple, get_token_headers, sign_access_token, + sign_access_token_multiple, verify_access_token, verify_access_token_multiple, + CoseEncryptCipher, CoseMacCipher, CoseSignCipher, MultipleEncryptCipher, MultipleMacCipher, + MultipleSignCipher, }; pub mod common; diff --git a/src/token/mod.rs b/src/token/mod.rs index 40d9874..93e24f4 100644 --- a/src/token/mod.rs +++ b/src/token/mod.rs @@ -167,8 +167,13 @@ use alloc::vec::Vec; use core::fmt::{Debug, Display}; use ciborium::value::Value; -use coset::{AsCborValue, CborSerializable, CoseEncrypt, CoseEncrypt0, CoseEncrypt0Builder, CoseEncryptBuilder, CoseKey, CoseRecipientBuilder, CoseSign, CoseSign1, CoseSign1Builder, CoseSignatureBuilder, CoseSignBuilder, EncryptionContext, Header, HeaderBuilder, ProtectedHeader}; use coset::cwt::ClaimsSet; +use coset::{ + AsCborValue, CborSerializable, CoseEncrypt, CoseEncrypt0, CoseEncrypt0Builder, + CoseEncryptBuilder, CoseKey, CoseRecipientBuilder, CoseSign, CoseSign1, CoseSign1Builder, + CoseSignBuilder, CoseSignatureBuilder, EncryptionContext, Header, HeaderBuilder, + ProtectedHeader, +}; use rand::{CryptoRng, RngCore}; use crate::common::cbor_values::ByteString; @@ -396,12 +401,8 @@ macro_rules! prepare_headers { ($key:expr, $unprotected:ident, $protected:ident, $rng:expr, $t:ty) => {{ let mut unprotected = $unprotected.unwrap_or_else(|| HeaderBuilder::new().build()); let mut protected = $protected.unwrap_or_else(|| HeaderBuilder::new().build()); - if let Err(e) = <$t>::set_headers($key, &mut unprotected, &mut protected, $rng) - { - Err(e) - } else { - Ok((unprotected, protected)) - } + <$t>::set_headers($key, &mut unprotected, &mut protected, $rng)?; + Ok::<(Header, Header), CoseCipherError<<$t>::Error>>((unprotected, protected)) }}; } @@ -432,7 +433,7 @@ macro_rules! prepare_headers { /// assert_eq!(decrypt_access_token::(&key, &token, None)?, claims); /// ``` pub fn encrypt_access_token( - key: T::EncryptKey, + key: &T::EncryptKey, claims: ClaimsSet, external_aad: Option<&[u8]>, unprotected_header: Option
, @@ -444,14 +445,14 @@ where RNG: RngCore + CryptoRng, { let (unprotected, protected) = - prepare_headers!(&key, unprotected_header, protected_header, &mut rng, T)?; + prepare_headers!(key, unprotected_header, protected_header, &mut rng, T)?; CoseEncrypt0Builder::new() .unprotected(unprotected.clone()) .protected(protected.clone()) .create_ciphertext( &claims.to_vec()?[..], external_aad.unwrap_or(&[0; 0]), - |payload, aad| T::encrypt(&key, payload, aad, &unprotected, &protected), + |payload, aad| T::encrypt(key, payload, aad, &unprotected, &protected), ) .build() .to_vec() @@ -496,7 +497,8 @@ where RNG: CryptoRng + RngCore, { let key = T::generate_cek(&mut rng); - let (unprotected, protected) = prepare_headers!(&key, unprotected_header, protected_header, &mut rng, T)?; + let (unprotected, protected) = + prepare_headers!(&key, unprotected_header, protected_header, &mut rng, T)?; let mut builder = CoseEncryptBuilder::new() .unprotected(unprotected.clone()) .protected(protected.clone()) @@ -508,14 +510,23 @@ where let serialized_key: Vec = key.into(); for rec_key in keys { let (rec_unprotected, rec_protected) = prepare_headers!(rec_key, None, None, &mut rng, T)?; - builder = builder.add_recipient(CoseRecipientBuilder::new().protected(rec_protected.clone()).unprotected(rec_unprotected.clone()).create_ciphertext( - // TODO: What should AAD be here? - EncryptionContext::EncRecipient, &serialized_key, &[0; 0], |payload, aad| T::encrypt(rec_key, payload, aad, &rec_protected, &rec_unprotected) - ).build()); + builder = builder.add_recipient( + CoseRecipientBuilder::new() + .protected(rec_protected.clone()) + .unprotected(rec_unprotected.clone()) + .create_ciphertext( + // TODO: What should AAD be here? + EncryptionContext::EncRecipient, + &serialized_key, + &[0; 0], + |payload, aad| { + T::encrypt(rec_key, payload, aad, &rec_protected, &rec_unprotected) + }, + ) + .build(), + ); } - builder.build() - .to_vec() - .map_err(AccessTokenError::from) + builder.build().to_vec().map_err(AccessTokenError::from) } /// Signs the given `claims` with the given headers and `aad` using the `key` and the cipher @@ -603,11 +614,14 @@ pub fn sign_access_token_multiple( protected_header: Option
, mut rng: RNG, ) -> Result> - where - T: MultipleSignCipher, - RNG: RngCore + CryptoRng, +where + T: MultipleSignCipher, + RNG: RngCore + CryptoRng, { - let (unprotected, protected) = (unprotected_header.unwrap_or_else(|| HeaderBuilder::default().build()), protected_header.unwrap_or_else(|| HeaderBuilder::default().build())); + let (unprotected, protected) = ( + unprotected_header.unwrap_or_else(|| HeaderBuilder::default().build()), + protected_header.unwrap_or_else(|| HeaderBuilder::default().build()), + ); let mut builder = CoseSignBuilder::new() .unprotected(unprotected.clone()) .protected(protected.clone()) @@ -615,15 +629,16 @@ pub fn sign_access_token_multiple( for key in keys { let (rec_unprotected, rec_protected) = prepare_headers!(key, None, None, &mut rng, T)?; - let signature = CoseSignatureBuilder::new().unprotected(rec_unprotected.clone()).protected(rec_protected.clone()).build(); + let signature = CoseSignatureBuilder::new() + .unprotected(rec_unprotected.clone()) + .protected(rec_protected.clone()) + .build(); builder = builder.add_created_signature(signature, external_aad.unwrap_or(&[0; 0]), |x| { T::sign(key, x, &unprotected, &protected) }); } - builder.build() - .to_vec() - .map_err(AccessTokenError::from) + builder.build().to_vec().map_err(AccessTokenError::from) } /// Returns the headers of the given signed ([`CoseSign1`] / [`CoseSign`]), @@ -777,9 +792,7 @@ where } } if matching_kid { - Err(AccessTokenError::from( - CoseCipherError::VerificationFailure, - )) + Err(AccessTokenError::from(CoseCipherError::VerificationFailure)) } else { Err(AccessTokenError::NoMatchingRecipient) } @@ -809,15 +822,13 @@ pub fn decrypt_access_token( where T: CoseEncryptCipher, { - let encrypt = - CoseEncrypt0::from_slice(token.as_slice()).map_err(AccessTokenError::from)?; + let encrypt = CoseEncrypt0::from_slice(token.as_slice()).map_err(AccessTokenError::from)?; let (unprotected, protected) = get_token_headers(token).ok_or(AccessTokenError::UnknownCoseStructure)?; // TODO: Verify protected header - let result = encrypt - .decrypt(external_aad.unwrap_or(&[0; 0]), |ciphertext, aad| { - T::decrypt(key, ciphertext, aad, &unprotected, &protected) - })?; + let result = encrypt.decrypt(external_aad.unwrap_or(&[0; 0]), |ciphertext, aad| { + T::decrypt(key, ciphertext, aad, &unprotected, &protected) + })?; ClaimsSet::from_slice(result.as_slice()).map_err(AccessTokenError::from) } @@ -849,8 +860,7 @@ where K: CoseEncryptCipher, C: CoseEncryptCipher, { - let encrypt = - CoseEncrypt::from_slice(token.as_slice()).map_err(AccessTokenError::from)?; + let encrypt = CoseEncrypt::from_slice(token.as_slice()).map_err(AccessTokenError::from)?; let (unprotected, protected) = get_token_headers(token).ok_or(AccessTokenError::UnknownCoseStructure)?; let aad = external_aad.unwrap_or(&[0; 0]); @@ -862,17 +872,18 @@ where .iter() .filter(|x| x.unprotected.key_id == kek_id || x.protected.header.key_id == kek_id); let mut content_keys = recipients.map(|r| { - r.decrypt(EncryptionContext::EncRecipient, &[0; 0], |ciphertext, aad| { - K::decrypt(kek, ciphertext, aad, &r.unprotected, &r.protected) - }) + r.decrypt( + EncryptionContext::EncRecipient, + &[0; 0], + |ciphertext, aad| K::decrypt(kek, ciphertext, aad, &r.unprotected, &r.protected), + ) }); // Our CEK must be contained exactly once. if let Some(content_key_result) = content_keys.next() { if content_keys.next().is_none() { let content_key = content_key_result.map_err(CoseCipherError::from_kek_error)?; - let target_key = C::DecryptKey::try_from(content_key).map_err(|_| { - CoseCipherError::DecryptionFailure - })?; + let target_key = C::DecryptKey::try_from(content_key) + .map_err(|_| CoseCipherError::DecryptionFailure)?; // TODO: Verify protected header let result = encrypt .decrypt(aad, |ciphertext, aad| { diff --git a/src/token/tests.rs b/src/token/tests.rs index b110c3f..5a9415a 100644 --- a/src/token/tests.rs +++ b/src/token/tests.rs @@ -10,12 +10,15 @@ */ #[cfg(not(feature = "std"))] -use alloc::vec; +use alloc::{ + string::{String, ToString}, + vec, +}; use ciborium::value::Value; -use coset::{AsCborValue, CoseKey, CoseKeyBuilder, CoseMac0Builder, HeaderBuilder}; use coset::cwt::ClaimsSetBuilder; use coset::iana::{Algorithm, CoapContentFormat, CwtClaimName}; +use coset::{AsCborValue, CoseKey, CoseKeyBuilder, CoseMac0Builder, HeaderBuilder}; use crate::common::test_helper::{FakeCrypto, FakeKey, FakeRng}; use crate::error::CoseCipherError; @@ -38,7 +41,9 @@ fn example_headers() -> (Header, Header) { 0x63, 0x68, 0x98, 0x99, 0x4F, 0xF0, 0xEC, 0x7B, 0xFC, 0xF6, 0xD3, 0xF9, 0x5B, ]) .build(); - let protected_header = HeaderBuilder::new().content_format(CoapContentFormat::Cbor).build(); + let protected_header = HeaderBuilder::new() + .content_format(CoapContentFormat::Cbor) + .build(); (unprotected_header, protected_header) } @@ -58,10 +63,7 @@ fn example_claims( key: CoseKey, ) -> Result::Error>> { Ok(ClaimsSetBuilder::new() - .claim( - CwtClaimName::Cnf, - key.to_cbor_value()?, - ) + .claim(CwtClaimName::Cnf, key.to_cbor_value()?) .build()) } @@ -168,14 +170,15 @@ fn test_get_headers_invalid() { } #[test] -fn test_encrypt_decrypt() -> Result<(), AccessTokenError<::Error>> { +fn test_encrypt_decrypt() -> Result<(), AccessTokenError<::Error>> +{ let key = example_key_one(); let (unprotected_header, protected_header) = example_headers(); let claims = example_claims(key.to_cose_key())?; let aad = example_aad(); let rng = FakeRng; let encrypted = encrypt_access_token::( - key.clone(), + &key, claims.clone(), Some(&aad), Some(unprotected_header.clone()), @@ -193,25 +196,29 @@ fn test_encrypt_decrypt() -> Result<(), AccessTokenError< Result<(), AccessTokenError<::Error>> { +fn test_encrypt_decrypt_multiple( +) -> Result<(), AccessTokenError<::Error>> { const AUDIENCE: &str = "example_aud"; let (unprotected_header, protected_header) = example_headers(); let key1 = example_key_one(); let key2 = example_key_two(); let invalid_key1 = FakeKey::try_from(vec![0, 0, 0, 0, 0, 0, 0]).expect("invalid test key"); - let invalid_key2 = FakeKey::try_from(vec![0, 0, 0, 0, 0, 0xDC, 0xAF]).expect("invalid test key"); + let invalid_key2 = + FakeKey::try_from(vec![0, 0, 0, 0, 0, 0xDC, 0xAF]).expect("invalid test key"); let rng = FakeRng; let aad = example_aad(); // Using example_claims doesn't make sense, since they contain a cnf for the key, // but we don't know the CEK at this point. - let claims = ClaimsSetBuilder::new().audience(AUDIENCE.to_string()).build(); + let claims = ClaimsSetBuilder::new() + .audience(AUDIENCE.to_string()) + .build(); let encrypted = encrypt_access_token_multiple::( vec![&key1, &key2], claims.clone(), Some(&aad), Some(unprotected_header.clone()), Some(protected_header.clone()), - rng + rng, )?; let (unprotected, protected) = get_token_headers(&encrypted).expect("invalid headers"); assert_header_is_part_of(&unprotected_header, &unprotected); @@ -222,16 +229,33 @@ fn test_encrypt_decrypt_multiple() -> Result<(), AccessTokenError<(&invalid_key1, &encrypted, Some(&aad)); - assert!(failed.err().filter(|x| matches!(x, AccessTokenError::NoMatchingRecipient)).is_some()); - let failed = decrypt_access_token_multiple::(&invalid_key2, &encrypted, Some(&aad)); - dbg!(&failed); - assert!(failed.err().filter(|x| matches!(x, AccessTokenError::CoseCipherError(CoseCipherError::DecryptionFailure))).is_some()); + let failed = decrypt_access_token_multiple::( + &invalid_key1, + &encrypted, + Some(&aad), + ); + assert!(failed + .err() + .filter(|x| matches!(x, AccessTokenError::NoMatchingRecipient)) + .is_some()); + let failed = decrypt_access_token_multiple::( + &invalid_key2, + &encrypted, + Some(&aad), + ); + assert!(failed + .err() + .filter(|x| matches!( + x, + AccessTokenError::CoseCipherError(CoseCipherError::DecryptionFailure) + )) + .is_some()); Ok(()) } #[test] -fn test_encrypt_decrypt_match_multiple() -> Result<(), AccessTokenError<::Error>> { +fn test_encrypt_decrypt_match_multiple( +) -> Result<(), AccessTokenError<::Error>> { let (unprotected_header, protected_header) = example_headers(); let key1 = example_key_one(); let rng = FakeRng; @@ -243,18 +267,24 @@ fn test_encrypt_decrypt_match_multiple() -> Result<(), AccessTokenError<(&key1, &encrypted, Some(&aad)).err().filter(|x| matches!(x, AccessTokenError::MultipleMatchingRecipients)).is_some()); + assert!( + decrypt_access_token_multiple::(&key1, &encrypted, Some(&aad)) + .err() + .filter(|x| matches!(x, AccessTokenError::MultipleMatchingRecipients)) + .is_some() + ); Ok(()) } #[test] -fn test_encrypt_decrypt_invalid_header() -> Result<(), AccessTokenError<::Error>> { +fn test_encrypt_decrypt_invalid_header( +) -> Result<(), AccessTokenError<::Error>> { let key = example_key_one(); let (unprotected_header, protected_header) = example_headers(); let (unprotected_invalid, protected_invalid) = example_invalid_headers(); @@ -262,7 +292,7 @@ fn test_encrypt_decrypt_invalid_header() -> Result<(), AccessTokenError<( - key.clone(), + &key, claims.clone(), Some(&aad), Some(unprotected_invalid), @@ -271,8 +301,8 @@ fn test_encrypt_decrypt_invalid_header() -> Result<(), AccessTokenError< Result<(), AccessTokenError<( - key, + &key, claims, Some(&aad), Some(unprotected_header), @@ -291,8 +321,8 @@ fn test_encrypt_decrypt_invalid_header() -> Result<(), AccessTokenError< Result<(), AccessTokenError< Result<(), AccessTokenError<::Error>> { +fn test_sign_verify_multiple() -> Result<(), AccessTokenError<::Error>> +{ const AUDIENCE: &str = "example_aud"; let key1 = example_key_one(); let key2 = example_key_two(); let invalid_key1 = FakeKey::try_from(vec![0, 0, 0, 0, 0, 0, 0]).expect("invalid test key"); - let invalid_key2 = FakeKey::try_from(vec![0, 0, 0, 0, 0, 0xDC, 0xAF]).expect("invalid test key"); + let invalid_key2 = + FakeKey::try_from(vec![0, 0, 0, 0, 0, 0xDC, 0xAF]).expect("invalid test key"); let (unprotected_header, protected_header) = example_headers(); - let claims = ClaimsSetBuilder::new().audience(AUDIENCE.to_string()).build(); + let claims = ClaimsSetBuilder::new() + .audience(AUDIENCE.to_string()) + .build(); let aad = example_aad(); let rng = FakeRng; let signed = sign_access_token_multiple::( @@ -346,7 +380,7 @@ fn test_sign_verify_multiple() -> Result<(), AccessTokenError<::UnknownCoseStructure)?; @@ -355,7 +389,20 @@ fn test_sign_verify_multiple() -> Result<(), AccessTokenError<(&key, &signed, Some(&aad))?; } - assert!(verify_access_token_multiple::(&invalid_key1, &signed, Some(&aad)).err().filter(|x| matches!(x, AccessTokenError::NoMatchingRecipient)).is_some()); - assert!(verify_access_token_multiple::(&invalid_key2, &signed, Some(&aad)).err().filter(|x| matches!(x, AccessTokenError::CoseCipherError(CoseCipherError::VerificationFailure))).is_some()); + assert!( + verify_access_token_multiple::(&invalid_key1, &signed, Some(&aad)) + .err() + .filter(|x| matches!(x, AccessTokenError::NoMatchingRecipient)) + .is_some() + ); + assert!( + verify_access_token_multiple::(&invalid_key2, &signed, Some(&aad)) + .err() + .filter(|x| matches!( + x, + AccessTokenError::CoseCipherError(CoseCipherError::VerificationFailure) + )) + .is_some() + ); Ok(()) -} \ No newline at end of file +} diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index c4db67e..e972bea 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -10,13 +10,15 @@ */ use ciborium::value::Value; -use coset::{CborSerializable, CoseKey, CoseKeyBuilder, Header, HeaderBuilder, iana, KeyType, Label, ProtectedHeader}; use coset::cwt::{ClaimsSetBuilder, Timestamp}; -use coset::iana::{Algorithm, CwtClaimName}; use coset::iana::EllipticCurve::P_256; +use coset::iana::{Algorithm, CwtClaimName}; +use coset::{ + iana, CborSerializable, CoseKey, CoseKeyBuilder, Header, HeaderBuilder, KeyType, Label, + ProtectedHeader, +}; use rand::{CryptoRng, Error, RngCore}; -use dcaf::{CoseSignCipher, sign_access_token, verify_access_token}; use dcaf::common::cbor_map::ToCborMap; use dcaf::common::cbor_values::ProofOfPossessionKey::PlainCoseKey; use dcaf::common::scope::TextEncodedScope; @@ -26,7 +28,8 @@ use dcaf::endpoints::token_req::{ TokenType, }; use dcaf::error::CoseCipherError; -use dcaf::token::{ToCoseKey, verify_access_token_multiple}; +use dcaf::token::{verify_access_token_multiple, ToCoseKey}; +use dcaf::{sign_access_token, verify_access_token, CoseSignCipher}; #[derive(Clone)] pub(crate) struct EC2P256Key { @@ -36,12 +39,7 @@ pub(crate) struct EC2P256Key { impl ToCoseKey for EC2P256Key { fn to_cose_key(&self) -> CoseKey { - CoseKeyBuilder::new_ec2_pub_key( - P_256, - self.x.to_vec(), - self.y.to_vec(), - ) - .build() + CoseKeyBuilder::new_ec2_pub_key(P_256, self.x.to_vec(), self.y.to_vec()).build() } } @@ -51,14 +49,18 @@ impl TryFrom> for EC2P256Key { fn try_from(value: Vec) -> Result { let key = CoseKey::from_slice(value.as_slice()).map_err(|x| x.to_string())?; assert_eq!(key.kty, KeyType::Assigned(iana::KeyType::EC2)); - assert_eq!(get_param(Label::Int(iana::Ec2KeyParameter::Crv as i64), &key.params), Some(Value::from(P_256 as u64))); + assert_eq!( + get_param(Label::Int(iana::Ec2KeyParameter::Crv as i64), &key.params), + Some(Value::from(P_256 as u64)) + ); - if let Some(Value::Bytes(x)) = get_param(Label::Int(iana::Ec2KeyParameter::X as i64), &key.params) { - if let Some(Value::Bytes(y)) = get_param(Label::Int(iana::Ec2KeyParameter::Y as i64), &key.params) { - return Ok(EC2P256Key { - x, - y, - }) + if let Some(Value::Bytes(x)) = + get_param(Label::Int(iana::Ec2KeyParameter::X as i64), &key.params) + { + if let Some(Value::Bytes(y)) = + get_param(Label::Int(iana::Ec2KeyParameter::Y as i64), &key.params) + { + return Ok(EC2P256Key { x, y }); } } return Err("x and y must be present in key as bytes".to_string()); @@ -148,11 +150,11 @@ impl CoseSignCipher for FakeCrypto { ) -> Result<(), CoseCipherError> { if signature == Self::sign( - key, - signed_data, - unprotected_header, - &protected_header.header, - ) + key, + signed_data, + unprotected_header, + &protected_header.header, + ) { Ok(()) } else { @@ -243,14 +245,18 @@ fn test_scenario() -> Result<(), String> { .audience(resource_server.to_string()) .issuer(auth_server.to_string()) .issued_at(Timestamp::WholeSeconds(47)) - .claim(CwtClaimName::Cnf, PlainCoseKey(key.to_cose_key()).to_ciborium_value()) + .claim( + CwtClaimName::Cnf, + PlainCoseKey(key.to_cose_key()).to_ciborium_value(), + ) .build(), // TODO: Proper headers Some(aad.as_slice()), Some(unprotected_headers), Some(protected_headers), rng, - ).map_err(|x| x.to_string())?; + ) + .map_err(|x| x.to_string())?; let response = AccessTokenResponse::builder() .access_token(token) .ace_profile(AceProfile::CoapDtls) @@ -262,7 +268,8 @@ fn test_scenario() -> Result<(), String> { let result = pseudo_send_receive(response.clone())?; assert_eq!(response, result); - verify_access_token::(&key, &response.access_token, Some(aad.as_slice())).map_err(|x| x.to_string())?; + verify_access_token::(&key, &response.access_token, Some(aad.as_slice())) + .map_err(|x| x.to_string())?; let error = ErrorResponse::builder() .error(ErrorCode::InvalidRequest) @@ -276,8 +283,8 @@ fn test_scenario() -> Result<(), String> { } fn pseudo_send_receive(input: T) -> Result - where - T: ToCborMap + PartialEq + Clone, +where + T: ToCborMap + PartialEq + Clone, { let mut serialized: Vec = Vec::new(); input From 0c0337e44d1d991478761702fb73437bcf09a4c3 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Tue, 25 Oct 2022 22:10:32 +0200 Subject: [PATCH 07/12] docs: update crate-level docs and README.md --- README.md | 294 +++++++++++++++++++++++++++++++---------------------- src/lib.rs | 87 +++++++++------- 2 files changed, 224 insertions(+), 157 deletions(-) diff --git a/README.md b/README.md index e01dcdd..febf1ed 100644 --- a/README.md +++ b/README.md @@ -4,175 +4,225 @@ # dcaf-rs -An implementation of the [ACE-OAuth framework](https://www.ietf.org/archive/id/draft-ietf-ace-oauth-authz-46.html) in -Rust. + -## Crate explanation +An implementation of the [ACE-OAuth framework (RFC 9200)](https://www.rfc-editor.org/rfc/rfc9200). This crate implements the ACE-OAuth (Authentication and Authorization for Constrained Environments using the OAuth 2.0 Framework) -framework as defined -in [`draft-ietf-ace-oauth-authz-46`](https://www.ietf.org/archive/id/draft-ietf-ace-oauth-authz-46.html). Key features -include CBOR-(de-)serializable data models such as `AccessTokenRequest`, as well as the possibility to create COSE -encrypted/signed access tokens -(as described in the draft) along with decryption/verification functions. Implementations of the cryptographic functions -must be provided by the user by implementing -`CoseEncrypt0Cipher` or `CoseSign1Cipher`. - -Note that actually transmitting the serialized values (e.g. via CoAP) or providing more complex features not mentioned -in the ACE-OAuth Internet Draft (e.g. a permission management system for the Authorization Server) is *out of scope* for -this crate. This also applies to cryptographic functions, as mentioned in the previous paragraph. - -The name DCAF was chosen because eventually, it's planned for this crate to support functionality from -the [Delegated CoAP Authentication and Authorization Framework (DCAF)](https://dcaf.science/) +framework as defined in [RFC 9200](https://www.rfc-editor.org/rfc/rfc9200). +Key features include CBOR-(de-)serializable data models such +as [`AccessTokenRequest`](https://docs.rs/dcaf/latest/dcaf/endpoints/token_req/struct.AccessTokenRequest.html), +as well as the possibility to create COSE encrypted/signed access tokens +(as described in the standard) along with decryption/verification functions. +Implementations of the cryptographic functions must be provided by the user by implementing +[`CoseEncryptCipher`](https://docs.rs/dcaf/latest/dcaf/token/trait.CoseEncryptCipher.html) +or [`CoseSignCipher`](https://docs.rs/dcaf/latest/dcaf/token/trait.CoseSignCipher.html). + +Note that actually transmitting the serialized values (e.g., via CoAP) or providing more complex +features not mentioned in the ACE-OAuth RFC (e.g., a permission management system for +the Authorization Server) is *out of scope* for this crate. +This also applies to cryptographic functions, as mentioned in the previous paragraph. + +The name DCAF was chosen because eventually, it's planned for this crate to support +functionality from the [Delegated CoAP Authentication and Authorization Framework (DCAF)](https://dcaf.science/) specified in [`draft-gerdes-ace-dcaf-authorize`](https://datatracker.ietf.org/doc/html/draft-gerdes-ace-dcaf-authorize-04) -(which was specified prior to ACE-OAuth and inspired many design choices in it)--- specifically, it's planned to support -using a CAM (Client Authorization Manager) -instead of just a SAM (Server Authorization Manager), as is done in ACE-OAuth. Compatibility with the -existing [DCAF implementation in C](https://gitlab.informatik.uni-bremen.de/DCAF/dcaf) -(which we'll call `libdcaf` to disambiguate from `dcaf` referring to this crate) is also an additional design goal, -though the primary objective is still to support ACE-OAuth. - -As one of the possible use-cases for this crate is usage on constrained IoT devices, requirements are minimal---as such, -while `alloc` is still needed, this crate offers +(which was specified prior to ACE-OAuth and inspired many design choices in it)--- +specifically, it's planned to support using a CAM (Client Authorization Manager) +instead of just a SAM (Server Authorization Manager), as is done in ACE-OAuth. +Compatibility with the existing [DCAF implementation in C](https://gitlab.informatik.uni-bremen.de/DCAF/dcaf) +(which we'll call `libdcaf` to disambiguate from `dcaf` referring to this crate) is also an +additional design goal, though the primary objective is still to support ACE-OAuth. + +As one of the possible use-cases for this crate is usage on constrained IoT devices, +requirements are minimal---as such, while `alloc` is still needed, this crate offers `no_std` support by omitting the default `std` feature. -## Usage +# Usage + ```toml [dependencies] dcaf = { version = "^0.3" } ``` + Or, if you plan to use this crate in a `no_std` environment: + ```toml [dependencies] dcaf = { version = "^0.3", default-features = false } ``` -## Example +# Example -As mentioned, the main features of this crate are ACE-OAuth data models and token creation/verification functions. We'll -quickly introduce both of these here. +As mentioned, the main features of this crate are ACE-OAuth data models and +token creation/verification functions. We'll quickly introduce both of these here. -### Data models +## Data models -[For example](https://www.ietf.org/archive/id/draft-ietf-ace-oauth-authz-46.html#figure-7), say you (the client) want to -request an access token from an Authorization Server. For this, you'd need to create an `AccessTokenRequest`, which has -to include at least a -`client_id`. We'll also specify an audience, a scope (using `TextEncodedScope`---note that binary-encoded scopes -or AIF-encoded scopes would also work), as well as a -`ProofOfPossessionKey` (the key the access token should be bound to) in the `req_cnf` field. +[For example](https://www.rfc-editor.org/rfc/rfc9200#figure-6), +let's assume you (the client) want to request an access token from an Authorization Server. +For this, you'd need to create +an [`AccessTokenRequest`](https://docs.rs/dcaf/latest/dcaf/endpoints/token_req/struct.AccessTokenRequest.html), which +has to include at least a +`client_id`. We'll also specify an audience, a scope ( +using [`TextEncodedScope`](https://docs.rs/dcaf/latest/dcaf/common/scope/struct.TextEncodedScope.html)---note that +[binary-encoded scopes](https://docs.rs/dcaf/latest/dcaf/common/scope/struct.BinaryEncodedScope.html) +or [AIF-encoded scopes](https://docs.rs/dcaf/latest/dcaf/common/scope/struct.AifEncodedScope.html) would also work), as +well as a +[`ProofOfPossessionKey`](https://docs.rs/dcaf/latest/dcaf/common/cbor_values/enum.ProofOfPossessionKey.html) (the key +the access token should be bound to) in the `req_cnf` field. Creating, serializing and then de-serializing such a structure would look like this: + ```rust use dcaf::{AccessTokenRequest, ToCborMap, ProofOfPossessionKey, TextEncodedScope}; let request = AccessTokenRequest::builder() - .client_id("myclient") - .audience("valve242") - .scope(TextEncodedScope::try_from("read")?) - .req_cnf(ProofOfPossessionKey::KeyId(base64::decode("6kg0dXJM13U")?)) - .build()?; +.client_id("myclient") +.audience("valve242") +.scope(TextEncodedScope::try_from("read") ? ) +.req_cnf(ProofOfPossessionKey::KeyId(base64::decode("6kg0dXJM13U") ? )) +.build() ?; let mut encoded = Vec::new(); -request.clone().serialize_into(&mut encoded)?; +request.clone().serialize_into( & mut encoded) ?; assert_eq!(AccessTokenRequest::deserialize_from(encoded.as_slice())?, request); ``` -### Access Tokens +## Access Tokens + +Following up from the previous example, let's assume we now want to create a signed +access token containing the existing `key`, as well as claims about the audience and issuer +of the token, using an existing cipher of type `FakeCrypto`[^cipher]: -Following up from the previous example, let's assume we now want to create a signed access token containing the -existing `key`, as well as claims about the audience and issuer of the token, using an existing `cipher`[^cipher]: ```rust -use dcaf::{ToCborMap, sign_access_token, verify_access_token}; -use coset::cwt::ClaimsSetBuilder; -use coset::Header; -use coset::iana::CwtClaimName; +# [derive(Clone)] + +let rng = FakeRng; +let key = FakeKey { key: [1, 2, 3, 4, 5], kid: [0xDC, 0xAF]}; +let cose_key: CoseKey = key.to_cose_key(); let claims = ClaimsSetBuilder::new() - .audience("valve242".to_string()) - .claim(CwtClaimName::Cnf, key.to_ciborium_value()) - .claim(CwtClaimName::Scope, Value::Text("read".to_string())) - .build(); -let token = sign_access_token(claims, &mut cipher, None, None, None)?; -assert!(verify_access_token(&token, &mut cipher, None).is_ok()); +.audience(String::from("coaps://rs.example.com")) +.issuer(String::from("coaps://as.example.com")) +.claim(CwtClaimName::Cnf, cose_key.to_cbor_value() ? ) +.build(); +let token = sign_access_token::( & key, claims, None, None, None, rng) ?; +assert!(verify_access_token::(&key, &token, None).is_ok()); ``` [^cipher]: Note that we are deliberately omitting details about the implementation of the -`cipher` here, since such implementations won't be in scope of this crate. - -## Provided Data Models - -### Token Endpoint +`cipher` here, since such implementations won't be in the scope of this crate. -The most commonly used models will probably be the token endpoint's `AccessTokenRequest` and -`AccessTokenResponse` described in -[section 5.8 of the ACE-OAuth draft](https://www.ietf.org/archive/id/draft-ietf-ace-oauth-authz-46.html#section-5.8). In -case of an error, an `ErrorResponse` should be used. +# Provided Data Models -After an initial Unauthorized Resource Request Message, an `AuthServerRequestCreationHint` can be used to provide -additional information to the client, as described in -[section 5.3 of the ACE-OAuth draft](https://www.ietf.org/archive/id/draft-ietf-ace-oauth-authz-46.html#section-5.3). - -### Common Data Types -Some types used across multiple scenarios include: +## Token Endpoint -- `Scope` (as described - in [section 5.8.1 of the ACE-OAuth draft](https://www.ietf.org/archive/id/draft-ietf-ace-oauth-authz-46.html#section-5.8.1)) - , either as a `TextEncodedScope`, a `BinaryEncodedScope` or an `AifEncodedScope`. -- `ProofOfPossessionKey` as specified - in [section 3.1 of RFC 8747](https://datatracker.ietf.org/doc/html/rfc8747#section-3.1). For example, this will be - used in the access token's `cnf` claim. -- While not really a data type, various constants representing values used in ACE-OAuth are provided in the `constants` - module. +The most commonly used models will probably be the token endpoint's +[`AccessTokenRequest`](https://docs.rs/dcaf/latest/dcaf/endpoints/token_req/struct.AccessTokenRequest.html) and +[`AccessTokenResponse`](https://docs.rs/dcaf/latest/dcaf/endpoints/token_req/struct.AccessTokenResponse.html) +described in [section 5.8 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-5.8). +In case of an error, +an [`ErrorResponse`](https://docs.rs/dcaf/latest/dcaf/endpoints/token_req/struct.ErrorResponse.html) +should be used. -## Creating Access Tokens +After an initial Unauthorized Resource Request Message, an +[`AuthServerRequestCreationHint`](https://docs.rs/dcaf/latest/dcaf/endpoints/creation_hint/struct.AuthServerRequestCreationHint.html) +can be used to provide additional information to the client, as described in +[section 5.3 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-5.3). -In order to create access tokens, you can use either `encrypt_access_token` or -`sign_access_token`, depending on whether you want the access token to be wrapped in a -`COSE_Encrypt0` or `COSE_Sign1` structure. Support for a combination of both is planned for the future. +## Common Data Types -Both functions take a `ClaimsSet` containing the claims that shall be part of the access token, a cipher implementing -the cryptographic operations -(explained further below), as well as optional `aad` (additional authenticated data) -and un-/protected headers. Note that if the headers you pass in set fields which the cipher wants to set as well, the -function will fail with a -`HeaderAlreadySet` error. The function will return a `Result` of the opaque `ByteString` containing the access token. - -## Verifying / decrypting Access Tokens - -In order to verify or decrypt existing access tokens represented as `ByteString`s, use `verify_access_token` -or `decrypt_access_token` respectively. - -Both functions take the access token, a `cipher` for the cryptographic operations and an optional `aad` (additional -authenticated data). +Some types used across multiple scenarios include: -`decrypt_access_token` will return a result containing the decrypted -`ClaimsSet`. -`verify_access_token` will return an empty result which indicates that the token was successfully verified---an `Err` +- [`Scope`](https://docs.rs/dcaf/latest/dcaf/common/scope/enum.Scope.html) (as described in + [section 5.8.1 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-5.8.1)), + either as a [`TextEncodedScope`](https://docs.rs/dcaf/latest/dcaf/common/scope/struct.TextEncodedScope.html), + a [`BinaryEncodedScope`](https://docs.rs/dcaf/latest/dcaf/common/scope/struct.BinaryEncodedScope.html) or + an [`AifEncodedScope`](https://docs.rs/dcaf/latest/dcaf/common/scope/struct.AifEncodedScope.html). +- [`ProofOfPossessionKey`](https://docs.rs/dcaf/latest/dcaf/common/cbor_values/enum.ProofOfPossessionKey.html) as + specified in + [section 3.1 of RFC 8747](https://www.rfc-editor.org/rfc/rfc8747#section-3.1). + For example, this will be used in the access token's `cnf` claim. +- While not really a data type, various constants representing values used in ACE-OAuth + are provided in the [`constants`](https://docs.rs/dcaf/latest/dcaf/common/constants/) module. + +# Creating Access Tokens + +In order to create access tokens, you can use +either [`encrypt_access_token`](https://docs.rs/dcaf/latest/dcaf/token/fn.encrypt_access_token.html) +or [`sign_access_token`](https://docs.rs/dcaf/latest/dcaf/token/fn.sign_access_token.html), +depending on whether you want the access token to be wrapped in a +`COSE_Encrypt0` or `COSE_Sign1` structure. Support for a combination of both is planned for the +future. In case you want to create a token intended for multiple recipients (each with their +own key), you can +use [`encrypt_access_token_multiple`](https://docs.rs/dcaf/latest/dcaf/token/fn.encrypt_access_token_multiple.html) +or [`sign_access_token_multiple`](https://docs.rs/dcaf/latest/dcaf/token/fn.sign_access_token_multiple.html). + +Both functions take a [`ClaimsSet`](coset::cwt::ClaimsSet) containing the claims that +shall be part of the access token, a key used to encrypt or sign the token, +optional `aad` (additional authenticated data), un-/protected headers and a cipher (explained +further below) identified by type parameter `T`. +Note that if the headers you pass in set fields which the cipher wants to set as well, +the function will fail with a `HeaderAlreadySet` error. +The function will return a [`Result`](https://doc.rust-lang.org/stable/core/result/enum.Result.html) of the opaque +[`ByteString`](https://docs.rs/dcaf/latest/dcaf/common/cbor_values/type.ByteString.html) containing the access token. + +# Verifying / decrypting Access Tokens + +In order to verify or decrypt existing access tokens represented +as [`ByteString`](https://docs.rs/dcaf/latest/dcaf/common/cbor_values/type.ByteString.html)s, +use [`verify_access_token`](https://docs.rs/dcaf/latest/dcaf/token/fn.verify_access_token.html) or +[`decrypt_access_token`](https://docs.rs/dcaf/latest/dcaf/token/fn.decrypt_access_token.html) respectively. +In case the token was created for multiple recipients (each with their own key), +use [`verify_access_token_multiple`](https://docs.rs/dcaf/latest/dcaf/token/fn.verify_access_token_multiple.html) +or [`decrypt_access_token_multiple`](https://docs.rs/dcaf/latest/dcaf/token/fn.decrypt_access_token_multiple.html). + +Both functions take the access token, a `key` used to decrypt or verify, optional `aad` +(additional authenticated data) and a cipher implementing cryptographic operations identified +by type parameter `T`. + +[`decrypt_access_token`](https://docs.rs/dcaf/latest/dcaf/token/fn.decrypt_access_token.html) will return a result +containing +the decrypted [`ClaimsSet`](coset::cwt::ClaimsSet). +[`verify_access_token`](https://docs.rs/dcaf/latest/dcaf/token/fn.verify_access_token.html) will return an empty result +which +indicates that the token was successfully +verified---an [`Err`](https://doc.rust-lang.org/stable/core/result/enum.Result.html) would indicate failure. -## Extracting Headers from an Access Token - -Regardless of whether token was signed, encrypted, or MAC-tagged, you can extract its headers using `get_token_headers`, -which will return an option containing both unprotected and protected headers (or which will be `None` in case the token -is invalid or neither a `COSE_Sign1`, `COSE_Encrypt0`, or `COSE_Mac0` structure). - -## COSE Cipher - -As mentioned before, cryptographic functions are outside the scope of this crate. For this reason, the various COSE -cipher traits exist; namely, -`CoseEncrypt0Cipher`, `CoseSign1Cipher`, and `CoseMac0Cipher`, each implementing a corresponding COSE operation as -specified in sections 4, 5, and 6 of -[RFC 8152](https://datatracker.ietf.org/doc/html/rfc8152). - -Note that these ciphers *don't* need to wrap their results in e.g. a `Cose_Encrypt0` structure, this part is already -handled using this library (which uses `coset`)---only the cryptographic algorithms themselves need to be implemented -(e.g. step 4 of -"how to decrypt a message" in [section 5.3 of RFC 8152](https://datatracker.ietf.org/doc/html/rfc8152#section-5.3)). - -When implementing any of the specific COSE ciphers, you'll also need to implement the -`CoseCipherCommon` trait, which can be used to set headers specific to your COSE cipher -(e.g. the used algorithm). +# Extracting Headers from an Access Token + +Regardless of whether a token was signed, encrypted, or MAC-tagged, you can extract its +headers using [`get_token_headers`](https://docs.rs/dcaf/latest/dcaf/token/fn.get_token_headers.html), +which will return an option containing both +unprotected and protected headers (or which will be [`None`](core::option::Option::None) in case +the token is invalid). + +# COSE Cipher + +As mentioned before, cryptographic functions are outside the scope of this crate. +For this reason, the various COSE cipher traits exist; namely, +[`CoseEncryptCipher`](core::token::CoseEncryptCipher), [`CoseSignCipher`](core::token::CoseSignCipher), +and [`CoseMacCipher`](core::token::CoseMacCipher), each implementing +a corresponding COSE operation as specified in sections 4, 5, and 6 of +[RFC 8152](https://www.rfc-editor.org/rfc/rfc8152). +There are also the traits [`MultipleEncryptCipher`](core::token::MultipleEncryptCipher), +[`MultipleSignCipher`](core::token::MultipleSignCipher), and +[`MultipleMacCipher`](core::token::MultipleMacCipher), +which are used for creating tokens intended for multiple recipients. + +Note that these ciphers *don't* need to wrap their results in, e.g., +a `Cose_Encrypt0` structure, as this part is already handled by this library +(which uses [`coset`](coset))---only the cryptographic algorithms themselves need to be implemented +(e.g., step 4 of "how to decrypt a message" +in [section 5.3 of RFC 8152](https://www.rfc-editor.org/rfc/rfc8152#section-5.3)). + +When implementing any of the specific COSE ciphers, you'll also need to specify the type +of the key (which must be convertible to a `CoseKey`) and implement a method which sets +headers for the token, for example, the used algorithm, the key ID, an IV, and so on. + + ## Changelog You can find a list of changes in [CHANGELOG.md](CHANGELOG.md). diff --git a/src/lib.rs b/src/lib.rs index a188e95..30ad8c6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,11 +14,11 @@ //! This crate implements the ACE-OAuth //! (Authentication and Authorization for Constrained Environments using the OAuth 2.0 Framework) //! framework as defined in [RFC 9200](https://www.rfc-editor.org/rfc/rfc9200). -//! Key features include CBOR-(de-)serializable data models such as [`AccessTokenRequest`], +//! Key features include CBOR-(de-)serializable data models such as [`AccessTokenRequest`](crate::endpoints::token_req::AccessTokenRequest), //! as well as the possibility to create COSE encrypted/signed access tokens //! (as described in the standard) along with decryption/verification functions. //! Implementations of the cryptographic functions must be provided by the user by implementing -//! [`CoseEncryptCipher`] or [`CoseSignCipher`]. +//! [`CoseEncryptCipher`](crate::token::CoseEncryptCipher) or [`CoseSignCipher`](crate::token::CoseSignCipher). //! //! Note that actually transmitting the serialized values (e.g., via CoAP) or providing more complex //! features not mentioned in the ACE-OAuth RFC (e.g., a permission management system for @@ -57,10 +57,10 @@ //! ## Data models //! [For example](https://www.rfc-editor.org/rfc/rfc9200#figure-6), //! let's assume you (the client) want to request an access token from an Authorization Server. -//! For this, you'd need to create an [`AccessTokenRequest`], which has to include at least a -//! `client_id`. We'll also specify an audience, a scope (using [`TextEncodedScope`]---note that -//! [binary-encoded scopes](BinaryEncodedScope) or [AIF-encoded scopes](AifEncodedScope) would also work), as well as a -//! [`ProofOfPossessionKey`] (the key the access token should be bound to) in the `req_cnf` field. +//! For this, you'd need to create an [`AccessTokenRequest`](crate::endpoints::token_req::AccessTokenRequest), which has to include at least a +//! `client_id`. We'll also specify an audience, a scope (using [`TextEncodedScope`](crate::common::scope::TextEncodedScope)---note that +//! [binary-encoded scopes](crate::common::scope::BinaryEncodedScope) or [AIF-encoded scopes](crate::common::scope::AifEncodedScope) would also work), as well as a +//! [`ProofOfPossessionKey`](crate::common::cbor_values::ProofOfPossessionKey) (the key the access token should be bound to) in the `req_cnf` field. //! //! Creating, serializing and then de-serializing such a structure would look like this: //! ``` @@ -137,8 +137,8 @@ //! # //! # impl CryptoRng for FakeRng {} //! # -//! # /// Implements basic operations from the [`CoseSignCipher`] trait without actually using any -//! # /// "real" cryptography. +//! # /// Implements basic operations from the [`CoseSignCipher`](crate::token::CoseSignCipher) trait +//! # /// without actually using any "real" cryptography. //! # /// This is purely to be used for testing and obviously offers no security at all. //! # impl CoseSignCipher for FakeCrypto { //! # type Error = String; @@ -224,72 +224,89 @@ //! # Provided Data Models //! //! ## Token Endpoint -//! The most commonly used models will probably be the token endpoint's [`AccessTokenRequest`] and -//! [`AccessTokenResponse`] described in [section 5.8 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-5.8). -//! In case of an error, an [`ErrorResponse`] should be used. +//! The most commonly used models will probably be the token endpoint's +//! [`AccessTokenRequest`](crate::endpoints::token_req::AccessTokenRequest) and +//! [`AccessTokenResponse`](crate::endpoints::token_req::AccessTokenResponse) +//! described in [section 5.8 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-5.8). +//! In case of an error, an [`ErrorResponse`](crate::endpoints::token_req::ErrorResponse) +//! should be used. //! -//! After an initial Unauthorized Resource Request Message, an [`AuthServerRequestCreationHint`] can -//! be used to provide additional information to the client, as described in +//! After an initial Unauthorized Resource Request Message, an +//! [`AuthServerRequestCreationHint`](crate::endpoints::creation_hint::AuthServerRequestCreationHint) +//! can be used to provide additional information to the client, as described in //! [section 5.3 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-5.3). //! //! ## Common Data Types //! Some types used across multiple scenarios include: -//! - [`Scope`] (as described in [section 5.8.1 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-5.8.1)), -//! either as a [`TextEncodedScope`], a [`BinaryEncodedScope`] or an [`AifEncodedScope`]. -//! - [`ProofOfPossessionKey`] as specified in [section 3.1 of RFC 8747](https://www.rfc-editor.org/rfc/rfc8747#section-3.1). +//! - [`Scope`](crate::common::scope::Scope) (as described in +//! [section 5.8.1 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-5.8.1)), +//! either as a [`TextEncodedScope`](crate::common::scope::TextEncodedScope), +//! a [`BinaryEncodedScope`](crate::common::scope::BinaryEncodedScope) or +//! an [`AifEncodedScope`](crate::common::scope::AifEncodedScope). +//! - [`ProofOfPossessionKey`](crate::common::cbor_values::ProofOfPossessionKey) as specified in +//! [section 3.1 of RFC 8747](https://www.rfc-editor.org/rfc/rfc8747#section-3.1). //! For example, this will be used in the access token's `cnf` claim. //! - While not really a data type, various constants representing values used in ACE-OAuth //! are provided in the [`constants`](crate::common::constants) module. //! //! # Creating Access Tokens -//! In order to create access tokens, you can use either [`encrypt_access_token`] or -//! [`sign_access_token`], depending on whether you want the access token to be wrapped in a +//! In order to create access tokens, you can use either [`encrypt_access_token`](crate::token::encrypt_access_token) +//! or [`sign_access_token`](crate::token::sign_access_token), +//! depending on whether you want the access token to be wrapped in a //! `COSE_Encrypt0` or `COSE_Sign1` structure. Support for a combination of both is planned for the //! future. In case you want to create a token intended for multiple recipients (each with their -//! own key), you can use [`encrypt_access_token_multiple`] or [`sign_access_token_multiple`]. +//! own key), you can use [`encrypt_access_token_multiple`](crate::token::encrypt_access_token_multiple) +//! or [`sign_access_token_multiple`](crate::token::sign_access_token_multiple). //! //! Both functions take a [`ClaimsSet`](coset::cwt::ClaimsSet) containing the claims that //! shall be part of the access token, a key used to encrypt or sign the token, //! optional `aad` (additional authenticated data), un-/protected headers and a cipher (explained //! further below) identified by type parameter `T`. //! Note that if the headers you pass in set fields which the cipher wants to set as well, -//! the function will fail with a -//! [`HeaderAlreadySet`](crate::error::CoseCipherError::HeaderAlreadySet) error. -//! The function will return a [`Result`] of the opaque [`ByteString`] containing the access token. +//! the function will fail with a `HeaderAlreadySet` error. +//! The function will return a [`Result`](::core::result::Result) of the opaque +//! [`ByteString`](crate::common::cbor_values::ByteString) containing the access token. //! //! # Verifying / decrypting Access Tokens -//! In order to verify or decrypt existing access tokens represented as [`ByteString`]s, -//! use [`verify_access_token`] or [`decrypt_access_token`] respectively. +//! In order to verify or decrypt existing access tokens represented as [`ByteString`](crate::common::cbor_values::ByteString)s, +//! use [`verify_access_token`](crate::token::verify_access_token) or +//! [`decrypt_access_token`](crate::token::decrypt_access_token) respectively. //! In case the token was created for multiple recipients (each with their own key), -//! use [`verify_access_token_multiple`] or [`decrypt_access_token_multiple`]. +//! use [`verify_access_token_multiple`](crate::token::verify_access_token_multiple) +//! or [`decrypt_access_token_multiple`](crate::token::decrypt_access_token_multiple). //! //! Both functions take the access token, a `key` used to decrypt or verify, optional `aad` //! (additional authenticated data) and a cipher implementing cryptographic operations identified //! by type parameter `T`. //! -//! [`decrypt_access_token`] will return a result containing the decrypted -//! [`ClaimsSet`](coset::cwt::ClaimsSet). -//! [`verify_access_token`] will return an empty result which indicates that the token -//! was successfully verified---an [`Err`](Result::Err) would indicate failure. +//! [`decrypt_access_token`](crate::token::decrypt_access_token) will return a result containing +//! the decrypted [`ClaimsSet`](coset::cwt::ClaimsSet). +//! [`verify_access_token`](crate::token::verify_access_token) will return an empty result which +//! indicates that the token was successfully verified---an [`Err`](::core::result::Result) +//! would indicate failure. //! //! # Extracting Headers from an Access Token //! Regardless of whether a token was signed, encrypted, or MAC-tagged, you can extract its -//! headers using [`get_token_headers`], which will return an option containing both -//! unprotected and protected headers (or which will be [`None`](Option::None) in case +//! headers using [`get_token_headers`](crate::token::get_token_headers), +//! which will return an option containing both +//! unprotected and protected headers (or which will be [`None`](core::option::Option::None) in case //! the token is invalid). //! //! # COSE Cipher //! As mentioned before, cryptographic functions are outside the scope of this crate. //! For this reason, the various COSE cipher traits exist; namely, -//! [`CoseEncryptCipher`], [`CoseSignCipher`], and [`CoseMacCipher`], each implementing +//! [`CoseEncryptCipher`](core::token::CoseEncryptCipher), [`CoseSignCipher`](core::token::CoseSignCipher), +//! and [`CoseMacCipher`](core::token::CoseMacCipher), each implementing //! a corresponding COSE operation as specified in sections 4, 5, and 6 of //! [RFC 8152](https://www.rfc-editor.org/rfc/rfc8152). -//! There are also the traits [`MultipleEncryptCipher`], [`MultipleSignCipher`], and -//! [`MultipleMacCipher`], which are used for creating tokens intended for multiple recipients. +//! There are also the traits [`MultipleEncryptCipher`](core::token::MultipleEncryptCipher), +//! [`MultipleSignCipher`](core::token::MultipleSignCipher), and +//! [`MultipleMacCipher`](core::token::MultipleMacCipher), +//! which are used for creating tokens intended for multiple recipients. //! //! Note that these ciphers *don't* need to wrap their results in, e.g., //! a `Cose_Encrypt0` structure, as this part is already handled by this library -//! (which uses [`coset`])---only the cryptographic algorithms themselves need to be implemented +//! (which uses [`coset`](coset))---only the cryptographic algorithms themselves need to be implemented //! (e.g., step 4 of "how to decrypt a message" in [section 5.3 of RFC 8152](https://www.rfc-editor.org/rfc/rfc8152#section-5.3)). //! //! When implementing any of the specific COSE ciphers, you'll also need to specify the type From 60f85c10170e190424739f3518cb4ff855158865 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Wed, 26 Oct 2022 13:28:49 +0200 Subject: [PATCH 08/12] docs: reference correct parameter names in token functions --- src/token/mod.rs | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/src/token/mod.rs b/src/token/mod.rs index 93e24f4..7ff86d8 100644 --- a/src/token/mod.rs +++ b/src/token/mod.rs @@ -406,9 +406,9 @@ macro_rules! prepare_headers { }}; } -/// Encrypts the given `claims` with the given headers and `aad` using the `key` and the cipher -/// given by type parameter `T`, returning the token as a serialized bytestring of -/// the [`CoseEncrypt0`] structure. +/// Encrypts the given `claims` with the given headers and `external_aad` using the +/// `key` and the cipher given by type parameter `T`, returning the token as a serialized +/// bytestring of the [`CoseEncrypt0`] structure. /// /// Note that this method will create a token intended for a single recipient. /// If you wish to create a token for more than one recipient, use @@ -459,8 +459,8 @@ where .map_err(AccessTokenError::from) } -/// Encrypts the given `claims` with the given headers and `aad` for each recipient by using the -/// `keys` with the cipher given by type parameter `T`, +/// Encrypts the given `claims` with the given headers and `external_aad` for each recipient +/// by using the `keys` with the cipher given by type parameter `T`, /// returning the token as a serialized bytestring of the [`CoseEncrypt`] structure. /// /// Note that the given `keys` must each have an associated `kid` (key ID) field when converted @@ -529,8 +529,8 @@ where builder.build().to_vec().map_err(AccessTokenError::from) } -/// Signs the given `claims` with the given headers and `aad` using the `key` and the cipher -/// given by type parameter `T`, returning the token as a serialized bytestring of +/// Signs the given `claims` with the given headers and `external_aad` using the `key` and the +/// cipher given by type parameter `T`, returning the token as a serialized bytestring of /// the [`CoseSign1`] structure. /// /// Note that this method will create a token intended for a single recipient. @@ -581,9 +581,9 @@ where .map_err(AccessTokenError::from) } -/// Signs the given `claims` with the given headers and `aad` for each recipient by using the `keys` -/// with the cipher given by type parameter `T`, returning the token as a serialized bytestring of -/// the [`CoseSign`] structure. +/// Signs the given `claims` with the given headers and `external_aad` for each recipient +/// by using the `keys` with the cipher given by type parameter `T`, +/// returning the token as a serialized bytestring of the [`CoseSign`] structure. /// /// For each key in `keys`, another signature will be added, created with that respective key. /// The given headers will be used for the [`CoseSign`] structure as a whole, not for each @@ -693,7 +693,7 @@ pub fn get_token_headers(token: &ByteString) -> Option<(Header, ProtectedHeader) } } -/// Verifies the given `token` and `aad` with the `key` using the cipher +/// Verifies the given `token` and `external_aad` with the `key` using the cipher /// given by type parameter `T`, returning an error in case it could not be verified. /// /// This method should be used when the given `token` is a [`CoseSign1`] rather than @@ -713,7 +713,7 @@ pub fn get_token_headers(token: &ByteString) -> Option<(Header, ProtectedHeader) pub fn verify_access_token( key: &T::VerifyKey, token: &ByteString, - aad: Option<&[u8]>, + external_aad: Option<&[u8]>, ) -> Result<(), AccessTokenError> where T: CoseSignCipher, @@ -722,7 +722,7 @@ where let (unprotected, protected) = get_token_headers(token).ok_or(AccessTokenError::UnknownCoseStructure)?; // TODO: Verify protected headers - sign.verify_signature(aad.unwrap_or(&[0; 0]), |signature, signed_data| { + sign.verify_signature(external_aad.unwrap_or(&[0; 0]), |signature, signed_data| { T::verify( key, signature, @@ -736,7 +736,7 @@ where .map_err(AccessTokenError::from) } -/// Verifies the given `token` and `aad` with the `key` using the cipher +/// Verifies the given `token` and `external_aad` with the `key` using the cipher /// given by type parameter `T`, returning an error in case it could not be verified. /// /// This method should be used when the given `token` is a [`CoseSign`] rather than @@ -754,7 +754,7 @@ where pub fn verify_access_token_multiple( key: &T::VerifyKey, token: &ByteString, - aad: Option<&[u8]>, + external_aad: Option<&[u8]>, ) -> Result<(), AccessTokenError> where T: CoseSignCipher, @@ -775,8 +775,10 @@ where for index in matching { matching_kid = true; // TODO: Verify protected headers - if let Ok(()) = - sign.verify_signature(index, aad.unwrap_or(&[0; 0]), |signature, signed_data| { + if let Ok(()) = sign.verify_signature( + index, + external_aad.unwrap_or(&[0; 0]), + |signature, signed_data| { T::verify( key, signature, @@ -786,8 +788,8 @@ where Some(&sign.signatures[index].unprotected), Some(&sign.signatures[index].protected), ) - }) - { + }, + ) { return Ok(()); } } @@ -798,8 +800,8 @@ where } } -/// Decrypts the given `token` and `aad` using the `key` and the cipher given by type parameter `T`, -/// returning the decrypted [`ClaimsSet`]. +/// Decrypts the given `token` and `external_aad` using the `key` and the cipher +/// given by type parameter `T`, returning the decrypted [`ClaimsSet`]. /// /// This method should be used when the given `token` is a [`CoseEncrypt0`] rather than /// [`CoseEncrypt`] (i.e., if it is intended for a single recipient). In case the token is an @@ -832,7 +834,7 @@ where ClaimsSet::from_slice(result.as_slice()).map_err(AccessTokenError::from) } -/// Decrypts the given `token` and `aad` using the Key Encryption Key `kek` and the cipher given +/// Decrypts the given `token` and `external_aad` using the Key Encryption Key `kek` and the cipher given /// by type parameter `T`, returning the decrypted [`ClaimsSet`]. /// /// Note that the given `kek` must have an associated `kid` (key ID) field when converted From dda23e977c8bf007f464786a0f1bc7993ac7a6a8 Mon Sep 17 00:00:00 2001 From: Falko <10247603+falko17@users.noreply.github.com> Date: Sun, 6 Nov 2022 13:56:07 +0100 Subject: [PATCH 09/12] docs: reword trait documentation for multiple ciphers Co-authored-by: Hugo Hakim Damer --- src/token/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/token/mod.rs b/src/token/mod.rs index 7ff86d8..ca97ad0 100644 --- a/src/token/mod.rs +++ b/src/token/mod.rs @@ -292,7 +292,7 @@ pub trait CoseEncryptCipher { add_common_cipher_functionality![Self::EncryptKey]; } -/// Intended for ciphers which encrypt for multiple recipients. +/// Intended for ciphers which can encrypt for multiple recipients. /// For this purpose, a method must be provided which generates the Content Encryption Key. /// /// If these recipients each use different key types, you can use an enum to represent them. @@ -351,7 +351,7 @@ pub trait CoseSignCipher { add_common_cipher_functionality![Self::SignKey]; } -/// Marker trait intended for ciphers which create signatures for multiple recipients. +/// Marker trait intended for ciphers which can create signatures for multiple recipients. /// /// If these recipients each use different key types, you can use an enum to represent them. pub trait MultipleSignCipher: CoseSignCipher {} @@ -389,7 +389,7 @@ pub trait CoseMacCipher { add_common_cipher_functionality![Self::ComputeKey]; } -/// Marker trait intended for ciphers which create MAC tags for multiple recipients. +/// Marker trait intended for ciphers which can create MAC tags for multiple recipients. /// /// If these recipients each use different key types, you can use an enum to represent them. pub trait MultipleMacCipher: CoseMacCipher {} From 265456ce8b3dea42a1befa83c95000d2432ddfa5 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Sun, 22 Jan 2023 20:18:45 +0100 Subject: [PATCH 10/12] refactor: replace cipher-typed keys with CoseKeys --- CHANGELOG.md | 3 +- src/common/test_helper.rs | 150 +++++++------------- src/lib.rs | 91 ++++++------- src/token/mod.rs | 271 +++++++++++++++---------------------- src/token/tests.rs | 67 +++++---- tests/integration_tests.rs | 137 ++++++++----------- 6 files changed, 300 insertions(+), 419 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17ce0d7..a0f50fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 This release mainly adds support for multiple token recipients, deals with the newly released RFCs, and fixes `no_std` support. +Note that the cipher interfaces have been refactored in a major way. ### Added @@ -27,7 +28,7 @@ and fixes `no_std` support. - The ciphers' API has been majorly changed. As a result, the API for the token functions has changed as well. Users no longer need to pass in an instance of the cipher, they only need to specify the type parameter, as the cipher's methods no longer need `self` as a parameter. Additionally, users now need to pass in the `key` for the - corresponding operation, whereas the type of the key is defined in the cipher. For more information, read the + corresponding operation, specified as a `CoseKey`. For more information, read the documentation of `CoseEncryptCipher`, `CoseSignCipher`, or `CoseMacCipher`, as well as of the token functions. - The documentation has been updated to refer to the recently released RFCs instead of the now outdated internet drafts. diff --git a/src/common/test_helper.rs b/src/common/test_helper.rs index 969ba0c..d3541a9 100644 --- a/src/common/test_helper.rs +++ b/src/common/test_helper.rs @@ -16,8 +16,8 @@ use core::convert::identity; use core::fmt::{Debug, Display}; use ciborium::value::Value; -use coset::iana::Algorithm; -use coset::{CoseKey, CoseKeyBuilder, Header, Label, ProtectedHeader}; +use coset::iana::{Algorithm, SymmetricKeyParameter}; +use coset::{iana, CoseKey, CoseKeyBuilder, Header, Label, ProtectedHeader}; use rand::{CryptoRng, Error, RngCore}; #[cfg(not(feature = "std"))] @@ -29,9 +29,26 @@ use { use crate::common::cbor_map::ToCborMap; use crate::error::{AccessTokenError, CoseCipherError, MultipleCoseError}; -use crate::token::{MultipleEncryptCipher, MultipleSignCipher, ToCoseKey}; +use crate::token::{CoseCipher, MultipleEncryptCipher, MultipleSignCipher}; use crate::{CoseEncryptCipher, CoseMacCipher, CoseSignCipher}; +/// Returns the value of the given symmetric [`key`]. +/// +/// # Panics +/// If [`key`] is not a symmetric key or has no valid key value. +fn get_symmetric_key_value(key: &CoseKey) -> Vec { + let k_label = iana::SymmetricKeyParameter::K as i64; + key.params + .iter() + .find(|x| matches!(x.0, Label::Int(k_label))) + .and_then(|x| match x { + (_, Value::Bytes(x)) => Some(x), + _ => None, + }) + .expect("Key value must be present!") + .clone() +} + /// Helper function for tests which ensures that [`value`] serializes to the hexadecimal bytestring /// [expected_hex] and deserializes back to [`value`]. /// @@ -88,12 +105,15 @@ where #[derive(Copy, Clone)] pub(crate) struct FakeCrypto {} -impl FakeCrypto { - fn set_headers_common( - key: &FakeKey, +impl CoseCipher for FakeCrypto { + type Error = String; + + fn set_headers( + key: &CoseKey, unprotected_header: &mut Header, protected_header: &mut Header, - ) -> Result<(), CoseCipherError> { + rng: RNG, + ) -> Result<(), CoseCipherError> { // We have to later verify these headers really are used. if let Some(label) = unprotected_header .rest @@ -110,55 +130,17 @@ impl FakeCrypto { } unprotected_header.rest.push((Label::Int(47), Value::Null)); protected_header.alg = Some(coset::Algorithm::Assigned(Algorithm::Direct)); - protected_header.key_id = key.kid.to_vec(); + protected_header.key_id = key.key_id.clone(); Ok(()) } } -#[derive(Clone)] -pub(crate) struct FakeKey { - key: [u8; 5], - kid: [u8; 2], -} - -impl ToCoseKey for FakeKey { - fn to_cose_key(&self) -> CoseKey { - CoseKeyBuilder::new_symmetric_key(self.key.to_vec()) - .key_id(self.kid.to_vec()) - .build() - } -} - -impl TryFrom> for FakeKey { - type Error = String; - - // Should be 5 bytes of key + 2 bytes of kid - fn try_from(mut value: Vec) -> Result { - let kid_value = value.split_off(5); - let key: [u8; 5] = value.try_into().map_err(|_| "Invalid input size")?; - let kid: [u8; 2] = kid_value.try_into().map_err(|_| "Invalid input size")?; - Ok(FakeKey { key, kid }) - } -} - -impl From for Vec { - fn from(k: FakeKey) -> Self { - let mut key = k.key.to_vec(); - key.append(&mut k.kid.to_vec()); - key - } -} - /// Implements basic operations from the [`CoseEncryptCipher`] trait without actually using any /// "real" cryptography. /// This is purely to be used for testing and obviously offers no security at all. impl CoseEncryptCipher for FakeCrypto { - type EncryptKey = FakeKey; - type DecryptKey = Self::EncryptKey; - type Error = String; - fn encrypt( - key: &Self::EncryptKey, + key: &CoseKey, plaintext: &[u8], aad: &[u8], protected_header: &Header, @@ -166,14 +148,14 @@ impl CoseEncryptCipher for FakeCrypto { ) -> Vec { // We put the key and the AAD before the data. // Again, this obviously isn't secure in any sane definition of the word. - let mut result: Vec = key.key.to_vec(); + let mut result: Vec = get_symmetric_key_value(key); result.append(&mut aad.to_vec()); result.append(&mut plaintext.to_vec()); result } fn decrypt( - key: &Self::DecryptKey, + key: &CoseKey, ciphertext: &[u8], aad: &[u8], unprotected_header: &Header, @@ -181,57 +163,45 @@ impl CoseEncryptCipher for FakeCrypto { ) -> Result, CoseCipherError> { // Now we just split off the AAD and key we previously put at the end of the data. // We return an error if it does not match. - if key.kid.to_vec() != protected_header.header.key_id { + if key.key_id.clone() != protected_header.header.key_id { // Mismatching key return Err(CoseCipherError::DecryptionFailure); } - if ciphertext.len() < (aad.len() + key.key.len()) { + let key_value = get_symmetric_key_value(key); + if ciphertext.len() < (aad.len() + key_value.len()) { return Err(CoseCipherError::Other( "Encrypted data has invalid length!".to_string(), )); } let mut result: Vec = ciphertext.to_vec(); - let plaintext = result.split_off(aad.len() + key.key.len()); - let aad_result = result.split_off(key.key.len()); - if aad == aad_result && key.key == result.as_slice() { + let plaintext = result.split_off(aad.len() + key_value.len()); + let aad_result = result.split_off(key_value.len()); + if aad == aad_result && key_value == result.as_slice() { Ok(plaintext) } else { Err(CoseCipherError::DecryptionFailure) } } - - fn set_headers( - key: &Self::EncryptKey, - unprotected_header: &mut Header, - protected_header: &mut Header, - rng: RNG, - ) -> Result<(), CoseCipherError> { - Self::set_headers_common(key, unprotected_header, protected_header) - } } /// Implements basic operations from the [`CoseSign1Cipher`] trait without actually using any /// "real" cryptography. /// This is purely to be used for testing and obviously offers no security at all. impl CoseSignCipher for FakeCrypto { - type SignKey = FakeKey; - type VerifyKey = Self::SignKey; - type Error = String; - fn sign( - key: &Self::SignKey, + key: &CoseKey, target: &[u8], unprotected_header: &Header, protected_header: &Header, ) -> Vec { // We simply append the key behind the data. let mut signature = target.to_vec(); - signature.append(&mut key.key.to_vec()); + signature.append(&mut get_symmetric_key_value(key)); signature } fn verify( - key: &Self::VerifyKey, + key: &CoseKey, signature: &[u8], signed_data: &[u8], unprotected_header: &Header, @@ -240,9 +210,9 @@ impl CoseSignCipher for FakeCrypto { protected_signature_header: Option<&ProtectedHeader>, ) -> Result<(), CoseCipherError> { let matching_kid = if let Some(protected) = protected_signature_header { - protected.header.key_id == key.kid + protected.header.key_id == key.key_id } else { - protected_header.header.key_id == key.kid + protected_header.header.key_id == key.key_id }; let signed_again = Self::sign( key, @@ -256,45 +226,32 @@ impl CoseSignCipher for FakeCrypto { Err(CoseCipherError::VerificationFailure) } } - - fn set_headers( - key: &Self::SignKey, - unprotected_header: &mut Header, - protected_header: &mut Header, - rng: RNG, - ) -> Result<(), CoseCipherError> { - Self::set_headers_common(&key, unprotected_header, protected_header) - } } /// Implements basic operations from the [`CoseMac0Cipher`] trait without actually using any /// "real" cryptography. /// This is purely to be used for testing and obviously offers no security at all. impl CoseMacCipher for FakeCrypto { - type ComputeKey = FakeKey; - type VerifyKey = Self::ComputeKey; - type Error = String; - fn compute( - key: &Self::ComputeKey, + key: &CoseKey, target: &[u8], unprotected_header: &Header, protected_header: &Header, ) -> Vec { // We simply append the key behind the data. let mut tag = target.to_vec(); - tag.append(&mut key.key.to_vec()); + tag.append(&mut get_symmetric_key_value(key)); tag } fn verify( - key: &Self::VerifyKey, + key: &CoseKey, tag: &[u8], maced_data: &[u8], unprotected_header: &Header, protected_header: &ProtectedHeader, ) -> Result<(), CoseCipherError> { - if protected_header.header.key_id == key.kid + if protected_header.header.key_id == key.key_id && tag == Self::compute( key, @@ -308,24 +265,17 @@ impl CoseMacCipher for FakeCrypto { Err(CoseCipherError::VerificationFailure) } } - - fn set_headers( - key: &Self::ComputeKey, - unprotected_header: &mut Header, - protected_header: &mut Header, - rng: RNG, - ) -> Result<(), CoseCipherError> { - Self::set_headers_common(&key, unprotected_header, protected_header) - } } impl MultipleEncryptCipher for FakeCrypto { - fn generate_cek(rng: &mut RNG) -> Self::EncryptKey { + fn generate_cek(rng: &mut RNG) -> CoseKey { let mut key = [0; 5]; let mut kid = [0; 2]; rng.fill_bytes(&mut key); rng.fill_bytes(&mut kid); - FakeKey { key, kid } + CoseKeyBuilder::new_symmetric_key(key.to_vec()) + .key_id(kid.to_vec()) + .build() } } diff --git a/src/lib.rs b/src/lib.rs index 30ad8c6..576f740 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,7 +87,7 @@ //! of the token, using an existing cipher of type `FakeCrypto`[^cipher]: //! ``` //! # use ciborium::value::Value; -//! # use coset::{AsCborValue, CoseKey, CoseKeyBuilder, Header, Label, ProtectedHeader}; +//! # use coset::{AsCborValue, CoseKey, CoseKeyBuilder, Header, iana, Label, ProtectedHeader}; //! # use coset::cwt::{ClaimsSetBuilder, Timestamp}; //! # use coset::iana::{Algorithm, CwtClaimName}; //! # use rand::{CryptoRng, RngCore}; @@ -95,27 +95,25 @@ //! # use dcaf::common::cbor_values::{ByteString, ProofOfPossessionKey}; //! # use dcaf::common::cbor_values::ProofOfPossessionKey::PlainCoseKey; //! # use dcaf::error::{AccessTokenError, CoseCipherError}; -//! # use dcaf::token::ToCoseKey; +//! use dcaf::token::CoseCipher; //! -//! #[derive(Clone)] -//! # pub(crate) struct FakeKey { -//! # key: [u8; 5], -//! # kid: [u8; 2], -//! # } -//! # -//! # impl ToCoseKey for FakeKey { -//! # fn to_cose_key(&self) -> CoseKey { -//! # CoseKeyBuilder::new_symmetric_key(self.key.to_vec()) -//! # .key_id(self.kid.to_vec()) -//! # .build() -//! # } -//! # } -//! # //! # struct FakeCrypto {} //! # //! # #[derive(Clone, Copy)] //! # pub(crate) struct FakeRng; //! # +//! # fn get_k_from_key(key: &CoseKey) -> Option> { +//! # const K_PARAM: i64 = iana::SymmetricKeyParameter::K as i64; +//! # for (label, value) in key.params.iter() { +//! # if let Label::Int(K_PARAM) = label { +//! # if let Value::Bytes(k_val) = value { +//! # return Some(k_val.clone()); +//! # } +//! # } +//! # } +//! # None +//! # } +//! # //! # impl RngCore for FakeRng { //! # fn next_u32(&mut self) -> u32 { //! # 0 @@ -137,20 +135,11 @@ //! # //! # impl CryptoRng for FakeRng {} //! # -//! # /// Implements basic operations from the [`CoseSignCipher`](crate::token::CoseSignCipher) trait -//! # /// without actually using any "real" cryptography. -//! # /// This is purely to be used for testing and obviously offers no security at all. -//! # impl CoseSignCipher for FakeCrypto { +//! # impl CoseCipher for FakeCrypto { //! # type Error = String; -//! # type SignKey = FakeKey; -//! # type VerifyKey = Self::SignKey; //! # -//! # fn set_headers( -//! # key: &FakeKey, -//! # unprotected_header: &mut Header, -//! # protected_header: &mut Header, -//! # rng: RNG -//! # ) -> Result<(), CoseCipherError> { +//! # fn set_headers(key: &CoseKey, unprotected_header: &mut Header, protected_header: &mut Header, rng: RNG) -> Result<(), CoseCipherError> { +//! # // We have to later verify these headers really are used. //! # if let Some(label) = unprotected_header //! # .rest //! # .iter() @@ -161,28 +150,31 @@ //! # if protected_header.alg != None { //! # return Err(CoseCipherError::existing_header("alg")); //! # } -//! # if !protected_header.key_id.is_empty() { -//! # return Err(CoseCipherError::existing_header("key_id")); -//! # } //! # unprotected_header.rest.push((Label::Int(47), Value::Null)); //! # protected_header.alg = Some(coset::Algorithm::Assigned(Algorithm::Direct)); -//! # protected_header.key_id = key.kid.to_vec(); //! # Ok(()) //! # } +//! # } +//! # +//! # /// Implements basic operations from the [`CoseSignCipher`](crate::token::CoseSignCipher) trait +//! # /// without actually using any "real" cryptography. +//! # /// This is purely to be used for testing and obviously offers no security at all. +//! # impl CoseSignCipher for FakeCrypto { //! # fn sign( -//! # key: &Self::SignKey, +//! # key: &CoseKey, //! # target: &[u8], //! # unprotected_header: &Header, //! # protected_header: &Header, //! # ) -> Vec { //! # // We simply append the key behind the data. //! # let mut signature = target.to_vec(); -//! # signature.append(&mut key.key.to_vec()); +//! # let k = get_k_from_key(key); +//! # signature.append(&mut k.expect("k must be present in key!")); //! # signature //! # } //! # //! # fn verify( -//! # key: &Self::VerifyKey, +//! # key: &CoseKey, //! # signature: &[u8], //! # signed_data: &[u8], //! # unprotected_header: &Header, @@ -190,13 +182,13 @@ //! # unprotected_signature_header: Option<&Header>, //! # protected_signature_header: Option<&ProtectedHeader>, //! # ) -> Result<(), CoseCipherError> { -//! # let matching_kid = if let Some(protected) = protected_signature_header { -//! # protected.header.key_id == key.kid -//! # } else { -//! # protected_header.header.key_id == key.kid -//! # }; -//! # let signed_again = Self::sign(key, signed_data, unprotected_header, &protected_header.header); -//! # if matching_kid && signed_again == signature +//! # if signature +//! # == Self::sign( +//! # key, +//! # signed_data, +//! # unprotected_header, +//! # &protected_header.header, +//! # ) //! # { //! # Ok(()) //! # } else { @@ -206,12 +198,11 @@ //! # } //! //! let rng = FakeRng; -//! let key = FakeKey { key: [1,2,3,4,5], kid: [0xDC, 0xAF]}; -//! let cose_key: CoseKey = key.to_cose_key(); +//! let key = CoseKeyBuilder::new_symmetric_key(vec![1,2,3,4,5]).key_id(vec![0xDC, 0xAF]).build(); //! let claims = ClaimsSetBuilder::new() //! .audience(String::from("coaps://rs.example.com")) //! .issuer(String::from("coaps://as.example.com")) -//! .claim(CwtClaimName::Cnf, cose_key.to_cbor_value()?) +//! .claim(CwtClaimName::Cnf, key.clone().to_cbor_value()?) //! .build(); //! let token = sign_access_token::(&key, claims, None, None, None, rng)?; //! assert!(verify_access_token::(&key, &token, None).is_ok()); @@ -295,13 +286,13 @@ //! # COSE Cipher //! As mentioned before, cryptographic functions are outside the scope of this crate. //! For this reason, the various COSE cipher traits exist; namely, -//! [`CoseEncryptCipher`](core::token::CoseEncryptCipher), [`CoseSignCipher`](core::token::CoseSignCipher), -//! and [`CoseMacCipher`](core::token::CoseMacCipher), each implementing +//! [`CoseEncryptCipher`](token::CoseEncryptCipher), [`CoseSignCipher`](token::CoseSignCipher), +//! and [`CoseMacCipher`](token::CoseMacCipher), each implementing //! a corresponding COSE operation as specified in sections 4, 5, and 6 of //! [RFC 8152](https://www.rfc-editor.org/rfc/rfc8152). -//! There are also the traits [`MultipleEncryptCipher`](core::token::MultipleEncryptCipher), -//! [`MultipleSignCipher`](core::token::MultipleSignCipher), and -//! [`MultipleMacCipher`](core::token::MultipleMacCipher), +//! There are also the traits [`MultipleEncryptCipher`](token::MultipleEncryptCipher), +//! [`MultipleSignCipher`](token::MultipleSignCipher), and +//! [`MultipleMacCipher`](token::MultipleMacCipher), //! which are used for creating tokens intended for multiple recipients. //! //! Note that these ciphers *don't* need to wrap their results in, e.g., diff --git a/src/token/mod.rs b/src/token/mod.rs index ca97ad0..0bd1663 100644 --- a/src/token/mod.rs +++ b/src/token/mod.rs @@ -17,7 +17,7 @@ //! library, since much of this just builds on COSE functionality and isn't ACE-OAuth specific. //! //! In order to use any of these methods, you will need to provide a cipher which handles -//! the cryptographic operations by implementingeither [`CoseEncryptCipher`], +//! the cryptographic operations by implementing either [`CoseEncryptCipher`], //! [`CoseMacCipher`] or [`CoseSignCipher`], depending on the intended operation. //! If you plan to support `CoseEncrypt` or `CoseSign` rather than just `CoseEncrypt0` or //! `CoseSign1` (i.e., if you have multiple recipients with separate keys), you will also need to @@ -31,7 +31,7 @@ //! # // TODO: There's really too much hidden code here. Should be heavily refactored once we have //! # // crypto implementations available. Same goes for crate-level docs. //! # use ciborium::value::Value; -//! # use coset::{AsCborValue, CoseKey, CoseKeyBuilder, Header, Label, ProtectedHeader}; +//! # use coset::{AsCborValue, CoseKey, CoseKeyBuilder, Header, iana, Label, ProtectedHeader}; //! # use coset::cwt::{ClaimsSetBuilder, Timestamp}; //! # use coset::iana::{Algorithm, CwtClaimName}; //! # use rand::{CryptoRng, RngCore}; @@ -39,24 +39,22 @@ //! # use dcaf::common::cbor_values::{ByteString, ProofOfPossessionKey}; //! # use dcaf::common::cbor_values::ProofOfPossessionKey::PlainCoseKey; //! # use dcaf::error::{AccessTokenError, CoseCipherError}; -//! # use dcaf::token::ToCoseKey; +//! use dcaf::token::CoseCipher; //! -//! #[derive(Clone)] -//! # pub(crate) struct FakeKey { -//! # key: [u8; 5], -//! # kid: [u8; 2], -//! # } +//! # struct FakeCrypto {} //! # -//! # impl ToCoseKey for FakeKey { -//! # fn to_cose_key(&self) -> CoseKey { -//! # CoseKeyBuilder::new_symmetric_key(self.key.to_vec()) -//! # .key_id(self.kid.to_vec()) -//! # .build() +//! # fn get_k_from_key(key: &CoseKey) -> Option> { +//! # const K_PARAM: i64 = iana::SymmetricKeyParameter::K as i64; +//! # for (label, value) in key.params.iter() { +//! # if let Label::Int(K_PARAM) = label { +//! # if let Value::Bytes(k_val) = value { +//! # return Some(k_val.clone()); +//! # } +//! # } //! # } +//! # None //! # } //! # -//! # struct FakeCrypto {} -//! # //! # #[derive(Clone, Copy)] //! # pub(crate) struct FakeRng; //! # @@ -81,20 +79,11 @@ //! # //! # impl CryptoRng for FakeRng {} //! # -//! # /// Implements basic operations from the [`CoseSignCipher`] trait without actually using any -//! # /// "real" cryptography. -//! # /// This is purely to be used for testing and obviously offers no security at all. -//! # impl CoseSignCipher for FakeCrypto { +//! # impl CoseCipher for FakeCrypto { //! # type Error = String; -//! # type SignKey = FakeKey; -//! # type VerifyKey = Self::SignKey; //! # -//! # fn set_headers( -//! # key: &FakeKey, -//! # unprotected_header: &mut Header, -//! # protected_header: &mut Header, -//! # rng: RNG -//! # ) -> Result<(), CoseCipherError> { +//! # fn set_headers(key: &CoseKey, unprotected_header: &mut Header, protected_header: &mut Header, rng: RNG) -> Result<(), CoseCipherError> { +//! # // We have to later verify these headers really are used. //! # if let Some(label) = unprotected_header //! # .rest //! # .iter() @@ -105,28 +94,31 @@ //! # if protected_header.alg != None { //! # return Err(CoseCipherError::existing_header("alg")); //! # } -//! # if !protected_header.key_id.is_empty() { -//! # return Err(CoseCipherError::existing_header("key_id")); -//! # } //! # unprotected_header.rest.push((Label::Int(47), Value::Null)); //! # protected_header.alg = Some(coset::Algorithm::Assigned(Algorithm::Direct)); -//! # protected_header.key_id = key.kid.to_vec(); //! # Ok(()) //! # } +//! # } +//! # +//! # /// Implements basic operations from the [`CoseSignCipher`](crate::token::CoseSignCipher) trait +//! # /// without actually using any "real" cryptography. +//! # /// This is purely to be used for testing and obviously offers no security at all. +//! # impl CoseSignCipher for FakeCrypto { //! # fn sign( -//! # key: &Self::SignKey, +//! # key: &CoseKey, //! # target: &[u8], //! # unprotected_header: &Header, //! # protected_header: &Header, //! # ) -> Vec { //! # // We simply append the key behind the data. //! # let mut signature = target.to_vec(); -//! # signature.append(&mut key.key.to_vec()); +//! # let k = get_k_from_key(key); +//! # signature.append(&mut k.expect("k must be present in key!")); //! # signature //! # } //! # //! # fn verify( -//! # key: &Self::VerifyKey, +//! # key: &CoseKey, //! # signature: &[u8], //! # signed_data: &[u8], //! # unprotected_header: &Header, @@ -134,13 +126,13 @@ //! # unprotected_signature_header: Option<&Header>, //! # protected_signature_header: Option<&ProtectedHeader>, //! # ) -> Result<(), CoseCipherError> { -//! # let matching_kid = if let Some(protected) = protected_signature_header { -//! # protected.header.key_id == key.kid -//! # } else { -//! # protected_header.header.key_id == key.kid -//! # }; -//! # let signed_again = Self::sign(key, signed_data, unprotected_header, &protected_header.header); -//! # if matching_kid && signed_again == signature +//! # if signature +//! # == Self::sign( +//! # key, +//! # signed_data, +//! # unprotected_header, +//! # &protected_header.header, +//! # ) //! # { //! # Ok(()) //! # } else { @@ -150,12 +142,11 @@ //! # } //! //! let rng = FakeRng; -//! let key = FakeKey { key: [1,2,3,4,5], kid: [0xDC, 0xAF]}; -//! let cose_key: CoseKey = key.to_cose_key(); +//! let key = CoseKeyBuilder::new_symmetric_key(vec![1,2,3,4,5]).key_id(vec![0xDC, 0xAF]).build(); //! let claims = ClaimsSetBuilder::new() //! .audience(String::from("coaps://rs.example.com")) //! .issuer(String::from("coaps://as.example.com")) -//! .claim(CwtClaimName::Cnf, cose_key.to_cbor_value()?) +//! .claim(CwtClaimName::Cnf, key.clone().to_cbor_value()?) //! .build(); //! let token = sign_access_token::(&key, claims, None, None, None, rng)?; //! assert!(verify_access_token::(&key, &token, None).is_ok()); @@ -182,95 +173,76 @@ use crate::error::{AccessTokenError, CoseCipherError, MultipleCoseError}; #[cfg(test)] mod tests; -/// Trait for keys which can be converted to [CoseKey]s from a reference of the original type. -pub trait ToCoseKey { - /// Converts a reference of itself to a [CoseKey]. +/// Trait for common parts required for [`CoseSignCipher`], [`CoseEncryptCipher`] +/// and [`CoseMacCipher`]. +pub trait CoseCipher { + /// Error type that this cipher uses in [`Result`]s returned by cryptographic operations. + type Error: Display + Debug; + + /// Sets headers specific to this cipher by adding new header fields to the given + /// `unprotected_header` and `protected_header`. + /// + /// The given `key` may be used to extract information for the headers (e.g., the key ID) + /// and `rng` may be used to generate random values for the headers (e.g., an IV). + /// + /// Before actually changing the headers, it will be verified that none of the header fields + /// that are about to be set are already set, so as not to overwrite them. In such a + /// case, an error is returned. /// - /// Note that this may lead to fields of the key being copied, - /// as we merely pass a reference in, even though [CoseKey] is not associated with any lifetime. - fn to_cose_key(&self) -> CoseKey; + /// This will usually not be called by users of `dcaf-rs`, but instead by access methods + /// such as [`encrypt_access_token`], which will later pass it to [coset]'s methods. + /// + /// # Errors + /// - When the fields that this method would set on the given headers are already set. + /// + /// # Example + /// Let's say our cipher needs to set the content type to + /// [`Cbor`](coset::iana::CoapContentFormat::Cbor) (in the unprotected header) + /// and the key ID to the ID of the passed in `key` (in the protected header). + /// Our implementation would first need to verify that these + /// header fields haven't already been set, then actually set them, so an implementation + /// of this function might look like the following: + /// ```ignore + /// fn set_headers( + /// key: &FakeKey, + /// unprotected_header: &mut Header, + /// protected_header: &mut Header, + /// rng: RNG + /// ) -> Result<(), CoseCipherError> { + /// if unprotected_header.content_type.is_some() { + /// return Err(CoseCipherError::existing_header("content_type")); + /// } + /// if !protected_header.key_id.is_empty() { + /// return Err(CoseCipherError::existing_header("kid")); + /// } + /// unprotected_header.content_type = Some(ContentType::Assigned(coset::iana::CoapContentFormat::Cbor)); + /// protected_header.key_id = key.kid.to_vec(); + /// Ok(()) + /// } + /// ``` + fn set_headers( + key: &CoseKey, + unprotected_header: &mut Header, + protected_header: &mut Header, + rng: RNG, + ) -> Result<(), CoseCipherError>; } // TODO: Examples in here are currently either not run or do not exist because they require too much // setup (see crate-level docs). This should be fixed once we have cipher implementations. -macro_rules! add_common_cipher_functionality { - [$a:ty] => { - /// Error type that this cipher uses in [`Result`]s returned by cryptographic operations. - type Error: Display + Debug; - - /// Sets headers specific to this cipher by adding new header fields to the given - /// `unprotected_header` and `protected_header`. - /// - /// The given `key` may be used to extract information for the headers (e.g., the key ID) - /// and `rng` may be used to generate random values for the headers (e.g., an IV). - /// - /// Before actually changing the headers, it will be verified that none of the header fields - /// that are about to be set are already set, so as not to overwrite them. In such a - /// case, an error is returned. - /// - /// This will usually not be called by users of `dcaf-rs`, but instead by access methods - /// such as [`encrypt_access_token`], which will later pass it to [coset]'s methods. - /// - /// # Errors - /// - When the fields that this method would set on the given headers are already set. - /// - /// # Example - /// Let's say our cipher needs to set the content type to - /// [`Cbor`](coset::iana::CoapContentFormat::Cbor) (in the unprotected header) - /// and the key ID to the ID of the passed in `key` (in the protected header). - /// Our implementation would first need to verify that these - /// header fields haven't already been set, then actually set them, so an implementation - /// of this function might look like the following: - /// ```ignore - /// fn set_headers( - /// key: &FakeKey, - /// unprotected_header: &mut Header, - /// protected_header: &mut Header, - /// rng: RNG - /// ) -> Result<(), CoseCipherError> { - /// if unprotected_header.content_type.is_some() { - /// return Err(CoseCipherError::existing_header("content_type")); - /// } - /// if !protected_header.key_id.is_empty() { - /// return Err(CoseCipherError::existing_header("kid")); - /// } - /// unprotected_header.content_type = Some(ContentType::Assigned(coset::iana::CoapContentFormat::Cbor)); - /// protected_header.key_id = key.kid.to_vec(); - /// Ok(()) - /// } - /// ``` - fn set_headers( - key: &$a, - unprotected_header: &mut Header, - protected_header: &mut Header, - rng: RNG - ) -> Result<(), CoseCipherError>; - } -} - /// Provides basic operations for encrypting and decrypting COSE structures. /// /// This will be used by [`encrypt_access_token`] and [`decrypt_access_token`] (as well as the /// variants for multiple recipients: [`encrypt_access_token_multiple`] /// and [`decrypt_access_token_multiple`]) to apply the /// corresponding cryptographic operations to the constructed token bytestring. -/// The [`set_headers` method](CoseEncryptCipher::set_headers) can be used to set parameters this +/// The [`set_headers` method](CoseCipher::set_headers) can be used to set parameters this /// cipher requires to be set. -pub trait CoseEncryptCipher { - /// Type of the encryption key. Needs to be serializable to a vector of bytes in case - /// [`encrypt_access_token_multiple`] is used, in which we need to serialize the - /// Key Encryption Keys. - type EncryptKey: ToCoseKey + Into>; - - /// Type of the decryption key. Needs to be deserializable from a vector of bytes in case - /// [`decrypt_access_token_multiple`] is used, in which we need to deserialize the - /// Key Encryption Keys. - type DecryptKey: ToCoseKey + TryFrom>; - +pub trait CoseEncryptCipher: CoseCipher { /// Encrypts the `plaintext` and `aad` with the given `key`, returning the result. fn encrypt( - key: &Self::EncryptKey, + key: &CoseKey, plaintext: &[u8], aad: &[u8], protected_header: &Header, @@ -282,14 +254,12 @@ pub trait CoseEncryptCipher { /// # Errors /// If the `ciphertext` and `aad` are invalid, i.e., can't be decrypted. fn decrypt( - key: &Self::DecryptKey, + key: &CoseKey, ciphertext: &[u8], aad: &[u8], unprotected_header: &Header, protected_header: &ProtectedHeader, ) -> Result, CoseCipherError>; - - add_common_cipher_functionality![Self::EncryptKey]; } /// Intended for ciphers which can encrypt for multiple recipients. @@ -301,7 +271,7 @@ pub trait MultipleEncryptCipher: CoseEncryptCipher { /// The content of the `CoseEncrypt` will then be encrypted with the key, while each recipient /// will be encrypted with a corresponding Key Encryption Key (KEK) provided by the caller /// of [`encrypt_access_token_multiple`]. - fn generate_cek(rng: &mut RNG) -> Self::EncryptKey; + fn generate_cek(rng: &mut RNG) -> CoseKey; } /// Provides basic operations for signing and verifying COSE structures. @@ -310,18 +280,12 @@ pub trait MultipleEncryptCipher: CoseEncryptCipher { /// equivalents for multiple recipients: [`sign_access_token_multiple`] and /// [`verify_access_token_multiple`]) to apply the /// corresponding cryptographic operations to the constructed token bytestring. -/// The [`set_headers` method](CoseSignCipher::set_headers) can be used to set parameters +/// The [`set_headers` method](CoseCipher::set_headers) can be used to set parameters /// this cipher requires to be set. -pub trait CoseSignCipher { - /// Type of the key used to create signatures. - type SignKey: ToCoseKey; - - /// Type of the key used to verify signatures. - type VerifyKey: ToCoseKey; - +pub trait CoseSignCipher: CoseCipher { /// Cryptographically signs the `target` value with the `key` and returns the signature. fn sign( - key: &Self::SignKey, + key: &CoseKey, target: &[u8], unprotected_header: &Header, protected_header: &Header, @@ -339,7 +303,7 @@ pub trait CoseSignCipher { /// # Errors /// If the `signature` is invalid or does not belong to the `signed_data`. fn verify( - key: &Self::VerifyKey, + key: &CoseKey, signature: &[u8], signed_data: &[u8], unprotected_header: &Header, @@ -347,8 +311,6 @@ pub trait CoseSignCipher { unprotected_signature_header: Option<&Header>, protected_signature_header: Option<&ProtectedHeader>, ) -> Result<(), CoseCipherError>; - - add_common_cipher_functionality![Self::SignKey]; } /// Marker trait intended for ciphers which can create signatures for multiple recipients. @@ -359,16 +321,10 @@ pub trait MultipleSignCipher: CoseSignCipher {} /// Provides basic operations for generating and verifying MAC tags for COSE structures. /// /// This trait is currently not used by any access token function. -pub trait CoseMacCipher { - /// Type of the key used to compute MAC tags. - type ComputeKey: ToCoseKey; - - /// Type of the key used to verify MAC tags. - type VerifyKey: ToCoseKey; - +pub trait CoseMacCipher: CoseCipher { /// Generates a MAC tag for the given `target` with the given `key` and returns it. fn compute( - key: &Self::ComputeKey, + key: &CoseKey, target: &[u8], unprotected_header: &Header, protected_header: &Header, @@ -379,14 +335,12 @@ pub trait CoseMacCipher { /// # Errors /// If the `tag` is invalid or does not belong to the `maced_data`. fn verify( - key: &Self::VerifyKey, + key: &CoseKey, tag: &[u8], maced_data: &[u8], unprotected_header: &Header, protected_header: &ProtectedHeader, ) -> Result<(), CoseCipherError>; - - add_common_cipher_functionality![Self::ComputeKey]; } /// Marker trait intended for ciphers which can create MAC tags for multiple recipients. @@ -433,7 +387,7 @@ macro_rules! prepare_headers { /// assert_eq!(decrypt_access_token::(&key, &token, None)?, claims); /// ``` pub fn encrypt_access_token( - key: &T::EncryptKey, + key: &CoseKey, claims: ClaimsSet, external_aad: Option<&[u8]>, unprotected_header: Option
, @@ -485,7 +439,7 @@ where /// )?; /// ``` pub fn encrypt_access_token_multiple( - keys: Vec<&T::EncryptKey>, + keys: Vec<&CoseKey>, claims: ClaimsSet, external_aad: Option<&[u8]>, unprotected_header: Option
, @@ -507,7 +461,7 @@ where external_aad.unwrap_or(&[0; 0]), |payload, aad| T::encrypt(&key, payload, aad, &protected, &unprotected), ); - let serialized_key: Vec = key.into(); + let serialized_key: Vec = key.to_vec().map_err(AccessTokenError::CoseError)?; for rec_key in keys { let (rec_unprotected, rec_protected) = prepare_headers!(rec_key, None, None, &mut rng, T)?; builder = builder.add_recipient( @@ -556,7 +510,7 @@ where /// assert!(verify_access_token::(&key, &token, None).is_ok()); /// ``` pub fn sign_access_token( - key: &T::SignKey, + key: &CoseKey, claims: ClaimsSet, external_aad: Option<&[u8]>, unprotected_header: Option
, @@ -607,7 +561,7 @@ where /// )?; /// ``` pub fn sign_access_token_multiple( - keys: Vec<&T::SignKey>, + keys: Vec<&CoseKey>, claims: ClaimsSet, external_aad: Option<&[u8]>, unprotected_header: Option
, @@ -711,7 +665,7 @@ pub fn get_token_headers(token: &ByteString) -> Option<(Header, ProtectedHeader) /// - When there's a verification error coming from the cipher `T` /// (e.g., if the `token`'s data does not match its signature). pub fn verify_access_token( - key: &T::VerifyKey, + key: &CoseKey, token: &ByteString, external_aad: Option<&[u8]>, ) -> Result<(), AccessTokenError> @@ -752,7 +706,7 @@ where /// - When there's a verification error coming from the cipher `T` /// (e.g., if the `token`'s data does not match its signature). pub fn verify_access_token_multiple( - key: &T::VerifyKey, + key: &CoseKey, token: &ByteString, external_aad: Option<&[u8]>, ) -> Result<(), AccessTokenError> @@ -760,14 +714,14 @@ where T: CoseSignCipher, { let sign = CoseSign::from_slice(token.as_slice()).map_err(AccessTokenError::CoseError)?; - let kid = key.to_cose_key().key_id; + let kid = &key.key_id; let (unprotected, protected) = get_token_headers(token).ok_or(AccessTokenError::UnknownCoseStructure)?; let matching = sign .signatures .iter() .enumerate() - .filter(|(_, s)| s.unprotected.key_id == kid || s.protected.header.key_id == kid) + .filter(|(_, s)| &s.unprotected.key_id == kid || &s.protected.header.key_id == kid) .map(|(i, _)| i); let mut matching_kid = false; // We iterate over each signature whose kid matches until it completes successfully. @@ -817,7 +771,7 @@ where /// - When the deserialized and decrypted [`CoseEncrypt0`] structure does not contain a valid /// [`ClaimsSet`]. pub fn decrypt_access_token( - key: &T::DecryptKey, + key: &CoseKey, token: &ByteString, external_aad: Option<&[u8]>, ) -> Result> @@ -854,7 +808,7 @@ where /// - When the [`CoseEncrypt`] contains either multiple matching recipients or none at all for /// the given `kek`. pub fn decrypt_access_token_multiple( - kek: &K::DecryptKey, + kek: &CoseKey, token: &ByteString, external_aad: Option<&[u8]>, ) -> Result>> @@ -866,8 +820,7 @@ where let (unprotected, protected) = get_token_headers(token).ok_or(AccessTokenError::UnknownCoseStructure)?; let aad = external_aad.unwrap_or(&[0; 0]); - let cose_kek: CoseKey = kek.to_cose_key(); - let kek_id = cose_kek.key_id.as_slice(); + let kek_id = kek.key_id.as_slice(); // One of the recipient structures should contain CEK encrypted with our KEK. let recipients = encrypt .recipients @@ -884,7 +837,7 @@ where if let Some(content_key_result) = content_keys.next() { if content_keys.next().is_none() { let content_key = content_key_result.map_err(CoseCipherError::from_kek_error)?; - let target_key = C::DecryptKey::try_from(content_key) + let target_key = CoseKey::from_slice(&content_key) .map_err(|_| CoseCipherError::DecryptionFailure)?; // TODO: Verify protected header let result = encrypt diff --git a/src/token/tests.rs b/src/token/tests.rs index 5a9415a..c0e1c83 100644 --- a/src/token/tests.rs +++ b/src/token/tests.rs @@ -20,19 +20,23 @@ use coset::cwt::ClaimsSetBuilder; use coset::iana::{Algorithm, CoapContentFormat, CwtClaimName}; use coset::{AsCborValue, CoseKey, CoseKeyBuilder, CoseMac0Builder, HeaderBuilder}; -use crate::common::test_helper::{FakeCrypto, FakeKey, FakeRng}; +use crate::common::test_helper::{FakeCrypto, FakeRng}; use crate::error::CoseCipherError; use super::*; /// Generates a test key with content `[1,2,3,4,5]` and key id `[0xDC, 0xAF]`. -fn example_key_one() -> FakeKey { - FakeKey::try_from(vec![1, 2, 3, 4, 5, 0xDC, 0xAF]).expect("invalid test key") +fn example_key_one() -> CoseKey { + CoseKeyBuilder::new_symmetric_key(vec![1, 2, 3, 4, 5]) + .key_id(vec![0xDC, 0xAF]) + .build() } /// Generates a test key with content `[10, 9, 8, 7, 6]` and key id `[0xCA, 0xFE]`. -fn example_key_two() -> FakeKey { - FakeKey::try_from(vec![10, 9, 8, 7, 6, 0xCA, 0xFE]).expect("invalid test key") +fn example_key_two() -> CoseKey { + CoseKeyBuilder::new_symmetric_key(vec![10, 9, 8, 7, 6, 0xCA, 0xFE]) + .key_id(vec![0xCA, 0xFE]) + .build() } fn example_headers() -> (Header, Header) { @@ -60,10 +64,10 @@ fn example_aad() -> Vec { } fn example_claims( - key: CoseKey, -) -> Result::Error>> { + key: &CoseKey, +) -> Result::Error>> { Ok(ClaimsSetBuilder::new() - .claim(CwtClaimName::Cnf, key.to_cbor_value()?) + .claim(CwtClaimName::Cnf, key.clone().to_cbor_value()?) .build()) } @@ -104,8 +108,7 @@ fn assert_header_is_part_of(subset: &Header, superset: &Header) { } #[test] -fn test_get_headers_enc() -> Result<(), AccessTokenError<::Error>> -{ +fn test_get_headers_enc() -> Result<(), AccessTokenError<::Error>> { let (unprotected_header, protected_header) = example_headers(); let enc_test = CoseEncrypt0Builder::new() .unprotected(unprotected_header.clone()) @@ -122,7 +125,7 @@ fn test_get_headers_enc() -> Result<(), AccessTokenError< Result<(), AccessTokenError<::Error>> { +fn test_get_headers_sign() -> Result<(), AccessTokenError<::Error>> { let (unprotected_header, protected_header) = example_headers(); let sign_test = CoseSign1Builder::new() .unprotected(unprotected_header.clone()) @@ -139,7 +142,7 @@ fn test_get_headers_sign() -> Result<(), AccessTokenError< Result<(), AccessTokenError<::Error>> { +fn test_get_headers_mac() -> Result<(), AccessTokenError<::Error>> { let (unprotected_header, protected_header) = example_headers(); let mac_test = CoseMac0Builder::new() .unprotected(unprotected_header.clone()) @@ -170,11 +173,10 @@ fn test_get_headers_invalid() { } #[test] -fn test_encrypt_decrypt() -> Result<(), AccessTokenError<::Error>> -{ +fn test_encrypt_decrypt() -> Result<(), AccessTokenError<::Error>> { let key = example_key_one(); let (unprotected_header, protected_header) = example_headers(); - let claims = example_claims(key.to_cose_key())?; + let claims = example_claims(&key)?; let aad = example_aad(); let rng = FakeRng; let encrypted = encrypt_access_token::( @@ -196,15 +198,18 @@ fn test_encrypt_decrypt() -> Result<(), AccessTokenError< Result<(), AccessTokenError<::Error>> { +fn test_encrypt_decrypt_multiple() -> Result<(), AccessTokenError<::Error>> +{ const AUDIENCE: &str = "example_aud"; let (unprotected_header, protected_header) = example_headers(); let key1 = example_key_one(); let key2 = example_key_two(); - let invalid_key1 = FakeKey::try_from(vec![0, 0, 0, 0, 0, 0, 0]).expect("invalid test key"); - let invalid_key2 = - FakeKey::try_from(vec![0, 0, 0, 0, 0, 0xDC, 0xAF]).expect("invalid test key"); + let invalid_key1 = CoseKeyBuilder::new_symmetric_key(vec![0; 5]) + .key_id(vec![0, 0]) + .build(); + let invalid_key2 = CoseKeyBuilder::new_symmetric_key(vec![0; 5]) + .key_id(vec![0xDC, 0xAF]) + .build(); let rng = FakeRng; let aad = example_aad(); // Using example_claims doesn't make sense, since they contain a cnf for the key, @@ -255,7 +260,7 @@ fn test_encrypt_decrypt_multiple( #[test] fn test_encrypt_decrypt_match_multiple( -) -> Result<(), AccessTokenError<::Error>> { +) -> Result<(), AccessTokenError<::Error>> { let (unprotected_header, protected_header) = example_headers(); let key1 = example_key_one(); let rng = FakeRng; @@ -284,11 +289,11 @@ fn test_encrypt_decrypt_match_multiple( #[test] fn test_encrypt_decrypt_invalid_header( -) -> Result<(), AccessTokenError<::Error>> { +) -> Result<(), AccessTokenError<::Error>> { let key = example_key_one(); let (unprotected_header, protected_header) = example_headers(); let (unprotected_invalid, protected_invalid) = example_invalid_headers(); - let claims = example_claims(key.to_cose_key())?; + let claims = example_claims(&key)?; let aad = example_aad(); let rng = FakeRng; let encrypted = encrypt_access_token::( @@ -334,10 +339,10 @@ fn test_encrypt_decrypt_invalid_header( } #[test] -fn test_sign_verify() -> Result<(), AccessTokenError<::Error>> { +fn test_sign_verify() -> Result<(), AccessTokenError<::Error>> { let key = example_key_one(); let (unprotected_header, protected_header) = example_headers(); - let claims = example_claims(key.to_cose_key())?; + let claims = example_claims(&key)?; let aad = example_aad(); let rng = FakeRng; let signed = sign_access_token::( @@ -360,14 +365,16 @@ fn test_sign_verify() -> Result<(), AccessTokenError< Result<(), AccessTokenError<::Error>> -{ +fn test_sign_verify_multiple() -> Result<(), AccessTokenError<::Error>> { const AUDIENCE: &str = "example_aud"; let key1 = example_key_one(); let key2 = example_key_two(); - let invalid_key1 = FakeKey::try_from(vec![0, 0, 0, 0, 0, 0, 0]).expect("invalid test key"); - let invalid_key2 = - FakeKey::try_from(vec![0, 0, 0, 0, 0, 0xDC, 0xAF]).expect("invalid test key"); + let invalid_key1 = CoseKeyBuilder::new_symmetric_key(vec![0; 5]) + .key_id(vec![0, 0]) + .build(); + let invalid_key2 = CoseKeyBuilder::new_symmetric_key(vec![0; 5]) + .key_id(vec![0xDC, 0xAF]) + .build(); let (unprotected_header, protected_header) = example_headers(); let claims = ClaimsSetBuilder::new() .audience(AUDIENCE.to_string()) diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index e972bea..8819ebd 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -12,7 +12,7 @@ use ciborium::value::Value; use coset::cwt::{ClaimsSetBuilder, Timestamp}; use coset::iana::EllipticCurve::P_256; -use coset::iana::{Algorithm, CwtClaimName}; +use coset::iana::{Algorithm, CwtClaimName, EllipticCurve}; use coset::{ iana, CborSerializable, CoseKey, CoseKeyBuilder, Header, HeaderBuilder, KeyType, Label, ProtectedHeader, @@ -28,54 +28,31 @@ use dcaf::endpoints::token_req::{ TokenType, }; use dcaf::error::CoseCipherError; -use dcaf::token::{verify_access_token_multiple, ToCoseKey}; +use dcaf::token::CoseCipher; use dcaf::{sign_access_token, verify_access_token, CoseSignCipher}; -#[derive(Clone)] -pub(crate) struct EC2P256Key { - x: Vec, - y: Vec, +fn create_ec2p256_key(x: Vec, y: Vec) -> CoseKey { + CoseKeyBuilder::new_ec2_pub_key(P_256, x, y).build() } -impl ToCoseKey for EC2P256Key { - fn to_cose_key(&self) -> CoseKey { - CoseKeyBuilder::new_ec2_pub_key(P_256, self.x.to_vec(), self.y.to_vec()).build() - } -} - -impl TryFrom> for EC2P256Key { - type Error = String; - - fn try_from(value: Vec) -> Result { - let key = CoseKey::from_slice(value.as_slice()).map_err(|x| x.to_string())?; - assert_eq!(key.kty, KeyType::Assigned(iana::KeyType::EC2)); - assert_eq!( - get_param(Label::Int(iana::Ec2KeyParameter::Crv as i64), &key.params), - Some(Value::from(P_256 as u64)) - ); - - if let Some(Value::Bytes(x)) = - get_param(Label::Int(iana::Ec2KeyParameter::X as i64), &key.params) - { - if let Some(Value::Bytes(y)) = - get_param(Label::Int(iana::Ec2KeyParameter::Y as i64), &key.params) - { - return Ok(EC2P256Key { x, y }); +fn get_x_y_from_key(key: &CoseKey) -> (Vec, Vec) { + const X_PARAM: i64 = iana::Ec2KeyParameter::X as i64; + const Y_PARAM: i64 = iana::Ec2KeyParameter::Y as i64; + let mut x: Option> = None; + let mut y: Option> = None; + for (label, value) in key.params.iter() { + if let Label::Int(X_PARAM) = label { + if let Value::Bytes(x_val) = value { + x = Some(x_val.clone()); + } + } else if let Label::Int(Y_PARAM) = label { + if let Value::Bytes(y_val) = value { + y = Some(y_val.clone()); } } - return Err("x and y must be present in key as bytes".to_string()); - - fn get_param(label: Label, params: &Vec<(Label, Value)>) -> Option { - let mut iter = params.iter().filter(|x| x.0 == label); - iter.map(|x| x.1.clone()).next() - } - } -} - -impl From for Vec { - fn from(k: EC2P256Key) -> Self { - k.to_cose_key().to_vec().expect("couldn't serialize key") } + let test = x.and_then(|a| y.map(|b| (a, b))); + test.expect("X and Y value must be present in key!") } #[derive(Clone, Copy)] @@ -118,29 +95,52 @@ fn example_aad() -> Vec { #[derive(Copy, Clone)] pub(crate) struct FakeCrypto {} +impl CoseCipher for FakeCrypto { + type Error = String; + + fn set_headers( + key: &CoseKey, + unprotected_header: &mut Header, + protected_header: &mut Header, + rng: RNG, + ) -> Result<(), CoseCipherError> { + // We have to later verify these headers really are used. + if let Some(label) = unprotected_header + .rest + .iter() + .find(|x| x.0 == Label::Int(47)) + { + return Err(CoseCipherError::existing_header_label(&label.0)); + } + if protected_header.alg != None { + return Err(CoseCipherError::existing_header("alg")); + } + unprotected_header.rest.push((Label::Int(47), Value::Null)); + protected_header.alg = Some(coset::Algorithm::Assigned(Algorithm::Direct)); + Ok(()) + } +} + /// Implements basic operations from the [`CoseSign1Cipher`] trait without actually using any /// "real" cryptography. /// This is purely to be used for testing and obviously offers no security at all. impl CoseSignCipher for FakeCrypto { - type SignKey = EC2P256Key; - type VerifyKey = Self::SignKey; - type Error = String; - fn sign( - key: &Self::SignKey, + key: &CoseKey, target: &[u8], unprotected_header: &Header, protected_header: &Header, ) -> Vec { // We simply append the key behind the data. let mut signature = target.to_vec(); - signature.append(&mut key.x.to_vec()); - signature.append(&mut key.y.to_vec()); + let (x, y) = get_x_y_from_key(key); + signature.append(&mut x.clone()); + signature.append(&mut y.clone()); signature } fn verify( - key: &Self::VerifyKey, + key: &CoseKey, signature: &[u8], signed_data: &[u8], unprotected_header: &Header, @@ -161,28 +161,6 @@ impl CoseSignCipher for FakeCrypto { Err(CoseCipherError::VerificationFailure) } } - - fn set_headers( - key: &Self::SignKey, - unprotected_header: &mut Header, - protected_header: &mut Header, - rng: RNG, - ) -> Result<(), CoseCipherError> { - // We have to later verify these headers really are used. - if let Some(label) = unprotected_header - .rest - .iter() - .find(|x| x.0 == Label::Int(47)) - { - return Err(CoseCipherError::existing_header_label(&label.0)); - } - if protected_header.alg != None { - return Err(CoseCipherError::existing_header("alg")); - } - unprotected_header.rest.push((Label::Int(47), Value::Null)); - protected_header.alg = Some(coset::Algorithm::Assigned(Algorithm::Direct)); - Ok(()) - } } /// We assume the following scenario here: @@ -204,15 +182,16 @@ fn test_scenario() -> Result<(), String> { let scope = TextEncodedScope::try_from("first second").map_err(|x| x.to_string())?; assert!(scope.elements().eq(["first", "second"])); // Taken from RFC 8747, section 3.2. - let key = EC2P256Key { - x: hex::decode("d7cc072de2205bdc1537a543d53c60a6acb62eccd890c7fa27c9e354089bbe13") + let key = CoseKeyBuilder::new_ec2_pub_key( + P_256, + hex::decode("d7cc072de2205bdc1537a543d53c60a6acb62eccd890c7fa27c9e354089bbe13") .map_err(|x| x.to_string())?, - y: hex::decode("f95e1d4b851a2cc80fff87d8e23f22afb725d535e515d020731e79a3b4e47120") + hex::decode("f95e1d4b851a2cc80fff87d8e23f22afb725d535e515d020731e79a3b4e47120") .map_err(|x| x.to_string())?, - }; + ) + .build(); let (unprotected_headers, protected_headers) = example_headers(); - let mut crypto = FakeCrypto {}; let aad = example_aad(); let hint: AuthServerRequestCreationHint = AuthServerRequestCreationHint::builder() @@ -231,7 +210,7 @@ fn test_scenario() -> Result<(), String> { .client_nonce(nonce) .ace_profile() .client_id(client_id) - .req_cnf(PlainCoseKey(key.to_cose_key())) + .req_cnf(PlainCoseKey(key.clone())) .build() .map_err(|x| x.to_string())?; let result = pseudo_send_receive(request.clone())?; @@ -247,7 +226,7 @@ fn test_scenario() -> Result<(), String> { .issued_at(Timestamp::WholeSeconds(47)) .claim( CwtClaimName::Cnf, - PlainCoseKey(key.to_cose_key()).to_ciborium_value(), + PlainCoseKey(key.clone()).to_ciborium_value(), ) .build(), // TODO: Proper headers From de653aa5af4eb7477eafee2c09f1bf37e2bfe241 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Mon, 23 Jan 2023 12:33:20 +0100 Subject: [PATCH 11/12] docs: a few fixes for crate-level docs Co-authored-by: Jan Romann --- README.md | 124 +++++++++++++++++++---------------------------------- src/lib.rs | 2 +- 2 files changed, 46 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index febf1ed..47c3a33 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,11 @@ An implementation of the [ACE-OAuth framework (RFC 9200)](https://www.rfc-editor This crate implements the ACE-OAuth (Authentication and Authorization for Constrained Environments using the OAuth 2.0 Framework) framework as defined in [RFC 9200](https://www.rfc-editor.org/rfc/rfc9200). -Key features include CBOR-(de-)serializable data models such -as [`AccessTokenRequest`](https://docs.rs/dcaf/latest/dcaf/endpoints/token_req/struct.AccessTokenRequest.html), +Key features include CBOR-(de-)serializable data models such as [`AccessTokenRequest`](https://docs.rs/dcaf/latest/dcaf/endpoints/token_req/struct.AccessTokenRequest.html), as well as the possibility to create COSE encrypted/signed access tokens (as described in the standard) along with decryption/verification functions. Implementations of the cryptographic functions must be provided by the user by implementing -[`CoseEncryptCipher`](https://docs.rs/dcaf/latest/dcaf/token/trait.CoseEncryptCipher.html) -or [`CoseSignCipher`](https://docs.rs/dcaf/latest/dcaf/token/trait.CoseSignCipher.html). +[`CoseEncryptCipher`](https://docs.rs/dcaf/latest/dcaf/token/trait.CoseEncryptCipher.html) or [`CoseSignCipher`](https://docs.rs/dcaf/latest/dcaf/token/trait.CoseSignCipher.html). Note that actually transmitting the serialized values (e.g., via CoAP) or providing more complex features not mentioned in the ACE-OAuth RFC (e.g., a permission management system for @@ -26,8 +24,7 @@ This also applies to cryptographic functions, as mentioned in the previous parag The name DCAF was chosen because eventually, it's planned for this crate to support functionality from the [Delegated CoAP Authentication and Authorization Framework (DCAF)](https://dcaf.science/) -specified -in [`draft-gerdes-ace-dcaf-authorize`](https://datatracker.ietf.org/doc/html/draft-gerdes-ace-dcaf-authorize-04) +specified in [`draft-gerdes-ace-dcaf-authorize`](https://datatracker.ietf.org/doc/html/draft-gerdes-ace-dcaf-authorize-04) (which was specified prior to ACE-OAuth and inspired many design choices in it)--- specifically, it's planned to support using a CAM (Client Authorization Manager) instead of just a SAM (Server Authorization Manager), as is done in ACE-OAuth. @@ -39,91 +36,74 @@ As one of the possible use-cases for this crate is usage on constrained IoT devi requirements are minimal---as such, while `alloc` is still needed, this crate offers `no_std` support by omitting the default `std` feature. -# Usage - +## Usage ```toml [dependencies] dcaf = { version = "^0.3" } ``` - Or, if you plan to use this crate in a `no_std` environment: - ```toml [dependencies] dcaf = { version = "^0.3", default-features = false } ``` -# Example - +## Example As mentioned, the main features of this crate are ACE-OAuth data models and token creation/verification functions. We'll quickly introduce both of these here. -## Data models - +### Data models [For example](https://www.rfc-editor.org/rfc/rfc9200#figure-6), let's assume you (the client) want to request an access token from an Authorization Server. -For this, you'd need to create -an [`AccessTokenRequest`](https://docs.rs/dcaf/latest/dcaf/endpoints/token_req/struct.AccessTokenRequest.html), which -has to include at least a -`client_id`. We'll also specify an audience, a scope ( -using [`TextEncodedScope`](https://docs.rs/dcaf/latest/dcaf/common/scope/struct.TextEncodedScope.html)---note that -[binary-encoded scopes](https://docs.rs/dcaf/latest/dcaf/common/scope/struct.BinaryEncodedScope.html) -or [AIF-encoded scopes](https://docs.rs/dcaf/latest/dcaf/common/scope/struct.AifEncodedScope.html) would also work), as -well as a -[`ProofOfPossessionKey`](https://docs.rs/dcaf/latest/dcaf/common/cbor_values/enum.ProofOfPossessionKey.html) (the key -the access token should be bound to) in the `req_cnf` field. +For this, you'd need to create an [`AccessTokenRequest`](https://docs.rs/dcaf/latest/dcaf/endpoints/token_req/struct.AccessTokenRequest.html), which has to include at least a +`client_id`. We'll also specify an audience, a scope (using [`TextEncodedScope`](https://docs.rs/dcaf/latest/dcaf/common/scope/struct.TextEncodedScope.html)---note that +[binary-encoded scopes](https://docs.rs/dcaf/latest/dcaf/common/scope/struct.BinaryEncodedScope.html) or [AIF-encoded scopes](https://docs.rs/dcaf/latest/dcaf/common/scope/struct.AifEncodedScope.html) would also work), as well as a +[`ProofOfPossessionKey`](https://docs.rs/dcaf/latest/dcaf/common/cbor_values/enum.ProofOfPossessionKey.html) (the key the access token should be bound to) in the `req_cnf` field. Creating, serializing and then de-serializing such a structure would look like this: - ```rust use dcaf::{AccessTokenRequest, ToCborMap, ProofOfPossessionKey, TextEncodedScope}; let request = AccessTokenRequest::builder() -.client_id("myclient") -.audience("valve242") -.scope(TextEncodedScope::try_from("read") ? ) -.req_cnf(ProofOfPossessionKey::KeyId(base64::decode("6kg0dXJM13U") ? )) -.build() ?; + .client_id("myclient") + .audience("valve242") + .scope(TextEncodedScope::try_from("read")?) + .req_cnf(ProofOfPossessionKey::KeyId(base64::decode("6kg0dXJM13U")?)) + .build()?; let mut encoded = Vec::new(); -request.clone().serialize_into( & mut encoded) ?; +request.clone().serialize_into(&mut encoded)?; assert_eq!(AccessTokenRequest::deserialize_from(encoded.as_slice())?, request); ``` -## Access Tokens - +### Access Tokens Following up from the previous example, let's assume we now want to create a signed access token containing the existing `key`, as well as claims about the audience and issuer of the token, using an existing cipher of type `FakeCrypto`[^cipher]: - ```rust +use dcaf::token::CoseCipher; -# [derive(Clone)] let rng = FakeRng; -let key = FakeKey { key: [1, 2, 3, 4, 5], kid: [0xDC, 0xAF]}; -let cose_key: CoseKey = key.to_cose_key(); +let key = CoseKeyBuilder::new_symmetric_key(vec![1,2,3,4,5]).key_id(vec![0xDC, 0xAF]).build(); let claims = ClaimsSetBuilder::new() -.audience(String::from("coaps://rs.example.com")) -.issuer(String::from("coaps://as.example.com")) -.claim(CwtClaimName::Cnf, cose_key.to_cbor_value() ? ) -.build(); -let token = sign_access_token::( & key, claims, None, None, None, rng) ?; + .audience(String::from("coaps://rs.example.com")) + .issuer(String::from("coaps://as.example.com")) + .claim(CwtClaimName::Cnf, key.clone().to_cbor_value()?) + .build(); +let token = sign_access_token::(&key, claims, None, None, None, rng)?; assert!(verify_access_token::(&key, &token, None).is_ok()); ``` [^cipher]: Note that we are deliberately omitting details about the implementation of the `cipher` here, since such implementations won't be in the scope of this crate. -# Provided Data Models - -## Token Endpoint +## Provided Data Models +### Token Endpoint The most commonly used models will probably be the token endpoint's [`AccessTokenRequest`](https://docs.rs/dcaf/latest/dcaf/endpoints/token_req/struct.AccessTokenRequest.html) and [`AccessTokenResponse`](https://docs.rs/dcaf/latest/dcaf/endpoints/token_req/struct.AccessTokenResponse.html) described in [section 5.8 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-5.8). -In case of an error, -an [`ErrorResponse`](https://docs.rs/dcaf/latest/dcaf/endpoints/token_req/struct.ErrorResponse.html) +In case of an error, an [`ErrorResponse`](https://docs.rs/dcaf/latest/dcaf/endpoints/token_req/struct.ErrorResponse.html) should be used. After an initial Unauthorized Resource Request Message, an @@ -131,32 +111,26 @@ After an initial Unauthorized Resource Request Message, an can be used to provide additional information to the client, as described in [section 5.3 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-5.3). -## Common Data Types - +### Common Data Types Some types used across multiple scenarios include: - - [`Scope`](https://docs.rs/dcaf/latest/dcaf/common/scope/enum.Scope.html) (as described in [section 5.8.1 of RFC 9200](https://www.rfc-editor.org/rfc/rfc9200#section-5.8.1)), either as a [`TextEncodedScope`](https://docs.rs/dcaf/latest/dcaf/common/scope/struct.TextEncodedScope.html), a [`BinaryEncodedScope`](https://docs.rs/dcaf/latest/dcaf/common/scope/struct.BinaryEncodedScope.html) or an [`AifEncodedScope`](https://docs.rs/dcaf/latest/dcaf/common/scope/struct.AifEncodedScope.html). -- [`ProofOfPossessionKey`](https://docs.rs/dcaf/latest/dcaf/common/cbor_values/enum.ProofOfPossessionKey.html) as - specified in +- [`ProofOfPossessionKey`](https://docs.rs/dcaf/latest/dcaf/common/cbor_values/enum.ProofOfPossessionKey.html) as specified in [section 3.1 of RFC 8747](https://www.rfc-editor.org/rfc/rfc8747#section-3.1). For example, this will be used in the access token's `cnf` claim. - While not really a data type, various constants representing values used in ACE-OAuth are provided in the [`constants`](https://docs.rs/dcaf/latest/dcaf/common/constants/) module. -# Creating Access Tokens - -In order to create access tokens, you can use -either [`encrypt_access_token`](https://docs.rs/dcaf/latest/dcaf/token/fn.encrypt_access_token.html) +## Creating Access Tokens +In order to create access tokens, you can use either [`encrypt_access_token`](https://docs.rs/dcaf/latest/dcaf/token/fn.encrypt_access_token.html) or [`sign_access_token`](https://docs.rs/dcaf/latest/dcaf/token/fn.sign_access_token.html), depending on whether you want the access token to be wrapped in a `COSE_Encrypt0` or `COSE_Sign1` structure. Support for a combination of both is planned for the future. In case you want to create a token intended for multiple recipients (each with their -own key), you can -use [`encrypt_access_token_multiple`](https://docs.rs/dcaf/latest/dcaf/token/fn.encrypt_access_token_multiple.html) +own key), you can use [`encrypt_access_token_multiple`](https://docs.rs/dcaf/latest/dcaf/token/fn.encrypt_access_token_multiple.html) or [`sign_access_token_multiple`](https://docs.rs/dcaf/latest/dcaf/token/fn.sign_access_token_multiple.html). Both functions take a [`ClaimsSet`](coset::cwt::ClaimsSet) containing the claims that @@ -168,10 +142,8 @@ the function will fail with a `HeaderAlreadySet` error. The function will return a [`Result`](https://doc.rust-lang.org/stable/core/result/enum.Result.html) of the opaque [`ByteString`](https://docs.rs/dcaf/latest/dcaf/common/cbor_values/type.ByteString.html) containing the access token. -# Verifying / decrypting Access Tokens - -In order to verify or decrypt existing access tokens represented -as [`ByteString`](https://docs.rs/dcaf/latest/dcaf/common/cbor_values/type.ByteString.html)s, +## Verifying and Decrypting Access Tokens +In order to verify or decrypt existing access tokens represented as [`ByteString`](https://docs.rs/dcaf/latest/dcaf/common/cbor_values/type.ByteString.html)s, use [`verify_access_token`](https://docs.rs/dcaf/latest/dcaf/token/fn.verify_access_token.html) or [`decrypt_access_token`](https://docs.rs/dcaf/latest/dcaf/token/fn.decrypt_access_token.html) respectively. In case the token was created for multiple recipients (each with their own key), @@ -182,41 +154,35 @@ Both functions take the access token, a `key` used to decrypt or verify, optiona (additional authenticated data) and a cipher implementing cryptographic operations identified by type parameter `T`. -[`decrypt_access_token`](https://docs.rs/dcaf/latest/dcaf/token/fn.decrypt_access_token.html) will return a result -containing +[`decrypt_access_token`](https://docs.rs/dcaf/latest/dcaf/token/fn.decrypt_access_token.html) will return a result containing the decrypted [`ClaimsSet`](coset::cwt::ClaimsSet). -[`verify_access_token`](https://docs.rs/dcaf/latest/dcaf/token/fn.verify_access_token.html) will return an empty result -which -indicates that the token was successfully -verified---an [`Err`](https://doc.rust-lang.org/stable/core/result/enum.Result.html) +[`verify_access_token`](https://docs.rs/dcaf/latest/dcaf/token/fn.verify_access_token.html) will return an empty result which +indicates that the token was successfully verified---an [`Err`](https://doc.rust-lang.org/stable/core/result/enum.Result.html) would indicate failure. -# Extracting Headers from an Access Token - +## Extracting Headers from an Access Token Regardless of whether a token was signed, encrypted, or MAC-tagged, you can extract its headers using [`get_token_headers`](https://docs.rs/dcaf/latest/dcaf/token/fn.get_token_headers.html), which will return an option containing both unprotected and protected headers (or which will be [`None`](core::option::Option::None) in case the token is invalid). -# COSE Cipher - +## COSE Cipher As mentioned before, cryptographic functions are outside the scope of this crate. For this reason, the various COSE cipher traits exist; namely, -[`CoseEncryptCipher`](core::token::CoseEncryptCipher), [`CoseSignCipher`](core::token::CoseSignCipher), -and [`CoseMacCipher`](core::token::CoseMacCipher), each implementing +[`CoseEncryptCipher`](token::CoseEncryptCipher), [`CoseSignCipher`](token::CoseSignCipher), +and [`CoseMacCipher`](token::CoseMacCipher), each implementing a corresponding COSE operation as specified in sections 4, 5, and 6 of [RFC 8152](https://www.rfc-editor.org/rfc/rfc8152). -There are also the traits [`MultipleEncryptCipher`](core::token::MultipleEncryptCipher), -[`MultipleSignCipher`](core::token::MultipleSignCipher), and -[`MultipleMacCipher`](core::token::MultipleMacCipher), +There are also the traits [`MultipleEncryptCipher`](token::MultipleEncryptCipher), +[`MultipleSignCipher`](token::MultipleSignCipher), and +[`MultipleMacCipher`](token::MultipleMacCipher), which are used for creating tokens intended for multiple recipients. Note that these ciphers *don't* need to wrap their results in, e.g., a `Cose_Encrypt0` structure, as this part is already handled by this library (which uses [`coset`](coset))---only the cryptographic algorithms themselves need to be implemented -(e.g., step 4 of "how to decrypt a message" -in [section 5.3 of RFC 8152](https://www.rfc-editor.org/rfc/rfc8152#section-5.3)). +(e.g., step 4 of "how to decrypt a message" in [section 5.3 of RFC 8152](https://www.rfc-editor.org/rfc/rfc8152#section-5.3)). When implementing any of the specific COSE ciphers, you'll also need to specify the type of the key (which must be convertible to a `CoseKey`) and implement a method which sets diff --git a/src/lib.rs b/src/lib.rs index 576f740..b4f59cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -258,7 +258,7 @@ //! The function will return a [`Result`](::core::result::Result) of the opaque //! [`ByteString`](crate::common::cbor_values::ByteString) containing the access token. //! -//! # Verifying / decrypting Access Tokens +//! # Verifying and Decrypting Access Tokens //! In order to verify or decrypt existing access tokens represented as [`ByteString`](crate::common::cbor_values::ByteString)s, //! use [`verify_access_token`](crate::token::verify_access_token) or //! [`decrypt_access_token`](crate::token::decrypt_access_token) respectively. From c03d64dd54f5ee05993c519f9dcad9462682cb8a Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Mon, 23 Jan 2023 12:45:50 +0100 Subject: [PATCH 12/12] style: fix clippy and check warnings in test code --- src/common/test_helper.rs | 26 +++++++++++++------------- tests/integration_tests.rs | 32 +++++++++++++------------------- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/src/common/test_helper.rs b/src/common/test_helper.rs index d3541a9..39d6ede 100644 --- a/src/common/test_helper.rs +++ b/src/common/test_helper.rs @@ -16,7 +16,7 @@ use core::convert::identity; use core::fmt::{Debug, Display}; use ciborium::value::Value; -use coset::iana::{Algorithm, SymmetricKeyParameter}; +use coset::iana::Algorithm; use coset::{iana, CoseKey, CoseKeyBuilder, Header, Label, ProtectedHeader}; use rand::{CryptoRng, Error, RngCore}; @@ -37,10 +37,10 @@ use crate::{CoseEncryptCipher, CoseMacCipher, CoseSignCipher}; /// # Panics /// If [`key`] is not a symmetric key or has no valid key value. fn get_symmetric_key_value(key: &CoseKey) -> Vec { - let k_label = iana::SymmetricKeyParameter::K as i64; + const K_LABEL: i64 = iana::SymmetricKeyParameter::K as i64; key.params .iter() - .find(|x| matches!(x.0, Label::Int(k_label))) + .find(|x| matches!(x.0, Label::Int(K_LABEL))) .and_then(|x| match x { (_, Value::Bytes(x)) => Some(x), _ => None, @@ -112,7 +112,7 @@ impl CoseCipher for FakeCrypto { key: &CoseKey, unprotected_header: &mut Header, protected_header: &mut Header, - rng: RNG, + _rng: RNG, ) -> Result<(), CoseCipherError> { // We have to later verify these headers really are used. if let Some(label) = unprotected_header @@ -143,8 +143,8 @@ impl CoseEncryptCipher for FakeCrypto { key: &CoseKey, plaintext: &[u8], aad: &[u8], - protected_header: &Header, - unprotected_header: &Header, + _protected_header: &Header, + _unprotected_header: &Header, ) -> Vec { // We put the key and the AAD before the data. // Again, this obviously isn't secure in any sane definition of the word. @@ -158,12 +158,12 @@ impl CoseEncryptCipher for FakeCrypto { key: &CoseKey, ciphertext: &[u8], aad: &[u8], - unprotected_header: &Header, + _unprotected_header: &Header, protected_header: &ProtectedHeader, ) -> Result, CoseCipherError> { // Now we just split off the AAD and key we previously put at the end of the data. // We return an error if it does not match. - if key.key_id.clone() != protected_header.header.key_id { + if &key.key_id != &protected_header.header.key_id { // Mismatching key return Err(CoseCipherError::DecryptionFailure); } @@ -191,8 +191,8 @@ impl CoseSignCipher for FakeCrypto { fn sign( key: &CoseKey, target: &[u8], - unprotected_header: &Header, - protected_header: &Header, + _unprotected_header: &Header, + _protected_header: &Header, ) -> Vec { // We simply append the key behind the data. let mut signature = target.to_vec(); @@ -206,7 +206,7 @@ impl CoseSignCipher for FakeCrypto { signed_data: &[u8], unprotected_header: &Header, protected_header: &ProtectedHeader, - unprotected_signature_header: Option<&Header>, + _unprotected_signature_header: Option<&Header>, protected_signature_header: Option<&ProtectedHeader>, ) -> Result<(), CoseCipherError> { let matching_kid = if let Some(protected) = protected_signature_header { @@ -235,8 +235,8 @@ impl CoseMacCipher for FakeCrypto { fn compute( key: &CoseKey, target: &[u8], - unprotected_header: &Header, - protected_header: &Header, + _unprotected_header: &Header, + _protected_header: &Header, ) -> Vec { // We simply append the key behind the data. let mut tag = target.to_vec(); diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 8819ebd..e73f878 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -12,11 +12,8 @@ use ciborium::value::Value; use coset::cwt::{ClaimsSetBuilder, Timestamp}; use coset::iana::EllipticCurve::P_256; -use coset::iana::{Algorithm, CwtClaimName, EllipticCurve}; -use coset::{ - iana, CborSerializable, CoseKey, CoseKeyBuilder, Header, HeaderBuilder, KeyType, Label, - ProtectedHeader, -}; +use coset::iana::{Algorithm, CwtClaimName}; +use coset::{iana, CoseKey, CoseKeyBuilder, Header, HeaderBuilder, Label, ProtectedHeader}; use rand::{CryptoRng, Error, RngCore}; use dcaf::common::cbor_map::ToCborMap; @@ -31,10 +28,6 @@ use dcaf::error::CoseCipherError; use dcaf::token::CoseCipher; use dcaf::{sign_access_token, verify_access_token, CoseSignCipher}; -fn create_ec2p256_key(x: Vec, y: Vec) -> CoseKey { - CoseKeyBuilder::new_ec2_pub_key(P_256, x, y).build() -} - fn get_x_y_from_key(key: &CoseKey) -> (Vec, Vec) { const X_PARAM: i64 = iana::Ec2KeyParameter::X as i64; const Y_PARAM: i64 = iana::Ec2KeyParameter::Y as i64; @@ -72,7 +65,8 @@ impl RngCore for FakeRng { } fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - Ok(dest.fill(0)) + dest.fill(0); + Ok(()) } } @@ -99,10 +93,10 @@ impl CoseCipher for FakeCrypto { type Error = String; fn set_headers( - key: &CoseKey, + _key: &CoseKey, unprotected_header: &mut Header, protected_header: &mut Header, - rng: RNG, + _rng: RNG, ) -> Result<(), CoseCipherError> { // We have to later verify these headers really are used. if let Some(label) = unprotected_header @@ -128,14 +122,14 @@ impl CoseSignCipher for FakeCrypto { fn sign( key: &CoseKey, target: &[u8], - unprotected_header: &Header, - protected_header: &Header, + _unprotected_header: &Header, + _protected_header: &Header, ) -> Vec { // We simply append the key behind the data. let mut signature = target.to_vec(); - let (x, y) = get_x_y_from_key(key); - signature.append(&mut x.clone()); - signature.append(&mut y.clone()); + let (mut x, mut y) = get_x_y_from_key(key); + signature.append(&mut x); + signature.append(&mut y); signature } @@ -145,8 +139,8 @@ impl CoseSignCipher for FakeCrypto { signed_data: &[u8], unprotected_header: &Header, protected_header: &ProtectedHeader, - unprotected_signature_header: Option<&Header>, - protected_signature_header: Option<&ProtectedHeader>, + _unprotected_signature_header: Option<&Header>, + _protected_signature_header: Option<&ProtectedHeader>, ) -> Result<(), CoseCipherError> { if signature == Self::sign(