001package com.nimbusds.jose.crypto;
002
003
004import java.security.SecureRandom;
005import java.security.interfaces.RSAPrivateKey;
006import java.util.HashSet;
007import java.util.Set;
008
009import javax.crypto.SecretKey;
010
011import net.jcip.annotations.ThreadSafe;
012
013import com.nimbusds.jose.EncryptionMethod;
014import com.nimbusds.jose.JOSEException;
015import com.nimbusds.jose.JWEAlgorithm;
016import com.nimbusds.jose.JWEDecrypter;
017import com.nimbusds.jose.JWEHeader;
018import com.nimbusds.jose.util.Base64URL;
019import com.nimbusds.jose.util.StringUtils;
020
021
022/**
023 * RSA decrypter of {@link com.nimbusds.jose.JWEObject JWE objects}. This class
024 * is thread-safe.
025 *
026 * <p>Supports the following JWE algorithms:
027 *
028 * <ul>
029 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA1_5}
030 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP}
031 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP_256}
032 * </ul>
033 *
034 * <p>Supports the following encryption methods:
035 *
036 * <ul>
037 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256}
038 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384}
039 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512}
040 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM}
041 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192GCM}
042 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM}
043 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256_DEPRECATED}
044 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512_DEPRECATED}
045 * </ul>
046 *
047 * <p>Accepts all {@link com.nimbusds.jose.JWEHeader#getRegisteredParameterNames
048 * registered JWE header parameters}. Use {@link #setAcceptedAlgorithms} and
049 * {@link #setAcceptedEncryptionMethods} to restrict the acceptable JWE
050 * algorithms and encryption methods.
051 * 
052 * @author David Ortiz
053 * @author Vladimir Dzhuvinov
054 * @version $version$ (2014-08-20)
055 *
056 */
057@ThreadSafe
058public class RSADecrypter extends RSACryptoProvider implements JWEDecrypter {
059
060
061        /**
062         * The accepted JWE algorithms.
063         */
064        private Set<JWEAlgorithm> acceptedAlgs =
065                new HashSet<>(supportedAlgorithms());
066
067
068        /**
069         * The accepted encryption methods.
070         */
071        private Set<EncryptionMethod> acceptedEncs =
072                new HashSet<>(supportedEncryptionMethods());
073
074
075        /**
076         * The critical header parameter checker.
077         */
078        private final CriticalHeaderParameterChecker critParamChecker =
079                new CriticalHeaderParameterChecker();
080
081
082        /**
083         * The private RSA key.
084         */
085        private final RSAPrivateKey privateKey;
086
087
088        /**
089         * Creates a new RSA decrypter.
090         *
091         * @param privateKey The private RSA key. Must not be {@code null}.
092         */
093        public RSADecrypter(final RSAPrivateKey privateKey) {
094
095                if (privateKey == null) {
096
097                        throw new IllegalArgumentException("The private RSA key must not be null");
098                }
099
100                this.privateKey = privateKey;
101        }
102
103
104        /**
105         * Gets the private RSA key.
106         *
107         * @return The private RSA key.
108         */
109        public RSAPrivateKey getPrivateKey() {
110
111                return privateKey;
112        }
113
114
115        @Override
116        public Set<JWEAlgorithm> getAcceptedAlgorithms() {
117
118                return acceptedAlgs;
119        }
120
121
122        @Override
123        public void setAcceptedAlgorithms(final Set<JWEAlgorithm> acceptedAlgs) {
124
125                if (acceptedAlgs == null) {
126                        throw new IllegalArgumentException("The accepted JWE algorithms must not be null");
127                }
128
129                if (! supportedAlgorithms().containsAll(acceptedAlgs)) {
130                        throw new IllegalArgumentException("Unsupported JWE algorithm(s)");
131                }
132
133                this.acceptedAlgs = acceptedAlgs;
134        }
135
136
137        @Override
138        public Set<EncryptionMethod> getAcceptedEncryptionMethods() {
139
140                return acceptedEncs;
141        }
142
143
144        @Override
145        public void setAcceptedEncryptionMethods(final Set<EncryptionMethod> acceptedEncs) {
146
147                if (acceptedEncs == null)
148                        throw new IllegalArgumentException("The accepted encryption methods must not be null");
149
150                if (!supportedEncryptionMethods().containsAll(acceptedEncs)) {
151                        throw new IllegalArgumentException("Unsupported encryption method(s)");
152                }
153
154                this.acceptedEncs = acceptedEncs;
155        }
156
157
158        @Override
159        public Set<String> getIgnoredCriticalHeaderParameters() {
160
161                return critParamChecker.getIgnoredCriticalHeaders();
162        }
163
164
165        @Override
166        public void setIgnoredCriticalHeaderParameters(final Set<String> headers) {
167
168                critParamChecker.setIgnoredCriticalHeaders(headers);
169        }
170
171
172        @Override
173        public byte[] decrypt(final JWEHeader header,
174                              final Base64URL encryptedKey,
175                              final Base64URL iv,
176                              final Base64URL cipherText,
177                              final Base64URL authTag) 
178                throws JOSEException {
179
180                // Validate required JWE parts
181                if (encryptedKey == null) {
182
183                        throw new JOSEException("The encrypted key must not be null");
184                }       
185
186                if (iv == null) {
187
188                        throw new JOSEException("The initialization vector (IV) must not be null");
189                }
190
191                if (authTag == null) {
192
193                        throw new JOSEException("The authentication tag must not be null");
194                }
195
196                if (! critParamChecker.headerPasses(header)) {
197
198                        throw new JOSEException("Unsupported critical header parameter");
199                }
200                
201
202                // Derive the content encryption key
203                JWEAlgorithm alg = header.getAlgorithm();
204
205                SecretKey cek;
206
207                if (alg.equals(JWEAlgorithm.RSA1_5)) {
208
209                        int keyLength = header.getEncryptionMethod().cekBitLength();
210
211                        // Protect against MMA attack by generating random CEK on failure,
212                        // see http://www.ietf.org/mail-archive/web/jose/current/msg01832.html
213                        SecureRandom randomGen = getSecureRandom();
214                        SecretKey randomCEK = AES.generateKey(keyLength, randomGen);
215
216                        try {
217                                cek = RSA1_5.decryptCEK(privateKey, encryptedKey.decode(), keyLength, keyEncryptionProvider);
218
219                                if (cek == null) {
220                                        // CEK length mismatch, signalled by null instead of
221                                        // exception to prevent MMA attack
222                                        cek = randomCEK;
223                                }
224
225                        } catch (Exception e) {
226                                // continue
227                                cek = randomCEK;
228                        }
229                
230                } else if (alg.equals(JWEAlgorithm.RSA_OAEP)) {
231
232                        cek = RSA_OAEP.decryptCEK(privateKey, encryptedKey.decode(), keyEncryptionProvider);
233
234                } else if (alg.equals(JWEAlgorithm.RSA_OAEP_256)) {
235                        
236                        cek = RSA_OAEP_256.decryptCEK(privateKey, encryptedKey.decode(), keyEncryptionProvider);
237                        
238                } else {
239                
240                        throw new JOSEException("Unsupported JWE algorithm, must be RSA1_5 or RSA_OAEP");
241                }
242
243                // Compose the AAD
244                byte[] aad = StringUtils.toByteArray(header.toBase64URL().toString());
245
246                // Decrypt the cipher text according to the JWE enc
247                EncryptionMethod enc = header.getEncryptionMethod();
248
249                byte[] plainText;
250
251                if (enc.equals(EncryptionMethod.A128CBC_HS256) ||
252                    enc.equals(EncryptionMethod.A192CBC_HS384) ||
253                    enc.equals(EncryptionMethod.A256CBC_HS512)    ) {
254
255                        plainText = AESCBC.decryptAuthenticated(
256                                cek,
257                                iv.decode(),
258                                cipherText.decode(),
259                                aad,
260                                authTag.decode(),
261                                contentEncryptionProvider,
262                                macProvider);
263
264                } else if (enc.equals(EncryptionMethod.A128GCM) ||
265                           enc.equals(EncryptionMethod.A192GCM) ||
266                           enc.equals(EncryptionMethod.A256GCM)    ) {
267
268                        plainText = AESGCM.decrypt(
269                                cek,
270                                iv.decode(),
271                                cipherText.decode(),
272                                aad,
273                                authTag.decode(),
274                                contentEncryptionProvider);
275
276                } else if (enc.equals(EncryptionMethod.A128CBC_HS256_DEPRECATED) ||
277                           enc.equals(EncryptionMethod.A256CBC_HS512_DEPRECATED)    ) {
278
279                        plainText = AESCBC.decryptWithConcatKDF(
280                                header,
281                                cek,
282                                encryptedKey,
283                                iv,
284                                cipherText,
285                                authTag,
286                                contentEncryptionProvider,
287                                macProvider);
288
289                } else {
290
291                        throw new JOSEException("Unsupported encryption method, must be A128CBC_HS256, A192CBC_HS384, A256CBC_HS512, A128GCM, A192GCM or A256GCM");
292                }
293
294
295                // Apply decompression if requested
296                return DeflateHelper.applyDecompression(header, plainText);
297        }
298}
299