kcode-k1-http-signature 0.1.0

Canonical K1-HTTP-1 request encoding and strict Ed25519 verification
Documentation
use ed25519_dalek::{Signature, VerifyingKey};
use std::error::Error;
use std::fmt::{self, Display, Formatter};

const MAGIC: &[u8; 9] = b"K1-HTTP-1";
const FIXED_ENCODED_LENGTH: usize = MAGIC.len() + 6 * 8 + 8 + 16 + 32;

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct CanonicalUsername(String);

impl CanonicalUsername {
    pub fn parse(value: &str) -> Result<Self, UsernameError> {
        if value.is_empty() {
            return Err(UsernameError::Empty);
        }
        if value.len() > 64 {
            return Err(UsernameError::TooLong);
        }
        if !value.is_ascii() {
            return Err(UsernameError::NonAscii);
        }
        if value.bytes().any(|byte| byte.is_ascii_control()) {
            return Err(UsernameError::ControlCharacter);
        }
        Ok(Self(value.to_ascii_lowercase()))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum UsernameError {
    Empty,
    TooLong,
    NonAscii,
    ControlCharacter,
}

impl Display for UsernameError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
        let message = match self {
            Self::Empty => "username must contain at least one byte",
            Self::TooLong => "username must contain at most 64 bytes",
            Self::NonAscii => "username must contain only ASCII bytes",
            Self::ControlCharacter => "username must not contain ASCII control characters",
        };
        formatter.write_str(message)
    }
}

impl Error for UsernameError {}

pub struct RequestBinding<'a> {
    pub server_id: &'a str,
    pub public_origin: &'a str,
    pub username: &'a CanonicalUsername,
    pub epoch: u64,
    pub nonce: [u8; 16],
    pub method: &'a str,
    pub target: &'a str,
    pub content_type: &'a str,
    pub body_sha256: [u8; 32],
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SignatureError {
    MalformedPublicKey,
    VerificationFailed,
}

impl Display for SignatureError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
        let message = match self {
            Self::MalformedPublicKey => "malformed Ed25519 public key",
            Self::VerificationFailed => "Ed25519 signature verification failed",
        };
        formatter.write_str(message)
    }
}

impl Error for SignatureError {}

pub fn encode(binding: &RequestBinding<'_>) -> Vec<u8> {
    let variable_length = binding.server_id.len()
        + binding.public_origin.len()
        + binding.username.as_str().len()
        + binding.method.len()
        + binding.target.len()
        + binding.content_type.len();
    let mut encoded = Vec::with_capacity(FIXED_ENCODED_LENGTH + variable_length);
    encoded.extend_from_slice(MAGIC);
    append_string(&mut encoded, binding.server_id);
    append_string(&mut encoded, binding.public_origin);
    append_string(&mut encoded, binding.username.as_str());
    encoded.extend_from_slice(&binding.epoch.to_be_bytes());
    encoded.extend_from_slice(&binding.nonce);
    append_string(&mut encoded, binding.method);
    append_string(&mut encoded, binding.target);
    append_string(&mut encoded, binding.content_type);
    encoded.extend_from_slice(&binding.body_sha256);
    encoded
}

pub fn verify(
    binding: &RequestBinding<'_>,
    public_key: &[u8; 32],
    signature: &[u8; 64],
) -> Result<(), SignatureError> {
    let verifying_key =
        VerifyingKey::from_bytes(public_key).map_err(|_| SignatureError::MalformedPublicKey)?;
    let parsed_signature = Signature::from_bytes(signature);
    verifying_key
        .verify_strict(&encode(binding), &parsed_signature)
        .map_err(|_| SignatureError::VerificationFailed)
}

fn append_string(encoded: &mut Vec<u8>, value: &str) {
    let length = u64::try_from(value.len()).expect("string length must fit in a u64");
    encoded.extend_from_slice(&length.to_be_bytes());
    encoded.extend_from_slice(value.as_bytes());
}