001    package com.nimbusds.jose.crypto;
002    
003    
004    import java.io.UnsupportedEncodingException;
005    import java.security.NoSuchAlgorithmException;
006    import java.security.SecureRandom;
007    import java.security.interfaces.RSAPublicKey;
008    
009    import javax.crypto.SecretKey;
010    
011    import com.nimbusds.jose.CompressionAlgorithm;
012    import com.nimbusds.jose.EncryptionMethod;
013    import com.nimbusds.jose.JOSEException;
014    import com.nimbusds.jose.JWEAlgorithm;
015    import com.nimbusds.jose.JWECryptoParts;
016    import com.nimbusds.jose.JWEEncrypter;
017    import com.nimbusds.jose.ReadOnlyJWEHeader;
018    import com.nimbusds.jose.util.Base64URL;
019    import com.nimbusds.jose.util.DeflateUtils;
020    
021    
022    
023    /**
024     * RSA encrypter of {@link com.nimbusds.jose.JWEObject JWE objects}. This class
025     * is thread-safe.
026     *
027     * <p>Supports the following JWE algorithms:
028     *
029     * <ul>
030     *     <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA1_5}
031     *     <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP}
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#A256CBC_HS512}
039     *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM}
040     *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM}
041     * </ul>
042     *
043     * @author David Ortiz
044     * @author Vladimir Dzhuvinov
045     * @version $version$ (2013-03-26)
046     */
047    public class RSAEncrypter extends RSACryptoProvider implements JWEEncrypter {
048    
049    
050            /**
051             * Random byte generator.
052             */
053            private final SecureRandom randomGen;
054    
055    
056            /**
057             * The public RSA key.
058             */
059            private final RSAPublicKey publicKey;
060    
061    
062            /**
063             * Creates a new RSA encrypter.
064             *
065             * @param publicKey The public RSA key. Must not be {@code null}.
066             *
067             * @throws JOSEException If the underlying secure random generator
068             *                       couldn't be instantiated.
069             */
070            public RSAEncrypter(final RSAPublicKey publicKey)
071                    throws JOSEException {
072    
073                    if (publicKey == null) {
074    
075                            throw new IllegalArgumentException("The public RSA key must not be null");
076                    }
077    
078                    this.publicKey = publicKey;
079    
080    
081                    try {
082                            randomGen = SecureRandom.getInstance("SHA1PRNG");
083    
084                    } catch(NoSuchAlgorithmException e) {
085    
086                            throw new JOSEException(e.getMessage(), e);
087                    }
088            }
089    
090    
091            /**
092             * Gets the public RSA key.
093             *
094             * @return The public RSA key.
095             */
096            public RSAPublicKey getPublicKey() {
097    
098                    return publicKey;
099            }
100    
101    
102            /**
103             * Applies compression to the specified plain text if requested.
104             *
105             * @param readOnlyJWEHeader The JWE header. Must not be {@code null}.
106             * @param bytes             The plain text bytes. Must not be 
107             *                          {@code null}.
108             *
109             * @return The bytes to encrypt.
110             *
111             * @throws JOSEException If compression failed or the requested 
112             *                       compression algorithm is not supported.
113             */
114            private static final byte[] applyCompression(final ReadOnlyJWEHeader readOnlyJWEHeader, final byte[] bytes)
115                    throws JOSEException {
116    
117                    CompressionAlgorithm compressionAlg = readOnlyJWEHeader.getCompressionAlgorithm();
118    
119                    if (compressionAlg == null) {
120    
121                            return bytes;
122    
123                    } else if (compressionAlg.equals(CompressionAlgorithm.DEF)) {
124    
125                            try {
126                                    return DeflateUtils.compress(bytes);
127    
128                            } catch (Exception e) {
129    
130                                    throw new JOSEException("Couldn't compress plain text: " + e.getMessage(), e);
131                            }
132    
133                    } else {
134    
135                            throw new JOSEException("Unsupported compression algorithm: " + compressionAlg);
136                    }
137            }
138    
139    
140            @Override
141            public JWECryptoParts encrypt(final ReadOnlyJWEHeader readOnlyJWEHeader, final byte[] bytes)
142                    throws JOSEException {
143    
144                    JWEAlgorithm alg = readOnlyJWEHeader.getAlgorithm();
145                    EncryptionMethod enc = readOnlyJWEHeader.getEncryptionMethod();
146    
147                    // Generate and encrypt the CMK according to the JWE alg
148                    final int keyLength = RSACryptoProvider.cmkBitLength(enc);
149    
150                    SecretKey cmk = AES.generateAESCMK(keyLength);
151    
152                    Base64URL encryptedKey = null; // The second JWE part
153    
154                    if (alg.equals(JWEAlgorithm.RSA1_5)) {
155    
156                            encryptedKey = Base64URL.encode(RSA1_5.encryptCMK(publicKey, cmk));
157    
158                    } else if (alg.equals(JWEAlgorithm.RSA_OAEP)) {
159    
160                            encryptedKey = Base64URL.encode(RSA_OAEP.encryptCMK(publicKey, cmk));
161    
162                    } else {
163    
164                            throw new JOSEException("Unsupported algorithm, must be RSA1_5 or RSA_OAEP");
165                    }
166    
167                    if (encryptedKey == null ) {
168    
169                            throw new JOSEException("Couldn't generate encrypted key");
170                    }
171    
172    
173                    // Apply compression if instructed
174                    byte[] plainText = applyCompression(readOnlyJWEHeader, bytes);
175                    
176    
177                    // Encrypt the plain text according to the JWE enc
178                    if (enc.equals(EncryptionMethod.A128CBC_HS256) || enc.equals(EncryptionMethod.A256CBC_HS512)) {
179    
180                            byte[] epu = null;
181    
182                            if (readOnlyJWEHeader.getEncryptionPartyUInfo() != null) {
183    
184                                    epu = readOnlyJWEHeader.getEncryptionPartyUInfo().decode();
185                            }
186    
187                            byte[] epv = null;
188                            
189                            if (readOnlyJWEHeader.getEncryptionPartyVInfo() != null) {
190    
191                                    epv = readOnlyJWEHeader.getEncryptionPartyVInfo().decode();
192                            }
193    
194                            SecretKey cek = ConcatKDF.generateCEK(cmk, enc, epu, epv);
195    
196                            byte[] iv = AESCBC.generateIV(randomGen);
197    
198                            byte[] cipherText = AESCBC.encrypt(cek, iv, plainText);
199    
200                            SecretKey cik = ConcatKDF.generateCIK(cmk, enc, epu, epv);
201    
202                            String macInput = readOnlyJWEHeader.toBase64URL().toString() + "." +
203                                              encryptedKey.toString() + "." +
204                                              Base64URL.encode(iv).toString() + "." +
205                                              Base64URL.encode(cipherText);
206    
207                            byte[] mac = HMAC.compute(cik, macInput.getBytes());
208    
209                            return new JWECryptoParts(encryptedKey,  
210                                                      Base64URL.encode(iv), 
211                                                      Base64URL.encode(cipherText),
212                                                      Base64URL.encode(mac));
213    
214                    } else if (enc.equals(EncryptionMethod.A128GCM) || enc.equals(EncryptionMethod.A256GCM)) {
215    
216                            byte[] iv = AESGCM.generateIV(randomGen);
217    
218                            // Compose the additional authenticated data
219                            String authDataString = readOnlyJWEHeader.toBase64URL().toString() + "." +
220                                                    encryptedKey.toString() + "." +
221                                                    Base64URL.encode(iv).toString();
222    
223                            byte[] authData;
224    
225                            try {
226                                    authData = authDataString.getBytes("UTF-8");
227    
228                            } catch (UnsupportedEncodingException e) {
229    
230                                    throw new JOSEException(e.getMessage(), e);
231                            }
232    
233                            
234                            AESGCM.Result result = AESGCM.encrypt(cmk, iv, plainText, authData);
235    
236                            return new JWECryptoParts(encryptedKey,  
237                                                      Base64URL.encode(iv), 
238                                                      Base64URL.encode(result.getCipherText()),
239                                                      Base64URL.encode(result.getAuthenticationTag()));
240    
241                    } else {
242    
243                            throw new JOSEException("Unsupported encryption method, must be A128CBC_HS256, A256CBC_HS512, A128GCM or A128GCM");
244                    }
245            }
246    }