hpke-http 2.0.2

Bounded whole-message HPKE engine for HTTP request and response protection
Documentation
//! Version 1 outer envelopes around encrypted BHTTP messages.

use crate::{Error, HARD_MAX_ID_LEN, Limits};

pub(crate) const REQUEST_MAGIC: &[u8; 4] = b"HHRQ";
pub(crate) const VERSION: u8 = 1;
pub(crate) const FLAGS: u8 = 0;
pub(crate) const KEM_ID: u16 = 0x0020;
pub(crate) const KDF_ID: u16 = 0x0001;
pub(crate) const AEAD_ID: u16 = 0x0003;
pub(crate) const ENC_LEN: usize = 32;
pub(crate) const TAG_LEN: usize = 16;
pub(crate) const RESPONSE_NONCE_LEN: usize = 32;

const REQUEST_FIXED_LEN: usize = 22;
const ISSUED_AT_OFFSET: usize = 14;
const IDS_OFFSET: usize = 22;

#[derive(Debug)]
pub(crate) struct ParsedRequest<'a> {
    pub header: &'a [u8],
    pub key_id: &'a [u8],
    pub psk_id: &'a [u8],
    pub issued_at_unix_s: u64,
    pub enc: &'a [u8],
    pub ciphertext: &'a [u8],
}

#[derive(Debug)]
pub(crate) struct ParsedResponse<'a> {
    pub nonce: &'a [u8],
    pub ciphertext: &'a [u8],
}

pub(crate) fn encode_request_header(
    key_id: &[u8],
    psk_id: &[u8],
    issued_at_unix_s: u64,
) -> Result<Vec<u8>, Error> {
    let key_id_len = u8::try_from(key_id.len()).map_err(|_| Error::LimitExceeded)?;
    let psk_id_len = u8::try_from(psk_id.len()).map_err(|_| Error::LimitExceeded)?;
    if key_id_len == 0 || psk_id_len == 0 {
        return Err(Error::InvalidConfiguration);
    }

    let capacity = REQUEST_FIXED_LEN
        .checked_add(key_id.len())
        .and_then(|length| length.checked_add(psk_id.len()))
        .ok_or(Error::LimitExceeded)?;
    let mut output = Vec::with_capacity(capacity);
    output.extend_from_slice(REQUEST_MAGIC);
    output.push(VERSION);
    output.push(FLAGS);
    output.push(key_id_len);
    output.push(psk_id_len);
    output.extend_from_slice(&KEM_ID.to_be_bytes());
    output.extend_from_slice(&KDF_ID.to_be_bytes());
    output.extend_from_slice(&AEAD_ID.to_be_bytes());
    output.extend_from_slice(&issued_at_unix_s.to_be_bytes());
    output.extend_from_slice(key_id);
    output.extend_from_slice(psk_id);
    Ok(output)
}

pub(crate) fn encode_request(
    header: &[u8],
    enc: &[u8],
    ciphertext: &[u8],
) -> Result<Vec<u8>, Error> {
    if enc.len() != ENC_LEN || ciphertext.len() < TAG_LEN {
        return Err(Error::CryptoFailure);
    }
    let capacity = header
        .len()
        .checked_add(enc.len())
        .and_then(|length| length.checked_add(ciphertext.len()))
        .ok_or(Error::LimitExceeded)?;
    let mut output = Vec::with_capacity(capacity);
    output.extend_from_slice(header);
    output.extend_from_slice(enc);
    output.extend_from_slice(ciphertext);
    Ok(output)
}

pub(crate) fn parse_request(input: &[u8], limits: Limits) -> Result<ParsedRequest<'_>, Error> {
    let minimum_len = REQUEST_FIXED_LEN
        .checked_add(2)
        .and_then(|length| length.checked_add(ENC_LEN + TAG_LEN))
        .ok_or(Error::MalformedEnvelope)?;
    if input.len() < minimum_len || input.get(..4) != Some(REQUEST_MAGIC.as_slice()) {
        return Err(Error::MalformedEnvelope);
    }
    if input[4] != VERSION {
        return Err(Error::UnsupportedVersion);
    }
    if input[5] != FLAGS {
        return Err(Error::MalformedEnvelope);
    }
    if read_u16(input, 8)? != KEM_ID
        || read_u16(input, 10)? != KDF_ID
        || read_u16(input, 12)? != AEAD_ID
    {
        return Err(Error::UnsupportedSuite);
    }

    let key_id_len = usize::from(input[6]);
    let psk_id_len = usize::from(input[7]);
    if key_id_len == 0
        || psk_id_len == 0
        || key_id_len > HARD_MAX_ID_LEN
        || psk_id_len > HARD_MAX_ID_LEN
    {
        return Err(Error::MalformedEnvelope);
    }
    let header_len = REQUEST_FIXED_LEN
        .checked_add(key_id_len)
        .and_then(|length| length.checked_add(psk_id_len))
        .ok_or(Error::MalformedEnvelope)?;
    let ciphertext_start = header_len
        .checked_add(ENC_LEN)
        .ok_or(Error::MalformedEnvelope)?;
    if input.len() < ciphertext_start.saturating_add(TAG_LEN) {
        return Err(Error::MalformedEnvelope);
    }
    let ciphertext_len = input.len() - ciphertext_start;
    if ciphertext_len - TAG_LEN > limits.max_encoded_message_len() {
        return Err(Error::LimitExceeded);
    }

    let issued_at_unix_s = u64::from_be_bytes(
        input[ISSUED_AT_OFFSET..IDS_OFFSET]
            .try_into()
            .map_err(|_| Error::MalformedEnvelope)?,
    );
    let key_id_start = REQUEST_FIXED_LEN;
    let psk_id_start = key_id_start + key_id_len;
    Ok(ParsedRequest {
        header: &input[..header_len],
        key_id: &input[key_id_start..psk_id_start],
        psk_id: &input[psk_id_start..header_len],
        issued_at_unix_s,
        enc: &input[header_len..ciphertext_start],
        ciphertext: &input[ciphertext_start..],
    })
}

pub(crate) fn encode_response(
    nonce: &[u8; RESPONSE_NONCE_LEN],
    ciphertext: &[u8],
) -> Result<Vec<u8>, Error> {
    if ciphertext.len() < TAG_LEN {
        return Err(Error::CryptoFailure);
    }
    let capacity = RESPONSE_NONCE_LEN
        .checked_add(ciphertext.len())
        .ok_or(Error::LimitExceeded)?;
    let mut output = Vec::with_capacity(capacity);
    output.extend_from_slice(nonce);
    output.extend_from_slice(ciphertext);
    Ok(output)
}

pub(crate) fn parse_response(input: &[u8], limits: Limits) -> Result<ParsedResponse<'_>, Error> {
    if input.len() < RESPONSE_NONCE_LEN + TAG_LEN {
        return Err(Error::MalformedEnvelope);
    }
    if input.len() - RESPONSE_NONCE_LEN - TAG_LEN > limits.max_encoded_message_len() {
        return Err(Error::LimitExceeded);
    }
    Ok(ParsedResponse {
        nonce: &input[..RESPONSE_NONCE_LEN],
        ciphertext: &input[RESPONSE_NONCE_LEN..],
    })
}

fn read_u16(input: &[u8], offset: usize) -> Result<u16, Error> {
    let bytes: [u8; 2] = input
        .get(offset..offset + 2)
        .ok_or(Error::MalformedEnvelope)?
        .try_into()
        .map_err(|_| Error::MalformedEnvelope)?;
    Ok(u16::from_be_bytes(bytes))
}