actpub_httpsig/rfc9421/
signature.rs1use sfv::{BareItem, Dictionary, FieldType, Item, Key, ListEntry, Parser};
14
15use crate::error::Error;
16
17pub const SIGNATURE_HEADER: &str = "signature";
21
22pub fn parse_signature_dict(raw: &str) -> Result<Vec<(String, Vec<u8>)>, Error> {
31 let dict: Dictionary =
32 Parser::new(raw)
33 .parse()
34 .map_err(|e: sfv::Error| Error::InvalidHeader {
35 name: SIGNATURE_HEADER,
36 reason: e.to_string(),
37 })?;
38
39 let mut out = Vec::with_capacity(dict.len());
40 for (label, entry) in dict {
41 let item = match entry {
42 ListEntry::Item(item) => item,
43 ListEntry::InnerList(_) => {
44 return Err(Error::MalformedSignatureHeader(format!(
45 "entry `{label}` must be a byte-sequence item, not an inner list"
46 )));
47 }
48 };
49 let BareItem::ByteSequence(bytes) = item.bare_item else {
50 return Err(Error::MalformedSignatureHeader(format!(
51 "entry `{label}` must be a byte sequence"
52 )));
53 };
54 out.push((label.into(), bytes));
55 }
56
57 Ok(out)
58}
59
60#[must_use]
69#[allow(
70 clippy::expect_used,
71 reason = "serialising an all-byte-sequence dictionary under validated keys cannot fail"
72)]
73pub fn serialise_signature_dict(entries: &[(String, Vec<u8>)]) -> String {
74 let mut dict = Dictionary::new();
75 for (label, bytes) in entries {
76 let key = Key::try_from(label.clone()).expect("signature label must be a valid sf-key");
77 dict.insert(
78 key,
79 ListEntry::Item(Item::new(BareItem::ByteSequence(bytes.clone()))),
80 );
81 }
82 FieldType::serialize(&dict).expect("byte-sequence dictionary is always serialisable")
83}
84
85#[cfg(test)]
86mod tests {
87 use pretty_assertions::assert_eq;
88
89 use super::*;
90
91 #[test]
92 fn roundtrip_single_entry() {
93 let sig_bytes = vec![0x01, 0x02, 0x03, 0x04];
94 let wire = serialise_signature_dict(&[("sig1".into(), sig_bytes.clone())]);
95 assert_eq!(wire, "sig1=:AQIDBA==:");
96 let parsed = parse_signature_dict(&wire).expect("parse");
97 assert_eq!(parsed, vec![("sig1".into(), sig_bytes)]);
98 }
99
100 #[test]
101 fn roundtrip_multiple_entries_preserves_order() {
102 let entries = vec![
103 ("sig1".to_owned(), vec![0u8; 32]),
104 ("sig2".to_owned(), vec![0xFFu8; 16]),
105 ];
106 let wire = serialise_signature_dict(&entries);
107 let parsed = parse_signature_dict(&wire).expect("parse");
108 assert_eq!(parsed, entries);
109 }
110
111 #[test]
112 fn inner_list_entry_is_rejected() {
113 let err = parse_signature_dict("sig1=(\"@method\")").expect_err("inner list");
114 assert!(matches!(err, Error::MalformedSignatureHeader(_)));
115 }
116
117 #[test]
118 fn non_byte_sequence_is_rejected() {
119 let err = parse_signature_dict("sig1=123").expect_err("integer");
120 assert!(matches!(err, Error::MalformedSignatureHeader(_)));
121 }
122}