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

//! # RSA Public Key Parser
//!
//! This module provides parsing functionality for RSA public keys
//! used in RS256 and RS384 JWT signature validation.

use crate::model::pk::PemLike;

#[cfg(not(any(feature = "fips", fips)))]
use crate::error::jwt_error::JWTError;
#[cfg(not(any(feature = "fips", fips)))]
use rsa::{pkcs1::DecodeRsaPublicKey, pkcs8::DecodePublicKey, RsaPublicKey};

#[cfg(not(any(feature = "fips", fips)))]
pub fn parse_rsa_pk(public_key: String) -> Result<RsaPublicKey, JWTError> {
    match parse_pem(public_key) {
        Some(encoded_pem) => match RsaPublicKey::from_public_key_der(encoded_pem.contents()) {
            Ok(result) => Ok(result),
            Err(_) => match RsaPublicKey::from_pkcs1_der(encoded_pem.contents()) {
                Ok(result) => Ok(result),
                Err(_) => Err(JWTError::RsaPublicKeyParseFailed(String::from(
                    "Error parsing RSA public key.",
                ))),
            },
        },
        None => Err(JWTError::RsaPublicKeyParseFailed(String::from(
            "Error parsing RSA public key.",
        ))),
    }
}

pub fn parse_pem(mut public_key: String) -> Option<pem::Pem> {
    if !public_key.has_pkcs1_header() && !public_key.has_pkcs8_header() {
        public_key.complete_to_pkcs8_pem();
    }

    public_key.encoded_pem()
}

#[cfg(test)]
#[cfg(not(any(feature = "fips", fips)))]
mod tests {
    use super::parse_rsa_pk;
    use rsa::{BigUint, RsaPublicKey};
    use std::str::FromStr;
    use test_case::test_case;

    extern crate test_case;

    // To re-generate this key: n=BigUint::from(2u32) e=BigUint::from(3u32)
    const VALID_PUBLIC_KEY_PEM: &str = r#"
-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKUzBgXo/7PlC4XuQOTc2dFwKYHSY8ww
eXHXUChtoG/tK/GrV31I61PUD8tB3k+rZg9qG1OQGllT/T+yDM8osV0CAwEAAQ==
-----END PUBLIC KEY-----
"#;

    const VALID_PUBLIC_KEY_PEM_WITH_NO_NEW_LINES: &str = r#"-----BEGIN PUBLIC KEY-----MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKUzBgXo/7PlC4XuQOTc2dFwKYHSY8ww
eXHXUChtoG/tK/GrV31I61PUD8tB3k+rZg9qG1OQGllT/T+yDM8osV0CAwEAAQ==-----END PUBLIC KEY-----"#;

    const VALID_PUBLIC_KEY_PEM_WITH_ONLY_INTERNAL_NEW_LINES: &str = r#"-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKUzBgXo/7PlC4XuQOTc2dFwKYHSY8ww
eXHXUChtoG/tK/GrV31I61PUD8tB3k+rZg9qG1OQGllT/T+yDM8osV0CAwEAAQ==
-----END PUBLIC KEY-----"#;
    const VALID_PUBLIC_KEY_PEM_WITH_TABS: &str = r#"-----BEGIN PUBLIC KEY-----
    MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKUzBgXo/7PlC4XuQOTc2dFwKYHSY8ww
eXHXUChtoG/tK/GrV31I61PUD8tB3k+rZg9qG1OQGllT/T+yDM8osV0CAwEAAQ==
        -----END PUBLIC KEY-----"#;

    const INCOMPLETE_VALID_PUBLIC_KEY_PEM: &str = r#"
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKUzBgXo/7PlC4XuQOTc2dFwKYHSY8ww
eXHXUChtoG/tK/GrV31I61PUD8tB3k+rZg9qG1OQGllT/T+yDM8osV0CAwEAAQ==
    "#;

    const INCOMPLETE_VALID_PUBLIC_KEY_PEM_WITH_NO_NEW_LINES: &str = r#"MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKUzBgXo/7PlC4XuQOTc2dFwKYHSY8ww
eXHXUChtoG/tK/GrV31I61PUD8tB3k+rZg9qG1OQGllT/T+yDM8osV0CAwEAAQ=="#;

    const INCOMPLETE_VALID_PUBLIC_KEY_PEM_WITH_NEW_LINES_AND_TABS: &str = r#"
    MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKUzBgXo/7PlC4XuQOTc2dFwKYHSY8ww
eXHXUChtoG/tK/GrV31I61PUD8tB3k+rZg9qG1OQGllT/T+yDM8osV0CAwEAAQ==
    "#;

    const INCOMPLETE_VALID_PUBLIC_KEY_PEM_WITH_TABS: &str = r#"    MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKUzBgXo/7PlC4XuQOTc2dFwKYHSY8ww
eXHXUChtoG/tK/GrV31I61PUD8tB3k+rZg9qG1OQGllT/T+yDM8osV0CAwEAAQ==    "#;

