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());
}