jwt_rustcrypto/
encode.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
use crate::{sign_es, sign_hmac, sign_rsa, Algorithm, Error, Header, SigningKey};
use base64::Engine;
use serde::Serialize;
use serde_json::Value as JsonValue;

/// Encodes a JWT using the provided header, signing key, and payload.
/// Returns the encoded JWT.
/// # Arguments
/// * `header` - The header of the JWT.
/// * `signing_key` - The signing key used to sign the JWT.
/// * `payload` - The payload of the JWT.
/// # Example
/// ```
/// use jwt_rustcrypto::{encode, Algorithm, Header, SigningKey};
/// use serde_json::json;
///
/// let header = Header::new(Algorithm::HS256);
/// let signing_key = SigningKey::from_secret(b"mysecret");
/// let payload = json!({ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 });
///
/// let encoded = encode(&header, &signing_key, &payload);
/// assert!(encoded.is_ok());
/// ```
pub fn encode<T: Serialize>(
    header: &Header,
    signing_key: &SigningKey,
    payload: &T,
) -> Result<String, Error> {
    let header_json = serde_json::to_value(header)?;
    let payload_json = serde_json::to_value(payload)?;
    let signing_input = get_signing_input(&payload_json, &header_json)?;

    let signature = match header.alg {
        Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => {
            sign_hmac(&signing_input, signing_key, &header.alg)
        }
        Algorithm::RS256
        | Algorithm::RS384
        | Algorithm::RS512
        | Algorithm::PS256
        | Algorithm::PS384
        | Algorithm::PS512 => sign_rsa(&signing_input, signing_key, &header.alg),
        Algorithm::ES256 | Algorithm::ES256K | Algorithm::ES384 | Algorithm::ES512 => {
            sign_es(&signing_input, signing_key, &header.alg)
        }
    }?;

    Ok(format!("{}.{}", signing_input, signature))
}

fn get_signing_input(payload: &JsonValue, header: &JsonValue) -> Result<String, Error> {
    let header_str = serde_json::to_string(header)?;
    let payload_str = serde_json::to_string(payload)?;
    let signing_input = format!(
        "{}.{}",
        base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(header_str.as_bytes()),
        base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(payload_str.as_bytes())
    );
    Ok(signing_input)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::Algorithm;
    use serde_json::json;
    use std::fs;
    use std::path::Path;

    const TEST_KEYS_DIR: &str = "tests/keys";

    fn load_key(file_name: &str) -> String {
        let path = Path::new(TEST_KEYS_DIR).join(file_name);
        fs::read_to_string(path).expect("Failed to read key file")
    }

    #[test]
    fn test_get_signing_input_valid_input() {
        let header = json!({ "alg": "HS256", "typ": "JWT" });
        let payload = json!({ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 });

        let result = get_signing_input(&payload, &header);
        assert!(result.is_ok());
        assert!(result.unwrap().contains('.'));
    }

    #[test]
    fn test_sign_rsa_invalid_digest() {
        let signing_key =
            SigningKey::from_rsa_pem(load_key("rsa_private_key_pkcs8.pem").as_bytes()).unwrap();
        let invalid_alg = Algorithm::ES512; // ES algorithms not supported in RSA signing

        let result = sign_rsa("data", &signing_key, &invalid_alg);
        assert!(result.is_err());
        assert_eq!(result.unwrap_err().to_string(), "Unsupported algorithm");
    }
}