use base64::{Engine as _, engine::general_purpose::STANDARD};
use sfv::{
BareItem, DictSerializer, Dictionary, Integer, KeyRef, ListEntry, ListSerializer, Parser,
StringRef, key_ref,
};
use crate::profile::{Component, SIGNATURE_LABEL};
pub const MIN_NONCE_BYTES: usize = 16;
const CREATED: &KeyRef = key_ref("created");
const NONCE: &KeyRef = key_ref("nonce");
const ALG: &KeyRef = key_ref("alg");
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Param {
Created,
Nonce,
Alg,
}
impl Param {
pub const SIGNING_ORDER: [Self; 3] = [Self::Created, Self::Nonce, Self::Alg];
const fn name(self) -> &'static str {
match self {
Self::Created => "created",
Self::Nonce => "nonce",
Self::Alg => "alg",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SignatureParams {
components: Vec<Component>,
created: Integer,
nonce: sfv::String,
alg: sfv::String,
order: [Param; 3],
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum SignatureInputError {
#[error("signature-input is not a structured field dictionary")]
Syntax,
#[error("signature-input must hold exactly one member labelled `integrity`")]
Label,
#[error("signature-input member is not a non-empty inner list")]
NotInnerList,
#[error("covered components must be plain strings")]
ComponentForm,
#[error("covered component `{0}` appears twice")]
DuplicateComponent(Component),
#[error("covered component is not part of the profile")]
UnsupportedComponent,
#[error("unexpected signature parameter")]
UnexpectedParameter,
#[error("missing signature parameter `{}`", .0.name())]
MissingParameter(Param),
#[error("invalid signature parameter `{}`", .0.name())]
InvalidParameter(Param),
}
impl SignatureParams {
pub fn new(created: i64, nonce: &str, alg: &str) -> Result<Self, SignatureInputError> {
let invalid = SignatureInputError::InvalidParameter;
if created <= 0 {
return Err(invalid(Param::Created));
}
if nonce.is_empty() {
return Err(invalid(Param::Nonce));
}
if alg.is_empty() {
return Err(invalid(Param::Alg));
}
Ok(Self {
components: Component::ALL.to_vec(),
created: Integer::try_from(created).map_err(|_| invalid(Param::Created))?,
nonce: sfv::String::from_string(nonce.to_owned()).map_err(|_| invalid(Param::Nonce))?,
alg: sfv::String::from_string(alg.to_owned()).map_err(|_| invalid(Param::Alg))?,
order: Param::SIGNING_ORDER,
})
}
pub fn parse(field: &str) -> Result<Self, SignatureInputError> {
let dictionary: Dictionary = Parser::new(field)
.parse()
.map_err(|_| SignatureInputError::Syntax)?;
let member = only_integrity_member(&dictionary).ok_or(SignatureInputError::Label)?;
let ListEntry::InnerList(inner_list) = member else {
return Err(SignatureInputError::NotInnerList);
};
if inner_list.items.is_empty() {
return Err(SignatureInputError::NotInnerList);
}
let mut components = Vec::with_capacity(inner_list.items.len());
for item in &inner_list.items {
let BareItem::String(identifier) = &item.bare_item else {
return Err(SignatureInputError::ComponentForm);
};
if !item.params.is_empty() {
return Err(SignatureInputError::ComponentForm);
}
let component = Component::from_identifier(identifier.as_str())
.ok_or(SignatureInputError::UnsupportedComponent)?;
if components.contains(&component) {
return Err(SignatureInputError::DuplicateComponent(component));
}
components.push(component);
}
let mut created = None;
let mut nonce = None;
let mut alg = None;
let mut order = Vec::with_capacity(3);
for (key, value) in &inner_list.params {
let param = match key.as_str() {
"created" => Param::Created,
"nonce" => Param::Nonce,
"alg" => Param::Alg,
_ => return Err(SignatureInputError::UnexpectedParameter),
};
let invalid = SignatureInputError::InvalidParameter(param);
match (param, value) {
(Param::Created, BareItem::Integer(value)) if i64::from(*value) > 0 => {
created = Some(*value);
}
(Param::Nonce, BareItem::String(value)) if !value.as_str().is_empty() => {
nonce = Some(value.clone());
}
(Param::Alg, BareItem::String(value)) if !value.as_str().is_empty() => {
alg = Some(value.clone());
}
_ => return Err(invalid),
}
order.push(param);
}
let missing = SignatureInputError::MissingParameter;
Ok(Self {
components,
created: created.ok_or(missing(Param::Created))?,
nonce: nonce.ok_or(missing(Param::Nonce))?,
alg: alg.ok_or(missing(Param::Alg))?,
order: order
.try_into()
.expect("created, nonce and alg are each present once"),
})
}
#[must_use]
pub fn components(&self) -> &[Component] {
&self.components
}
#[must_use]
pub fn created(&self) -> i64 {
i64::from(self.created)
}
#[must_use]
pub fn nonce(&self) -> &str {
self.nonce.as_str()
}
#[must_use]
pub fn alg(&self) -> &str {
self.alg.as_str()
}
#[must_use]
pub fn serialize(&self) -> String {
let mut list = ListSerializer::new();
{
let mut inner = list.inner_list();
for component in &self.components {
let identifier = StringRef::from_str(component.identifier())
.expect("component identifiers are printable ASCII");
let _ = inner.bare_item(identifier);
}
let mut params = inner.finish();
for param in self.order {
params = match param {
Param::Created => params.parameter(CREATED, self.created),
Param::Nonce => params.parameter(NONCE, &*self.nonce),
Param::Alg => params.parameter(ALG, &*self.alg),
};
}
}
list.finish().expect("a list with one member serializes")
}
#[must_use]
pub fn to_field(&self) -> String {
format!("{SIGNATURE_LABEL}={}", self.serialize())
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum SignatureFieldError {
#[error("signature is not a structured field dictionary")]
Syntax,
#[error("signature must hold exactly one member labelled `integrity`")]
Label,
#[error("signature member is not a non-empty byte sequence")]
NotByteSequence,
}
pub fn parse_signature(field: &str) -> Result<Vec<u8>, SignatureFieldError> {
let dictionary: Dictionary = Parser::new(field)
.parse()
.map_err(|_| SignatureFieldError::Syntax)?;
let member = only_integrity_member(&dictionary).ok_or(SignatureFieldError::Label)?;
match member {
ListEntry::Item(item) if item.params.is_empty() => match &item.bare_item {
BareItem::ByteSequence(bytes) if !bytes.is_empty() => Ok(bytes.clone()),
_ => Err(SignatureFieldError::NotByteSequence),
},
_ => Err(SignatureFieldError::NotByteSequence),
}
}
#[must_use]
pub fn signature_field(signature: &[u8]) -> String {
let mut dictionary = DictSerializer::new();
let _ = dictionary.bare_item(key_ref(SIGNATURE_LABEL), signature);
dictionary
.finish()
.expect("a dictionary with one member serializes")
}
fn only_integrity_member(dictionary: &Dictionary) -> Option<&ListEntry> {
match dictionary.iter().collect::<Vec<_>>().as_slice() {
[(label, member)] if label.as_str() == SIGNATURE_LABEL => Some(member),
_ => None,
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum NonceError {
#[error("nonce is not canonical padded base64")]
NotCanonicalBase64,
#[error("nonce decodes to fewer than {MIN_NONCE_BYTES} bytes")]
TooShort,
}
pub fn validate_nonce(nonce: &str) -> Result<(), NonceError> {
let decoded = STANDARD
.decode(nonce)
.map_err(|_| NonceError::NotCanonicalBase64)?;
if STANDARD.encode(&decoded) != nonce {
return Err(NonceError::NotCanonicalBase64);
}
if decoded.len() < MIN_NONCE_BYTES {
return Err(NonceError::TooShort);
}
Ok(())
}
pub fn generate_nonce() -> Result<String, getrandom::Error> {
let mut bytes = [0u8; MIN_NONCE_BYTES];
getrandom::fill(&mut bytes)?;
Ok(STANDARD.encode(bytes))
}