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
98
99
100
101
102
103
104
105
106
use base64::engine::general_purpose;
use base64::Engine;
#[warn(unused_imports)] // Trait used by base64::engine::general_purpose
use rsa::traits::PublicKeyParts;
use rsa::RsaPublicKey;
use serde::Serialize;

/// Represents a single JSON Web Key (JWK).
///
/// A JWK is a JSON object that represents a cryptographic key. The
/// members of the object represent properties of the key, including
/// its value and usage.
#[derive(Serialize)]
pub struct Jwk {
    /// The key type parameter defining the cryptographic algorithm family
    /// used with the key, such as RSA or EC.
    pub kty: String,
    /// The intended use of the public key. Commonly used values include
    /// `sig` (for signature) or `enc` (for encryption).
    pub use_: String,
    /// A unique identifier for the key. This can be used to match a specific key.
    pub kid: String,
    /// The RSA public key modulus for the RSA public key represented
    /// as a base64url-encoded string.
    pub n: String,
    /// The RSA public key exponent for the RSA public key represented
    /// as a base64url-encoded string.
    pub e: String,
}

impl Jwk {
    /// Creates a new `Jwk` instance for a given RSA public key.
    ///
    /// This method initializes a JSON Web Key (JWK) with the specified key
    /// identifier (`kid`) and RSA public key. The `kty` field is set to `"RSA"`
    /// to indicate the key type, and the `use_` field is set to `"sig"` to
    /// specify that the key is intended for signing operations. The modulus (`n`)
    /// and exponent (`e`) of the RSA public key are encoded using base64url
    /// without padding, in accordance with the JWK specification.
    ///
    /// # Parameters
    ///
    /// - `kid`: A unique identifier for the key. This identifier is used to
    ///   match a specific key and should be unique within the set of keys in a JWKS.
    /// - `public_key`: A reference to an `RsaPublicKey` that contains the public
    ///   key information to be included in the JWK.
    ///
    /// # Returns
    ///
    /// Returns a `Jwk` instance representing the provided RSA public key.
    ///
    /// # Examples
    ///
    /// ```
    /// use rsa::RsaPublicKey;
    /// use your_crate::Jwk;
    ///
    /// // Assume `public_key` is a valid `RsaPublicKey` instance
    /// let kid = "example_kid";
    /// let jwk = Jwk::new(kid, &public_key);
    ///
    /// assert_eq!(jwk.kid, kid);
    /// assert_eq!(jwk.kty, "RSA");
    /// ```
    pub fn new(kid: &str, public_key: &RsaPublicKey) -> Self {
        Self {
            kty: "RSA".to_string(),
            use_: "sig".to_string(),
            kid: kid.to_string(),
            n: general_purpose::URL_SAFE_NO_PAD.encode(public_key.n().to_bytes_be()),
            e: general_purpose::URL_SAFE_NO_PAD.encode(public_key.e().to_bytes_be()),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use rand::rngs::OsRng;
    use rsa::RsaPrivateKey;

    fn generate_test_rsa_public_key() -> RsaPublicKey {
        let mut rng = OsRng;
        let bits = 2048;
        let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
        private_key.to_public_key()
    }

    #[test]
    fn test_jwk_new_sets_correct_fields() {
        let test_kid = "test_kid";
        let test_public_key = generate_test_rsa_public_key();

        let jwk = Jwk::new(test_kid, &test_public_key);

        assert_eq!(jwk.kty, "RSA");
        assert_eq!(jwk.use_, "sig");
        assert_eq!(jwk.kid, test_kid);

        let n_encoded = general_purpose::URL_SAFE_NO_PAD.encode(test_public_key.n().to_bytes_be());
        let e_encoded = general_purpose::URL_SAFE_NO_PAD.encode(test_public_key.e().to_bytes_be());

        assert_eq!(jwk.n, n_encoded, "Modulus (n) is not correctly encoded.");
        assert_eq!(jwk.e, e_encoded, "Exponent (e) is not correctly encoded.");
    }
}