use std::convert::TryFrom;
use crate::{
api::model::{JWTClaims, SigningAlgorithm, SigningKeyLength},
error::jwt_error::JWTError,
model::{
claims::CustomClaims, claims::TryFromUntrustedJWTCompactClaims,
untrusted_token::untrusted_jwt_compact::UntrustedJWT,
},
};
use jwt_compact::{Claims, UntrustedToken};
use pdk_core::ffi::{self, Message, ProtobufError};
use super::signature_validator::SignatureValidation;
mod hostcall {
#[cfg_attr(test, faux::create)]
#[derive(Default)]
pub struct ProxyWasmCall;
#[cfg_attr(test, faux::methods)]
impl ProxyWasmCall {
pub fn call(
&self,
function_name: &str,
arguments: Option<&[u8]>,
) -> Result<
Option<pdk_core::classy::proxy_wasm::types::Bytes>,
pdk_core::classy::proxy_wasm::types::Status,
> {
pdk_core::classy::proxy_wasm::hostcalls::call_foreign_function(function_name, arguments)
}
}
}
pub struct ProxyWasmSignatureValidator {
algorithm: SigningAlgorithm,
key_length: SigningKeyLength,
pk_der: Vec<u8>,
ffi: hostcall::ProxyWasmCall,
}
pub enum Key {
Pem(String),
Der(Vec<u8>),
HexEncodedDer(String),
}
impl ProxyWasmSignatureValidator {
fn new_with_ffi(
algorithm: SigningAlgorithm,
key_length: SigningKeyLength,
key: Key,
ffi: hostcall::ProxyWasmCall,
) -> Result<Self, JWTError> {
let pk_der = match key {
Key::Pem(pem) => match algorithm {
SigningAlgorithm::Es => crate::parser::es_pk_parser::parse_pem(pem)?
.contents()
.to_vec(),
SigningAlgorithm::Rsa => crate::parser::rsa_pk_parser::parse_pem(pem)
.ok_or(JWTError::SigningKeyLengthParseFailed)?
.contents()
.to_vec(),
},
Key::HexEncodedDer(encoded) => const_hex::decode(encoded)
.map_err(|_| JWTError::SigningKeyLengthParseFailed)?
.to_vec(),
Key::Der(der) => der,
};
Ok(Self {
algorithm,
key_length,
pk_der,
ffi,
})
}
pub fn new(
algorithm: SigningAlgorithm,
key_length: SigningKeyLength,
key: Key,
) -> Result<Self, JWTError> {
Self::new_with_ffi(
algorithm,
key_length,
key,
hostcall::ProxyWasmCall::default(),
)
}
}
impl SignatureValidation for ProxyWasmSignatureValidator {
fn validate(&self, token: String) -> Result<JWTClaims, JWTError> {
let data = token
.rsplit_once('.')
.ok_or(JWTError::BearerMalformed)?
.0
.as_bytes();
let untrusted_token: UntrustedToken = UntrustedJWT::new(&token)?.into();
let signature = if let SigningAlgorithm::Es = self.algorithm {
let signature = untrusted_token.signature_bytes();
match self.key_length {
SigningKeyLength::Len256 => p256::ecdsa::Signature::try_from(signature)
.map_err(|e| JWTError::ValidationFailed(e.to_string()))?
.to_der()
.to_bytes()
.to_vec(),
SigningKeyLength::Len384 => p384::ecdsa::Signature::try_from(signature)
.map_err(|e| JWTError::ValidationFailed(e.to_string()))?
.to_der()
.to_bytes()
.to_vec(),
SigningKeyLength::Len512 => p521::ecdsa::Signature::try_from(signature)
.map_err(|e| JWTError::ValidationFailed(e.to_string()))?
.to_der()
.to_bytes()
.to_vec(),
}
} else {
untrusted_token.signature_bytes().to_vec()
};
let args = ffi::VerifySignatureArguments {
hash_function: match self.key_length {
SigningKeyLength::Len256 => "sha256",
SigningKeyLength::Len384 => "sha384",
SigningKeyLength::Len512 => "sha512",
}
.to_string(),
signature,
public_key: self.pk_der.clone(),
text: data.to_vec(),
..Default::default()
};
let args = args.write_to_bytes()?;
match self.ffi.call("verify_signature", Some(&args)) {
Ok(Some(bytes)) => match ffi::VerifySignatureResult::parse_from_bytes(&bytes) {
Ok(result) if result.result => {
let claims: Claims<CustomClaims> =
untrusted_token.deserialize_claims_unchecked()?;
let claims = JWTClaims::try_from_untrusted_jwt_compact_claims(claims, token)?;
Ok(claims)
}
Err(_) => Err(JWTError::ValidationFailed(
"Invalid deserialization".to_string(),
)),
Ok(_) => Err(JWTError::ValidationFailed("Invalid token".to_string())),
},
Ok(None) => Err(JWTError::ValidationFailed("Invalid token".to_string())),
Err(_) => Err(JWTError::ValidationFailed(
"Invalid serialization".to_string(),
)),
}
}
}
impl From<ProtobufError> for JWTError {
fn from(value: ProtobufError) -> Self {
Self::ValidationFailed(value.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
use pdk_core::ffi::{Message, VerifySignatureResult};
#[test]
fn hex_encoded() {
let encoded_der = "30819b301006072a8648ce3d020106052b8104002303818600040181ce07673fbf7c16c2ee5984c30d003b734af705593c36745449ec69414b118a53cdbf749c84ed25470f51bbceb1d64867543c7e76eab80620c572f95201cca32c0089be3be8c932c9880f93405766ade6ab8cc899344db9a53db258fd4d527749c4f72f56d7c15b8f243ce9af918e1696a3f96c0c025f06ec2ab0a093ac5bb29d767b";
let token = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImV4cCI6MTcxNjIzOTAyMiwicm9sZSI6Ik1lbWJlciJ9.ASFo8KygRitPeJw2HG_xJct9VXGupGriBzn9AV0W3jsApPhTVZFVFj5s-P-KaoJ2-39_CbMTas0mCF08jyy68J4VAB9h1fDWwIDqofWUWGnPr2NJABsK3ecUOE0j6g1AXjWZaEDHeDxoE4eMqnMn3H7xJGfITDWXHNR6fvbpCsX_aYsQ".to_string();
let mut mock = hostcall::ProxyWasmCall::faux();
faux::when!(mock.call("verify_signature", _)).then(|(_, _)| {
let result = VerifySignatureResult {
result: true,
..Default::default()
};
Ok(Some(result.write_to_bytes().unwrap()))
});
let validator = ProxyWasmSignatureValidator::new_with_ffi(
SigningAlgorithm::Es,
SigningKeyLength::Len512,
Key::HexEncodedDer(encoded_der.to_string()),
mock,
)
.unwrap();
assert!(validator.validate(token).is_ok());
}
#[test]
fn der() {
let encoded_der = "30819b301006072a8648ce3d020106052b8104002303818600040181ce07673fbf7c16c2ee5984c30d003b734af705593c36745449ec69414b118a53cdbf749c84ed25470f51bbceb1d64867543c7e76eab80620c572f95201cca32c0089be3be8c932c9880f93405766ade6ab8cc899344db9a53db258fd4d527749c4f72f56d7c15b8f243ce9af918e1696a3f96c0c025f06ec2ab0a093ac5bb29d767b";
let token = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImV4cCI6MTcxNjIzOTAyMiwicm9sZSI6Ik1lbWJlciJ9.ASFo8KygRitPeJw2HG_xJct9VXGupGriBzn9AV0W3jsApPhTVZFVFj5s-P-KaoJ2-39_CbMTas0mCF08jyy68J4VAB9h1fDWwIDqofWUWGnPr2NJABsK3ecUOE0j6g1AXjWZaEDHeDxoE4eMqnMn3H7xJGfITDWXHNR6fvbpCsX_aYsQ".to_string();
let mut mock = hostcall::ProxyWasmCall::faux();
faux::when!(mock.call("verify_signature", _)).then(|(_, _)| {
let result = VerifySignatureResult {
result: true,
..Default::default()
};
Ok(Some(result.write_to_bytes().unwrap()))
});
let validator = ProxyWasmSignatureValidator::new_with_ffi(
SigningAlgorithm::Es,
SigningKeyLength::Len512,
Key::Der(const_hex::decode(encoded_der).unwrap()),
mock,
)
.unwrap();
assert!(validator.validate(token).is_ok());
}
#[test]
fn pem() {
let pem = r#"-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ
PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47
6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM
Al8G7CqwoJOsW7Kddns=
-----END PUBLIC KEY-----"#;
let token = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImV4cCI6MTcxNjIzOTAyMiwicm9sZSI6Ik1lbWJlciJ9.ASFo8KygRitPeJw2HG_xJct9VXGupGriBzn9AV0W3jsApPhTVZFVFj5s-P-KaoJ2-39_CbMTas0mCF08jyy68J4VAB9h1fDWwIDqofWUWGnPr2NJABsK3ecUOE0j6g1AXjWZaEDHeDxoE4eMqnMn3H7xJGfITDWXHNR6fvbpCsX_aYsQ".to_string();
let mut mock = hostcall::ProxyWasmCall::faux();
faux::when!(mock.call("verify_signature", _)).then(|(_, _)| {
let result = VerifySignatureResult {
result: true,
..Default::default()
};
Ok(Some(result.write_to_bytes().unwrap()))
});
let validator = ProxyWasmSignatureValidator::new_with_ffi(
SigningAlgorithm::Es,
SigningKeyLength::Len512,
Key::Pem(pem.to_string()),
mock,
)
.unwrap();
assert!(validator.validate(token).is_ok());
}
#[test]
fn invalid_token() {
let pem = r#"-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ
PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47
6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM
Al8G7CqwoJOsW7Kddns=
-----END PUBLIC KEY-----"#;
let token = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImV4cCI6MTcxNjIzOTAyMiwicm9sZSI6Ik1lbWJlciJ9.ASFo8KygRitPeJw2HG_xJct9VXGupGriBzn9AV0W3jsApPhTVZFVFj5s-P-KaoJ2-39_CbMTas0mCF08jyy68J4VAB9h1fDWwIDqofWUWGnPr2NJABsK3ecUOE0j6g1AXjWZaEDHeDxoE4eMqnMn3H7xJGfITDWXHNR6fvbpCsX_aYsQ".to_string();
let mut mock = hostcall::ProxyWasmCall::faux();
faux::when!(mock.call("verify_signature", _)).then(|(_, _)| {
let result = VerifySignatureResult {
result: false,
..Default::default()
};
Ok(Some(result.write_to_bytes().unwrap()))
});
let validator = ProxyWasmSignatureValidator::new_with_ffi(
SigningAlgorithm::Es,
SigningKeyLength::Len512,
Key::Pem(pem.to_string()),
mock,
)
.unwrap();
assert!(validator.validate(token).is_err());
}
#[test]
fn malformed_token() {
let pem = r#"-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ
PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47
6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM
Al8G7CqwoJOsW7Kddns=
-----END PUBLIC KEY-----"#;
let token = "malformed".to_string();
let mut mock = hostcall::ProxyWasmCall::faux();
faux::when!(mock.call("verify_signature", _)).then(|(_, _)| {
let result = VerifySignatureResult {
result: true,
..Default::default()
};
Ok(Some(result.write_to_bytes().unwrap()))
});
let validator = ProxyWasmSignatureValidator::new_with_ffi(
SigningAlgorithm::Es,
SigningKeyLength::Len512,
Key::Pem(pem.to_string()),
mock,
)
.unwrap();
assert!(validator.validate(token).is_err());
}
}