cheers_verify/
artifact.rs1use std::sync::Arc;
11
12use base64::engine::general_purpose::URL_SAFE_NO_PAD;
13use base64::Engine as _;
14use serde::{Deserialize, Serialize};
15
16use cheers_core::{CodecError, SignedArtifact};
17
18use crate::jwks::{JwksDoc, JwksError, KeySet};
19use crate::key_set::{KeySetVerifier, VerifyError};
20use crate::public_verifier::PasetoV4PublicVerifier;
21
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31pub struct TrustDoc {
32 pub issuer: String,
33 pub anchor: AnchorDoc,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(rename_all = "snake_case")]
39pub enum AnchorDoc {
40 Pinned(String),
42 KeySet(JwksDoc),
44}
45
46#[derive(Clone)]
47enum Anchor {
48 Pinned(Arc<PasetoV4PublicVerifier>),
49 KeySet(KeySetVerifier),
50}
51
52#[derive(Clone)]
54pub struct IssuerTrust {
55 issuer: String,
56 anchor: Anchor,
57}
58
59impl std::fmt::Debug for IssuerTrust {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 let anchor = match self.anchor {
62 Anchor::Pinned(_) => "pinned",
63 Anchor::KeySet(_) => "key-set",
64 };
65 f.debug_struct("IssuerTrust")
66 .field("issuer", &self.issuer)
67 .field("anchor", &anchor)
68 .finish()
69 }
70}
71
72impl IssuerTrust {
73 pub fn pinned(issuer: impl Into<String>, key: PasetoV4PublicVerifier) -> Self {
75 Self {
76 issuer: issuer.into(),
77 anchor: Anchor::Pinned(Arc::new(key)),
78 }
79 }
80
81 pub fn key_set(issuer: impl Into<String>, keys: KeySetVerifier) -> Self {
84 Self {
85 issuer: issuer.into(),
86 anchor: Anchor::KeySet(keys),
87 }
88 }
89
90 pub fn issuer(&self) -> &str {
91 &self.issuer
92 }
93
94 pub fn export(&self) -> TrustDoc {
97 let anchor = match &self.anchor {
98 Anchor::Pinned(key) => AnchorDoc::Pinned(URL_SAFE_NO_PAD.encode(key.public_key().as_bytes())),
99 Anchor::KeySet(keys) => AnchorDoc::KeySet(keys.key_set().doc().clone()),
100 };
101 TrustDoc {
102 issuer: self.issuer.clone(),
103 anchor,
104 }
105 }
106
107 pub fn from_doc(doc: TrustDoc) -> Result<Self, JwksError> {
110 match doc.anchor {
111 AnchorDoc::Pinned(b64) => {
112 let bad = |reason: String| JwksError::BadKey {
113 kid: "<pinned>".into(),
114 reason,
115 };
116 let bytes = URL_SAFE_NO_PAD
117 .decode(&b64)
118 .map_err(|e| bad(format!("base64url decode: {e}")))?;
119 let key: [u8; 32] = bytes
120 .try_into()
121 .map_err(|v: Vec<u8>| bad(format!("expected 32 bytes, got {}", v.len())))?;
122 let verifier = PasetoV4PublicVerifier::from_public_key(&key).map_err(|e| bad(e.to_string()))?;
123 Ok(Self::pinned(doc.issuer, verifier))
124 }
125 AnchorDoc::KeySet(jwks) => Ok(Self::key_set(
126 doc.issuer,
127 KeySetVerifier::from_key_set(KeySet::from_doc(jwks)?),
128 )),
129 }
130 }
131
132 pub async fn verify<T: SignedArtifact>(&self, token: &str) -> Result<T, VerifyError> {
134 let artifact: T = match &self.anchor {
135 Anchor::Pinned(key) => key.verify_artifact(token).map_err(from_codec)?,
136 Anchor::KeySet(keys) => keys.verify_artifact(token).await?,
137 };
138 if artifact.issuer() != self.issuer {
139 return Err(VerifyError::BadIssuer {
140 expected: self.issuer.clone(),
141 got: artifact.issuer().to_owned(),
142 });
143 }
144 Ok(artifact)
145 }
146}
147
148fn from_codec(e: CodecError) -> VerifyError {
149 match e {
150 CodecError::SignatureMismatch => VerifyError::SignatureMismatch,
151 CodecError::Serde(e) => VerifyError::BadClaims(e.to_string()),
152 other => VerifyError::Malformed(other.to_string()),
153 }
154}