Skip to main content

jwt_simple/algorithms/jwe/
rsa_oaep.rs

1//! RSA-OAEP key management algorithm for JWE.
2//!
3//! Implements RSA-OAEP (RSA with OAEP using SHA-1).
4//!
5//! Note: RSA-OAEP-256 (with SHA-256) is not currently supported because the underlying
6//! boring/superboring crates do not expose the API to specify the OAEP hash function.
7
8#[cfg(any(feature = "pure-rust", target_arch = "wasm32", target_arch = "wasm64"))]
9use superboring as boring;
10
11use boring::pkey::{Private, Public};
12use boring::rsa::{Padding, Rsa};
13use serde::{de::DeserializeOwned, Serialize};
14
15use crate::claims::*;
16use crate::error::*;
17use crate::jwe_header::JWEHeader;
18use crate::jwe_token::{DecryptionOptions, EncryptionOptions, JWEToken, JWETokenMetadata};
19
20const MIN_RSA_MODULUS_BITS: i32 = 2048;
21
22fn validate_modulus_bits(bits: i32) -> Result<(), Error> {
23    ensure!(bits >= MIN_RSA_MODULUS_BITS, JWTError::WeakKey);
24    Ok(())
25}
26
27/// RSA public key for encryption (RSA-OAEP with SHA-1).
28#[derive(Debug, Clone)]
29pub struct RsaOaepEncryptionKey {
30    pk: Rsa<Public>,
31    key_id: Option<String>,
32}
33
34impl RsaOaepEncryptionKey {
35    /// Create an encryption key from a DER-encoded public key.
36    pub fn from_der(der: &[u8]) -> Result<Self, Error> {
37        let pk = Rsa::<Public>::public_key_from_der(der)
38            .or_else(|_| Rsa::<Public>::public_key_from_der_pkcs1(der))?;
39        validate_modulus_bits(pk.n().num_bits())?;
40        Ok(RsaOaepEncryptionKey { pk, key_id: None })
41    }
42
43    /// Create an encryption key from a PEM-encoded public key.
44    pub fn from_pem(pem: &str) -> Result<Self, Error> {
45        let pem = pem.trim();
46        let pk = Rsa::<Public>::public_key_from_pem(pem.as_bytes())
47            .or_else(|_| Rsa::<Public>::public_key_from_pem_pkcs1(pem.as_bytes()))?;
48        validate_modulus_bits(pk.n().num_bits())?;
49        Ok(RsaOaepEncryptionKey { pk, key_id: None })
50    }
51
52    /// Export the key as DER.
53    pub fn to_der(&self) -> Result<Vec<u8>, Error> {
54        self.pk.public_key_to_der().map_err(Into::into)
55    }
56
57    /// Export the key as PEM.
58    pub fn to_pem(&self) -> Result<String, Error> {
59        let bytes = self.pk.public_key_to_pem()?;
60        Ok(String::from_utf8(bytes)?)
61    }
62
63    /// Set the key ID.
64    pub fn with_key_id(mut self, key_id: impl Into<String>) -> Self {
65        self.key_id = Some(key_id.into());
66        self
67    }
68
69    /// Get the key ID.
70    pub fn key_id(&self) -> Option<&str> {
71        self.key_id.as_deref()
72    }
73
74    fn wrap_key(&self, cek: &[u8]) -> Result<Vec<u8>, Error> {
75        let mut encrypted = vec![0u8; self.pk.size() as usize];
76        let encrypted_len = self
77            .pk
78            .public_encrypt(cek, &mut encrypted, Padding::PKCS1_OAEP)
79            .map_err(|_| JWTError::InvalidEncryptionKey)?;
80        encrypted.truncate(encrypted_len);
81
82        Ok(encrypted)
83    }
84
85    /// Encrypt claims into a JWE token.
86    pub fn encrypt<CustomClaims: Serialize>(
87        &self,
88        claims: JWTClaims<CustomClaims>,
89    ) -> Result<String, Error> {
90        self.encrypt_with_options(claims, &EncryptionOptions::default())
91    }
92
93    /// Encrypt claims into a JWE token with options.
94    pub fn encrypt_with_options<CustomClaims: Serialize>(
95        &self,
96        claims: JWTClaims<CustomClaims>,
97        options: &EncryptionOptions,
98    ) -> Result<String, Error> {
99        let content_encryption = options.content_encryption;
100        let mut header = JWEHeader::new("RSA-OAEP", content_encryption.alg_name());
101
102        if let Some(key_id) = &self.key_id {
103            header.key_id = Some(key_id.clone());
104        }
105        if let Some(key_id) = &options.key_id {
106            header.key_id = Some(key_id.clone());
107        }
108        if let Some(cty) = &options.content_type {
109            header.content_type = Some(cty.clone());
110        }
111
112        JWEToken::build_from_claims(&header, &claims, content_encryption, |cek| {
113            self.wrap_key(cek)
114        })
115    }
116}
117
118/// RSA key pair for decryption (RSA-OAEP with SHA-1).
119#[derive(Clone)]
120pub struct RsaOaepDecryptionKey {
121    sk: Rsa<Private>,
122    key_id: Option<String>,
123}
124
125impl std::fmt::Debug for RsaOaepDecryptionKey {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        f.debug_struct("RsaOaepDecryptionKey")
128            .field("key_id", &self.key_id)
129            .field("modulus_bits", &self.sk.n().num_bits())
130            .finish_non_exhaustive()
131    }
132}
133
134impl RsaOaepDecryptionKey {
135    /// Create a decryption key from a DER-encoded private key.
136    pub fn from_der(der: &[u8]) -> Result<Self, Error> {
137        let sk = Rsa::<Private>::private_key_from_der(der)?;
138        if !sk.check_key()? {
139            bail!(JWTError::InvalidKeyPair);
140        }
141        validate_modulus_bits(sk.n().num_bits())?;
142        Ok(RsaOaepDecryptionKey { sk, key_id: None })
143    }
144
145    /// Create a decryption key from a PEM-encoded private key.
146    pub fn from_pem(pem: &str) -> Result<Self, Error> {
147        let pem = pem.trim();
148        let sk = Rsa::<Private>::private_key_from_pem(pem.as_bytes())?;
149        if !sk.check_key()? {
150            bail!(JWTError::InvalidKeyPair);
151        }
152        validate_modulus_bits(sk.n().num_bits())?;
153        Ok(RsaOaepDecryptionKey { sk, key_id: None })
154    }
155
156    /// Generate a new RSA key pair.
157    pub fn generate(modulus_bits: usize) -> Result<Self, Error> {
158        match modulus_bits {
159            2048 | 3072 | 4096 => {}
160            _ => bail!(JWTError::UnsupportedRSAModulus),
161        };
162        let sk = Rsa::<Private>::generate(modulus_bits as u32)?;
163        Ok(RsaOaepDecryptionKey { sk, key_id: None })
164    }
165
166    /// Export the private key as DER.
167    pub fn to_der(&self) -> Result<Vec<u8>, Error> {
168        self.sk.private_key_to_der().map_err(Into::into)
169    }
170
171    /// Export the private key as PEM.
172    pub fn to_pem(&self) -> Result<String, Error> {
173        let bytes = self.sk.private_key_to_pem()?;
174        Ok(String::from_utf8(bytes)?)
175    }
176
177    /// Get the public encryption key.
178    pub fn encryption_key(&self) -> RsaOaepEncryptionKey {
179        let pk = Rsa::<Public>::from_public_components(
180            self.sk.n().to_owned().expect("failed to get modulus"),
181            self.sk.e().to_owned().expect("failed to get exponent"),
182        )
183        .expect("failed to create public key");
184        RsaOaepEncryptionKey {
185            pk,
186            key_id: self.key_id.clone(),
187        }
188    }
189
190    /// Set the key ID.
191    pub fn with_key_id(mut self, key_id: impl Into<String>) -> Self {
192        self.key_id = Some(key_id.into());
193        self
194    }
195
196    /// Get the key ID.
197    pub fn key_id(&self) -> Option<&str> {
198        self.key_id.as_deref()
199    }
200
201    fn unwrap_key(&self, encrypted_key: &[u8]) -> Result<Vec<u8>, Error> {
202        let mut cek = vec![0u8; self.sk.size() as usize];
203        let cek_len = self
204            .sk
205            .private_decrypt(encrypted_key, &mut cek, Padding::PKCS1_OAEP)
206            .map_err(|_| JWTError::KeyUnwrapFailed)?;
207        cek.truncate(cek_len);
208
209        Ok(cek)
210    }
211
212    /// Encrypt claims into a JWE token.
213    pub fn encrypt<CustomClaims: Serialize>(
214        &self,
215        claims: JWTClaims<CustomClaims>,
216    ) -> Result<String, Error> {
217        self.encryption_key().encrypt(claims)
218    }
219
220    /// Encrypt claims into a JWE token with options.
221    pub fn encrypt_with_options<CustomClaims: Serialize>(
222        &self,
223        claims: JWTClaims<CustomClaims>,
224        options: &EncryptionOptions,
225    ) -> Result<String, Error> {
226        self.encryption_key().encrypt_with_options(claims, options)
227    }
228
229    /// Decrypt a JWE token and return the claims.
230    pub fn decrypt_token<CustomClaims: DeserializeOwned>(
231        &self,
232        token: &str,
233        options: Option<DecryptionOptions>,
234    ) -> Result<JWTClaims<CustomClaims>, Error> {
235        JWEToken::decrypt("RSA-OAEP", token, options, |_header, encrypted_key| {
236            self.unwrap_key(encrypted_key)
237        })
238    }
239
240    /// Decode token metadata without decrypting.
241    pub fn decode_metadata(token: &str) -> Result<JWETokenMetadata, Error> {
242        JWEToken::decode_metadata(token)
243    }
244}