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