Skip to content

Commit 0bba012

Browse files
feat: add AES-CCM cipher suites
Add TLS 1.2 ECDHE-ECDSA AES-CCM/CCM-8 and TLS 1.3 AES-128-CCM cipher suites using the RustCrypto ccm crate.
1 parent 9b0ef47 commit 0bba012

5 files changed

Lines changed: 366 additions & 2 deletions

File tree

Cargo.lock

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ resolver = "1" # Hack to enable the `custom` feature of `getrandom`
1919
# default features often have std breaking no_std and potentially other unwanted
2020
[dependencies]
2121
aead = { version = "0.6", default-features = false }
22+
aes = { version = "0.9", default-features = false }
2223
aes-gcm = { version = "0.11", default-features = false, features = ["aes", "alloc"] }
24+
ccm = { version = "0.6.0-rc.3", default-features = false }
2325
chacha20poly1305 = { version = "0.11", default-features = false }
2426
crypto-common = { version = "0.2", default-features = false }
2527
der = { version = "0.8", default-features = false }

src/aead.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use aead::Buffer;
22
use rustls::crypto::cipher::{BorrowedPayload, PrefixedPayload};
33

4+
pub mod ccm;
45
pub mod chacha20;
56
pub mod gcm;
67

src/aead/ccm.rs

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
#[cfg(feature = "alloc")]
2+
use alloc::boxed::Box;
3+
4+
use super::{DecryptBufferAdapter, EncryptBufferAdapter};
5+
6+
use aead::AeadInOut;
7+
use aes::{Aes128, Aes256};
8+
use ccm::{
9+
Ccm,
10+
consts::{U8, U12, U16},
11+
};
12+
use crypto_common::{KeyInit, KeySizeUser};
13+
use paste::paste;
14+
use rustls::crypto::cipher::{
15+
self, AeadKey, InboundOpaqueMessage, InboundPlainMessage, MessageDecrypter, MessageEncrypter,
16+
OutboundOpaqueMessage, OutboundPlainMessage, PrefixedPayload, Tls13AeadAlgorithm,
17+
UnsupportedOperationError,
18+
};
19+
use rustls::{ConnectionTrafficSecrets, ContentType, ProtocolVersion};
20+
21+
#[cfg(feature = "tls12")]
22+
use {
23+
aead::AeadCore,
24+
crypto_common::typenum::Unsigned,
25+
rustls::crypto::cipher::{KeyBlockShape, Tls12AeadAlgorithm},
26+
};
27+
28+
/// AES-128-CCM with 16-byte tag and 12-byte nonce (RFC 5116 / RFC 6655).
29+
pub type Aes128Ccm = Ccm<Aes128, U16, U12>;
30+
/// AES-256-CCM with 16-byte tag and 12-byte nonce.
31+
pub type Aes256Ccm = Ccm<Aes256, U16, U12>;
32+
/// AES-128-CCM with 8-byte tag and 12-byte nonce (CCM-8).
33+
pub type Aes128Ccm8 = Ccm<Aes128, U8, U12>;
34+
/// AES-256-CCM with 8-byte tag and 12-byte nonce (CCM-8).
35+
pub type Aes256Ccm8 = Ccm<Aes256, U8, U12>;
36+
37+
#[cfg(feature = "tls12")]
38+
const TLS12_CCM_EXPLICIT_NONCE_LEN: usize = 8;
39+
40+
#[cfg(feature = "tls12")]
41+
const TLS12_CCM_OVERHEAD: usize = TLS12_CCM_EXPLICIT_NONCE_LEN + 16;
42+
43+
#[cfg(feature = "tls12")]
44+
const TLS12_CCM_8_OVERHEAD: usize = TLS12_CCM_EXPLICIT_NONCE_LEN + 8;
45+
46+
macro_rules! impl_ccm_tls13 {
47+
($name: ident, $aead: ty, $overhead: expr) => {
48+
paste! {
49+
pub struct [<Tls13 $name>];
50+
51+
impl Tls13AeadAlgorithm for [<Tls13 $name>] {
52+
fn encrypter(&self, key: AeadKey, iv: cipher::Iv) -> Box<dyn MessageEncrypter> {
53+
Box::new([<Tls13Cipher $name>](
54+
$aead::new_from_slice(key.as_ref()).unwrap(),
55+
iv,
56+
))
57+
}
58+
59+
fn decrypter(&self, key: AeadKey, iv: cipher::Iv) -> Box<dyn MessageDecrypter> {
60+
Box::new([<Tls13Cipher $name>](
61+
$aead::new_from_slice(key.as_ref()).unwrap(),
62+
iv,
63+
))
64+
}
65+
66+
fn key_len(&self) -> usize {
67+
$aead::key_size()
68+
}
69+
70+
fn extract_keys(
71+
&self,
72+
_key: AeadKey,
73+
_iv: cipher::Iv,
74+
) -> Result<ConnectionTrafficSecrets, UnsupportedOperationError> {
75+
// rustls::ConnectionTrafficSecrets has no CCM variants.
76+
Err(UnsupportedOperationError)
77+
}
78+
}
79+
80+
struct [<Tls13Cipher $name>]($aead, cipher::Iv);
81+
82+
impl MessageEncrypter for [<Tls13Cipher $name>] {
83+
fn encrypt(&mut self, m: OutboundPlainMessage<'_>, seq: u64) -> Result<OutboundOpaqueMessage, rustls::Error> {
84+
let total_len = self.encrypted_payload_len(m.payload.len());
85+
let mut payload = PrefixedPayload::with_capacity(total_len);
86+
87+
let nonce = cipher::Nonce::new(&self.1, seq).0;
88+
let aad = cipher::make_tls13_aad(total_len);
89+
payload.extend_from_chunks(&m.payload);
90+
payload.extend_from_slice(&m.typ.to_array());
91+
92+
self.0
93+
.encrypt_in_place(&nonce.into(), &aad, &mut EncryptBufferAdapter(&mut payload))
94+
.map_err(|_| rustls::Error::EncryptError)
95+
.map(|_| OutboundOpaqueMessage::new(
96+
ContentType::ApplicationData,
97+
ProtocolVersion::TLSv1_2,
98+
payload,
99+
))
100+
}
101+
102+
fn encrypted_payload_len(&self, payload_len: usize) -> usize {
103+
payload_len + 1 + $overhead
104+
}
105+
}
106+
107+
impl MessageDecrypter for [<Tls13Cipher $name>] {
108+
fn decrypt<'a>(&mut self, mut m: InboundOpaqueMessage<'a>, seq: u64) -> Result<InboundPlainMessage<'a>, rustls::Error> {
109+
let payload = &mut m.payload;
110+
let nonce = cipher::Nonce::new(&self.1, seq).0;
111+
let aad = cipher::make_tls13_aad(payload.len());
112+
113+
self.0
114+
.decrypt_in_place(&nonce.into(), &aad, &mut DecryptBufferAdapter(payload))
115+
.map_err(|_| rustls::Error::DecryptError)?;
116+
117+
m.into_tls13_unpadded_message()
118+
}
119+
}
120+
}
121+
};
122+
}
123+
124+
#[cfg(feature = "tls12")]
125+
macro_rules! impl_ccm_tls12 {
126+
($name: ident, $aead: ty, $nonce: expr, $overhead: expr) => {
127+
paste! {
128+
#[cfg(feature = "tls12")]
129+
pub struct [<Tls12 $name>];
130+
131+
#[cfg(feature = "tls12")]
132+
impl Tls12AeadAlgorithm for [<Tls12 $name>] {
133+
fn encrypter(&self, key: AeadKey, write_iv: &[u8], explicit: &[u8]) -> Box<dyn MessageEncrypter> {
134+
Box::new([<Tls12Cipher $name Encrypter>](
135+
$aead::new_from_slice(key.as_ref()).unwrap(),
136+
{
137+
let mut iv: [u8; 12] = [0; 12];
138+
iv[..4].copy_from_slice(write_iv);
139+
iv[4..].copy_from_slice(explicit);
140+
iv
141+
},
142+
))
143+
}
144+
145+
fn decrypter(&self, dec_key: AeadKey, dec_iv: &[u8]) -> Box<dyn MessageDecrypter> {
146+
Box::new([<Tls12Cipher $name Decrypter>](
147+
$aead::new_from_slice(dec_key.as_ref()).unwrap(),
148+
dec_iv.try_into().unwrap(),
149+
))
150+
}
151+
152+
fn key_block_shape(&self) -> KeyBlockShape {
153+
KeyBlockShape {
154+
enc_key_len: $aead::key_size(),
155+
fixed_iv_len: 4,
156+
explicit_nonce_len: 8,
157+
}
158+
}
159+
160+
fn extract_keys(
161+
&self,
162+
_key: AeadKey,
163+
_iv: &[u8],
164+
_explicit: &[u8],
165+
) -> Result<ConnectionTrafficSecrets, UnsupportedOperationError> {
166+
// rustls::ConnectionTrafficSecrets has no CCM variants.
167+
Err(UnsupportedOperationError)
168+
}
169+
}
170+
171+
#[cfg(feature = "tls12")]
172+
struct [<Tls12Cipher $name Encrypter>]($aead, [u8; 12]);
173+
174+
#[cfg(feature = "tls12")]
175+
impl MessageEncrypter for [<Tls12Cipher $name Encrypter>] {
176+
fn encrypt(&mut self, m: OutboundPlainMessage<'_>, seq: u64) -> Result<OutboundOpaqueMessage, rustls::Error> {
177+
let total_len = self.encrypted_payload_len(m.payload.len());
178+
let mut payload = PrefixedPayload::with_capacity(total_len);
179+
180+
let nonce = cipher::Nonce::new(&self.1.into(), seq).0;
181+
let aad = cipher::make_tls12_aad(seq, m.typ, m.version, m.payload.len());
182+
payload.extend_from_slice(&nonce.as_ref()[4..]); // explicit
183+
payload.extend_from_chunks(&m.payload);
184+
185+
self.0
186+
.encrypt_inout_detached(&nonce.into(), &aad, (&mut payload.as_mut()[$nonce..]).into())
187+
.map(|tag| payload.extend(tag.as_ref() as &[u8]))
188+
.map_err(|_| rustls::Error::EncryptError)
189+
.map(|_| OutboundOpaqueMessage::new(m.typ, m.version, payload))
190+
}
191+
192+
fn encrypted_payload_len(&self, payload_len: usize) -> usize {
193+
payload_len + $nonce + <$aead as AeadCore>::TagSize::USIZE
194+
}
195+
}
196+
197+
#[cfg(feature = "tls12")]
198+
struct [<Tls12Cipher $name Decrypter>]($aead, [u8; 4]);
199+
200+
#[cfg(feature = "tls12")]
201+
impl MessageDecrypter for [<Tls12Cipher $name Decrypter>] {
202+
fn decrypt<'a>(&mut self, mut m: InboundOpaqueMessage<'a>, seq: u64) -> Result<InboundPlainMessage<'a>, rustls::Error> {
203+
type TagSize = <$aead as AeadCore>::TagSize;
204+
205+
let payload = &m.payload;
206+
207+
if payload.len() < $overhead {
208+
return Err(rustls::Error::DecryptError);
209+
}
210+
211+
let nonce: aead::Nonce<$aead> = {
212+
let mut nonce = [0u8; 12];
213+
nonce[..4].copy_from_slice(&self.1); // dec_iv
214+
nonce[4..].copy_from_slice(&payload[..$nonce]);
215+
nonce.into()
216+
};
217+
218+
let aad = cipher::make_tls12_aad(seq, m.typ, m.version, payload.len() - $overhead);
219+
220+
let payload = &mut m.payload;
221+
let tag_pos = {
222+
let payload = &mut payload[$nonce..];
223+
let tag_pos = payload.len() - TagSize::to_usize();
224+
let (msg, tag) = payload.split_at_mut(tag_pos);
225+
226+
let tag = ccm::Tag::<TagSize>::try_from(&*tag)
227+
.map_err(|_| rustls::Error::DecryptError)?;
228+
self.0
229+
.decrypt_inout_detached(&nonce, &aad, msg.into(), &tag)
230+
.map_err(|_| rustls::Error::DecryptError)?;
231+
tag_pos
232+
};
233+
234+
// Defer truncation until after successful decrypt so a failure cannot
235+
// leave the buffer shifted relative to the wire layout.
236+
payload.rotate_left($nonce);
237+
payload.truncate(tag_pos);
238+
Ok(m.into_plain_message())
239+
}
240+
}
241+
}
242+
};
243+
}
244+
245+
impl_ccm_tls13! {Aes128Ccm, Aes128Ccm, 16}
246+
impl_ccm_tls13! {Aes128Ccm8, Aes128Ccm8, 8}
247+
248+
#[cfg(feature = "tls12")]
249+
impl_ccm_tls12! {Aes128Ccm, Aes128Ccm, TLS12_CCM_EXPLICIT_NONCE_LEN, TLS12_CCM_OVERHEAD}
250+
251+
#[cfg(feature = "tls12")]
252+
impl_ccm_tls12! {Aes256Ccm, Aes256Ccm, TLS12_CCM_EXPLICIT_NONCE_LEN, TLS12_CCM_OVERHEAD}
253+
254+
#[cfg(feature = "tls12")]
255+
impl_ccm_tls12! {Aes128Ccm8, Aes128Ccm8, TLS12_CCM_EXPLICIT_NONCE_LEN, TLS12_CCM_8_OVERHEAD}
256+
257+
#[cfg(feature = "tls12")]
258+
impl_ccm_tls12! {Aes256Ccm8, Aes256Ccm8, TLS12_CCM_EXPLICIT_NONCE_LEN, TLS12_CCM_8_OVERHEAD}

0 commit comments

Comments
 (0)