Skip to content

Commit cfbc33e

Browse files
authored
Merge pull request #32 from namib-project/aes_cbc_mac
Add (RustCrypto only) support for AES-CBC-MAC
2 parents f77bb10 + 61e09de commit cfbc33e

File tree

16 files changed

+395
-95
lines changed

16 files changed

+395
-95
lines changed

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ rustcrypto = ["rustcrypto-aes-gcm", "rustcrypto-aes-kw", "rustcrypto-ecdsa", "ru
2020
rustcrypto-encrypt = ["rustcrypto-aes-gcm", "rustcrypto-aes-ccm", "rustcrypto-chacha20-poly1305"]
2121
rustcrypto-sign = ["rustcrypto-ecdsa"]
2222
rustcrypto-key-distribution = ["rustcrypto-aes-kw"]
23-
rustcrypto-mac = ["rustcrypto-hmac"]
23+
rustcrypto-mac = ["rustcrypto-hmac", "rustcrypto-aes-cbc-mac"]
2424
rustcrypto-aes-gcm = ["dep:aes-gcm", "dep:typenum", "dep:aead", "dep:aes"]
2525
rustcrypto-aes-ccm = ["dep:ccm", "dep:typenum", "dep:aead", "dep:aes"]
2626
rustcrypto-chacha20-poly1305 = ["dep:chacha20poly1305", "dep:typenum", "dep:aead"]
2727
rustcrypto-aes-kw = ["dep:aes-kw", "dep:aes", "dep:typenum", "dep:crypto-common"]
2828
rustcrypto-ecdsa = ["dep:ecdsa", "dep:p256", "dep:p384", "dep:digest", "dep:sha2", "dep:elliptic-curve"]
2929
rustcrypto-hmac = ["dep:hmac", "dep:digest", "dep:sha2"]
30+
rustcrypto-aes-cbc-mac = ["dep:cbc-mac", "dep:aes", "dep:crypto-common", "dep:digest", "dep:typenum"]
3031

3132
[dependencies]
3233
serde = { version = "1.0", default-features = false, features = ["derive"] }
@@ -52,6 +53,7 @@ aes-kw = { version = "0.2.1", optional = true, default-features = false, feature
5253
aes = { version = "0.8.4", optional = true, default-features = false }
5354
hmac = { version = "0.12.1", optional = true, default-features = false }
5455
digest = { version = "0.10.7", optional = true, default-features = false }
56+
cbc-mac = { version = "0.1.1", optional = true, default-features = false }
5557
sha2 = { version = "0.10.8", optional = true, default-features = false }
5658
elliptic-curve = { version = "0.13.8", default-features = false, optional = true }
5759
ecdsa = { version = "0.16.9", optional = true, default-features = false, features = ["sha2"] }

build.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024 The NAMIB Project Developers.
2+
* Copyright (c) 2024-2025 The NAMIB Project Developers.
33
* Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
44
* https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
55
* <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
@@ -32,7 +32,8 @@ fn main() {
3232
},
3333
rustcrypto_mac_base: {
3434
any(
35-
feature = "rustcrypto-hmac"
35+
feature = "rustcrypto-hmac",
36+
feature = "rustcrypto-aes-cbc-mac"
3637
)
3738
},
3839
rustcrypto_base: {

src/error/mod.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,6 @@ impl<T> From<CoseCipherError<T>> for AccessTokenError<T>
557557
where
558558
T: Display,
559559
{
560-
#[must_use]
561560
fn from(error: CoseCipherError<T>) -> Self {
562561
AccessTokenError::CoseCipherError(error)
563562
}
@@ -567,7 +566,6 @@ impl<T> From<CoseError> for AccessTokenError<T>
567566
where
568567
T: Display,
569568
{
570-
#[must_use]
571569
fn from(error: CoseError) -> Self {
572570
AccessTokenError::CoseError(error)
573571
}

src/token/cose/crypto_impl/openssl/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ impl From<openssl::aes::KeyError> for CoseCipherError<CoseOpensslCipherError> {
9797
/// - [x] AES-CCM-16-128-256
9898
/// - [x] AES-CCM-64-128-128
9999
/// - [x] AES-CCM-64-128-256
100-
/// - [ ] ChaCha20/Poly1305
100+
/// - [x] ChaCha20/Poly1305
101101
/// - Content Key Distribution Methods (for COSE_Recipients)
102102
/// - Direct Encryption
103103
/// - [ ] Direct Key with KDF
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
* Copyright (c) 2025 The NAMIB Project Developers.
3+
* Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
* https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5+
* <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6+
* option. This file may not be copied, modified, or distributed
7+
* except according to those terms.
8+
*
9+
* SPDX-License-Identifier: MIT OR Apache-2.0
10+
*/
11+
use coset::{iana, Algorithm};
12+
use crypto_common::BlockSizeUser;
13+
use digest::Mac;
14+
use rand::{CryptoRng, RngCore};
15+
use typenum::{IsLess, U256};
16+
17+
use crate::error::CoseCipherError;
18+
use crate::token::cose::crypto_impl::rustcrypto::RustCryptoContext;
19+
use crate::token::cose::{CoseSymmetricKey, CryptoBackend};
20+
use aes::cipher::{BlockCipher, BlockEncryptMut};
21+
use aes::{Aes128, Aes256};
22+
use alloc::vec::Vec;
23+
use cbc_mac::CbcMac;
24+
use crypto_common::KeyInit;
25+
26+
impl<RNG: RngCore + CryptoRng> RustCryptoContext<RNG> {
27+
/// Compute the CBC-MAC of `payload` using the given `key` with the block cipher
28+
/// `C`.
29+
fn compute_cbc_mac_using_block_cipher<
30+
C: BlockCipher + BlockEncryptMut + Clone,
31+
const TAG_LEN: usize,
32+
>(
33+
key: &CoseSymmetricKey<'_, <Self as CryptoBackend>::Error>,
34+
payload: &[u8],
35+
) -> Vec<u8>
36+
where
37+
CbcMac<C>: KeyInit + Mac,
38+
<C as BlockSizeUser>::BlockSize: typenum::IsLess<U256>,
39+
<<C as BlockSizeUser>::BlockSize as IsLess<U256>>::Output: typenum::NonZero,
40+
{
41+
// Key length must have been validated by caller as per the API contract of
42+
// `MacCryptoBackend`.
43+
let mut cbc_mac = <CbcMac<C> as Mac>::new_from_slice(&key.k).expect("key length invalid");
44+
cbc_mac.update(payload);
45+
let mut result = cbc_mac.finalize().into_bytes().to_vec();
46+
result.truncate(TAG_LEN);
47+
result
48+
}
49+
50+
/// Verify the CBC-MAC of `payload` using the given `key` with the HMAC function `MAC`.
51+
fn verify_cbc_mac_using_block_cipher<
52+
C: BlockCipher + BlockEncryptMut + Clone,
53+
const TAG_LEN: usize,
54+
>(
55+
key: &CoseSymmetricKey<'_, <Self as CryptoBackend>::Error>,
56+
payload: &[u8],
57+
tag: &[u8],
58+
) -> Result<(), CoseCipherError<<Self as CryptoBackend>::Error>>
59+
where
60+
CbcMac<C>: KeyInit + Mac,
61+
<C as BlockSizeUser>::BlockSize: typenum::IsLess<U256>,
62+
<<C as BlockSizeUser>::BlockSize as IsLess<U256>>::Output: typenum::NonZero,
63+
{
64+
let mut cbc_mac = <CbcMac<C> as Mac>::new_from_slice(&key.k).unwrap();
65+
cbc_mac.update(payload);
66+
67+
// Validate length of tag as verify_truncated_left() does not know the expected length.
68+
if tag.len() != TAG_LEN {
69+
return Err(CoseCipherError::VerificationFailure);
70+
}
71+
72+
cbc_mac
73+
.verify_truncated_left(tag)
74+
.map_err(CoseCipherError::from)
75+
}
76+
77+
/// Compute the CBC-MAC of `payload` using the given `key` with the CBC-MAC function
78+
/// specified in the `algorithm`.
79+
pub(super) fn compute_cbc_mac(
80+
algorithm: iana::Algorithm,
81+
key: &CoseSymmetricKey<'_, <Self as CryptoBackend>::Error>,
82+
payload: &[u8],
83+
) -> Result<Vec<u8>, CoseCipherError<<Self as CryptoBackend>::Error>> {
84+
match algorithm {
85+
iana::Algorithm::AES_MAC_128_64 => Ok(Self::compute_cbc_mac_using_block_cipher::<
86+
Aes128,
87+
8,
88+
>(key, payload)),
89+
iana::Algorithm::AES_MAC_128_128 => Ok(Self::compute_cbc_mac_using_block_cipher::<
90+
Aes128,
91+
16,
92+
>(key, payload)),
93+
iana::Algorithm::AES_MAC_256_64 => Ok(Self::compute_cbc_mac_using_block_cipher::<
94+
Aes256,
95+
8,
96+
>(key, payload)),
97+
iana::Algorithm::AES_MAC_256_128 => Ok(Self::compute_cbc_mac_using_block_cipher::<
98+
Aes256,
99+
16,
100+
>(key, payload)),
101+
a => Err(CoseCipherError::UnsupportedAlgorithm(Algorithm::Assigned(
102+
a,
103+
))),
104+
}
105+
}
106+
107+
/// Verify the CBC-MAC `tag` of `payload` using the given `key` with the CBC-MAC
108+
/// function specified in the `algorithm`.
109+
pub(super) fn verify_cbc_mac(
110+
algorithm: iana::Algorithm,
111+
key: &CoseSymmetricKey<'_, <Self as CryptoBackend>::Error>,
112+
tag: &[u8],
113+
payload: &[u8],
114+
) -> Result<(), CoseCipherError<<Self as CryptoBackend>::Error>> {
115+
match algorithm {
116+
iana::Algorithm::AES_MAC_128_64 => {
117+
Self::verify_cbc_mac_using_block_cipher::<Aes128, 8>(key, payload, tag)
118+
}
119+
iana::Algorithm::AES_MAC_128_128 => {
120+
Self::verify_cbc_mac_using_block_cipher::<Aes128, 16>(key, payload, tag)
121+
}
122+
iana::Algorithm::AES_MAC_256_64 => {
123+
Self::verify_cbc_mac_using_block_cipher::<Aes256, 8>(key, payload, tag)
124+
}
125+
iana::Algorithm::AES_MAC_256_128 => {
126+
Self::verify_cbc_mac_using_block_cipher::<Aes256, 16>(key, payload, tag)
127+
}
128+
a => Err(CoseCipherError::UnsupportedAlgorithm(Algorithm::Assigned(
129+
a,
130+
))),
131+
}
132+
}
133+
}

src/token/cose/crypto_impl/rustcrypto/mac/hmac.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ use sha2::{Sha256, Sha384, Sha512};
1717
use crate::error::CoseCipherError;
1818
use crate::token::cose::crypto_impl::rustcrypto::RustCryptoContext;
1919
use crate::token::cose::{CoseSymmetricKey, CryptoBackend};
20-
use aead::Buffer;
2120
use alloc::vec::Vec;
2221

2322
impl<RNG: RngCore + CryptoRng> RustCryptoContext<RNG> {

src/token/cose/crypto_impl/rustcrypto/mac/mod.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024 The NAMIB Project Developers.
2+
* Copyright (c) 2024-2025 The NAMIB Project Developers.
33
* Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
44
* https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
55
* <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
@@ -17,6 +17,8 @@ use alloc::vec::Vec;
1717
use coset::iana;
1818
use rand::{CryptoRng, RngCore};
1919

20+
#[cfg(feature = "rustcrypto-aes-cbc-mac")]
21+
mod aes_cbc_mac;
2022
#[cfg(feature = "rustcrypto-hmac")]
2123
mod hmac;
2224

@@ -41,4 +43,25 @@ impl<RNG: RngCore + CryptoRng> MacCryptoBackend for RustCryptoContext<RNG> {
4143
) -> Result<(), CoseCipherError<Self::Error>> {
4244
Self::verify_hmac(algorithm, &key, tag, payload)
4345
}
46+
47+
#[cfg(feature = "rustcrypto-aes-cbc-mac")]
48+
fn compute_cbc_mac(
49+
&mut self,
50+
algorithm: iana::Algorithm,
51+
key: CoseSymmetricKey<'_, Self::Error>,
52+
payload: &[u8],
53+
) -> Result<Vec<u8>, CoseCipherError<Self::Error>> {
54+
Self::compute_cbc_mac(algorithm, &key, payload)
55+
}
56+
57+
#[cfg(feature = "rustcrypto-aes-cbc-mac")]
58+
fn verify_cbc_mac(
59+
&mut self,
60+
algorithm: iana::Algorithm,
61+
key: CoseSymmetricKey<'_, Self::Error>,
62+
tag: &[u8],
63+
payload: &[u8],
64+
) -> Result<(), CoseCipherError<Self::Error>> {
65+
Self::verify_cbc_mac(algorithm, &key, tag, payload)
66+
}
4467
}

src/token/cose/crypto_impl/rustcrypto/mod.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ impl From<ecdsa::elliptic_curve::Error> for CoseCipherError<CoseRustCryptoCipher
8585
}
8686
}
8787

88-
#[cfg(feature = "rustcrypto-hmac")]
88+
#[cfg(any(feature = "rustcrypto-hmac", feature = "rustcrypto-aes-cbc-mac"))]
8989
impl From<digest::MacError> for CoseCipherError<CoseRustCryptoCipherError> {
9090
fn from(_value: digest::MacError) -> Self {
9191
CoseCipherError::VerificationFailure
@@ -132,11 +132,11 @@ impl From<ecdsa::Error> for CoseCipherError<CoseRustCryptoCipherError> {
132132
/// - [x] HMAC 256/256
133133
/// - [x] HMAC 384/384
134134
/// - [x] HMAC 512/512
135-
/// - [ ] AES-CBC-MAC
136-
/// - [ ] AES-MAC 128/64
137-
/// - [ ] AES-MAC 256/64
138-
/// - [ ] AES-MAC 128/128
139-
/// - [ ] AES-MAC 256/128
135+
/// - [x] AES-CBC-MAC
136+
/// - [x] AES-MAC 128/64
137+
/// - [x] AES-MAC 256/64
138+
/// - [x] AES-MAC 128/128
139+
/// - [x] AES-MAC 256/128
140140
/// - Content Encryption Algorithms (for COSE_Encrypt and COSE_Encrypt0)
141141
/// - [x] AES-GCM
142142
/// - [x] A128GCM
@@ -151,7 +151,7 @@ impl From<ecdsa::Error> for CoseCipherError<CoseRustCryptoCipherError> {
151151
/// - [x] AES-CCM-16-128-256
152152
/// - [x] AES-CCM-64-128-128
153153
/// - [x] AES-CCM-64-128-256
154-
/// - [ ] ChaCha20/Poly1305
154+
/// - [x] ChaCha20/Poly1305
155155
/// - Content Key Distribution Methods (for COSE_Recipients)
156156
/// - Direct Encryption
157157
/// - [ ] Direct Key with KDF

src/token/cose/maced/mac/tests.rs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ use crate::token::cose::{test_helper, CryptoBackend};
3232

3333
#[cfg(feature = "openssl")]
3434
use crate::token::cose::test_helper::openssl_ctx;
35-
#[cfg(all(feature = "rustcrypto-hmac", feature = "rustcrypto-aes-kw"))]
35+
#[cfg(all(
36+
any(feature = "rustcrypto-hmac", feature = "rustcrypto-aes-cbc-mac"),
37+
feature = "rustcrypto-aes-kw"
38+
))]
3639
use crate::token::cose::test_helper::rustcrypto_ctx;
3740

3841
impl<B: CryptoBackend + MacCryptoBackend + KeyDistributionCryptoBackend> CoseStructTestHelper<B>
@@ -179,6 +182,39 @@ fn cose_examples_mac_self_signed<B: MacCryptoBackend + KeyDistributionCryptoBack
179182
test_helper::perform_cose_self_signed_test::<CoseMac, B>(test_path, backend);
180183
}
181184

185+
// As of now, we don't support CBC-MAC with the OpenSSL backend.
186+
// OpenSSL does not provide a ready-made function for performing CBC-MAC,
187+
// which means that we would need to build the missing functionality from the
188+
// existing CBC functions ourselves (which is probably not worth the effort).
189+
#[cfg(all(feature = "rustcrypto-aes-cbc-mac", feature = "rustcrypto-aes-kw"))]
190+
#[rstest]
191+
#[cfg_attr(
192+
all(feature = "rustcrypto-aes-cbc-mac", feature = "rustcrypto-aes-kw"),
193+
case::rustcrypto(rustcrypto_ctx())
194+
)]
195+
fn cose_examples_cbc_mac_mac_reference_output<
196+
B: MacCryptoBackend + KeyDistributionCryptoBackend,
197+
>(
198+
#[files("tests/cose_examples/cbc-mac-examples/cbc-mac-0*.json")] test_path: PathBuf,
199+
#[case] backend: B,
200+
) {
201+
test_helper::perform_cose_reference_output_test::<CoseMac, B>(test_path, backend);
202+
}
203+
204+
// As of now, we don't support CBC-MAC with the OpenSSL backend.
205+
#[cfg(all(feature = "rustcrypto-aes-cbc-mac", feature = "rustcrypto-aes-kw"))]
206+
#[rstest]
207+
#[cfg_attr(
208+
all(feature = "rustcrypto-aes-cbc-mac", feature = "rustcrypto-aes-kw"),
209+
case::rustcrypto(rustcrypto_ctx())
210+
)]
211+
fn cose_examples_cbc_mac_mac_self_signed<B: MacCryptoBackend + KeyDistributionCryptoBackend>(
212+
#[files("tests/cose_examples/cbc-mac-examples/cbc-mac-0*.json")] test_path: PathBuf,
213+
#[case] backend: B,
214+
) {
215+
test_helper::perform_cose_self_signed_test::<CoseMac, B>(test_path, backend);
216+
}
217+
182218
#[rstest]
183219
#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))]
184220
#[cfg_attr(

