use ring::signature;
use serde::Deserialize;
use thiserror::Error;
use crate::{jwk, misc::base64url_decode};
#[derive(Debug, Error)]
pub enum VerifyError {
#[error("the token must consist of three dot-separated parts")]
IncorrectFormat,
#[error("token part {index} contained invalid base64: {reason}")]
InvalidPartBase64 {
index: usize,
reason: base64::DecodeError,
},
#[error("the token header contained invalid JSON: {0}")]
InvalidHeaderJson(serde_json::Error),
#[error("the token 'kid' could not be found in the JWKs document: {kid}")]
KidNotMatched { kid: String },
#[error("the matching JWK is of an unsupported type")]
UnsupportedKeyType,
#[error("the token signature did not validate using the matching JWK")]
BadSignature,
}
pub fn verify<'a>(
input: &'a str,
keys: impl IntoIterator<Item = &'a jwk::Key>,
) -> Result<Vec<u8>, VerifyError> {
let mut parts = input.split('.');
let header = parts.next().ok_or(VerifyError::IncorrectFormat)?;
let payload = parts.next().ok_or(VerifyError::IncorrectFormat)?;
let signature = parts.next().ok_or(VerifyError::IncorrectFormat)?;
if parts.next().is_some() {
return Err(VerifyError::IncorrectFormat);
}
let message_len = header.len() + payload.len() + 1;
let message = input[..message_len].as_bytes();
let header = base64url_decode(header)
.map_err(|reason| VerifyError::InvalidPartBase64 { index: 1, reason })?;
let payload = base64url_decode(payload)
.map_err(|reason| VerifyError::InvalidPartBase64 { index: 2, reason })?;
let signature = base64url_decode(signature)
.map_err(|reason| VerifyError::InvalidPartBase64 { index: 3, reason })?;
#[derive(Deserialize)]
struct Header {
kid: String,
}
let header: Header = serde_json::from_slice(&header).map_err(VerifyError::InvalidHeaderJson)?;
let matched_keys: Vec<&jwk::Key> = keys
.into_iter()
.filter(|key| key.kid == header.kid)
.collect();
if matched_keys.len() != 1 {
return Err(VerifyError::KidNotMatched { kid: header.kid });
}
let key = matched_keys.first().unwrap();
match key.data {
jwk::KeyData::Okp(jwk::OkpKey {
alg: jwk::OkpAlg::EdDsa,
crv: jwk::OkpCurve::Ed25519,
ref x,
}) => {
signature::UnparsedPublicKey::new(&signature::ED25519, x)
.verify(message, &signature)
.map_err(|_err| VerifyError::BadSignature)?;
}
jwk::KeyData::Rsa(jwk::RsaKey {
alg: jwk::RsaAlg::Rs256,
ref n,
ref e,
}) => {
signature::RsaPublicKeyComponents { n, e }
.verify(&signature::RSA_PKCS1_2048_8192_SHA256, message, &signature)
.map_err(|_err| VerifyError::BadSignature)?;
}
_ => return Err(VerifyError::UnsupportedKeyType),
}
Ok(payload)
}