use crate::{aead::Aead, header_key::HeaderKey, iv};
use ::ring::{aead, hkdf};
use core::fmt;
use s2n_quic_core::crypto::{label, CryptoError};
use zeroize::{Zeroize, Zeroizing};
mod negotiated;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[path = "cipher_suite/x86.rs"]
mod platform;
mod ring;
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
use self::ring as platform;
pub use negotiated::NegotiatedCipherSuite;
macro_rules! impl_cipher_suite {
(
$name:ident,
$lower:ident,
$digest:path,
$cipher:path,
$cipher_key_len:expr,
$header_protection:path,
$key_label:expr,
$iv_label:expr,
$hp_label:expr,
$key_update_label:expr,
$confidentiality_limit:expr,
$integrity_limit:expr,
$test_name:ident
) => {
mod $lower {
use super::*;
pub const KEY_LEN: usize = $cipher_key_len;
pub const TAG_LEN: usize = 16;
pub const NONCE_LEN: usize = crate::aesgcm::NONCE_LEN;
type Key = platform::$lower::Key;
#[allow(non_camel_case_types, clippy::all)]
pub struct $name {
secret: hkdf::Prk,
iv: iv::Iv,
key: Key,
}
impl $name {
pub fn new(secret: hkdf::Prk) -> (Self, HeaderKey) {
let iv = Self::new_iv(&secret);
let key = {
let secret = Self::new_key_secret(&secret);
Key::new(&*secret)
};
let header_key = Self::new_header_key(&secret);
let key = Self { secret, iv, key };
(key, header_key)
}
#[inline]
pub fn update(&self) -> Self {
let secret: hkdf::Prk = self
.secret
.expand(&[&$key_update_label], $digest)
.expect("label size verified")
.into();
let iv = Self::new_iv(&secret);
let key = {
let key = Self::new_key_secret(&secret);
self.key.update(&*key)
};
Self { secret, iv, key }
}
#[inline]
pub fn update_pmtu(&mut self, mtu: u16) {
if self.key.should_update_pmtu(mtu) {
let secret = Self::new_key_secret(&self.secret);
self.key.update_pmtu(&*secret, mtu);
}
}
fn new_key_secret(secret: &hkdf::Prk) -> Zeroizing<[u8; KEY_LEN]> {
let mut key = Zeroizing::new([0u8; KEY_LEN]);
secret
.expand(&[&$key_label], &$cipher)
.expect("label size verified")
.fill(&mut key.as_mut())
.expect("fill size verified");
key
}
fn new_iv(secret: &hkdf::Prk) -> iv::Iv {
iv::Iv::new(secret, &$iv_label)
}
fn new_header_key(secret: &hkdf::Prk) -> HeaderKey {
HeaderKey::new::<{ KEY_LEN }>(secret, &$hp_label, &$header_protection)
}
}
impl Zeroize for $name {
fn zeroize(&mut self) {
self.iv.zeroize();
self.key.zeroize();
}
}
impl s2n_quic_core::crypto::Key for $name {
#[inline]
fn decrypt(
&self,
packet_number: u64,
header: &[u8],
payload: &mut [u8],
) -> Result<(), CryptoError> {
let nonce = self.iv.nonce(packet_number);
let payload_len = payload
.len()
.checked_sub(TAG_LEN)
.ok_or_else(|| CryptoError::DECRYPT_ERROR)?;
let (payload, tag) = payload.split_at_mut(payload_len);
let tag = {
use core::convert::TryInto;
let res = (&tag[..]).try_into();
unsafe {
unsafe_assert!(res.is_ok());
}
res.unwrap()
};
self.key.decrypt(&nonce, header, payload, tag)?;
Ok(())
}
#[inline]
fn encrypt(
&self,
packet_number: u64,
header: &[u8],
payload: &mut [u8],
) -> Result<(), CryptoError> {
let nonce = self.iv.nonce(packet_number);
let payload_len = payload
.len()
.checked_sub(TAG_LEN)
.ok_or_else(|| CryptoError::DECRYPT_ERROR)?;
let (payload, tag) = payload.split_at_mut(payload_len);
let tag = {
use core::convert::TryInto;
let res = tag.try_into();
unsafe {
unsafe_assert!(res.is_ok());
}
res.unwrap()
};
self.key.encrypt(&nonce, header, payload, tag)?;
Ok(())
}
#[inline]
fn tag_len(&self) -> usize {
$cipher.tag_len()
}
#[inline]
fn aead_confidentiality_limit(&self) -> u64 {
$confidentiality_limit
}
#[inline]
fn aead_integrity_limit(&self) -> u64 {
$integrity_limit
}
#[inline]
fn cipher_suite(&self) -> s2n_quic_core::crypto::tls::CipherSuite {
s2n_quic_core::crypto::tls::CipherSuite::$name
}
}
impl fmt::Debug for $name {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct(stringify!($name)).finish()
}
}
impl Drop for $name {
fn drop(&mut self) {
self.zeroize();
}
}
#[test]
fn $test_name() {
fn compute_vec_label(len: usize, label: &[u8]) -> Vec<u8> {
let mut out = vec![];
label::compute_label(len, label, &mut out);
out
}
insta::assert_debug_snapshot!(
concat!("integrity_", stringify!($test_name)),
$integrity_limit
);
insta::assert_debug_snapshot!(
concat!("confidentiality_", stringify!($test_name)),
$confidentiality_limit
);
assert_eq!(KEY_LEN, $cipher.key_len(), "key len mismatch");
assert_eq!(
compute_vec_label($cipher.key_len(), b"quic key"),
$key_label,
"key label mismatch"
);
assert_eq!(
compute_vec_label(iv::NONCE_LEN, b"quic iv"),
$iv_label,
"iv label mismatch"
);
assert_eq!(
compute_vec_label($header_protection.key_len(), b"quic hp"),
$hp_label,
"hp label mismatch"
);
assert_eq!(
compute_vec_label(
$digest.hmac_algorithm().digest_algorithm().output_len,
b"quic ku"
),
$key_update_label,
"key update label mismatch"
);
}
}
pub use $lower::$name;
};
}
impl_cipher_suite!(
TLS_AES_256_GCM_SHA384,
aes256_gcm,
hkdf::HKDF_SHA384,
aead::AES_256_GCM,
256 / 8, aead::quic::AES_256,
label::QUIC_KEY_32,
label::QUIC_IV_12,
label::QUIC_HP_32,
label::QUIC_KU_48,
u64::pow(2, 23), u64::pow(2, 52), tls_aes_256_gcm_sha384_test
);
impl_cipher_suite!(
TLS_CHACHA20_POLY1305_SHA256,
chacha20_poly1305,
hkdf::HKDF_SHA256,
aead::CHACHA20_POLY1305,
256 / 8, aead::quic::CHACHA20,
label::QUIC_KEY_32,
label::QUIC_IV_12,
label::QUIC_HP_32,
label::QUIC_KU_32,
u64::pow(2, 62), u64::pow(2, 36), tls_chacha20_poly1305_sha256_test
);
impl_cipher_suite!(
TLS_AES_128_GCM_SHA256,
aes128_gcm,
hkdf::HKDF_SHA256,
aead::AES_128_GCM,
128 / 8, aead::quic::AES_128,
label::QUIC_KEY_16,
label::QUIC_IV_12,
label::QUIC_HP_16,
label::QUIC_KU_32,
u64::pow(2, 23), u64::pow(2, 52), tls_aes_128_gcm_sha256_test
);