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