1#![warn(missing_docs)]
2use std::sync::LazyLock;
57
58use aes::{Aes128, Aes256};
59use ccm::Ccm;
60use ccm::aead::{AeadCore, AeadInOut, KeyInit};
61use ccm::consts::{U8, U12, U16};
62use rustls::crypto::CryptoProvider;
63use rustls::{
64 CipherSuite, CipherSuiteCommon, SupportedCipherSuite, Tls12CipherSuite, Tls13CipherSuite,
65};
66
67mod tls12;
68mod tls13;
69
70pub(crate) trait CcmVariant: Send + Sync + 'static {
76 type Cipher: AeadInOut + AeadCore<NonceSize = U12> + KeyInit + Send + Sync;
77 const KEY_LEN: usize;
78 const TAG_LEN: usize;
79}
80
81pub(crate) enum Aes128Ccm8V {}
82impl CcmVariant for Aes128Ccm8V {
83 type Cipher = Ccm<Aes128, U8, U12>;
84 const KEY_LEN: usize = 16;
85 const TAG_LEN: usize = 8;
86}
87
88pub(crate) enum Aes128Ccm16V {}
89impl CcmVariant for Aes128Ccm16V {
90 type Cipher = Ccm<Aes128, U16, U12>;
91 const KEY_LEN: usize = 16;
92 const TAG_LEN: usize = 16;
93}
94
95pub(crate) enum Aes256Ccm8V {}
96impl CcmVariant for Aes256Ccm8V {
97 type Cipher = Ccm<Aes256, U8, U12>;
98 const KEY_LEN: usize = 32;
99 const TAG_LEN: usize = 8;
100}
101
102pub(crate) enum Aes256Ccm16V {}
103impl CcmVariant for Aes256Ccm16V {
104 type Cipher = Ccm<Aes256, U16, U12>;
105 const KEY_LEN: usize = 32;
106 const TAG_LEN: usize = 16;
107}
108
109const CONFIDENTIALITY_LIMIT: u64 = 1 << 23;
114
115fn tls12_base() -> &'static Tls12CipherSuite {
120 let base = rustls::crypto::aws_lc_rs::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256;
121 let SupportedCipherSuite::Tls12(s) = base else {
122 unreachable!()
123 };
124 s
125}
126
127static SUITE_TLS12_128_CCM: LazyLock<Tls12CipherSuite> = LazyLock::new(|| {
128 let base = tls12_base();
129 Tls12CipherSuite {
130 common: CipherSuiteCommon {
131 suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CCM,
132 hash_provider: base.common.hash_provider,
133 confidentiality_limit: CONFIDENTIALITY_LIMIT,
134 },
135 prf_provider: base.prf_provider,
136 kx: base.kx,
137 sign: base.sign,
138 aead_alg: &tls12::Tls12CcmAead::<Aes128Ccm16V>::NEW,
139 }
140});
141
142static SUITE_TLS12_256_CCM: LazyLock<Tls12CipherSuite> = LazyLock::new(|| {
143 let base = tls12_base();
144 Tls12CipherSuite {
145 common: CipherSuiteCommon {
146 suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CCM,
147 hash_provider: base.common.hash_provider,
148 confidentiality_limit: CONFIDENTIALITY_LIMIT,
149 },
150 prf_provider: base.prf_provider,
151 kx: base.kx,
152 sign: base.sign,
153 aead_alg: &tls12::Tls12CcmAead::<Aes256Ccm16V>::NEW,
154 }
155});
156
157static SUITE_TLS12_128_CCM8: LazyLock<Tls12CipherSuite> = LazyLock::new(|| {
158 let base = tls12_base();
159 Tls12CipherSuite {
160 common: CipherSuiteCommon {
161 suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8,
162 hash_provider: base.common.hash_provider,
163 confidentiality_limit: CONFIDENTIALITY_LIMIT,
164 },
165 prf_provider: base.prf_provider,
166 kx: base.kx,
167 sign: base.sign,
168 aead_alg: &tls12::Tls12CcmAead::<Aes128Ccm8V>::NEW,
169 }
170});
171
172static SUITE_TLS12_256_CCM8: LazyLock<Tls12CipherSuite> = LazyLock::new(|| {
173 let base = tls12_base();
174 Tls12CipherSuite {
175 common: CipherSuiteCommon {
176 suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8,
177 hash_provider: base.common.hash_provider,
178 confidentiality_limit: CONFIDENTIALITY_LIMIT,
179 },
180 prf_provider: base.prf_provider,
181 kx: base.kx,
182 sign: base.sign,
183 aead_alg: &tls12::Tls12CcmAead::<Aes256Ccm8V>::NEW,
184 }
185});
186
187fn tls13_base() -> &'static Tls13CipherSuite {
192 let base = rustls::crypto::aws_lc_rs::cipher_suite::TLS13_AES_128_GCM_SHA256;
193 let SupportedCipherSuite::Tls13(s) = base else {
194 unreachable!()
195 };
196 s
197}
198
199static SUITE_TLS13_128_CCM: LazyLock<Tls13CipherSuite> = LazyLock::new(|| {
200 let base = tls13_base();
201 Tls13CipherSuite {
202 common: CipherSuiteCommon {
203 suite: CipherSuite::TLS13_AES_128_CCM_SHA256,
204 hash_provider: base.common.hash_provider,
205 confidentiality_limit: CONFIDENTIALITY_LIMIT,
206 },
207 hkdf_provider: base.hkdf_provider,
208 aead_alg: &tls13::Tls13CcmAead::<Aes128Ccm16V>::NEW,
209 quic: None,
210 }
211});
212
213static SUITE_TLS13_128_CCM8: LazyLock<Tls13CipherSuite> = LazyLock::new(|| {
214 let base = tls13_base();
215 Tls13CipherSuite {
216 common: CipherSuiteCommon {
217 suite: CipherSuite::TLS13_AES_128_CCM_8_SHA256,
218 hash_provider: base.common.hash_provider,
219 confidentiality_limit: CONFIDENTIALITY_LIMIT,
220 },
221 hkdf_provider: base.hkdf_provider,
222 aead_alg: &tls13::Tls13CcmAead::<Aes128Ccm8V>::NEW,
223 quic: None,
224 }
225});
226
227pub static TLS_ECDHE_ECDSA_WITH_AES_128_CCM: LazyLock<SupportedCipherSuite> =
233 LazyLock::new(|| SupportedCipherSuite::Tls12(&SUITE_TLS12_128_CCM));
234
235pub static TLS_ECDHE_ECDSA_WITH_AES_256_CCM: LazyLock<SupportedCipherSuite> =
237 LazyLock::new(|| SupportedCipherSuite::Tls12(&SUITE_TLS12_256_CCM));
238
239pub static TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: LazyLock<SupportedCipherSuite> =
241 LazyLock::new(|| SupportedCipherSuite::Tls12(&SUITE_TLS12_128_CCM8));
242
243pub static TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8: LazyLock<SupportedCipherSuite> =
245 LazyLock::new(|| SupportedCipherSuite::Tls12(&SUITE_TLS12_256_CCM8));
246
247pub static TLS13_AES_128_CCM_SHA256: LazyLock<SupportedCipherSuite> =
251 LazyLock::new(|| SupportedCipherSuite::Tls13(&SUITE_TLS13_128_CCM));
252
253pub static TLS13_AES_128_CCM_8_SHA256: LazyLock<SupportedCipherSuite> =
257 LazyLock::new(|| SupportedCipherSuite::Tls13(&SUITE_TLS13_128_CCM8));
258
259pub fn all_suites() -> [SupportedCipherSuite; 6] {
261 [
262 *TLS_ECDHE_ECDSA_WITH_AES_128_CCM,
263 *TLS_ECDHE_ECDSA_WITH_AES_256_CCM,
264 *TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8,
265 *TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8,
266 *TLS13_AES_128_CCM_SHA256,
267 *TLS13_AES_128_CCM_8_SHA256,
268 ]
269}
270
271pub fn crypto_provider() -> CryptoProvider {
286 let mut provider = rustls::crypto::aws_lc_rs::default_provider();
287 provider.cipher_suites.extend(all_suites());
288 provider
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294
295 #[test]
296 fn all_suites_accessible() {
297 let suites = all_suites();
298 assert_eq!(
299 suites[0].suite(),
300 CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CCM
301 );
302 assert_eq!(
303 suites[1].suite(),
304 CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CCM
305 );
306 assert_eq!(
307 suites[2].suite(),
308 CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8
309 );
310 assert_eq!(
311 suites[3].suite(),
312 CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8
313 );
314 assert_eq!(suites[4].suite(), CipherSuite::TLS13_AES_128_CCM_SHA256);
315 assert_eq!(suites[5].suite(), CipherSuite::TLS13_AES_128_CCM_8_SHA256);
316 }
317
318 #[test]
319 fn crypto_provider_includes_all_ccm() {
320 let provider = crypto_provider();
321 for suite in all_suites() {
322 assert!(
323 provider
324 .cipher_suites
325 .iter()
326 .any(|s| s.suite() == suite.suite()),
327 "missing {:?}",
328 suite.suite()
329 );
330 }
331 }
332
333 #[test]
334 fn ccm_round_trip() {
335 let key = [0x42u8; 16];
336 let nonce = ccm::aead::array::Array::from([1u8; 12]);
337 let aad = b"additional data";
338 let plaintext = b"hello CCM";
339
340 let cipher = <Ccm<Aes128, U16, U12> as KeyInit>::new_from_slice(&key).unwrap();
342 let mut buf = plaintext.to_vec();
343 let tag = cipher
344 .encrypt_inout_detached(&nonce, aad.as_slice(), buf.as_mut_slice().into())
345 .unwrap();
346 assert_eq!(tag.len(), 16);
347 cipher
348 .decrypt_inout_detached(&nonce, aad.as_slice(), buf.as_mut_slice().into(), &tag)
349 .unwrap();
350 assert_eq!(&buf, plaintext);
351
352 let cipher8 = <Ccm<Aes128, U8, U12> as KeyInit>::new_from_slice(&key).unwrap();
354 let mut buf = plaintext.to_vec();
355 let tag = cipher8
356 .encrypt_inout_detached(&nonce, aad.as_slice(), buf.as_mut_slice().into())
357 .unwrap();
358 assert_eq!(tag.len(), 8);
359 cipher8
360 .decrypt_inout_detached(&nonce, aad.as_slice(), buf.as_mut_slice().into(), &tag)
361 .unwrap();
362 assert_eq!(&buf, plaintext);
363 }
364
365 #[test]
366 fn ccm_tampered_fails() {
367 let key = [0x42u8; 16];
368 let nonce = ccm::aead::array::Array::from([2u8; 12]);
369 let cipher = <Ccm<Aes128, U16, U12> as KeyInit>::new_from_slice(&key).unwrap();
370 let mut buf = b"secret".to_vec();
371 let tag = cipher
372 .encrypt_inout_detached(&nonce, b"", buf.as_mut_slice().into())
373 .unwrap();
374 buf[0] ^= 0xff;
375 assert!(
376 cipher
377 .decrypt_inout_detached(&nonce, b"", buf.as_mut_slice().into(), &tag)
378 .is_err()
379 );
380 }
381
382 #[test]
383 fn ccm256_round_trip() {
384 let key = [0x42u8; 32];
385 let nonce = ccm::aead::array::Array::from([3u8; 12]);
386 let cipher = <Ccm<Aes256, U16, U12> as KeyInit>::new_from_slice(&key).unwrap();
387 let mut buf = b"aes-256-ccm".to_vec();
388 let tag = cipher
389 .encrypt_inout_detached(&nonce, b"", buf.as_mut_slice().into())
390 .unwrap();
391 cipher
392 .decrypt_inout_detached(&nonce, b"", buf.as_mut_slice().into(), &tag)
393 .unwrap();
394 assert_eq!(&buf, b"aes-256-ccm");
395 }
396
397 #[test]
398 fn tls12_key_block_shapes() {
399 use rustls::crypto::cipher::Tls12AeadAlgorithm;
400
401 let aead128 = tls12::Tls12CcmAead::<Aes128Ccm16V>::NEW;
402 assert_eq!(aead128.key_block_shape().enc_key_len, 16);
403
404 let aead256 = tls12::Tls12CcmAead::<Aes256Ccm16V>::NEW;
405 assert_eq!(aead256.key_block_shape().enc_key_len, 32);
406
407 let aead128_8 = tls12::Tls12CcmAead::<Aes128Ccm8V>::NEW;
408 assert_eq!(aead128_8.key_block_shape().enc_key_len, 16);
409 assert_eq!(aead128_8.key_block_shape().fixed_iv_len, 4);
410 assert_eq!(aead128_8.key_block_shape().explicit_nonce_len, 8);
411 }
412
413 #[test]
414 fn tls13_key_lens() {
415 use rustls::crypto::cipher::Tls13AeadAlgorithm;
416
417 assert_eq!(tls13::Tls13CcmAead::<Aes128Ccm16V>::NEW.key_len(), 16);
418 assert_eq!(tls13::Tls13CcmAead::<Aes128Ccm8V>::NEW.key_len(), 16);
419 }
420}