    // To re-generate this key see the test below: should_parse_longer_plain_text_pem_correctly
    const VALID_LONGER_PUBLIC_KEY_PEM: &str = r#"
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8+dw6EKuZ9BBi3yuW2EP
H1b26QQwG5IT7oe16kV/5bR10IqYXrMfOg9hgeR2hMm4Nx81iXaET2Piiluk+y4A
WpppndludQpzl9ngRxZhWsuJP80AelbeJBYCZsncY6pHGsVjNeqGuXyUaZ9JTG9t
FkH5sZeIGEMffkpBYlk4TQlKsc11tgG3ThVedJ4GrWZiR365UBr8U4Ddc5e/YrLG
QXYTD+rXI50d8WIHiCVYGfs4XCyp+O2pWYfXM02akp95rC28GlOXyn3/58PcLat+
uxKGW3VbpcrX/GHvB3mhF7Xg9RY54FdU47CszRNcf8/wPIs48MC7HAf60DH+YAt1
tQIDAQAB
-----END PUBLIC KEY-----
"#;

    #[test_case(VALID_PUBLIC_KEY_PEM; "should_parse_pem_correctly")]
    #[test_case(VALID_PUBLIC_KEY_PEM_WITH_NO_NEW_LINES; "should_parse_pem_with_no_new_lines_correctly")]
    #[test_case(VALID_PUBLIC_KEY_PEM_WITH_ONLY_INTERNAL_NEW_LINES; "should_parse_pem_with_only_internal_new_lines_correctly")]
    #[test_case(VALID_PUBLIC_KEY_PEM_WITH_TABS; "should_parse_pem_with_tabs_correctly")]
    fn should_parse_valid_pem_correctly(pem: &str) {
        let parsed_key = parse_rsa_pk(String::from(pem)).unwrap();

        let n = BigUint::from_str(
            "86521899508642643190219074874184953161152558718207214618598059900988711563825627\
        44295420315688750661664821233322032216880673401416115713978144842695881053",
        )
        .unwrap();
        let e = BigUint::from_str("65537").unwrap();
        let pk = RsaPublicKey::new(n, e).unwrap();
        assert_eq!(parsed_key, pk);
    }

    #[test_case(INCOMPLETE_VALID_PUBLIC_KEY_PEM; "should_parse_incomplete_pem_correctly")]
    #[test_case(INCOMPLETE_VALID_PUBLIC_KEY_PEM_WITH_NO_NEW_LINES; "should_parse_incomplete_pem_with_no_new_lines_correctly")]
    #[test_case(INCOMPLETE_VALID_PUBLIC_KEY_PEM_WITH_NEW_LINES_AND_TABS; "should_parse_incomplete_pem_with_new_lines_and_tabs_correctly")]
    #[test_case(INCOMPLETE_VALID_PUBLIC_KEY_PEM_WITH_TABS; "should_parse_incomplete_pem_with_tabs_correctly")]
    fn should_parse_valid_but_incomplete_pem_correctly(pem: &str) {
        let parsed_key = parse_rsa_pk(String::from(pem)).unwrap();

        let n = BigUint::from_str(
            "86521899508642643190219074874184953161152558718207214618598059900988711563825627\
        44295420315688750661664821233322032216880673401416115713978144842695881053",
        )
        .unwrap();
        let e = BigUint::from_str("65537").unwrap();
        let pk = RsaPublicKey::new(n, e).unwrap();
        assert_eq!(parsed_key, pk);
    }

    #[test]
    fn should_parse_longer_plain_text_pem_correctly() {
        let parsed_key = parse_rsa_pk(String::from(VALID_LONGER_PUBLIC_KEY_PEM)).unwrap();

        let n_values: &[u64] = &vec![
            18073000273240618421u64,
            4362643270715251719,
            12730775729709502448,
            16209886871622735075,
            18186079585683183541,
            1334854899168824023,
            18439922549718220475,
            12406779369733212797,
            9788348723511861113,
            4061170072518764889,
            17393472992181754363,
            8508161622339198237,
            15957264752954230337,
            5151758777459692416,
            1539796344672970338,
            5382308935132886862,
            9100157887714970889,
            4754026146047738655,
            10694253927331753238,
            1929057098617239932,
            1585943035959814727,
            9889848708665105956,
            10942023365596109515,
            11126597911582149235,
            16323960563504644186,
            3971952260879241059,
            1108309801550793144,
            8489437684621844282,
            17187906520675771828,
            6266452135728878099,
            4723005722527338271,
            17575140213690099664,
        ];
        let n = BigUint::from_slice_native(n_values);
        let e = BigUint::from(65537u32);
        let pk = RsaPublicKey::new(n, e).unwrap();
        assert_eq!(parsed_key, pk);
    }

    #[test]
    fn should_fail_on_a_bad_formed_public_key() {
        let result = parse_rsa_pk(String::from("i am not a valid key"));
        assert!(result.is_err());
    }

    #[test]
    fn should_fail_on_a_bad_formed_public_key_for_es256() {
        let result = parse_rsa_pk(String::from("i am not a valid key"));
        assert!(result.is_err());
    }

    #[test]
    fn should_fail_on_a_bad_formed_pk_with_header_and_footer() {
        let result = parse_rsa_pk(String::from(
            "-----BEGIN PUBLIC KEY-----i am not a valid key content-----END PUBLIC KEY-----",
        ));
        assert!(result.is_err());
    }

    #[test]
    fn should_fail_on_a_bad_formed_pk_with_header_and_footer_and_b64_encoded() {
        let result = parse_rsa_pk(String::from("-----BEGIN PUBLIC KEY-----aSBhbSBub3QgYSB2YWxpZCBrZXkgY29udGVudCBidXQgYmFzZTY0IGVuY29kZWQ=-----END PUBLIC KEY-----"));
        assert!(result.is_err());
    }
}