1use super::ImplementationError;
2use crate::components::{self, CoveredComponent, HTTPField};
3use crate::keyring::{Algorithm, KeyRing};
4use indexmap::IndexMap;
5use regex::bytes::Regex;
6use sfv::SerializeValue;
7use std::fmt::Write as _;
8use std::sync::LazyLock;
9use time::{Duration, UtcDateTime};
10static OBSOLETE_LINE_FOLDING: LazyLock<Regex> =
11 LazyLock::new(|| Regex::new(r"\s*\r\n\s+").unwrap());
12
13#[derive(Clone, Debug)]
15pub struct SignatureParams {
16 pub raw: sfv::Parameters,
18 pub details: ParameterDetails,
20}
21
22#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct ParameterDetails {
25 pub algorithm: Option<Algorithm>,
27 pub created: Option<i64>,
29 pub expires: Option<i64>,
31 pub keyid: Option<String>,
33 pub nonce: Option<String>,
35 pub tag: Option<String>,
37}
38
39impl From<sfv::Parameters> for SignatureParams {
40 fn from(value: sfv::Parameters) -> Self {
41 let mut parameter_details = ParameterDetails {
42 algorithm: None,
43 created: None,
44 expires: None,
45 keyid: None,
46 nonce: None,
47 tag: None,
48 };
49
50 for (key, val) in &value {
51 match key.as_str() {
52 "alg" => {
53 parameter_details.algorithm = val.as_string().and_then(|algorithm_string| {
54 match algorithm_string.as_str() {
55 "ed25519" => Some(Algorithm::Ed25519),
56 "rsa-pss-sha512" => Some(Algorithm::RsaPssSha512),
57 "rsa-v1_5-sha256" => Some(Algorithm::RsaV1_5Sha256),
58 "hmac-sha256" => Some(Algorithm::HmacSha256),
59 "ecdsa-p256-sha256" => Some(Algorithm::EcdsaP256Sha256),
60 "ecdsa-p384-sha384" => Some(Algorithm::EcdsaP384Sha384),
61 _ => None,
62 }
63 });
64 }
65 "keyid" => {
66 parameter_details.keyid = val.as_string().map(|s| s.as_str().to_string());
67 }
68 "tag" => parameter_details.tag = val.as_string().map(|s| s.as_str().to_string()),
69 "nonce" => {
70 parameter_details.nonce = val.as_string().map(|s| s.as_str().to_string());
71 }
72 "created" => {
73 parameter_details.created = val.as_integer().map(std::convert::Into::into);
74 }
75 "expires" => {
76 parameter_details.expires = val.as_integer().map(std::convert::Into::into);
77 }
78 _ => {}
79 }
80 }
81
82 Self {
83 raw: value,
84 details: parameter_details,
85 }
86 }
87}
88
89pub struct SecurityAdvisory {
92 pub is_expired: Option<bool>,
95 pub nonce_is_invalid: Option<bool>,
98}
99
100impl ParameterDetails {
101 pub fn possibly_insecure<F>(&self, nonce_validator: F) -> SecurityAdvisory
106 where
107 F: FnOnce(&String) -> bool,
108 {
109 SecurityAdvisory {
110 is_expired: self.expires.map(|expires| {
111 if let Ok(expiry) = UtcDateTime::from_unix_timestamp(expires) {
112 let now = UtcDateTime::now();
113 return now >= expiry;
114 }
115
116 true
117 }),
118 nonce_is_invalid: self.nonce.as_ref().map(nonce_validator),
119 }
120 }
121}
122
123struct SignatureBaseBuilder {
124 components: Vec<CoveredComponent>,
125 parameters: SignatureParams,
126}
127
128impl TryFrom<sfv::InnerList> for SignatureBaseBuilder {
129 type Error = ImplementationError;
130
131 fn try_from(value: sfv::InnerList) -> Result<Self, Self::Error> {
132 Ok(SignatureBaseBuilder {
133 components: value
134 .items
135 .iter()
136 .map(|item| (*item).clone().try_into())
137 .collect::<Result<Vec<CoveredComponent>, ImplementationError>>()?,
138 parameters: value.params.into(),
141 })
142 }
143}
144
145impl SignatureBaseBuilder {
146 fn into_signature_base(
147 self,
148 message: &impl SignedMessage,
149 ) -> Result<SignatureBase, ImplementationError> {
150 Ok(SignatureBase {
151 components: IndexMap::from_iter(
152 self.components
153 .into_iter()
154 .map(|component| match message.lookup_component(&component) {
155 v if v.len() == 1 => Ok((component, v[0].to_owned())),
156 v if v.len() > 1 && matches!(component, CoveredComponent::HTTP(_)) => {
157 let mut register: Vec<String> = vec![];
158
159 for header_value in v.into_iter() {
160 register.push(
161 String::from_utf8(
164 OBSOLETE_LINE_FOLDING
165 .replace_all(header_value.as_bytes().trim_ascii(), b" ")
166 .into_owned(),
167 )
168 .map_err(|_| ImplementationError::NonAsciiContentFound)?,
169 );
170 }
171
172 Ok((component, register.join(", ")))
173 }
174 _ => Err(ImplementationError::LookupError(component)),
175 })
176 .collect::<Result<Vec<(CoveredComponent, String)>, ImplementationError>>()?,
177 ),
178 parameters: self.parameters,
179 })
180 }
181}
182
183#[derive(Clone, Debug)]
185pub struct SignatureBase {
186 pub components: IndexMap<CoveredComponent, String>,
188 pub parameters: SignatureParams,
190}
191
192impl SignatureBase {
193 fn into_ascii(self) -> Result<(String, String), ImplementationError> {
196 let mut output = String::new();
197
198 let mut signature_params_line_items: Vec<sfv::Item> = vec![];
199
200 for (component, serialized_value) in self.components {
201 let sfv_item = match component {
202 CoveredComponent::HTTP(http) => sfv::Item::try_from(http)?,
203 CoveredComponent::Derived(derived) => sfv::Item::try_from(derived)?,
204 };
205
206 let _ = writeln!(
207 output,
208 "{}: {}",
209 sfv_item.serialize_value(),
210 serialized_value
211 );
212 signature_params_line_items.push(sfv_item);
213 }
214
215 let signature_params_line = vec![sfv::ListEntry::InnerList(sfv::InnerList::with_params(
216 signature_params_line_items,
217 self.parameters.raw,
218 ))]
219 .serialize_value()
220 .ok_or(ImplementationError::SignatureParamsSerialization)?;
221
222 let _ = write!(output, "\"@signature-params\": {signature_params_line}");
223
224 if output.is_ascii() {
225 Ok((output, signature_params_line))
226 } else {
227 Err(ImplementationError::NonAsciiContentFound)
228 }
229 }
230}
231
232pub trait SignedMessage {
235 fn lookup_component(&self, name: &CoveredComponent) -> Vec<String>;
247}
248
249pub trait UnsignedMessage {
252 fn fetch_components_to_cover(&self) -> IndexMap<CoveredComponent, String>;
257 fn register_header_contents(&mut self, signature_input: String, signature_header: String);
262}
263
264pub trait GenerateSignature {
269 fn generate_signature(
271 &self,
272 algorithm: Algorithm,
273 msg: &[u8],
274 ) -> Result<Vec<u8>, ImplementationError>;
275}
276
277impl GenerateSignature for [u8] {
278 fn generate_signature(
279 &self,
280 algorithm: Algorithm,
281 msg: &[u8],
282 ) -> Result<Vec<u8>, ImplementationError> {
283 let signature = match algorithm {
284 Algorithm::Ed25519 => {
285 use ed25519_dalek::{Signer, SigningKey};
286 let signing_key_dalek = SigningKey::try_from(self)
287 .map_err(|_| ImplementationError::InvalidKeyLength)?;
288
289 signing_key_dalek.sign(msg).to_vec()
290 }
291 other => return Err(ImplementationError::UnsupportedAlgorithm(other)),
292 };
293
294 Ok(signature)
295 }
296}
297
298impl GenerateSignature for Vec<u8> {
299 fn generate_signature(
300 &self,
301 algorithm: Algorithm,
302 msg: &[u8],
303 ) -> Result<Vec<u8>, ImplementationError> {
304 self.as_slice().generate_signature(algorithm, msg)
305 }
306}
307
308impl GenerateSignature for [u8; 32] {
309 fn generate_signature(
310 &self,
311 algorithm: Algorithm,
312 msg: &[u8],
313 ) -> Result<Vec<u8>, ImplementationError> {
314 self.as_slice().generate_signature(algorithm, msg)
315 }
316}
317
318pub struct MessageSigner {
321 pub keyid: String,
323 pub nonce: String,
325 pub tag: String,
327}
328
329impl MessageSigner {
330 pub fn generate_signature_headers_content(
338 &self,
339 message: &mut impl UnsignedMessage,
340 expires: Duration,
341 algorithm: Algorithm,
342 signer: &(impl GenerateSignature + ?Sized),
343 ) -> Result<(), ImplementationError> {
344 let components_to_cover = message.fetch_components_to_cover();
345 let mut sfv_parameters = sfv::Parameters::new();
346
347 sfv_parameters.insert(
348 sfv::KeyRef::constant("keyid").to_owned(),
349 sfv::BareItem::String(
350 sfv::StringRef::from_str(&self.keyid)
351 .map_err(|_| {
352 ImplementationError::ParsingError(
353 "keyid contains non-printable ASCII characters".into(),
354 )
355 })?
356 .to_owned(),
357 ),
358 );
359
360 sfv_parameters.insert(
361 sfv::KeyRef::constant("nonce").to_owned(),
362 sfv::BareItem::String(
363 sfv::StringRef::from_str(&self.nonce)
364 .map_err(|_| {
365 ImplementationError::ParsingError(
366 "nonce contains non-printable ASCII characters".into(),
367 )
368 })?
369 .to_owned(),
370 ),
371 );
372
373 sfv_parameters.insert(
374 sfv::KeyRef::constant("tag").to_owned(),
375 sfv::BareItem::String(
376 sfv::StringRef::from_str(&self.tag)
377 .map_err(|_| {
378 ImplementationError::ParsingError(
379 "tag contains non-printable ASCII characters".into(),
380 )
381 })?
382 .to_owned(),
383 ),
384 );
385
386 sfv_parameters.insert(
387 sfv::KeyRef::constant("alg").to_owned(),
388 sfv::BareItem::String(
389 sfv::StringRef::from_str(&format!("{}", algorithm))
390 .map_err(|_| {
391 ImplementationError::ParsingError(
392 "tag contains non-printable ASCII characters".into(),
393 )
394 })?
395 .to_owned(),
396 ),
397 );
398
399 let created = UtcDateTime::now();
400 let expiry = created + expires;
401
402 sfv_parameters.insert(
403 sfv::KeyRef::constant("created").to_owned(),
404 sfv::BareItem::Integer(sfv::Integer::constant(created.unix_timestamp())),
405 );
406
407 sfv_parameters.insert(
408 sfv::KeyRef::constant("expires").to_owned(),
409 sfv::BareItem::Integer(sfv::Integer::constant(expiry.unix_timestamp())),
410 );
411
412 let (signature_base, signature_params_content) = SignatureBase {
413 components: components_to_cover,
414 parameters: sfv_parameters.into(),
415 }
416 .into_ascii()?;
417
418 let signature = sfv::Item {
419 bare_item: sfv::BareItem::ByteSequence(
420 signer.generate_signature(algorithm, signature_base.as_bytes())?,
421 ),
422 params: sfv::Parameters::new(),
423 }
424 .serialize_value();
425
426 message.register_header_contents(signature_params_content, signature);
427
428 Ok(())
429 }
430}
431
432#[derive(Clone, Debug)]
436pub struct ParsedLabel {
437 pub label: sfv::Key,
439 pub signature: Vec<u8>,
441 pub base: SignatureBase,
444}
445
446#[derive(Clone, Debug)]
448pub struct MessageVerifier {
449 pub parsed: ParsedLabel,
451}
452
453#[derive(Clone, Debug)]
456pub struct SignatureTiming {
457 pub generation: Duration,
459 pub verification: Duration,
461}
462
463impl MessageVerifier {
464 pub fn parse<P>(message: &impl SignedMessage, pick: P) -> Result<Self, ImplementationError>
473 where
474 P: Fn(&(sfv::Key, sfv::InnerList)) -> bool,
475 {
476 let signature_input = message
477 .lookup_component(&CoveredComponent::HTTP(HTTPField {
478 name: "signature-input".to_string(),
479 parameters: components::HTTPFieldParametersSet(vec![]),
480 }))
481 .into_iter()
482 .filter_map(|sig_input| sfv::Parser::new(&sig_input).parse_dictionary().ok())
483 .reduce(|mut acc, sig_input| {
484 acc.extend(sig_input);
485 acc
486 })
487 .ok_or(ImplementationError::ParsingError(
488 "No validly-formatted `Signature-Input` headers found".to_string(),
489 ))?;
490
491 let mut signature_header = message
492 .lookup_component(&CoveredComponent::HTTP(HTTPField {
493 name: "signature".to_string(),
494 parameters: components::HTTPFieldParametersSet(vec![]),
495 }))
496 .into_iter()
497 .filter_map(|sig_input| sfv::Parser::new(&sig_input).parse_dictionary().ok())
498 .reduce(|mut acc, sig_input| {
499 acc.extend(sig_input);
500 acc
501 })
502 .ok_or(ImplementationError::ParsingError(
503 "No validly-formatted `Signature` headers found".to_string(),
504 ))?;
505
506 let (label, innerlist) = signature_input
507 .into_iter()
508 .filter_map(|(label, listentry)| match listentry {
509 sfv::ListEntry::InnerList(inner_list) => Some((label, inner_list)),
510 sfv::ListEntry::Item(_) => None,
511 })
512 .find(pick)
513 .ok_or(ImplementationError::ParsingError(
514 "No matching label and signature base found".into(),
515 ))?;
516
517 let signature = match signature_header.shift_remove(&label).ok_or(
518 ImplementationError::ParsingError("No matching signature found from label".into()),
519 )? {
520 sfv::ListEntry::Item(sfv::Item {
521 bare_item,
522 params: _,
523 }) => match bare_item {
524 sfv::GenericBareItem::ByteSequence(sequence) => sequence,
525 other_type => {
526 return Err(ImplementationError::ParsingError(format!(
527 "Invalid type for signature found, expected byte sequence: {other_type:?}"
528 )));
529 }
530 },
531 other_type @ sfv::ListEntry::InnerList(_) => {
532 return Err(ImplementationError::ParsingError(format!(
533 "Invalid type for signature found, expected byte sequence: {other_type:?}"
534 )));
535 }
536 };
537
538 let builder = SignatureBaseBuilder::try_from(innerlist)?;
539 let base = builder.into_signature_base(message)?;
540
541 Ok(MessageVerifier {
542 parsed: ParsedLabel {
543 label,
544 signature,
545 base,
546 },
547 })
548 }
549
550 pub fn verify(
560 self,
561 keyring: &KeyRing,
562 key_id: Option<String>,
563 ) -> Result<SignatureTiming, ImplementationError> {
564 let keying_material = (match key_id {
565 Some(key) => keyring.get(&key),
566 None => self
567 .parsed
568 .base
569 .parameters
570 .details
571 .keyid
572 .as_ref()
573 .and_then(|key| keyring.get(key)),
574 })
575 .ok_or(ImplementationError::NoSuchKey)?;
576 let generation = UtcDateTime::now();
577 let (base_representation, _) = self.parsed.base.into_ascii()?;
578 let generation = UtcDateTime::now() - generation;
579 match &keying_material.0 {
580 Algorithm::Ed25519 => {
581 use ed25519_dalek::{Signature, Verifier, VerifyingKey};
582 let verifying_key = VerifyingKey::try_from(keying_material.1.as_slice())
583 .map_err(|_| ImplementationError::InvalidKeyLength)?;
584
585 let sig = Signature::try_from(self.parsed.signature.as_slice())
586 .map_err(|_| ImplementationError::InvalidSignatureLength)?;
587
588 let verification = UtcDateTime::now();
589 verifying_key
590 .verify(base_representation.as_bytes(), &sig)
591 .map_err(ImplementationError::FailedToVerify)
592 .map(|()| SignatureTiming {
593 generation,
594 verification: UtcDateTime::now() - verification,
595 })
596 }
597 other => Err(ImplementationError::UnsupportedAlgorithm(other.clone())),
598 }
599 }
600}
601
602#[cfg(test)]
603mod tests {
604
605 use crate::components::{DerivedComponent, HTTPField, HTTPFieldParametersSet};
606 use indexmap::IndexMap;
607
608 use super::*;
609
610 struct StandardTestVector {}
611
612 impl SignedMessage for StandardTestVector {
613 fn lookup_component(&self, name: &CoveredComponent) -> Vec<String> {
614 match name {
615 CoveredComponent::HTTP(HTTPField { name, .. }) => {
616 if name == "signature" {
617 return vec!["sig1=:uz2SAv+VIemw+Oo890bhYh6Xf5qZdLUgv6/PbiQfCFXcX/vt1A8Pf7OcgL2yUDUYXFtffNpkEr5W6dldqFrkDg==:".to_owned()];
618 }
619
620 if name == "signature-input" {
621 return vec![r#"sig1=("@authority");created=1735689600;keyid="poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U";alg="ed25519";expires=1735693200;nonce="gubxywVx7hzbYKatLgzuKDllDAIXAkz41PydU7aOY7vT+Mb3GJNxW0qD4zJ+IOQ1NVtg+BNbTCRUMt1Ojr5BgA==";tag="web-bot-auth""#.to_owned()];
622 }
623 vec![]
624 }
625 CoveredComponent::Derived(DerivedComponent::Authority { .. }) => {
626 vec!["example.com".to_string()]
627 }
628 _ => vec![],
629 }
630 }
631 }
632
633 #[test]
634 fn test_parsing_as_http_signature() {
635 let test = StandardTestVector {};
636 let verifier = MessageVerifier::parse(&test, |(_, _)| true).unwrap();
637 let expected_signature_params = "(\"@authority\");created=1735689600;keyid=\"poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U\";alg=\"ed25519\";expires=1735693200;nonce=\"gubxywVx7hzbYKatLgzuKDllDAIXAkz41PydU7aOY7vT+Mb3GJNxW0qD4zJ+IOQ1NVtg+BNbTCRUMt1Ojr5BgA==\";tag=\"web-bot-auth\"";
638 let expected_base = format!(
639 "\"@authority\": example.com\n\"@signature-params\": {expected_signature_params}"
640 );
641 let (base, signature_params) = verifier.parsed.base.into_ascii().unwrap();
642 assert_eq!(base, expected_base.as_str());
643 assert_eq!(signature_params, expected_signature_params);
644 }
645
646 #[test]
647 fn test_verifying_as_http_signature() {
648 let test = StandardTestVector {};
649 let public_key: [u8; ed25519_dalek::PUBLIC_KEY_LENGTH] = [
650 0x26, 0xb4, 0x0b, 0x8f, 0x93, 0xff, 0xf3, 0xd8, 0x97, 0x11, 0x2f, 0x7e, 0xbc, 0x58,
651 0x2b, 0x23, 0x2d, 0xbd, 0x72, 0x51, 0x7d, 0x08, 0x2f, 0xe8, 0x3c, 0xfb, 0x30, 0xdd,
652 0xce, 0x43, 0xd1, 0xbb,
653 ];
654 let mut keyring = KeyRing::default();
655 keyring.import_raw(
656 "poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0U".to_string(),
657 Algorithm::Ed25519,
658 public_key.to_vec(),
659 );
660 let verifier = MessageVerifier::parse(&test, |(_, _)| true).unwrap();
661 let timing = verifier.verify(&keyring, None).unwrap();
662 assert!(timing.generation.whole_nanoseconds() > 0);
663 assert!(timing.verification.whole_nanoseconds() > 0);
664 }
665
666 #[test]
667 fn test_signing() {
668 struct SigningTest {}
669 impl UnsignedMessage for SigningTest {
670 fn fetch_components_to_cover(&self) -> IndexMap<CoveredComponent, String> {
671 IndexMap::from_iter([
672 (
673 CoveredComponent::Derived(DerivedComponent::Method { req: false }),
674 "POST".to_string(),
675 ),
676 (
677 CoveredComponent::Derived(DerivedComponent::Authority { req: false }),
678 "example.com".to_string(),
679 ),
680 (
681 CoveredComponent::HTTP(HTTPField {
682 name: "content-length".to_string(),
683 parameters: HTTPFieldParametersSet(vec![]),
684 }),
685 "18".to_string(),
686 ),
687 ])
688 }
689
690 fn register_header_contents(
691 &mut self,
692 _signature_input: String,
693 _signature_header: String,
694 ) {
695 }
696 }
697
698 let signer = MessageSigner {
699 keyid: "test".into(),
700 nonce: "another-test".into(),
701 tag: "web-bot-auth".into(),
702 };
703
704 let private_key: [u8; ed25519_dalek::SECRET_KEY_LENGTH] = [
705 0x9f, 0x83, 0x62, 0xf8, 0x7a, 0x48, 0x4a, 0x95, 0x4e, 0x6e, 0x74, 0x0c, 0x5b, 0x4c,
706 0x0e, 0x84, 0x22, 0x91, 0x39, 0xa2, 0x0a, 0xa8, 0xab, 0x56, 0xff, 0x66, 0x58, 0x6f,
707 0x6a, 0x7d, 0x29, 0xc5,
708 ];
709
710 let mut test = SigningTest {};
711
712 assert!(
713 signer
714 .generate_signature_headers_content(
715 &mut test,
716 Duration::seconds(10),
717 Algorithm::Ed25519,
718 &private_key
719 )
720 .is_ok()
721 );
722 }
723
724 #[test]
725 fn signature_base_generates_the_expected_representation() {
726 let sigbase = SignatureBase {
727 components: IndexMap::from_iter([
728 (
729 CoveredComponent::Derived(DerivedComponent::Method { req: false }),
730 "POST".to_string(),
731 ),
732 (
733 CoveredComponent::Derived(DerivedComponent::Authority { req: false }),
734 "example.com".to_string(),
735 ),
736 (
737 CoveredComponent::HTTP(HTTPField {
738 name: "content-length".to_string(),
739 parameters: HTTPFieldParametersSet(vec![]),
740 }),
741 "18".to_string(),
742 ),
743 ]),
744 parameters: IndexMap::from_iter([
745 (
746 sfv::Key::from_string("keyid".into()).unwrap(),
747 sfv::BareItem::String(sfv::String::from_string("test".to_string()).unwrap()),
748 ),
749 (
750 sfv::Key::from_string("created".into()).unwrap(),
751 sfv::BareItem::Integer(sfv::Integer::constant(1_618_884_473_i64)),
752 ),
753 ])
754 .into(),
755 };
756
757 let expected_base = "\"@method\": POST\n\"@authority\": example.com\n\"content-length\": 18\n\"@signature-params\": (\"@method\" \"@authority\" \"content-length\");keyid=\"test\";created=1618884473";
758 let (base, _) = sigbase.into_ascii().unwrap();
759 assert_eq!(base, expected_base);
760 }
761}