#![warn(missing_docs)]
use std::sync::LazyLock;
use aes::{Aes128, Aes256};
use ccm::Ccm;
use ccm::aead::{AeadCore, AeadInOut, KeyInit};
use ccm::consts::{U8, U12, U16};
use rustls::crypto::CryptoProvider;
use rustls::{
CipherSuite, CipherSuiteCommon, SupportedCipherSuite, Tls12CipherSuite, Tls13CipherSuite,
};
mod tls12;
mod tls13;
pub(crate) trait CcmVariant: Send + Sync + 'static {
type Cipher: AeadInOut + AeadCore<NonceSize = U12> + KeyInit + Send + Sync;
const KEY_LEN: usize;
const TAG_LEN: usize;
}
pub(crate) enum Aes128Ccm8V {}
impl CcmVariant for Aes128Ccm8V {
type Cipher = Ccm<Aes128, U8, U12>;
const KEY_LEN: usize = 16;
const TAG_LEN: usize = 8;
}
pub(crate) enum Aes128Ccm16V {}
impl CcmVariant for Aes128Ccm16V {
type Cipher = Ccm<Aes128, U16, U12>;
const KEY_LEN: usize = 16;
const TAG_LEN: usize = 16;
}
pub(crate) enum Aes256Ccm8V {}
impl CcmVariant for Aes256Ccm8V {
type Cipher = Ccm<Aes256, U8, U12>;
const KEY_LEN: usize = 32;
const TAG_LEN: usize = 8;
}
pub(crate) enum Aes256Ccm16V {}
impl CcmVariant for Aes256Ccm16V {
type Cipher = Ccm<Aes256, U16, U12>;
const KEY_LEN: usize = 32;
const TAG_LEN: usize = 16;
}
const CONFIDENTIALITY_LIMIT: u64 = 1 << 23;
fn tls12_base() -> &'static Tls12CipherSuite {
let base = rustls::crypto::aws_lc_rs::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256;
let SupportedCipherSuite::Tls12(s) = base else {
unreachable!()
};
s
}
static SUITE_TLS12_128_CCM: LazyLock<Tls12CipherSuite> = LazyLock::new(|| {
let base = tls12_base();
Tls12CipherSuite {
common: CipherSuiteCommon {
suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CCM,
hash_provider: base.common.hash_provider,
confidentiality_limit: CONFIDENTIALITY_LIMIT,
},
prf_provider: base.prf_provider,
kx: base.kx,
sign: base.sign,
aead_alg: &tls12::Tls12CcmAead::<Aes128Ccm16V>::NEW,
}
});
static SUITE_TLS12_256_CCM: LazyLock<Tls12CipherSuite> = LazyLock::new(|| {
let base = tls12_base();
Tls12CipherSuite {
common: CipherSuiteCommon {
suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CCM,
hash_provider: base.common.hash_provider,
confidentiality_limit: CONFIDENTIALITY_LIMIT,
},
prf_provider: base.prf_provider,
kx: base.kx,
sign: base.sign,
aead_alg: &tls12::Tls12CcmAead::<Aes256Ccm16V>::NEW,
}
});
static SUITE_TLS12_128_CCM8: LazyLock<Tls12CipherSuite> = LazyLock::new(|| {
let base = tls12_base();
Tls12CipherSuite {
common: CipherSuiteCommon {
suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8,
hash_provider: base.common.hash_provider,
confidentiality_limit: CONFIDENTIALITY_LIMIT,
},
prf_provider: base.prf_provider,
kx: base.kx,
sign: base.sign,
aead_alg: &tls12::Tls12CcmAead::<Aes128Ccm8V>::NEW,
}
});
static SUITE_TLS12_256_CCM8: LazyLock<Tls12CipherSuite> = LazyLock::new(|| {
let base = tls12_base();
Tls12CipherSuite {
common: CipherSuiteCommon {
suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8,
hash_provider: base.common.hash_provider,
confidentiality_limit: CONFIDENTIALITY_LIMIT,
},
prf_provider: base.prf_provider,
kx: base.kx,
sign: base.sign,
aead_alg: &tls12::Tls12CcmAead::<Aes256Ccm8V>::NEW,
}
});
fn tls13_base() -> &'static Tls13CipherSuite {
let base = rustls::crypto::aws_lc_rs::cipher_suite::TLS13_AES_128_GCM_SHA256;
let SupportedCipherSuite::Tls13(s) = base else {
unreachable!()
};
s
}
static SUITE_TLS13_128_CCM: LazyLock<Tls13CipherSuite> = LazyLock::new(|| {
let base = tls13_base();
Tls13CipherSuite {
common: CipherSuiteCommon {
suite: CipherSuite::TLS13_AES_128_CCM_SHA256,
hash_provider: base.common.hash_provider,
confidentiality_limit: CONFIDENTIALITY_LIMIT,
},
hkdf_provider: base.hkdf_provider,
aead_alg: &tls13::Tls13CcmAead::<Aes128Ccm16V>::NEW,
quic: None,
}
});
static SUITE_TLS13_128_CCM8: LazyLock<Tls13CipherSuite> = LazyLock::new(|| {
let base = tls13_base();
Tls13CipherSuite {
common: CipherSuiteCommon {
suite: CipherSuite::TLS13_AES_128_CCM_8_SHA256,
hash_provider: base.common.hash_provider,
confidentiality_limit: CONFIDENTIALITY_LIMIT,
},
hkdf_provider: base.hkdf_provider,
aead_alg: &tls13::Tls13CcmAead::<Aes128Ccm8V>::NEW,
quic: None,
}
});
pub static TLS_ECDHE_ECDSA_WITH_AES_128_CCM: LazyLock<SupportedCipherSuite> =
LazyLock::new(|| SupportedCipherSuite::Tls12(&SUITE_TLS12_128_CCM));
pub static TLS_ECDHE_ECDSA_WITH_AES_256_CCM: LazyLock<SupportedCipherSuite> =
LazyLock::new(|| SupportedCipherSuite::Tls12(&SUITE_TLS12_256_CCM));
pub static TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: LazyLock<SupportedCipherSuite> =
LazyLock::new(|| SupportedCipherSuite::Tls12(&SUITE_TLS12_128_CCM8));
pub static TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8: LazyLock<SupportedCipherSuite> =
LazyLock::new(|| SupportedCipherSuite::Tls12(&SUITE_TLS12_256_CCM8));
pub static TLS13_AES_128_CCM_SHA256: LazyLock<SupportedCipherSuite> =
LazyLock::new(|| SupportedCipherSuite::Tls13(&SUITE_TLS13_128_CCM));
pub static TLS13_AES_128_CCM_8_SHA256: LazyLock<SupportedCipherSuite> =
LazyLock::new(|| SupportedCipherSuite::Tls13(&SUITE_TLS13_128_CCM8));
pub fn all_suites() -> [SupportedCipherSuite; 6] {
[
*TLS_ECDHE_ECDSA_WITH_AES_128_CCM,
*TLS_ECDHE_ECDSA_WITH_AES_256_CCM,
*TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8,
*TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8,
*TLS13_AES_128_CCM_SHA256,
*TLS13_AES_128_CCM_8_SHA256,
]
}
pub fn crypto_provider() -> CryptoProvider {
let mut provider = rustls::crypto::aws_lc_rs::default_provider();
provider.cipher_suites.extend(all_suites());
provider
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_suites_accessible() {
let suites = all_suites();
assert_eq!(
suites[0].suite(),
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CCM
);
assert_eq!(
suites[1].suite(),
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CCM
);
assert_eq!(
suites[2].suite(),
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8
);
assert_eq!(
suites[3].suite(),
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8
);
assert_eq!(suites[4].suite(), CipherSuite::TLS13_AES_128_CCM_SHA256);
assert_eq!(suites[5].suite(), CipherSuite::TLS13_AES_128_CCM_8_SHA256);
}
#[test]
fn crypto_provider_includes_all_ccm() {
let provider = crypto_provider();
for suite in all_suites() {
assert!(
provider
.cipher_suites
.iter()
.any(|s| s.suite() == suite.suite()),
"missing {:?}",
suite.suite()
);
}
}
#[test]
fn ccm_round_trip() {
let key = [0x42u8; 16];
let nonce = ccm::aead::array::Array::from([1u8; 12]);
let aad = b"additional data";
let plaintext = b"hello CCM";
let cipher = <Ccm<Aes128, U16, U12> as KeyInit>::new_from_slice(&key).unwrap();
let mut buf = plaintext.to_vec();
let tag = cipher
.encrypt_inout_detached(&nonce, aad.as_slice(), buf.as_mut_slice().into())
.unwrap();
assert_eq!(tag.len(), 16);
cipher
.decrypt_inout_detached(&nonce, aad.as_slice(), buf.as_mut_slice().into(), &tag)
.unwrap();
assert_eq!(&buf, plaintext);
let cipher8 = <Ccm<Aes128, U8, U12> as KeyInit>::new_from_slice(&key).unwrap();
let mut buf = plaintext.to_vec();
let tag = cipher8
.encrypt_inout_detached(&nonce, aad.as_slice(), buf.as_mut_slice().into())
.unwrap();
assert_eq!(tag.len(), 8);
cipher8
.decrypt_inout_detached(&nonce, aad.as_slice(), buf.as_mut_slice().into(), &tag)
.unwrap();
assert_eq!(&buf, plaintext);
}
#[test]
fn ccm_tampered_fails() {
let key = [0x42u8; 16];
let nonce = ccm::aead::array::Array::from([2u8; 12]);
let cipher = <Ccm<Aes128, U16, U12> as KeyInit>::new_from_slice(&key).unwrap();
let mut buf = b"secret".to_vec();
let tag = cipher
.encrypt_inout_detached(&nonce, b"", buf.as_mut_slice().into())
.unwrap();
buf[0] ^= 0xff;
assert!(
cipher
.decrypt_inout_detached(&nonce, b"", buf.as_mut_slice().into(), &tag)
.is_err()
);
}
#[test]
fn ccm256_round_trip() {
let key = [0x42u8; 32];
let nonce = ccm::aead::array::Array::from([3u8; 12]);
let cipher = <Ccm<Aes256, U16, U12> as KeyInit>::new_from_slice(&key).unwrap();
let mut buf = b"aes-256-ccm".to_vec();
let tag = cipher
.encrypt_inout_detached(&nonce, b"", buf.as_mut_slice().into())
.unwrap();
cipher
.decrypt_inout_detached(&nonce, b"", buf.as_mut_slice().into(), &tag)
.unwrap();
assert_eq!(&buf, b"aes-256-ccm");
}
#[test]
fn tls12_key_block_shapes() {
use rustls::crypto::cipher::Tls12AeadAlgorithm;
let aead128 = tls12::Tls12CcmAead::<Aes128Ccm16V>::NEW;
assert_eq!(aead128.key_block_shape().enc_key_len, 16);
let aead256 = tls12::Tls12CcmAead::<Aes256Ccm16V>::NEW;
assert_eq!(aead256.key_block_shape().enc_key_len, 32);
let aead128_8 = tls12::Tls12CcmAead::<Aes128Ccm8V>::NEW;
assert_eq!(aead128_8.key_block_shape().enc_key_len, 16);
assert_eq!(aead128_8.key_block_shape().fixed_iv_len, 4);
assert_eq!(aead128_8.key_block_shape().explicit_nonce_len, 8);
}
#[test]
fn tls13_key_lens() {
use rustls::crypto::cipher::Tls13AeadAlgorithm;
assert_eq!(tls13::Tls13CcmAead::<Aes128Ccm16V>::NEW.key_len(), 16);
assert_eq!(tls13::Tls13CcmAead::<Aes128Ccm8V>::NEW.key_len(), 16);
}
}