001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2016, 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 java.security.interfaces.RSAPublicKey;
022import java.util.Arrays;
023import java.util.Collections;
024import java.util.HashSet;
025import java.util.Set;
026import javax.crypto.SecretKey;
027
028import com.nimbusds.jose.crypto.impl.*;
029import net.jcip.annotations.ThreadSafe;
030
031import com.nimbusds.jose.EncryptionMethod;
032import com.nimbusds.jose.JOSEException;
033import com.nimbusds.jose.JWEAlgorithm;
034import com.nimbusds.jose.JWECryptoParts;
035import com.nimbusds.jose.JWEEncrypter;
036import com.nimbusds.jose.JWEHeader;
037import com.nimbusds.jose.jwk.RSAKey;
038import com.nimbusds.jose.util.Base64URL;
039
040
041/**
042 * RSA encrypter of {@link com.nimbusds.jose.JWEObject JWE objects}. Expects a
043 * public RSA key.
044 *
045 * <p>Encrypts the plain text with a generated AES key (the Content Encryption
046 * Key) according to the specified JOSE encryption method, then encrypts the
047 * CEK with the public RSA key and returns it alongside the IV, cipher text and
048 * authentication tag. See RFC 7518, sections
049 * <a href="https://tools.ietf.org/html/rfc7518#section-4.2">4.2</a> and
050 * <a href="https://tools.ietf.org/html/rfc7518#section-4.3">4.3</a> for more
051 * information.
052 *
053 * <p>This class is thread-safe.
054 *
055 * <p>Supports the following key management algorithms:
056 *
057 * <ul>
058 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP_256}
059 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP_384}
060 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP_512}
061 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP} (deprecated)
062 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA1_5} (deprecated)
063 * </ul>
064 *
065 * <p>Supports the following content encryption algorithms:
066 *
067 * <ul>
068 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256}
069 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384}
070 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512}
071 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM}
072 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192GCM}
073 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM}
074 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256_DEPRECATED}
075 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512_DEPRECATED}
076 *     <li>{@link com.nimbusds.jose.EncryptionMethod#XC20P}
077 * </ul>
078 *
079 * @author David Ortiz
080 * @author Vladimir Dzhuvinov
081 * @author Jun Yu
082 * @version 2021-09-26
083 */
084@ThreadSafe
085public class RSAEncrypter extends RSACryptoProvider implements JWEEncrypter {
086
087
088        /**
089         * The public RSA key.
090         */
091        private final RSAPublicKey publicKey;
092
093        
094        /**
095         * The externally supplied AES content encryption key (CEK) to use,
096         * {@code null} to generate a CEK for each JWE.
097         */
098        private final SecretKey contentEncryptionKey;
099
100        
101        /**
102         * Creates a new RSA encrypter.
103         *
104         * @param publicKey The public RSA key. Must not be {@code null}.
105         */
106        public RSAEncrypter(final RSAPublicKey publicKey) {
107
108                this(publicKey, null);
109        }
110
111
112        /**
113         * Creates a new RSA encrypter.
114         *
115         *  @param rsaJWK The RSA JSON Web Key (JWK). Must not be {@code null}.
116         *
117         * @throws JOSEException If the RSA JWK extraction failed.
118         */
119        public RSAEncrypter(final RSAKey rsaJWK)
120                throws JOSEException {
121
122                this(rsaJWK.toRSAPublicKey());
123        }
124
125
126        /**
127         * Creates a new RSA encrypter with an optionally specified content
128         * encryption key (CEK).
129         *
130         * @param publicKey            The public RSA key. Must not be
131         *                             {@code null}.
132         * @param contentEncryptionKey The content encryption key (CEK) to use.
133         *                             If specified its algorithm must be "AES"
134         *                             or "ChaCha20" and its length must match
135         *                             the expected for the JWE encryption
136         *                             method ("enc"). If {@code null} a CEK
137         *                             will be generated for each JWE.
138         */
139        public RSAEncrypter(final RSAPublicKey publicKey, final SecretKey contentEncryptionKey) {
140                
141                if (publicKey == null) {
142                        throw new IllegalArgumentException("The public RSA key must not be null");
143                }
144                this.publicKey = publicKey;
145
146                Set<String> acceptableCEKAlgs = Collections.unmodifiableSet(
147                        new HashSet<>(Arrays.asList("AES", "ChaCha20"))
148                );
149                
150                if (contentEncryptionKey != null) {
151                        if (contentEncryptionKey.getAlgorithm() == null || ! acceptableCEKAlgs.contains(contentEncryptionKey.getAlgorithm())) {
152                                throw new IllegalArgumentException("The algorithm of the content encryption key (CEK) must be AES or ChaCha20");
153                        } else {
154                                this.contentEncryptionKey = contentEncryptionKey;
155                        }
156                } else {
157                        this.contentEncryptionKey = null;
158                }
159        }
160        
161        
162        /**
163         * Gets the public RSA key.
164         *
165         * @return The public RSA key.
166         */
167        public RSAPublicKey getPublicKey() {
168                
169                return publicKey;
170        }
171
172
173        @Override
174        public JWECryptoParts encrypt(final JWEHeader header, final byte[] clearText)
175                throws JOSEException {
176
177                final JWEAlgorithm alg = header.getAlgorithm();
178                final EncryptionMethod enc = header.getEncryptionMethod();
179
180                // Generate and encrypt the CEK according to the enc method
181                final SecretKey cek;
182                if (contentEncryptionKey != null) {
183                        // Use externally supplied CEK
184                        cek = contentEncryptionKey;
185                } else {
186                        // Generate and encrypt the CEK according to the enc method
187                        cek = ContentCryptoProvider.generateCEK(enc, getJCAContext().getSecureRandom());
188                }
189
190                final Base64URL encryptedKey; // The second JWE part
191
192                if (alg.equals(JWEAlgorithm.RSA1_5)) {
193                        encryptedKey = Base64URL.encode(RSA1_5.encryptCEK(publicKey, cek, getJCAContext().getKeyEncryptionProvider()));
194                } else if (alg.equals(JWEAlgorithm.RSA_OAEP)) {
195                        encryptedKey = Base64URL.encode(RSA_OAEP.encryptCEK(publicKey, cek, getJCAContext().getKeyEncryptionProvider()));
196                } else if (alg.equals(JWEAlgorithm.RSA_OAEP_256)) {
197                        encryptedKey = Base64URL.encode(RSA_OAEP_SHA2.encryptCEK(publicKey, cek, 256, getJCAContext().getKeyEncryptionProvider()));
198                } else if (alg.equals(JWEAlgorithm.RSA_OAEP_384)) {
199                        encryptedKey = Base64URL.encode(RSA_OAEP_SHA2.encryptCEK(publicKey, cek, 384, getJCAContext().getKeyEncryptionProvider()));
200                } else if (alg.equals(JWEAlgorithm.RSA_OAEP_512)) {
201                        encryptedKey = Base64URL.encode(RSA_OAEP_SHA2.encryptCEK(publicKey, cek, 512, getJCAContext().getKeyEncryptionProvider()));
202                } else {
203                        throw new JOSEException(AlgorithmSupportMessage.unsupportedJWEAlgorithm(alg, SUPPORTED_ALGORITHMS));
204                }
205
206                return ContentCryptoProvider.encrypt(header, clearText, cek, encryptedKey, getJCAContext());
207        }
208}