1use crate::invoice::Signed;
2
3use super::signature::KeyRing;
4use super::{Invoice, Signature, SignatureError, SignatureRole};
5use ed25519_dalek::{PublicKey, Signature as EdSignature};
6use tracing::debug;
7
8use std::borrow::{Borrow, BorrowMut};
9use std::fmt::Debug;
10use std::str::FromStr;
11
12const GREEDY_VERIFICATION_ROLES: &[SignatureRole] = &[SignatureRole::Creator];
13const CREATIVE_INTEGITY_ROLES: &[SignatureRole] = &[SignatureRole::Creator];
14const AUTHORITATIVE_INTEGRITY_ROLES: &[SignatureRole] =
15 &[SignatureRole::Creator, SignatureRole::Approver];
16const EXHAUSTIVE_VERIFICATION_ROLES: &[SignatureRole] = &[
17 SignatureRole::Creator,
18 SignatureRole::Approver,
19 SignatureRole::Host,
20 SignatureRole::Proxy,
21];
22
23pub trait Verified: super::sealed::Sealed {}
25
26#[derive(Debug, Clone)]
28pub enum VerificationStrategy {
29 CreativeIntegrity,
32 AuthoritativeIntegrity,
35 GreedyVerification,
39 ExhaustiveVerification,
41 MultipleAttestation(Vec<SignatureRole>),
43 MultipleAttestationGreedy(Vec<SignatureRole>),
50}
51
52impl Default for VerificationStrategy {
53 fn default() -> Self {
54 VerificationStrategy::GreedyVerification
55 }
56}
57
58impl FromStr for VerificationStrategy {
61 type Err = &'static str;
62
63 fn from_str(s: &str) -> Result<Self, Self::Err> {
64 if s.is_empty() {
65 return Err("Cannot parse VerificationStrategy from an empty string");
66 }
67 let normalized = s.trim().to_lowercase();
70 let parts: Vec<&str> = normalized.splitn(2, '[').collect();
71 match parts[0] {
74 "creativeintegrity" => Ok(Self::CreativeIntegrity),
75 "authoritativeintegrity" => Ok(Self::AuthoritativeIntegrity),
76 "greedyverification" => Ok(Self::GreedyVerification),
77 "exhaustiveverification" => Ok(Self::ExhaustiveVerification),
78 "multipleattestation" => Ok(Self::MultipleAttestation(parse_roles(parts.get(1))?)),
79 "multipleattestationgreedy" => {
80 Ok(Self::MultipleAttestationGreedy(parse_roles(parts.get(1))?))
81 }
82 _ => Err("Unknown verification strategy"),
83 }
84 }
85}
86
87impl<'de> serde::Deserialize<'de> for VerificationStrategy {
90 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
91 where
92 D: serde::Deserializer<'de>,
93 {
94 deserializer.deserialize_string(StrategyVisitor)
95 }
96}
97
98struct StrategyVisitor;
99
100impl<'de> serde::de::Visitor<'de> for StrategyVisitor {
101 type Value = VerificationStrategy;
102
103 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
104 formatter.write_str("a valid verification strategy value")
105 }
106
107 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
108 where
109 E: serde::de::Error,
110 {
111 match v.parse::<VerificationStrategy>() {
112 Ok(s) => Ok(s),
113 Err(e) => Err(E::custom(e)),
114 }
115 }
116
117 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
118 where
119 E: serde::de::Error,
120 {
121 self.visit_str(&v)
122 }
123}
124
125fn parse_roles(r: Option<&&str>) -> Result<Vec<SignatureRole>, &'static str> {
126 let raw = r.ok_or("Multiple attestation strategy is missing roles")?;
127 if !raw.ends_with(']') {
128 return Err("Missing closing ']' on roles");
129 }
130 raw.trim_end_matches(']')
131 .split(',')
132 .map(|role| role.parse::<SignatureRole>())
133 .collect::<Result<Vec<_>, _>>()
134}
135
136impl VerificationStrategy {
138 fn verify_signature(&self, sig: &Signature, cleartext: &[u8]) -> Result<(), SignatureError> {
139 let pk = base64::decode(sig.key.as_bytes())
140 .map_err(|_| SignatureError::CorruptKey(sig.key.clone()))?;
141 let sig_block = base64::decode(sig.signature.as_bytes())
142 .map_err(|_| SignatureError::CorruptSignature(sig.key.clone()))?;
143
144 let pubkey =
145 PublicKey::from_bytes(&pk).map_err(|_| SignatureError::CorruptKey(sig.key.clone()))?;
146 let ed_sig = EdSignature::try_from(sig_block.as_slice())
147 .map_err(|_| SignatureError::CorruptSignature(sig.key.clone()))?;
148 pubkey
149 .verify_strict(cleartext, &ed_sig)
150 .map_err(|_| SignatureError::Unverified(sig.key.clone()))
151 }
152 pub fn verify<I>(
170 &self,
171 invoice: I,
172 keyring: &KeyRing,
173 ) -> Result<VerifiedInvoice<I>, SignatureError>
174 where
175 I: Borrow<Invoice> + Into<Invoice>,
176 {
177 let inv = invoice.borrow();
178 let (roles, all_valid, all_verified, all_roles) = match self {
179 VerificationStrategy::GreedyVerification => {
180 (GREEDY_VERIFICATION_ROLES, true, true, true)
181 }
182 VerificationStrategy::CreativeIntegrity => (CREATIVE_INTEGITY_ROLES, false, true, true),
183 VerificationStrategy::AuthoritativeIntegrity => {
184 (AUTHORITATIVE_INTEGRITY_ROLES, false, false, false)
185 }
186 VerificationStrategy::ExhaustiveVerification => {
187 (EXHAUSTIVE_VERIFICATION_ROLES, true, true, false)
188 }
189 VerificationStrategy::MultipleAttestation(a) => (a.as_slice(), false, true, true),
190 VerificationStrategy::MultipleAttestationGreedy(a) => (a.as_slice(), true, true, true),
191 };
192
193 match inv.signature.as_ref() {
195 None => {
196 debug!(id = %inv.bindle.id, "No signatures on invoice");
197 Err(SignatureError::Unverified(
198 "No signatures found on invoice. At least one signature is required"
199 .to_string(),
200 ))
201 }
202 Some(signatures) => {
203 let mut known_key = false;
204 let mut filled_roles: Vec<SignatureRole> = vec![];
205 for s in signatures {
206 debug!(by = %s.by, "Checking signature");
207 let target_role = roles.contains(&s.role);
208
209 if !all_valid && !target_role {
212 debug!("Not a target role, and not running all_valid");
213 continue;
214 }
215
216 let role = s.role.clone();
217 let cleartext = inv.cleartext(&s.by, &role);
218
219 self.verify_signature(s, cleartext.as_bytes())?;
225 debug!("Signature verified");
226
227 if !target_role && !all_verified {
228 debug!("Not a target role, not checking for verification");
229 continue;
230 } else if all_roles {
231 filled_roles.push(role);
232 }
233 let pubkey = base64::decode(&s.key)
235 .map_err(|_| SignatureError::CorruptKey(s.key.to_string()))?;
236 let pko = PublicKey::from_bytes(pubkey.as_slice())
237 .map_err(|_| SignatureError::CorruptKey(s.key.to_string()))?;
238
239 debug!("Looking for key");
240 if keyring.contains(&pko) {
242 debug!("Found key {}", s.by);
243 known_key = true;
244 } else if all_verified {
245 return Err(SignatureError::Unverified(
248 "strategy requires that all signatures for role(s) must be verified"
249 .to_owned(),
250 ));
251 }
252 }
253 if !known_key {
254 debug!("No known key");
255 return Err(SignatureError::NoKnownKey);
258 }
259 if all_roles {
262 for should_role in roles {
263 if !filled_roles.contains(should_role) {
264 return Err(SignatureError::Unverified(format!(
265 "No signature found for role {:?}",
266 should_role,
267 )));
268 }
269 }
270 }
271 Ok(VerifiedInvoice(invoice))
272 }
273 }
274 }
275}
276
277pub struct VerifiedInvoice<T: Into<crate::Invoice>>(T);
280
281impl<T: Into<crate::Invoice>> Verified for VerifiedInvoice<T> {}
282
283impl<T: Into<crate::Invoice>> super::sealed::Sealed for VerifiedInvoice<T> {}
284
285impl<T> Signed for VerifiedInvoice<T>
286where
287 T: Signed + Into<crate::Invoice>,
288{
289 fn signed(self) -> crate::Invoice {
290 self.0.signed()
291 }
292}
293
294impl<T> Borrow<crate::Invoice> for VerifiedInvoice<T>
295where
296 T: Into<crate::Invoice> + Borrow<crate::Invoice>,
297{
298 fn borrow(&self) -> &crate::Invoice {
299 self.0.borrow()
300 }
301}
302
303impl<T> BorrowMut<crate::Invoice> for VerifiedInvoice<T>
304where
305 T: Into<crate::Invoice> + BorrowMut<crate::Invoice>,
306{
307 fn borrow_mut(&mut self) -> &mut crate::Invoice {
308 self.0.borrow_mut()
309 }
310}
311
312pub(crate) struct NoopVerified<T: Into<crate::Invoice>>(pub(crate) T);
314
315impl<T: Into<crate::Invoice>> Verified for NoopVerified<T> {}
316
317impl<T: Into<crate::Invoice>> super::sealed::Sealed for NoopVerified<T> {}
318
319impl<T> Signed for NoopVerified<T>
320where
321 T: Signed + Into<crate::Invoice>,
322{
323 fn signed(self) -> crate::Invoice {
324 self.0.signed()
325 }
326}
327
328#[allow(clippy::from_over_into)]
330impl<T: Into<crate::Invoice>> Into<crate::Invoice> for NoopVerified<T> {
331 fn into(self) -> crate::Invoice {
332 self.0.into()
333 }
334}
335
336#[allow(clippy::from_over_into)]
339impl<T: Into<crate::Invoice>> Into<crate::Invoice> for VerifiedInvoice<T> {
340 fn into(self) -> crate::Invoice {
341 self.0.into()
342 }
343}
344
345impl<T> Debug for VerifiedInvoice<T>
346where
347 T: Debug + Into<crate::Invoice>,
348{
349 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
350 self.0.fmt(f)
351 }
352}
353
354#[cfg(test)]
355mod test {
356 use super::*;
357 use crate::invoice::*;
358 use std::convert::TryInto;
359
360 #[test]
361 fn test_parse_verification_strategies() {
362 let strat = "CreativeIntegrity"
364 .parse::<VerificationStrategy>()
365 .expect("should parse");
366 assert!(
367 matches!(strat, VerificationStrategy::CreativeIntegrity),
368 "Should parse to the correct type"
369 );
370 let strat = "AuthoritativeIntegrity"
371 .parse::<VerificationStrategy>()
372 .expect("should parse");
373 assert!(
374 matches!(strat, VerificationStrategy::AuthoritativeIntegrity),
375 "Should parse to the correct type"
376 );
377 let strat = "GreedyVerification"
378 .parse::<VerificationStrategy>()
379 .expect("should parse");
380 assert!(
381 matches!(strat, VerificationStrategy::GreedyVerification),
382 "Should parse to the correct type"
383 );
384 let strat = "ExhaustiveVerification"
385 .parse::<VerificationStrategy>()
386 .expect("should parse");
387 assert!(
388 matches!(strat, VerificationStrategy::ExhaustiveVerification),
389 "Should parse to the correct type"
390 );
391 let strat = "MultipleAttestation[Creator, Host, Approver]"
392 .parse::<VerificationStrategy>()
393 .expect("should parse");
394 assert!(
395 matches!(strat, VerificationStrategy::MultipleAttestation(_)),
396 "Should parse to the correct type"
397 );
398 let strat = "MultipleAttestationGreedy[Creator, Host, Approver]"
399 .parse::<VerificationStrategy>()
400 .expect("should parse");
401
402 match strat {
404 VerificationStrategy::MultipleAttestationGreedy(roles) => {
405 assert!(
406 roles.contains(&SignatureRole::Creator),
407 "Roles should contain creator"
408 );
409 assert!(
410 roles.contains(&SignatureRole::Host),
411 "Roles should contain host"
412 );
413 assert!(
414 roles.contains(&SignatureRole::Approver),
415 "Roles should contain approver"
416 );
417 }
418 _ => panic!("Wrong type returned"),
419 }
420
421 let strat = "CrEaTiVeInTeGrItY"
423 .parse::<VerificationStrategy>()
424 .expect("mixed case should parse");
425 assert!(
426 matches!(strat, VerificationStrategy::CreativeIntegrity),
427 "Should parse to the correct type"
428 );
429 let strat = " multipleAttestAtion[Creator, Host, Approver] "
430 .parse::<VerificationStrategy>()
431 .expect("extra spaces should parse");
432 assert!(
433 matches!(strat, VerificationStrategy::MultipleAttestation(_)),
434 "Should parse to the correct type"
435 );
436
437 "nopenopenope"
439 .parse::<VerificationStrategy>()
440 .expect_err("non-existent strategy shouldn't parse");
441 "Creative Integrity"
442 .parse::<VerificationStrategy>()
443 .expect_err("spacing in the middle shouldn't parse");
444 "MultipleAttestationCreator, Host, Approver]"
445 .parse::<VerificationStrategy>()
446 .expect_err("missing start brace shouldn't parse");
447 "MultipleAttestation[Creator, Host, Approver"
448 .parse::<VerificationStrategy>()
449 .expect_err("missing end brace shouldn't parse");
450 "MultipleAttestation[Blah, Host, Approver]"
451 .parse::<VerificationStrategy>()
452 .expect_err("Invalid role shouldn't parse");
453 }
454
455 #[test]
456 fn test_strategy_deserialize() {
457 #[derive(serde::Deserialize)]
458 struct StrategyMock {
459 verification_strategy: VerificationStrategy,
460 }
461
462 let toml_value = r#"
463 verification_strategy = "MultipleAttestation[Creator, Host, Approver]"
464 "#;
465
466 let mock: StrategyMock = toml::from_str(toml_value).expect("toml should parse");
467
468 assert!(
469 matches!(
470 mock.verification_strategy,
471 VerificationStrategy::MultipleAttestation(_)
472 ),
473 "Should parse to the correct type"
474 );
475
476 let toml_value = r#"
479 verification_strategy = "CreativeIntegrity"
480 "#;
481
482 let mock: StrategyMock = toml::from_str(toml_value).expect("toml should parse");
483
484 assert!(
485 matches!(
486 mock.verification_strategy,
487 VerificationStrategy::CreativeIntegrity
488 ),
489 "Should parse to the correct type"
490 );
491
492 let json_value = r#"
494 {
495 "verification_strategy": "MultipleAttestation[Creator, Host, Approver]"
496 }
497 "#;
498
499 let mock: StrategyMock = serde_json::from_str(json_value).expect("json should parse");
500
501 assert!(
502 matches!(
503 mock.verification_strategy,
504 VerificationStrategy::MultipleAttestation(_)
505 ),
506 "Should parse to the correct type"
507 );
508 }
509
510 #[test]
511 fn test_verification_strategies() {
512 let invoice = r#"
513 bindleVersion = "1.0.0"
514
515 [bindle]
516 name = "arecebo"
517 version = "1.2.3"
518
519 [[parcel]]
520 [parcel.label]
521 sha256 = "aaabbbcccdddeeefff"
522 name = "telescope.gif"
523 mediaType = "image/gif"
524 size = 123_456
525
526 [[parcel]]
527 [parcel.label]
528 sha256 = "111aaabbbcccdddeee"
529 name = "telescope.txt"
530 mediaType = "text/plain"
531 size = 123_456
532 "#;
533 let invoice: crate::Invoice = toml::from_str(invoice).expect("a nice clean parse");
534
535 let key_creator = SecretKeyEntry::new("Test Creator", vec![SignatureRole::Creator]);
536 let key_approver = SecretKeyEntry::new("Test Approver", vec![SignatureRole::Approver]);
537 let key_host = SecretKeyEntry::new("Test Host", vec![SignatureRole::Host]);
538 let key_proxy = SecretKeyEntry::new("Test Proxy", vec![SignatureRole::Proxy]);
539 let keyring_keys = vec![
540 key_approver.clone().try_into().expect("convert to pubkey"),
541 key_host.clone().try_into().expect("convert to pubkey"),
542 key_creator.clone().try_into().expect("convert to pubkey"),
543 key_proxy.clone().try_into().expect("convert to pubkey"),
544 ];
545 let keyring = KeyRing::new(keyring_keys);
546
547 {
549 let mut inv = invoice.clone();
550 inv.sign(SignatureRole::Host, &key_host)
551 .expect("signed as host");
552 VerificationStrategy::CreativeIntegrity
554 .verify(inv.clone(), &keyring)
555 .expect_err("inv should not pass: Requires creator");
556 VerificationStrategy::AuthoritativeIntegrity
557 .verify(inv.clone(), &keyring)
558 .expect_err("inv should not pass: Requires creator or approver");
559 VerificationStrategy::GreedyVerification
560 .verify(inv.clone(), &keyring)
561 .expect_err("inv should not pass: Requires creator and all valid");
562 VerificationStrategy::MultipleAttestationGreedy(vec![SignatureRole::Host])
563 .verify(inv.clone(), &keyring)
564 .expect("inv should pass: Only requires host");
565 VerificationStrategy::MultipleAttestationGreedy(vec![
566 SignatureRole::Host,
567 SignatureRole::Proxy,
568 ])
569 .verify(inv.clone(), &keyring)
570 .expect_err("inv should not pass: Requires proxy");
571
572 VerificationStrategy::ExhaustiveVerification
573 .verify(inv, &keyring)
574 .expect("inv should not pass: Requires that all signatures must be verified");
575 }
576 {
578 let mut inv = invoice.clone();
579 inv.sign(SignatureRole::Host, &key_host)
580 .expect("signed as host");
581 inv.sign(SignatureRole::Creator, &key_creator)
582 .expect("signed as creator");
583 VerificationStrategy::CreativeIntegrity
585 .verify(inv.clone(), &keyring)
586 .expect("inv should pass: Signed by creator");
587 VerificationStrategy::AuthoritativeIntegrity
588 .verify(inv.clone(), &keyring)
589 .expect("inv should pass: Signed by creator");
590 VerificationStrategy::GreedyVerification
591 .verify(inv.clone(), &keyring)
592 .expect("inv should pass: Requires creator and all valid");
593 VerificationStrategy::MultipleAttestationGreedy(vec![SignatureRole::Host])
594 .verify(inv.clone(), &keyring)
595 .expect("inv should pass: Only requires host");
596 VerificationStrategy::MultipleAttestationGreedy(vec![
597 SignatureRole::Host,
598 SignatureRole::Proxy,
599 ])
600 .verify(inv.clone(), &keyring)
601 .expect_err("inv should not pass: Requires proxy");
602
603 VerificationStrategy::ExhaustiveVerification
604 .verify(inv, &keyring)
605 .expect("inv should not pass: Requires that all signatures must be verified");
606 }
607 {
609 let mut inv = invoice.clone();
610 inv.sign(SignatureRole::Host, &key_host)
611 .expect("signed as host");
612 inv.sign(SignatureRole::Approver, &key_approver)
613 .expect("signed as approver");
614 VerificationStrategy::CreativeIntegrity
616 .verify(inv.clone(), &keyring)
617 .expect_err("inv should not pass: not signed by creator");
618 VerificationStrategy::AuthoritativeIntegrity
619 .verify(inv.clone(), &keyring)
620 .expect("inv should pass: Signed by approver");
621 VerificationStrategy::GreedyVerification
622 .verify(inv.clone(), &keyring)
623 .expect_err("inv should not pass: Requires creator and all valid");
624 VerificationStrategy::MultipleAttestationGreedy(vec![SignatureRole::Host])
625 .verify(inv.clone(), &keyring)
626 .expect("inv should pass: Only requires host");
627 VerificationStrategy::MultipleAttestationGreedy(vec![
628 SignatureRole::Host,
629 SignatureRole::Proxy,
630 ])
631 .verify(inv.clone(), &keyring)
632 .expect_err("inv should not pass: Requires proxy");
633
634 VerificationStrategy::ExhaustiveVerification
635 .verify(inv, &keyring)
636 .expect("inv should not pass: Requires that all signatures must be verified");
637 }
638 {
640 let mut inv = invoice.clone();
641 inv.sign(SignatureRole::Host, &key_host)
642 .expect("signed as host");
643 inv.sign(SignatureRole::Creator, &key_creator)
644 .expect("signed as creator");
645 inv.sign(SignatureRole::Proxy, &key_proxy)
646 .expect("signed as proxy");
647 VerificationStrategy::CreativeIntegrity
649 .verify(inv.clone(), &keyring)
650 .expect("inv should pass: signed by creator");
651 VerificationStrategy::AuthoritativeIntegrity
652 .verify(inv.clone(), &keyring)
653 .expect("inv should pass: Signed by creator");
654 VerificationStrategy::GreedyVerification
655 .verify(inv.clone(), &keyring)
656 .expect("inv should pass: Requires creator and all valid");
657 VerificationStrategy::MultipleAttestationGreedy(vec![SignatureRole::Host])
658 .verify(inv.clone(), &keyring)
659 .expect("inv should pass: Only requires host");
660 VerificationStrategy::MultipleAttestationGreedy(vec![
661 SignatureRole::Host,
662 SignatureRole::Proxy,
663 ])
664 .verify(inv.clone(), &keyring)
665 .expect("inv should pass: Requires proxy");
666
667 VerificationStrategy::ExhaustiveVerification
668 .verify(inv, &keyring)
669 .expect("inv should pass: Requires that all signatures must be verified");
670 }
671 println!("Signed by creator, host, and unknown key");
672 {
673 let mut inv = invoice;
674 inv.sign(SignatureRole::Host, &key_host)
675 .expect("signed as host");
676 inv.sign(SignatureRole::Creator, &key_creator)
677 .expect("signed as creator");
678
679 let key_anon = SecretKeyEntry::new("Unknown key", vec![SignatureRole::Approver]);
681 inv.sign(SignatureRole::Approver, &key_anon)
682 .expect("signed with unknown key");
683
684 VerificationStrategy::CreativeIntegrity
686 .verify(inv.clone(), &keyring)
687 .expect("inv should pass: signed by creator");
688 VerificationStrategy::AuthoritativeIntegrity
689 .verify(inv.clone(), &keyring)
690 .expect("inv should pass: Signed by creator");
691 VerificationStrategy::GreedyVerification
692 .verify(inv.clone(), &keyring)
693 .expect_err(
694 "inv should not pass: Requires creator and all known, anon is not known",
695 );
696 VerificationStrategy::MultipleAttestation(vec![SignatureRole::Host])
697 .verify(inv.clone(), &keyring)
698 .expect("inv should pass: Only requires host");
699 VerificationStrategy::MultipleAttestationGreedy(vec![
700 SignatureRole::Host,
701 SignatureRole::Approver,
702 ])
703 .verify(inv.clone(), &keyring)
704 .expect_err("inv should not pass: Requires approver to be known");
705
706 VerificationStrategy::ExhaustiveVerification
707 .verify(inv, &keyring)
708 .expect_err("inv should not pass: Requires that all signatures must be verified");
709 }
710 }
711}