use std::sync::Arc;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine as _;
use serde::{Deserialize, Serialize};
use cheers_core::{CodecError, SignedArtifact};
use crate::jwks::{JwksDoc, JwksError, KeySet};
use crate::key_set::{KeySetVerifier, VerifyError};
use crate::public_verifier::PasetoV4PublicVerifier;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TrustDoc {
pub issuer: String,
pub anchor: AnchorDoc,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AnchorDoc {
Pinned(String),
KeySet(JwksDoc),
}
#[derive(Clone)]
enum Anchor {
Pinned(Arc<PasetoV4PublicVerifier>),
KeySet(KeySetVerifier),
}
#[derive(Clone)]
pub struct IssuerTrust {
issuer: String,
anchor: Anchor,
}
impl std::fmt::Debug for IssuerTrust {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let anchor = match self.anchor {
Anchor::Pinned(_) => "pinned",
Anchor::KeySet(_) => "key-set",
};
f.debug_struct("IssuerTrust")
.field("issuer", &self.issuer)
.field("anchor", &anchor)
.finish()
}
}
impl IssuerTrust {
pub fn pinned(issuer: impl Into<String>, key: PasetoV4PublicVerifier) -> Self {
Self {
issuer: issuer.into(),
anchor: Anchor::Pinned(Arc::new(key)),
}
}
pub fn key_set(issuer: impl Into<String>, keys: KeySetVerifier) -> Self {
Self {
issuer: issuer.into(),
anchor: Anchor::KeySet(keys),
}
}
pub fn issuer(&self) -> &str {
&self.issuer
}
pub fn export(&self) -> TrustDoc {
let anchor = match &self.anchor {
Anchor::Pinned(key) => AnchorDoc::Pinned(URL_SAFE_NO_PAD.encode(key.public_key().as_bytes())),
Anchor::KeySet(keys) => AnchorDoc::KeySet(keys.key_set().doc().clone()),
};
TrustDoc {
issuer: self.issuer.clone(),
anchor,
}
}
pub fn from_doc(doc: TrustDoc) -> Result<Self, JwksError> {
match doc.anchor {
AnchorDoc::Pinned(b64) => {
let bad = |reason: String| JwksError::BadKey {
kid: "<pinned>".into(),
reason,
};
let bytes = URL_SAFE_NO_PAD
.decode(&b64)
.map_err(|e| bad(format!("base64url decode: {e}")))?;
let key: [u8; 32] = bytes
.try_into()
.map_err(|v: Vec<u8>| bad(format!("expected 32 bytes, got {}", v.len())))?;
let verifier = PasetoV4PublicVerifier::from_public_key(&key).map_err(|e| bad(e.to_string()))?;
Ok(Self::pinned(doc.issuer, verifier))
}
AnchorDoc::KeySet(jwks) => Ok(Self::key_set(
doc.issuer,
KeySetVerifier::from_key_set(KeySet::from_doc(jwks)?),
)),
}
}
pub async fn verify<T: SignedArtifact>(&self, token: &str) -> Result<T, VerifyError> {
let artifact: T = match &self.anchor {
Anchor::Pinned(key) => key.verify_artifact(token).map_err(from_codec)?,
Anchor::KeySet(keys) => keys.verify_artifact(token).await?,
};
if artifact.issuer() != self.issuer {
return Err(VerifyError::BadIssuer {
expected: self.issuer.clone(),
got: artifact.issuer().to_owned(),
});
}
Ok(artifact)
}
}
fn from_codec(e: CodecError) -> VerifyError {
match e {
CodecError::SignatureMismatch => VerifyError::SignatureMismatch,
CodecError::Serde(e) => VerifyError::BadClaims(e.to_string()),
other => VerifyError::Malformed(other.to_string()),
}
}