001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2025, Connect2id Ltd.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.jose.crypto;
019
020
021import com.nimbusds.jose.*;
022import com.nimbusds.jose.crypto.impl.*;
023import com.nimbusds.jose.crypto.opts.CipherMode;
024import com.nimbusds.jose.jwk.RSAKey;
025import com.nimbusds.jose.util.Base64URL;
026import net.jcip.annotations.ThreadSafe;
027
028import javax.crypto.SecretKey;
029import java.security.interfaces.RSAPublicKey;
030import java.util.Collections;
031import java.util.Objects;
032import java.util.Set;
033
034
035/**
036 * RSA encrypter of {@link com.nimbusds.jose.JWEObject JWE objects}. Expects a
037 * public RSA key.
038 *
039 * <p>Encrypts the plain text with a generated AES key (the Content Encryption
040 * Key) according to the specified JOSE encryption method, then encrypts the
041 * CEK with the public RSA key and returns it alongside the IV, cipher text and
042 * authentication tag. See RFC 7518, sections
043 * <a href="https://tools.ietf.org/html/rfc7518#section-4.2">4.2</a> and
044 * <a href="https://tools.ietf.org/html/rfc7518#section-4.3">4.3</a> for more
045 * information.
046 *
047 * <p>This class is thread-safe.
048 *
049 * <p>Supports the following key management algorithms:
050 *
051 * <ul>
052 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP_256}
053 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP_384}
054 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP_512}
055 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP} (deprecated)
056 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA1_5} (deprecated)
057 * </ul>
058 *
059 * <p>Supports the following content encryption algorithms:
060 *
061 * <ul>
062 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256}
063 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384}
064 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512}
065 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM}
066 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192GCM}
067 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM}
068 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256_DEPRECATED}
069 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512_DEPRECATED}
070 *     <li>{@link com.nimbusds.jose.EncryptionMethod#XC20P}
071 * </ul>
072 *
073 * @author David Ortiz
074 * @author Vladimir Dzhuvinov
075 * @author Jun Yu
076 * @author Egor Puzanov
077 * @version 2025-07-19
078 */
079@ThreadSafe
080public class RSAEncrypter extends RSACryptoProvider implements JWEEncrypter {
081
082
083        /**
084         * The public RSA key.
085         */
086        private final RSAPublicKey publicKey;
087
088
089        /**
090         * The configured options, empty set if none.
091         */
092        private final Set<JWEEncrypterOption> opts;
093
094        
095        /**
096         * Creates a new RSA encrypter.
097         *
098         * @param publicKey The public RSA key. Must not be {@code null}.
099         */
100        public RSAEncrypter(final RSAPublicKey publicKey) {
101
102                this(publicKey, null);
103        }
104
105
106        /**
107         * Creates a new RSA encrypter.
108         *
109         *  @param rsaJWK The RSA JSON Web Key (JWK). Must not be {@code null}.
110         *
111         * @throws JOSEException If the RSA JWK extraction failed.
112         */
113        public RSAEncrypter(final RSAKey rsaJWK)
114                throws JOSEException {
115
116                this(rsaJWK.toRSAPublicKey());
117        }
118
119
120        /**
121         * Creates a new RSA encrypter with an optionally specified content
122         * encryption key (CEK).
123         *
124         * @param publicKey            The public RSA key. Must not be
125         *                             {@code null}.
126         * @param contentEncryptionKey The content encryption key (CEK) to use.
127         *                             If specified its algorithm must be "AES"
128         *                             or "ChaCha20" and its length must match
129         *                             the expected for the JWE encryption
130         *                             method ("enc"). If {@code null} a CEK
131         *                             will be generated for each JWE.
132         */
133        public RSAEncrypter(final RSAPublicKey publicKey, final SecretKey contentEncryptionKey) {
134
135                this(publicKey, contentEncryptionKey, Collections.<JWEEncrypterOption>emptySet());
136        }
137
138
139        /**
140         * Creates a new RSA encrypter with an optionally specified content
141         * encryption key (CEK).
142         *
143         * @param publicKey            The public RSA key. Must not be
144         *                             {@code null}.
145         * @param contentEncryptionKey The content encryption key (CEK) to use.
146         *                             If specified its algorithm must be "AES"
147         *                             or "ChaCha20" and its length must match
148         *                             the expected for the JWE encryption
149         *                             method ("enc"). If {@code null} a CEK
150         *                             will be generated for each JWE.
151         * @param opts                 The encryption options, empty or
152         *                             {@code null} if none.
153         */
154        public RSAEncrypter(final RSAPublicKey publicKey,
155                            final SecretKey contentEncryptionKey,
156                            final Set<JWEEncrypterOption> opts) {
157
158                super(contentEncryptionKey);
159                this.publicKey = Objects.requireNonNull(publicKey);
160                this.opts = opts != null ? opts : Collections.<JWEEncrypterOption>emptySet();
161        }
162        
163        
164        /**
165         * Gets the public RSA key.
166         *
167         * @return The public RSA key.
168         */
169        public RSAPublicKey getPublicKey() {
170                
171                return publicKey;
172        }
173
174
175        /**
176         * Encrypts the specified clear text of a {@link JWEObject JWE object}.
177         *
178         * @param header    The JSON Web Encryption (JWE) header. Must specify
179         *                  a supported JWE algorithm and method. Must not be
180         *                  {@code null}.
181         * @param clearText The clear text to encrypt. Must not be {@code null}.
182         *
183         * @return The resulting JWE crypto parts.
184         *
185         * @throws JOSEException If the JWE algorithm or method is not
186         *                       supported or if encryption failed for some
187         *                       other internal reason.
188         */
189        @Deprecated
190        public JWECryptoParts encrypt(final JWEHeader header, final byte[] clearText)
191                throws JOSEException {
192
193                return encrypt(header, clearText, AAD.compute(header));
194        }
195
196
197        private CipherMode resolveCipherModeForOAEP() {
198
199                if (opts.contains(CipherMode.ENCRYPT_DECRYPT)) {
200                        return CipherMode.ENCRYPT_DECRYPT;
201                } else {
202                        // The default cipher mode for RSA OAEP
203                        return CipherMode.WRAP_UNWRAP;
204                }
205                // The contains ENCRYPT_DECRYPT / WRAP_UNWRAP is not checked
206        }
207
208
209        @Override
210        public JWECryptoParts encrypt(final JWEHeader header, final byte[] clearText, final byte[] aad)
211                throws JOSEException {
212
213                final JWEAlgorithm alg = JWEHeaderValidation.getAlgorithmAndEnsureNotNull(header);
214                final EncryptionMethod enc = header.getEncryptionMethod();
215                final SecretKey cek = getCEK(enc); // Generate and encrypt the CEK according to the enc method
216
217                final Base64URL encryptedKey; // The second JWE part
218
219                if (alg.equals(JWEAlgorithm.RSA1_5)) {
220                        encryptedKey = Base64URL.encode(RSA1_5.encryptCEK(publicKey, cek, getJCAContext().getKeyEncryptionProvider()));
221                } else if (alg.equals(JWEAlgorithm.RSA_OAEP)) {
222                        encryptedKey = Base64URL.encode(RSA_OAEP.encryptCEK(publicKey, cek, resolveCipherModeForOAEP(), getJCAContext().getKeyEncryptionProvider()));
223                } else if (alg.equals(JWEAlgorithm.RSA_OAEP_256)) {
224                        encryptedKey = Base64URL.encode(RSA_OAEP_SHA2.encryptCEK(publicKey, cek, 256, resolveCipherModeForOAEP(), getJCAContext().getKeyEncryptionProvider()));
225                } else if (alg.equals(JWEAlgorithm.RSA_OAEP_384)) {
226                        encryptedKey = Base64URL.encode(RSA_OAEP_SHA2.encryptCEK(publicKey, cek, 384, resolveCipherModeForOAEP(), getJCAContext().getKeyEncryptionProvider()));
227                } else if (alg.equals(JWEAlgorithm.RSA_OAEP_512)) {
228                        encryptedKey = Base64URL.encode(RSA_OAEP_SHA2.encryptCEK(publicKey, cek, 512, resolveCipherModeForOAEP(), getJCAContext().getKeyEncryptionProvider()));
229                } else {
230                        throw new JOSEException(AlgorithmSupportMessage.unsupportedJWEAlgorithm(alg, SUPPORTED_ALGORITHMS));
231                }
232
233                return ContentCryptoProvider.encrypt(header, clearText, aad, cek, encryptedKey, getJCAContext());
234        }
235}