pdk-jwt-lib 1.10.0

PDK JWT Library
Documentation
// Copyright (c) 2026, Salesforce, Inc.,
// All rights reserved.
// For full license text, see the LICENSE.txt file

//! JWT signature validation
//!
//! This module provides JWT signature validation functionality supporting multiple
//! algorithms including HMAC, RSA, and ES. It validates JWT tokens and
//! extracts claims when the signature is valid.
//!
//! ## Primary types
//!
//! - [`SignatureValidation`]: trait for signature validation implementations
//! - [`SignatureValidator`]: main validator that creates algorithm-specific validators
//!

use crate::{
    error::jwt_error::JWTError,
    model::{
        claims::JWTClaims,
        signing_algorithm::{SigningAlgorithm, SigningKeyLength},
    },
};
use std::{cell::RefCell, rc::Rc};

/// Trait that all signature algorithm validators should implement.
pub trait SignatureValidation {
    /// Validates whether a JWT token (passed in [`String`] format) is valid.
    /// If the token is valid, the result is the content of the JWT extracted into a [`JWTClaims`].
    /// When not valid, it results in a [`JWTError`].
    fn validate(&self, token: String) -> Result<JWTClaims, JWTError>;
}

/// Main signature validator implementation. It receives the specifications of the signature check:
///   * Algorithm
///   * Key Length
///   * Verifying Key (RSA, ES) or Signing Secret (HMAC)
///
/// And it generates the corresponding specific validator for the use case.
pub struct SignatureValidator {
    validator: Rc<RefCell<dyn SignatureValidation>>,
}

impl SignatureValidator {
    /// Create a new [`SignatureValidator`] from the given algorithm, key length and key.
    pub fn new(
        algorithm: SigningAlgorithm,
        key_length: SigningKeyLength,
        key: String,
    ) -> Result<Self, JWTError> {
        #[cfg(any(feature = "fips", fips))]
        {
            Ok(SignatureValidator {
                validator: Rc::new(RefCell::new(
                    super::proxy_wasm_validator::ProxyWasmSignatureValidator::new(
                        algorithm,
                        key_length,
                        super::proxy_wasm_validator::Key::Pem(key),
                    )?,
                )),
            })
        }

        #[cfg(not(any(feature = "fips", fips)))]
        match algorithm {
            SigningAlgorithm::Rsa => Ok(SignatureValidator {
                validator: Rc::new(RefCell::new(
                    super::rsa_validator::RsaSignatureValidator::new(key_length, key)?,
                )),
            }),
            SigningAlgorithm::Es => match key_length {
                SigningKeyLength::Len256 => Ok(SignatureValidator {
                    validator: Rc::new(RefCell::new(super::es_validator::EsSignatureValidator::<
                        p256::NistP256,
                    >::new(key_length, key)?)),
                }),
                SigningKeyLength::Len384 => Ok(SignatureValidator {
                    validator: Rc::new(RefCell::new(super::es_validator::EsSignatureValidator::<
                        p384::NistP384,
                    >::new(key_length, key)?)),
                }),
                _ => Err(JWTError::InvalidKeyLength(
                    "Signing key length is invalid for ES algorithm".to_string(),
                )),
            },
            SigningAlgorithm::Hmac => Ok(SignatureValidator {
                validator: Rc::new(RefCell::new(
                    super::hmac_validator::HmacSignatureValidator::new(key_length, key),
                )),
            }),
        }
    }

    /// Validate a JWT token.
    pub fn validate(&self, token: String) -> Result<JWTClaims, JWTError> {
        self.validator.borrow().validate(token)
    }
}

#[cfg(test)]
pub mod tests {
    use crate::error::jwt_error::{JWTError, JWTErrorEnumKind};

    // Helper to perform assertions over the error handling in different SignatureValidation implementators
    pub fn assert_error_kind<T>(validation: &Result<T, JWTError>, kind: JWTErrorEnumKind)
    where
        T: std::fmt::Debug,
    {
        assert!(validation.is_err());
        assert_eq!(
            JWTErrorEnumKind::from(validation.as_ref().unwrap_err()),
            kind
        );
    }

    #[cfg(not(any(feature = "fips", fips)))]
    mod signature_validator_test {
        use crate::model::signing_algorithm::{SigningAlgorithm, SigningKeyLength};
        use crate::validator::signature_validator::SignatureValidator;

        const RSA_PUBLIC_KEY_PEM_256: &str = "-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnFRZJoSbY3lJmruwlXQM
wiOlr4EtBJHSj7IrCYqx9rFEW+JUwoe5iMZl33D6tUsr1GsG50R7JL2KvxHrj0u6
A3A9beI41XdRfJatugaffroLuZCUYla097uVSQacUSBfBVEsT0TU9MrxBW0HGIXz
dGwuA9GRncgk7kcfPgJLjwzcBBZATVaASO28Rrf6eQ12w0UNJL4XM+/yPyQatbvO
D5KqX8ue2a0uO+7yE8x0A99UMIHYo1zedp+Qr+p0+/5VTWjEZT5I6JGUIMjSVqn0
L0z0WMKJtSdG/CQpZyGnWBz6irOd5xJHAyH7mr02dOJVSXMJXvchCEMMlWghqlqQ
XwIDAQAB
-----END PUBLIC KEY-----";

        #[test]
        fn validation_success() {
            let token: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.e30.AC3VWMTRNDUh2S2MbT3SSzVg4P7BykOCWYSlMRYgh7xUGo4lisfjaTKpqydtoouB-mJaWRtq09rnP1BG1IMLXxPjYNPsVbTvcpktifS_qAnzjLtNpki_a7H1Uxey7mPn6MQf0bzD4inYb_zz7OgPb6U57l8B7kHKhA7vS6-a56zOxDnrZm2pwtNhkoXWBfQ6LiRWjfPOQUED3ep3YZ-BQR5sT3KUNV_nrgi6Qhc1wPOoTbEaLbjCQjnML6jxpJxpXKL2zL8ARHy-avxqtwCiWW3LQ-DQhneEVYzZ79q-8FKD8GcII9g3MCX5MZVmpYS3Z3WPP1Xzb4IVtUUYK6dogg";
            let signature_validator = SignatureValidator::new(
                SigningAlgorithm::Rsa,
                SigningKeyLength::Len256,
                RSA_PUBLIC_KEY_PEM_256.to_string(),
            )
            .unwrap();

            let validation = signature_validator.validate(token.to_string());

            assert!(validation.is_ok());
        }
    }
}