src/token/cose/maced/mac0/tests.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use crate::token::cose::{test_helper, CryptoBackend};
2626

2727
#[cfg(feature = "openssl")]
2828
use crate::token::cose::test_helper::openssl_ctx;
29-
#[cfg(feature = "rustcrypto-hmac")]
29+
#[cfg(any(feature = "rustcrypto-hmac", feature = "rustcrypto-aes-cbc-mac"))]
3030
use crate::token::cose::test_helper::rustcrypto_ctx;
3131

3232
impl<B: CryptoBackend + MacCryptoBackend> CoseStructTestHelper<B> for CoseMac0 {
@@ -160,6 +160,34 @@ fn cose_examples_mac0_self_signed<B: MacCryptoBackend>(
160160
test_helper::perform_cose_self_signed_test::<CoseMac0, B>(test_path, backend);
161161
}
162162

163+
// As of now, we don't support CBC-MAC with the OpenSSL backend.
164+
#[cfg(feature = "rustcrypto-aes-cbc-mac")]
165+
#[rstest]
166+
#[cfg_attr(
167+
all(feature = "rustcrypto-aes-cbc-mac"),
168+
case::rustcrypto(rustcrypto_ctx())
169+
)]
170+
fn cose_examples_cbc_mac_mac0_reference_output<B: MacCryptoBackend>(
171+
#[files("tests/cose_examples/cbc-mac-examples/cbc-mac-enc-*.json")] test_path: PathBuf,
172+
#[case] backend: B,
173+
) {
174+
test_helper::perform_cose_reference_output_test::<CoseMac0, B>(test_path, backend);
175+
}
176+
177+
// As of now, we don't support CBC-MAC with the OpenSSL backend.
178+
#[cfg(feature = "rustcrypto-aes-cbc-mac")]
179+
#[rstest]
180+
#[cfg_attr(
181+
all(feature = "rustcrypto-aes-cbc-mac"),
182+
case::rustcrypto(rustcrypto_ctx())
183+
)]
184+
fn cose_examples_cbc_mac_mac0_self_signed<B: MacCryptoBackend>(
185+
#[files("tests/cose_examples/cbc-mac-examples/cbc-mac-enc-*.json")] test_path: PathBuf,
186+
#[case] backend: B,
187+
) {
188+
test_helper::perform_cose_self_signed_test::<CoseMac0, B>(test_path, backend);
189+
}
190+
163191
#[rstest]
164192
#[cfg_attr(feature = "openssl", case::openssl(openssl_ctx()))]
165193
#[cfg_attr(feature = "rustcrypto-hmac", case::rustcrypto(rustcrypto_ctx()))]

0 commit comments

Comments
 (0)