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