001package com.nimbusds.jose.crypto;
002
003
004import java.security.interfaces.RSAPrivateKey;
005import java.util.Set;
006import javax.crypto.SecretKey;
007
008import net.jcip.annotations.ThreadSafe;
009
010import com.nimbusds.jose.*;
011import com.nimbusds.jose.jwk.RSAKey;
012import com.nimbusds.jose.util.Base64URL;
013
014
015/**
016 * RSA decrypter of {@link com.nimbusds.jose.JWEObject JWE objects}. This class
017 * is thread-safe.
018 *
019 * <p>Supports the following key management algorithms:
020 *
021 * <ul>
022 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA1_5}
023 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP}
024 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP_256}
025 * </ul>
026 *
027 * <p>Supports the following content encryption algorithms:
028 *
029 * <ul>
030 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256}
031 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384}
032 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512}
033 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM}
034 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192GCM}
035 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM}
036 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256_DEPRECATED}
037 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512_DEPRECATED}
038 * </ul>
039 * 
040 * @author David Ortiz
041 * @author Vladimir Dzhuvinov
042 * @version 2015-06-08
043 */
044@ThreadSafe
045public class RSADecrypter extends RSACryptoProvider implements JWEDecrypter, CriticalHeaderParamsAware {
046
047
048        /**
049         * The critical header policy.
050         */
051        private final CriticalHeaderParamsDeferral critPolicy = new CriticalHeaderParamsDeferral();
052
053
054        /**
055         * The private RSA key.
056         */
057        private final RSAPrivateKey privateKey;
058
059
060        /**
061         * Creates a new RSA decrypter.
062         *
063         * @param privateKey The private RSA key. Must not be {@code null}.
064         */
065        public RSADecrypter(final RSAPrivateKey privateKey) {
066
067                this(privateKey, null);
068        }
069
070
071        /**
072         * Creates a new RSA decrypter.
073         *
074         * @param rsaJWK The RSA JSON Web Key (JWK). Must contain a private
075         *               part. Must not be {@code null}.
076         *
077         * @throws JOSEException If the RSA JWK doesn't contain a private part
078         *                       or its extraction failed.
079         */
080        public RSADecrypter(final RSAKey rsaJWK)
081                throws JOSEException {
082
083                if (! rsaJWK.isPrivate()) {
084                        throw new JOSEException("The RSA JWK doesn't contain a private part");
085                }
086
087                privateKey = rsaJWK.toRSAPrivateKey();
088        }
089
090
091        /**
092         * Creates a new RSA decrypter.
093         *
094         * @param privateKey     The private RSA key. Must not be {@code null}.
095         * @param defCritHeaders The names of the critical header parameters
096         *                       that are deferred to the application for
097         *                       processing, empty set or {@code null} if none.
098         */
099        public RSADecrypter(final RSAPrivateKey privateKey,
100                            final Set<String> defCritHeaders) {
101
102                if (privateKey == null) {
103                        throw new IllegalArgumentException("The private RSA key must not be null");
104                }
105
106                this.privateKey = privateKey;
107
108                critPolicy.setDeferredCriticalHeaderParams(defCritHeaders);
109        }
110
111
112        /**
113         * Gets the private RSA key.
114         *
115         * @return The private RSA key.
116         */
117        public RSAPrivateKey getPrivateKey() {
118
119                return privateKey;
120        }
121
122
123        @Override
124        public Set<String> getProcessedCriticalHeaderParams() {
125
126                return critPolicy.getProcessedCriticalHeaderParams();
127        }
128
129
130        @Override
131        public Set<String> getDeferredCriticalHeaderParams() {
132
133                return critPolicy.getProcessedCriticalHeaderParams();
134        }
135
136
137        @Override
138        public byte[] decrypt(final JWEHeader header,
139                              final Base64URL encryptedKey,
140                              final Base64URL iv,
141                              final Base64URL cipherText,
142                              final Base64URL authTag) 
143                throws JOSEException {
144
145                // Validate required JWE parts
146                if (encryptedKey == null) {
147                        throw new JOSEException("Missing JWE encrypted key");
148                }       
149
150                if (iv == null) {
151                        throw new JOSEException("Missing JWE initialization vector (IV)");
152                }
153
154                if (authTag == null) {
155                        throw new JOSEException("Missing JWE authentication tag");
156                }
157
158                critPolicy.ensureHeaderPasses(header);
159                
160
161                // Derive the content encryption key
162                JWEAlgorithm alg = header.getAlgorithm();
163
164                SecretKey cek;
165
166                if (alg.equals(JWEAlgorithm.RSA1_5)) {
167
168                        int keyLength = header.getEncryptionMethod().cekBitLength();
169
170                        // Protect against MMA attack by generating random CEK on failure,
171                        // see http://www.ietf.org/mail-archive/web/jose/current/msg01832.html
172                        final SecretKey randomCEK = ContentCryptoProvider.generateCEK(header.getEncryptionMethod(), getJCAContext().getSecureRandom());
173
174                        try {
175                                cek = RSA1_5.decryptCEK(privateKey, encryptedKey.decode(), keyLength, getJCAContext().getKeyEncryptionProvider());
176
177                                if (cek == null) {
178                                        // CEK length mismatch, signalled by null instead of
179                                        // exception to prevent MMA attack
180                                        cek = randomCEK;
181                                }
182
183                        } catch (Exception e) {
184                                // continue
185                                cek = randomCEK;
186                        }
187                
188                } else if (alg.equals(JWEAlgorithm.RSA_OAEP)) {
189
190                        cek = RSA_OAEP.decryptCEK(privateKey, encryptedKey.decode(), getJCAContext().getKeyEncryptionProvider());
191
192                } else if (alg.equals(JWEAlgorithm.RSA_OAEP_256)) {
193                        
194                        cek = RSA_OAEP_256.decryptCEK(privateKey, encryptedKey.decode(), getJCAContext().getKeyEncryptionProvider());
195                        
196                } else {
197                
198                        throw new JOSEException(AlgorithmSupportMessage.unsupportedJWEAlgorithm(alg, SUPPORTED_ALGORITHMS));
199                }
200
201                return ContentCryptoProvider.decrypt(header, encryptedKey, iv, cipherText, authTag, cek, getJCAContext());
202        }
203}
204