use crate::tls::Error;
use alloc::vec::Vec;
pub const ECH_VERSION_DRAFT_22: u16 = 0xfe0d;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct HpkeSymCipherSuite {
pub kdf_id: u16,
pub aead_id: u16,
}
impl HpkeSymCipherSuite {
pub fn encode_into(self, out: &mut Vec<u8>) {
out.extend_from_slice(&self.kdf_id.to_be_bytes());
out.extend_from_slice(&self.aead_id.to_be_bytes());
}
pub fn decode(buf: &[u8]) -> Result<Self, Error> {
if buf.len() != 4 {
return Err(Error::EchDecodeError);
}
Ok(Self {
kdf_id: u16::from_be_bytes([buf[0], buf[1]]),
aead_id: u16::from_be_bytes([buf[2], buf[3]]),
})
}
}
#[derive(Clone, Debug)]
pub struct HpkeKeyConfig {
pub config_id: u8,
pub kem_id: u16,
pub public_key: Vec<u8>,
pub cipher_suites: Vec<HpkeSymCipherSuite>,
}
impl HpkeKeyConfig {
pub(crate) fn encode_into(&self, out: &mut Vec<u8>) {
out.push(self.config_id);
out.extend_from_slice(&self.kem_id.to_be_bytes());
let pk_len: u16 = u16::try_from(self.public_key.len()).unwrap_or(u16::MAX);
out.extend_from_slice(&pk_len.to_be_bytes());
out.extend_from_slice(&self.public_key);
let cs_bytes: usize = self.cipher_suites.len() * 4;
let cs_bytes_u16: u16 = u16::try_from(cs_bytes).unwrap_or(u16::MAX);
out.extend_from_slice(&cs_bytes_u16.to_be_bytes());
for cs in &self.cipher_suites {
cs.encode_into(out);
}
}
fn decode(rd: &mut Reader<'_>) -> Result<Self, Error> {
let config_id = rd.read_u8()?;
let kem_id = rd.read_u16()?;
let pk_len = rd.read_u16()? as usize;
if pk_len == 0 {
return Err(Error::EchDecodeError);
}
let public_key = rd.read(pk_len)?.to_vec();
let cs_bytes = rd.read_u16()? as usize;
if cs_bytes < 4 || !cs_bytes.is_multiple_of(4) {
return Err(Error::EchDecodeError);
}
let cs_buf = rd.read(cs_bytes)?;
let mut cipher_suites = Vec::with_capacity(cs_bytes / 4);
for chunk in cs_buf.chunks_exact(4) {
cipher_suites.push(HpkeSymCipherSuite::decode(chunk)?);
}
Ok(Self {
config_id,
kem_id,
public_key,
cipher_suites,
})
}
}
#[derive(Clone, Debug)]
pub struct EchConfigContents {
pub key_config: HpkeKeyConfig,
pub maximum_name_length: u8,
pub public_name: Vec<u8>,
pub extensions: Vec<u8>,
}
impl EchConfigContents {
fn encode_into(&self, out: &mut Vec<u8>) {
self.key_config.encode_into(out);
out.push(self.maximum_name_length);
let pn_len: u8 = u8::try_from(self.public_name.len()).unwrap_or(255);
out.push(pn_len);
out.extend_from_slice(&self.public_name);
let ext_len: u16 = u16::try_from(self.extensions.len()).unwrap_or(u16::MAX);
out.extend_from_slice(&ext_len.to_be_bytes());
out.extend_from_slice(&self.extensions);
}
fn decode(rd: &mut Reader<'_>) -> Result<Self, Error> {
let key_config = HpkeKeyConfig::decode(rd)?;
let maximum_name_length = rd.read_u8()?;
let pn_len = rd.read_u8()? as usize;
if pn_len == 0 {
return Err(Error::EchDecodeError);
}
let public_name = rd.read(pn_len)?.to_vec();
let ext_len = rd.read_u16()? as usize;
let extensions = rd.read(ext_len)?.to_vec();
validate_config_extensions(&extensions)?;
Ok(Self {
key_config,
maximum_name_length,
public_name,
extensions,
})
}
}
#[derive(Clone, Debug)]
pub struct EchConfig {
pub version: u16,
pub contents: Option<EchConfigContents>,
pub raw_contents: Vec<u8>,
}
impl EchConfig {
pub fn new(contents: EchConfigContents) -> Self {
let mut raw = Vec::new();
contents.encode_into(&mut raw);
Self {
version: ECH_VERSION_DRAFT_22,
contents: Some(contents),
raw_contents: raw,
}
}
pub fn is_supported(&self) -> bool {
self.version == ECH_VERSION_DRAFT_22 && self.contents.is_some()
}
fn encode_into(&self, out: &mut Vec<u8>) {
out.extend_from_slice(&self.version.to_be_bytes());
let len: u16 = u16::try_from(self.raw_contents.len()).unwrap_or(u16::MAX);
out.extend_from_slice(&len.to_be_bytes());
out.extend_from_slice(&self.raw_contents);
}
fn decode_entry(rd: &mut Reader<'_>) -> Result<Self, Error> {
let version = rd.read_u16()?;
let len = rd.read_u16()? as usize;
let raw = rd.read(len)?.to_vec();
let contents = if version == ECH_VERSION_DRAFT_22 {
let mut inner = Reader::new(&raw);
let c = EchConfigContents::decode(&mut inner)?;
if !inner.is_empty() {
return Err(Error::EchDecodeError);
}
Some(c)
} else {
None
};
Ok(Self {
version,
contents,
raw_contents: raw,
})
}
}
#[derive(Clone, Debug)]
pub struct EchConfigList {
pub configs: Vec<EchConfig>,
}
impl EchConfigList {
pub fn new(configs: Vec<EchConfig>) -> Self {
Self { configs }
}
pub fn first_supported(&self) -> Option<&EchConfig> {
self.configs.iter().find(|c| c.is_supported())
}
pub fn encode(&self) -> Vec<u8> {
let mut inner = Vec::new();
for cfg in &self.configs {
cfg.encode_into(&mut inner);
}
let mut out = Vec::with_capacity(2 + inner.len());
let len: u16 = u16::try_from(inner.len()).unwrap_or(u16::MAX);
out.extend_from_slice(&len.to_be_bytes());
out.extend_from_slice(&inner);
out
}
pub fn decode(buf: &[u8]) -> Result<Self, Error> {
let mut rd = Reader::new(buf);
let inner_len = rd.read_u16()? as usize;
let inner = rd.read(inner_len)?;
if !rd.is_empty() {
return Err(Error::EchDecodeError);
}
let mut entries = Vec::new();
let mut sub = Reader::new(inner);
while !sub.is_empty() {
entries.push(EchConfig::decode_entry(&mut sub)?);
}
if entries.is_empty() {
return Err(Error::EchDecodeError);
}
Ok(Self { configs: entries })
}
}
struct Reader<'a> {
buf: &'a [u8],
pos: usize,
}
impl<'a> Reader<'a> {
fn new(buf: &'a [u8]) -> Self {
Self { buf, pos: 0 }
}
fn is_empty(&self) -> bool {
self.pos >= self.buf.len()
}
fn remaining(&self) -> usize {
self.buf.len().saturating_sub(self.pos)
}
fn read_u8(&mut self) -> Result<u8, Error> {
if self.remaining() < 1 {
return Err(Error::EchDecodeError);
}
let v = self.buf[self.pos];
self.pos += 1;
Ok(v)
}
fn read_u16(&mut self) -> Result<u16, Error> {
if self.remaining() < 2 {
return Err(Error::EchDecodeError);
}
let v = u16::from_be_bytes([self.buf[self.pos], self.buf[self.pos + 1]]);
self.pos += 2;
Ok(v)
}
fn read(&mut self, n: usize) -> Result<&'a [u8], Error> {
if self.remaining() < n {
return Err(Error::EchDecodeError);
}
let s = &self.buf[self.pos..self.pos + n];
self.pos += n;
Ok(s)
}
}
fn validate_config_extensions(buf: &[u8]) -> Result<(), Error> {
let mut rd = Reader::new(buf);
while !rd.is_empty() {
let ty = rd.read_u16()?;
let len = rd.read_u16()? as usize;
let _data = rd.read(len)?;
if ty & 0x8000 != 0 {
return Err(Error::EchDecodeError);
}
}
Ok(())
}