Skip to content

Commit

Permalink
Merge pull request #10 from namib-project/9-support-other-cose-ciphers
Browse files Browse the repository at this point in the history
  • Loading branch information
falko17 authored Jan 23, 2023
2 parents 6a3f4ec + c03d64d commit 879c55a
Show file tree
Hide file tree
Showing 9 changed files with 1,574 additions and 703 deletions.
17 changes: 16 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,29 @@ 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.
Note that the cipher interfaces have been refactored in a major way.

### 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, 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.

### Fixed
Expand Down
21 changes: 11 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>"]
rust-version = "1.58"
Expand All @@ -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" }
240 changes: 128 additions & 112 deletions README.md

Large diffs are not rendered by default.

241 changes: 200 additions & 41 deletions src/common/test_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 core::fmt::{Debug, Display};

use ciborium::value::Value;
use coset::iana::Algorithm;
use coset::{Header, Label};
use coset::{iana, CoseKey, CoseKeyBuilder, Header, Label, ProtectedHeader};
use rand::{CryptoRng, Error, RngCore};

#[cfg(not(feature = "std"))]
use {
Expand All @@ -29,6 +27,28 @@ use {
alloc::vec::Vec,
};

use crate::common::cbor_map::ToCborMap;
use crate::error::{AccessTokenError, CoseCipherError, MultipleCoseError};
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<u8> {
const K_LABEL: i64 = 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`].
///
Expand Down Expand Up @@ -81,17 +101,18 @@ 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 {
impl CoseCipher for FakeCrypto {
type Error = String;

fn header(
&self,
fn set_headers<RNG: RngCore + CryptoRng>(
key: &CoseKey,
unprotected_header: &mut Header,
protected_header: &mut Header,
_rng: RNG,
) -> Result<(), CoseCipherError<Self::Error>> {
// We have to later verify these headers really are used.
if let Some(label) = unprotected_header
Expand All @@ -104,60 +125,102 @@ impl CoseCipherCommon for FakeCrypto {
if protected_header.alg != None {
return Err(CoseCipherError::existing_header("alg"));
}
if !protected_header.key_id.is_empty() {
return Err(CoseCipherError::existing_header("kid"));
}
unprotected_header.rest.push((Label::Int(47), Value::Null));
protected_header.alg = Some(coset::Algorithm::Assigned(Algorithm::Direct));
protected_header.key_id = key.key_id.clone();
Ok(())
}
}

/// Implements basic operations from the [`CoseEncrypt0Cipher`] trait without actually using any
/// 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<u8> {
// We simply put AAD behind the data and call it a day.
let mut result: Vec<u8> = vec![];
result.append(&mut data.to_vec());
impl CoseEncryptCipher for FakeCrypto {
fn encrypt(
key: &CoseKey,
plaintext: &[u8],
aad: &[u8],
_protected_header: &Header,
_unprotected_header: &Header,
) -> Vec<u8> {
// 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<u8> = get_symmetric_key_value(key);
result.append(&mut aad.to_vec());
result.append(&mut plaintext.to_vec());
result
}

fn decrypt(
&mut self,
data: &[u8],
key: &CoseKey,
ciphertext: &[u8],
aad: &[u8],
_unprotected_header: &Header,
protected_header: &ProtectedHeader,
) -> Result<Vec<u8>, CoseCipherError<Self::Error>> {
// Now we just split off the AAD we previously put at the end of the data.
// 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 &key.key_id != &protected_header.header.key_id {
// Mismatching key
return Err(CoseCipherError::DecryptionFailure);
}
let key_value = get_symmetric_key_value(key);
if ciphertext.len() < (aad.len() + key_value.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<u8> = data.to_vec();
let aad_result = result.split_off(data.len() - aad.len());
if aad == aad_result {
Ok(result)
let mut result: Vec<u8> = ciphertext.to_vec();
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::Other("AADs don't match!".to_string()))
Err(CoseCipherError::DecryptionFailure)
}
}
}

/// 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<u8> {
data.to_vec()
impl CoseSignCipher for FakeCrypto {
fn sign(
key: &CoseKey,
target: &[u8],
_unprotected_header: &Header,
_protected_header: &Header,
) -> Vec<u8> {
// We simply append the key behind the data.
let mut signature = target.to_vec();
signature.append(&mut get_symmetric_key_value(key));
signature
}

fn verify_signature(
&mut self,
sig: &[u8],
data: &[u8],
fn verify(
key: &CoseKey,
signature: &[u8],
signed_data: &[u8],
unprotected_header: &Header,
protected_header: &ProtectedHeader,
_unprotected_signature_header: Option<&Header>,
protected_signature_header: Option<&ProtectedHeader>,
) -> Result<(), CoseCipherError<Self::Error>> {
if sig == self.generate_signature(data) {
let matching_kid = if let Some(protected) = protected_signature_header {
protected.header.key_id == key.key_id
} else {
protected_header.header.key_id == key.key_id
};
let signed_again = Self::sign(
key,
signed_data,
unprotected_header,
&protected_header.header,
);
if matching_kid && signed_again == signature {
Ok(())
} else {
Err(CoseCipherError::VerificationFailure)
Expand All @@ -168,20 +231,116 @@ impl CoseSign1Cipher for FakeCrypto {
/// 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<u8> {
target.to_vec()
impl CoseMacCipher for FakeCrypto {
fn compute(
key: &CoseKey,
target: &[u8],
_unprotected_header: &Header,
_protected_header: &Header,
) -> Vec<u8> {
// We simply append the key behind the data.
let mut tag = target.to_vec();
tag.append(&mut get_symmetric_key_value(key));
tag
}

fn verify_tag(
&mut self,
fn verify(
key: &CoseKey,
tag: &[u8],
maced_data: &[u8],
unprotected_header: &Header,
protected_header: &ProtectedHeader,
) -> Result<(), CoseCipherError<Self::Error>> {
if tag == self.generate_tag(maced_data) {
if protected_header.header.key_id == key.key_id
&& tag
== Self::compute(
key,
maced_data,
unprotected_header,
&protected_header.header,
)
{
Ok(())
} else {
Err(CoseCipherError::VerificationFailure)
}
}
}

impl MultipleEncryptCipher for FakeCrypto {
fn generate_cek<RNG: RngCore + CryptoRng>(rng: &mut RNG) -> CoseKey {
let mut key = [0; 5];
let mut kid = [0; 2];
rng.fill_bytes(&mut key);
rng.fill_bytes(&mut kid);
CoseKeyBuilder::new_symmetric_key(key.to_vec())
.key_id(kid.to_vec())
.build()
}
}

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> {
dest.fill(0);
Ok(())
}
}

impl CryptoRng for FakeRng {}

// Makes the tests easier later on, as we use String as the error type in there.
impl<C, K> From<CoseCipherError<MultipleCoseError<C, K>>> for CoseCipherError<String>
where
C: Display,
K: Display,
{
fn from(x: CoseCipherError<MultipleCoseError<C, K>>) -> 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<C, K> From<AccessTokenError<MultipleCoseError<C, K>>> for AccessTokenError<String>
where
C: Display,
K: Display,
{
fn from(x: AccessTokenError<MultipleCoseError<C, K>>) -> Self {
match x {
AccessTokenError::CoseError(x) => AccessTokenError::CoseError(x),
AccessTokenError::CoseCipherError(x) => {
AccessTokenError::CoseCipherError(CoseCipherError::from(x))
}
AccessTokenError::UnknownCoseStructure => AccessTokenError::UnknownCoseStructure,
AccessTokenError::NoMatchingRecipient => AccessTokenError::NoMatchingRecipient,
AccessTokenError::MultipleMatchingRecipients => {
AccessTokenError::MultipleMatchingRecipients
}
}
}
}
Loading

0 comments on commit 879c55a

Please sign in to comment.