#![allow(dead_code)]
use crate::cipher::{Aes128, Aes256, BlockCipher, BlockCipher64, TdesEde3};
use crate::ct::ConstantTimeEq;
use crate::hash::{Digest, Hmac, Sha1, Sha256};
use crate::rng::RngCore;
use crate::tls::ContentType;
use crate::tls::Error;
use crate::tls::codec::CipherSuite;
use crate::tls::version::ProtocolVersion;
use alloc::vec;
use alloc::vec::Vec;
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum LegacyKx {
Rsa,
EcdheRsa,
}
#[derive(Clone, Copy)]
pub(crate) struct LegacyCbcSuite {
pub(crate) suite: CipherSuite,
pub(crate) cipher: CbcCipherAlg,
pub(crate) mac: CbcMacAlg,
pub(crate) kx: LegacyKx,
}
pub(crate) const LEGACY_CBC_SUITES: [LegacyCbcSuite; 10] = [
LegacyCbcSuite {
suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA256,
cipher: CbcCipherAlg::Aes256,
mac: CbcMacAlg::Sha256,
kx: LegacyKx::EcdheRsa,
},
LegacyCbcSuite {
suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
cipher: CbcCipherAlg::Aes128,
mac: CbcMacAlg::Sha256,
kx: LegacyKx::EcdheRsa,
},
LegacyCbcSuite {
suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
cipher: CbcCipherAlg::Aes256,
mac: CbcMacAlg::Sha1,
kx: LegacyKx::EcdheRsa,
},
LegacyCbcSuite {
suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
cipher: CbcCipherAlg::Aes128,
mac: CbcMacAlg::Sha1,
kx: LegacyKx::EcdheRsa,
},
LegacyCbcSuite {
suite: CipherSuite::TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
cipher: CbcCipherAlg::Tdes,
mac: CbcMacAlg::Sha1,
kx: LegacyKx::EcdheRsa,
},
LegacyCbcSuite {
suite: CipherSuite::TLS_RSA_WITH_AES_256_CBC_SHA256,
cipher: CbcCipherAlg::Aes256,
mac: CbcMacAlg::Sha256,
kx: LegacyKx::Rsa,
},
LegacyCbcSuite {
suite: CipherSuite::TLS_RSA_WITH_AES_128_CBC_SHA256,
cipher: CbcCipherAlg::Aes128,
mac: CbcMacAlg::Sha256,
kx: LegacyKx::Rsa,
},
LegacyCbcSuite {
suite: CipherSuite::TLS_RSA_WITH_AES_256_CBC_SHA,
cipher: CbcCipherAlg::Aes256,
mac: CbcMacAlg::Sha1,
kx: LegacyKx::Rsa,
},
LegacyCbcSuite {
suite: CipherSuite::TLS_RSA_WITH_AES_128_CBC_SHA,
cipher: CbcCipherAlg::Aes128,
mac: CbcMacAlg::Sha1,
kx: LegacyKx::Rsa,
},
LegacyCbcSuite {
suite: CipherSuite::TLS_RSA_WITH_3DES_EDE_CBC_SHA,
cipher: CbcCipherAlg::Tdes,
mac: CbcMacAlg::Sha1,
kx: LegacyKx::Rsa,
},
];
pub(crate) fn lookup_legacy_cbc(s: CipherSuite) -> Option<LegacyCbcSuite> {
LEGACY_CBC_SUITES.iter().copied().find(|p| p.suite == s)
}
pub(crate) struct CbcKeyMaterial {
pub(crate) client_mac: Vec<u8>,
pub(crate) server_mac: Vec<u8>,
pub(crate) client_key: Vec<u8>,
pub(crate) server_key: Vec<u8>,
pub(crate) client_iv: Vec<u8>,
pub(crate) server_iv: Vec<u8>,
}
pub(crate) fn cbc_key_block_len(cipher: CbcCipherAlg, mac: CbcMacAlg, explicit_iv: bool) -> usize {
let fixed_iv = if explicit_iv { 0 } else { cipher.block_size() };
2 * mac.key_len() + 2 * cipher.key_len() + 2 * fixed_iv
}
pub(crate) fn split_cbc_key_block(
kb: &[u8],
cipher: CbcCipherAlg,
mac: CbcMacAlg,
explicit_iv: bool,
) -> CbcKeyMaterial {
let mk = mac.key_len();
let ek = cipher.key_len();
let iv = if explicit_iv { 0 } else { cipher.block_size() };
let mut o = 0;
let mut take = |n: usize| {
let s = kb[o..o + n].to_vec();
o += n;
s
};
CbcKeyMaterial {
client_mac: take(mk),
server_mac: take(mk),
client_key: take(ek),
server_key: take(ek),
client_iv: take(iv),
server_iv: take(iv),
}
}
pub(crate) struct LegacyCrypters {
pub(crate) client: CbcRecordCrypter,
pub(crate) server: CbcRecordCrypter,
}
pub(crate) fn build_legacy_crypters(
ls: LegacyCbcSuite,
version: ProtocolVersion,
master: &[u8; 48],
client_random: &[u8; 32],
server_random: &[u8; 32],
) -> LegacyCrypters {
if version == ProtocolVersion::SSLv3 {
let kb_len = cbc_key_block_len(ls.cipher, ls.mac, false);
let mut kb = vec![0u8; kb_len];
super::ssl3::ssl3_key_block(master, server_random, client_random, &mut kb);
let km = split_cbc_key_block(&kb, ls.cipher, ls.mac, false);
let client = CbcRecordCrypter::new_ssl3(
ls.cipher,
&km.client_key,
ls.mac,
&km.client_mac,
&km.client_iv,
);
let server = CbcRecordCrypter::new_ssl3(
ls.cipher,
&km.server_key,
ls.mac,
&km.server_mac,
&km.server_iv,
);
return LegacyCrypters { client, server };
}
let explicit_iv = version.as_u16() >= ProtocolVersion::TLSv1_1.as_u16();
let kb_len = cbc_key_block_len(ls.cipher, ls.mac, explicit_iv);
let mut kb = vec![0u8; kb_len + 64];
crate::tls::crypto::prf::key_block_legacy(master, server_random, client_random, &mut kb);
let km = split_cbc_key_block(&kb[..kb_len], ls.cipher, ls.mac, explicit_iv);
let client_iv_seed = &kb[kb_len..kb_len + 32];
let server_iv_seed = &kb[kb_len + 32..kb_len + 64];
let client = CbcRecordCrypter::new(
ls.cipher,
&km.client_key,
ls.mac,
&km.client_mac,
explicit_iv,
&km.client_iv,
client_iv_seed,
);
let server = CbcRecordCrypter::new(
ls.cipher,
&km.server_key,
ls.mac,
&km.server_mac,
explicit_iv,
&km.server_iv,
server_iv_seed,
);
LegacyCrypters { client, server }
}
#[derive(Clone, Copy)]
pub(crate) enum CbcCipherAlg {
Aes128,
Aes256,
Tdes,
}
impl CbcCipherAlg {
pub(crate) fn block_size(self) -> usize {
match self {
CbcCipherAlg::Aes128 | CbcCipherAlg::Aes256 => 16,
CbcCipherAlg::Tdes => 8,
}
}
pub(crate) fn key_len(self) -> usize {
match self {
CbcCipherAlg::Aes128 => 16,
CbcCipherAlg::Aes256 => 32,
CbcCipherAlg::Tdes => 24,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum CbcMacAlg {
Sha1,
Sha256,
}
impl CbcMacAlg {
pub(crate) fn mac_len(self) -> usize {
match self {
CbcMacAlg::Sha1 => 20,
CbcMacAlg::Sha256 => 32,
}
}
pub(crate) fn key_len(self) -> usize {
self.mac_len()
}
}
enum Cipher {
Aes128(Aes128),
Aes256(Aes256),
Tdes(TdesEde3),
}
impl Cipher {
fn cbc_encrypt(&self, iv: &[u8], buf: &mut [u8]) {
match self {
Cipher::Aes128(c) => cbc_encrypt16(c, iv, buf),
Cipher::Aes256(c) => cbc_encrypt16(c, iv, buf),
Cipher::Tdes(c) => cbc_encrypt8(c, iv, buf),
}
}
fn cbc_decrypt(&self, iv: &[u8], buf: &mut [u8]) {
match self {
Cipher::Aes128(c) => cbc_decrypt16(c, iv, buf),
Cipher::Aes256(c) => cbc_decrypt16(c, iv, buf),
Cipher::Tdes(c) => cbc_decrypt8(c, iv, buf),
}
}
}
fn cbc_encrypt16<C: BlockCipher>(c: &C, iv: &[u8], buf: &mut [u8]) {
let mut chain = [0u8; 16];
chain.copy_from_slice(iv);
for blk in buf.chunks_exact_mut(16) {
for (b, ch) in blk.iter_mut().zip(chain.iter()) {
*b ^= *ch;
}
let b: &mut [u8; 16] = blk.try_into().unwrap();
c.encrypt_block(b);
chain.copy_from_slice(blk);
}
}
fn cbc_decrypt16<C: BlockCipher>(c: &C, iv: &[u8], buf: &mut [u8]) {
let mut chain = [0u8; 16];
chain.copy_from_slice(iv);
for blk in buf.chunks_exact_mut(16) {
let saved = <[u8; 16]>::try_from(&blk[..]).unwrap();
let b: &mut [u8; 16] = blk.try_into().unwrap();
c.decrypt_block(b);
for (b, ch) in blk.iter_mut().zip(chain.iter()) {
*b ^= *ch;
}
chain = saved;
}
}
fn cbc_encrypt8<C: BlockCipher64>(c: &C, iv: &[u8], buf: &mut [u8]) {
let mut chain = [0u8; 8];
chain.copy_from_slice(iv);
for blk in buf.chunks_exact_mut(8) {
for (b, ch) in blk.iter_mut().zip(chain.iter()) {
*b ^= *ch;
}
let b: &mut [u8; 8] = blk.try_into().unwrap();
c.encrypt_block(b);
chain.copy_from_slice(blk);
}
}
fn cbc_decrypt8<C: BlockCipher64>(c: &C, iv: &[u8], buf: &mut [u8]) {
let mut chain = [0u8; 8];
chain.copy_from_slice(iv);
for blk in buf.chunks_exact_mut(8) {
let saved = <[u8; 8]>::try_from(&blk[..]).unwrap();
let b: &mut [u8; 8] = blk.try_into().unwrap();
c.decrypt_block(b);
for (b, ch) in blk.iter_mut().zip(chain.iter()) {
*b ^= *ch;
}
chain = saved;
}
}
#[inline]
fn ct_eq_u8(a: u8, b: u8) -> u8 {
let d = a ^ b;
let z = (d as i32 - 1) >> 8; z as u8
}
#[inline]
fn ct_le(a: usize, b: usize) -> u8 {
let r = (b as i64).wrapping_sub(a as i64); !((r >> 63) as u8) }
pub(crate) struct CbcRecordCrypter {
cipher: Cipher,
mac_key: Vec<u8>,
mac: CbcMacAlg,
block_size: usize,
explicit_iv: bool,
chain: Vec<u8>,
iv_rng: crate::rng::HmacDrbg<crate::hash::Sha256>,
ssl3: bool,
seq: u64,
}
impl CbcRecordCrypter {
#[allow(dead_code)] pub(crate) fn new(
cipher_alg: CbcCipherAlg,
enc_key: &[u8],
mac_alg: CbcMacAlg,
mac_key: &[u8],
explicit_iv: bool,
initial_iv: &[u8],
iv_seed: &[u8],
) -> Self {
let cipher = match cipher_alg {
CbcCipherAlg::Aes128 => {
Cipher::Aes128(Aes128::new(enc_key.try_into().expect("aes128 key")))
}
CbcCipherAlg::Aes256 => {
Cipher::Aes256(Aes256::new(enc_key.try_into().expect("aes256 key")))
}
CbcCipherAlg::Tdes => {
Cipher::Tdes(TdesEde3::new(enc_key.try_into().expect("3des key")))
}
};
CbcRecordCrypter {
cipher,
mac_key: mac_key.to_vec(),
mac: mac_alg,
block_size: cipher_alg.block_size(),
explicit_iv,
chain: initial_iv.to_vec(),
iv_rng: crate::rng::HmacDrbg::new(iv_seed, b"tls-cbc-explicit-iv", &[]),
ssl3: false,
seq: 0,
}
}
pub(crate) fn new_ssl3(
cipher_alg: CbcCipherAlg,
enc_key: &[u8],
mac_alg: CbcMacAlg,
mac_key: &[u8],
initial_iv: &[u8],
) -> Self {
let mut c = Self::new(
cipher_alg,
enc_key,
mac_alg,
mac_key,
false,
initial_iv,
b"ssl3-unused",
);
c.ssl3 = true;
c
}
fn compute_mac(&self, ct: ContentType, version: ProtocolVersion, content: &[u8]) -> Vec<u8> {
if self.ssl3 {
let m = match self.mac {
CbcMacAlg::Sha1 => super::ssl3::Ssl3Mac::Sha1,
CbcMacAlg::Sha256 => super::ssl3::Ssl3Mac::Sha1,
};
return super::ssl3::ssl3_record_mac(m, &self.mac_key, self.seq, ct, content);
}
let mut header = [0u8; 13];
header[..8].copy_from_slice(&self.seq.to_be_bytes());
header[8] = ct.as_u8();
header[9..11].copy_from_slice(&version.as_u16().to_be_bytes());
header[11..13].copy_from_slice(&(content.len() as u16).to_be_bytes());
match self.mac {
CbcMacAlg::Sha1 => Hmac::<Sha1>::new(&self.mac_key)
.chain(&header)
.chain(content)
.finalize()
.as_ref()
.to_vec(),
CbcMacAlg::Sha256 => Hmac::<Sha256>::new(&self.mac_key)
.chain(&header)
.chain(content)
.finalize()
.as_ref()
.to_vec(),
}
}
fn equalize_mac_blocks(&self, content_len: usize, total: usize) {
const BLOCK: usize = 64; const OVERHEAD: usize = 9; const HEADER: usize = 13; let mac_len = self.mac.mac_len();
let max_content = total.saturating_sub(mac_len + 1);
let real_blocks = (HEADER + content_len + OVERHEAD).div_ceil(BLOCK);
let max_blocks = (HEADER + max_content + OVERHEAD).div_ceil(BLOCK);
let extra = max_blocks.saturating_sub(real_blocks);
if extra == 0 {
return;
}
let dummy = vec![0u8; extra * BLOCK];
match self.mac {
CbcMacAlg::Sha1 => {
let mut h = Sha1::new();
h.update(&dummy);
let _ = h.finalize();
}
CbcMacAlg::Sha256 => {
let mut h = Sha256::new();
h.update(&dummy);
let _ = h.finalize();
}
}
}
#[allow(dead_code)]
pub(crate) fn encrypt(
&mut self,
ct: ContentType,
version: ProtocolVersion,
plaintext: &[u8],
) -> Vec<u8> {
let mac = self.compute_mac(ct, version, plaintext);
let mut buf = Vec::with_capacity(plaintext.len() + mac.len() + self.block_size);
buf.extend_from_slice(plaintext);
buf.extend_from_slice(&mac);
let pad_total = self.block_size - (buf.len() % self.block_size);
let pad_val = (pad_total - 1) as u8;
buf.resize(buf.len() + pad_total, pad_val);
let out = if self.explicit_iv {
let mut iv = vec![0u8; self.block_size];
self.iv_rng.fill_bytes(&mut iv);
self.cipher.cbc_encrypt(&iv, &mut buf);
let mut out = iv;
out.extend_from_slice(&buf);
out
} else {
let iv = core::mem::take(&mut self.chain);
self.cipher.cbc_encrypt(&iv, &mut buf);
self.chain = buf[buf.len() - self.block_size..].to_vec();
buf
};
self.seq = self.seq.wrapping_add(1);
out
}
#[allow(dead_code)]
pub(crate) fn decrypt(
&mut self,
ct: ContentType,
version: ProtocolVersion,
fragment: &[u8],
) -> Result<Vec<u8>, Error> {
let bs = self.block_size;
let mac_len = self.mac.mac_len();
let (iv, ciphertext): (Vec<u8>, &[u8]) = if self.explicit_iv {
if fragment.len() < bs {
return Err(Error::BadRecordMac);
}
(fragment[..bs].to_vec(), &fragment[bs..])
} else {
(self.chain.clone(), fragment)
};
if ciphertext.is_empty() || !ciphertext.len().is_multiple_of(bs) {
return Err(Error::BadRecordMac);
}
let total = ciphertext.len();
if total < mac_len + 1 {
return Err(Error::BadRecordMac);
}
let next_chain = ciphertext[total - bs..].to_vec();
let mut buf = ciphertext.to_vec();
self.cipher.cbc_decrypt(&iv, &mut buf);
if !self.explicit_iv {
self.chain = next_chain;
}
let pad_len = buf[total - 1] as usize;
let mut good = ct_le(mac_len + pad_len + 1, total);
if !self.ssl3 {
let window = core::cmp::min(256, total);
for i in 0..window {
let byte = buf[total - 1 - i];
let in_pad = ct_le(i, pad_len); let is_val = ct_eq_u8(byte, pad_len as u8);
good &= is_val | !in_pad;
}
}
let cand = total
.wrapping_sub(mac_len)
.wrapping_sub(pad_len)
.wrapping_sub(1);
let mask = 0usize.wrapping_sub((good & 1) as usize);
let content_len = cand & mask;
let received_mac = &buf[content_len..content_len + mac_len];
let computed_mac = self.compute_mac(ct, version, &buf[..content_len]);
if !self.ssl3 {
self.equalize_mac_blocks(content_len, total);
}
let mac_ok = computed_mac.as_slice().ct_eq(received_mac);
self.seq = self.seq.wrapping_add(1);
if good == 0xff && bool::from(mac_ok) {
Ok(buf[..content_len].to_vec())
} else {
Err(Error::BadRecordMac)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tls::version::ProtocolVersion;
fn pair(
cipher: CbcCipherAlg,
mac: CbcMacAlg,
explicit_iv: bool,
) -> (CbcRecordCrypter, CbcRecordCrypter) {
let enc = vec![0x11u8; cipher.key_len()];
let mk = vec![0x22u8; mac.key_len()];
let iv = vec![0x33u8; cipher.block_size()];
(
CbcRecordCrypter::new(cipher, &enc, mac, &mk, explicit_iv, &iv, b"iv-seed-a"),
CbcRecordCrypter::new(cipher, &enc, mac, &mk, explicit_iv, &iv, b"iv-seed-b"),
)
}
#[test]
fn roundtrip_all_variants() {
for &cipher in &[
CbcCipherAlg::Aes128,
CbcCipherAlg::Aes256,
CbcCipherAlg::Tdes,
] {
for &mac in &[CbcMacAlg::Sha1, CbcMacAlg::Sha256] {
for &explicit in &[true, false] {
let (mut enc, mut dec) = pair(cipher, mac, explicit);
for len in [0usize, 1, 15, 16, 17, 31, 200] {
let pt: Vec<u8> = (0..len).map(|i| (i as u8).wrapping_mul(7)).collect();
let rec = enc.encrypt(
ContentType::ApplicationData,
ProtocolVersion::TLSv1_1,
&pt,
);
let got = dec
.decrypt(ContentType::ApplicationData, ProtocolVersion::TLSv1_1, &rec)
.expect("decrypt ok");
assert_eq!(got, pt, "cipher/mac/explicit roundtrip len={len}");
}
}
}
}
}
#[test]
fn tampering_yields_bad_record_mac() {
let (mut enc, mut dec) = pair(CbcCipherAlg::Aes128, CbcMacAlg::Sha1, true);
let pt = b"provisioning config payload";
let rec = enc.encrypt(ContentType::ApplicationData, ProtocolVersion::TLSv1_1, pt);
let mut bad = rec.clone();
let last = bad.len() - 1;
bad[last] ^= 0x01;
assert!(matches!(
dec.decrypt(ContentType::ApplicationData, ProtocolVersion::TLSv1_1, &bad),
Err(Error::BadRecordMac)
));
assert!(matches!(
dec.decrypt(
ContentType::ApplicationData,
ProtocolVersion::TLSv1_1,
&rec[..rec.len() - 1]
),
Err(Error::BadRecordMac)
));
}
#[test]
fn legacy_suite_lookup() {
let s = lookup_legacy_cbc(CipherSuite::TLS_RSA_WITH_AES_128_CBC_SHA).unwrap();
assert!(matches!(s.cipher, CbcCipherAlg::Aes128));
assert!(matches!(s.mac, CbcMacAlg::Sha1));
assert!(matches!(s.kx, LegacyKx::Rsa));
let s = lookup_legacy_cbc(CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA256).unwrap();
assert!(matches!(s.cipher, CbcCipherAlg::Aes256));
assert!(matches!(s.mac, CbcMacAlg::Sha256));
assert!(matches!(s.kx, LegacyKx::EcdheRsa));
let s = lookup_legacy_cbc(CipherSuite::TLS_RSA_WITH_3DES_EDE_CBC_SHA).unwrap();
assert!(matches!(s.cipher, CbcCipherAlg::Tdes));
assert!(lookup_legacy_cbc(CipherSuite::AES_128_GCM_SHA256).is_none());
}
#[test]
fn cbc_key_block_layout() {
let len = cbc_key_block_len(CbcCipherAlg::Aes128, CbcMacAlg::Sha1, true);
assert_eq!(len, 72);
let kb: Vec<u8> = (0..len as u8).collect();
let km = split_cbc_key_block(&kb, CbcCipherAlg::Aes128, CbcMacAlg::Sha1, true);
assert_eq!(km.client_mac, &kb[0..20]);
assert_eq!(km.server_mac, &kb[20..40]);
assert_eq!(km.client_key, &kb[40..56]);
assert_eq!(km.server_key, &kb[56..72]);
assert!(km.client_iv.is_empty() && km.server_iv.is_empty());
let len = cbc_key_block_len(CbcCipherAlg::Aes256, CbcMacAlg::Sha1, false);
assert_eq!(len, 136);
let kb: Vec<u8> = (0..len as u8).collect();
let km = split_cbc_key_block(&kb, CbcCipherAlg::Aes256, CbcMacAlg::Sha1, false);
assert_eq!(km.client_mac.len(), 20);
assert_eq!(km.client_key.len(), 32);
assert_eq!(km.client_iv, &kb[104..120]);
assert_eq!(km.server_iv, &kb[120..136]);
}
#[test]
fn mac_input_known_answer() {
let mk = vec![0x22u8; 20];
let c = CbcRecordCrypter::new(
CbcCipherAlg::Aes128,
&[0u8; 16],
CbcMacAlg::Sha1,
&mk,
true,
&[0u8; 16],
b"seed",
);
let mac = c.compute_mac(
ContentType::ApplicationData,
ProtocolVersion::TLSv1_0,
b"hi",
);
let expected = [
0xa2, 0xf1, 0xca, 0x0b, 0x8e, 0xce, 0x38, 0x5e, 0x27, 0x0b, 0x9b, 0xab, 0x0e, 0x55,
0x38, 0x2c, 0xda, 0x40, 0x81, 0xc1,
];
assert_eq!(mac, expected);
}
}