001package com.nimbusds.jose.crypto;
002
003
004import java.security.NoSuchAlgorithmException;
005import java.security.SecureRandom;
006import java.security.interfaces.RSAPublicKey;
007
008import javax.crypto.SecretKey;
009
010import com.nimbusds.jose.EncryptionMethod;
011import com.nimbusds.jose.JOSEException;
012import com.nimbusds.jose.JWEAlgorithm;
013import com.nimbusds.jose.JWECryptoParts;
014import com.nimbusds.jose.JWEEncrypter;
015import com.nimbusds.jose.ReadOnlyJWEHeader;
016import com.nimbusds.jose.util.Base64URL;
017import com.nimbusds.jose.util.StringUtils;
018
019
020
021/**
022 * RSA encrypter of {@link com.nimbusds.jose.JWEObject JWE objects}. This class
023 * is thread-safe.
024 *
025 * <p>Supports the following JWE algorithms:
026 *
027 * <ul>
028 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA1_5}
029 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP}
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#A256CBC_HS512}
037 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM}
038 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM}
039 * </ul>
040 *
041 * @author David Ortiz
042 * @author Vladimir Dzhuvinov
043 * @version $version$ (2013-05-29)
044 */
045public class RSAEncrypter extends RSACryptoProvider implements JWEEncrypter {
046
047
048        /**
049         * Random byte generator.
050         */
051        private static SecureRandom randomGen;
052
053
054        /**
055         * The public RSA key.
056         */
057        private final RSAPublicKey publicKey;
058
059
060        /**
061         * Initialises the secure random byte generator.
062         *
063         * @throws JOSEException If the secure random byte generator couldn't 
064         *                       be instantiated.
065         */
066        private void initSecureRandom()
067                throws JOSEException {
068
069                try {
070                        randomGen = SecureRandom.getInstance("SHA1PRNG");
071
072                } catch(NoSuchAlgorithmException e) {
073
074                        throw new JOSEException(e.getMessage(), e);
075                }
076        }
077
078
079        /**
080         * Creates a new RSA encrypter.
081         *
082         * @param publicKey The public RSA key. Must not be {@code null}.
083         *
084         * @throws JOSEException If the underlying secure random generator
085         *                       couldn't be instantiated.
086         */
087        public RSAEncrypter(final RSAPublicKey publicKey)
088                throws JOSEException {
089
090                if (publicKey == null) {
091
092                        throw new IllegalArgumentException("The public RSA key must not be null");
093                }
094
095                this.publicKey = publicKey;
096
097
098                if (randomGen == null) {
099
100                        initSecureRandom();
101                }
102        }
103
104
105        /**
106         * Gets the public RSA key.
107         *
108         * @return The public RSA key.
109         */
110        public RSAPublicKey getPublicKey() {
111
112                return publicKey;
113        }
114
115
116        @Override
117        public JWECryptoParts encrypt(final ReadOnlyJWEHeader readOnlyJWEHeader, final byte[] bytes)
118                throws JOSEException {
119
120                JWEAlgorithm alg = readOnlyJWEHeader.getAlgorithm();
121                EncryptionMethod enc = readOnlyJWEHeader.getEncryptionMethod();
122
123                // Generate and encrypt the CEK according to the enc method
124                SecretKey cek = AES.generateKey(enc.cekBitLength());
125
126                Base64URL encryptedKey = null; // The second JWE part
127
128                if (alg.equals(JWEAlgorithm.RSA1_5)) {
129
130                        encryptedKey = Base64URL.encode(RSA1_5.encryptCEK(publicKey, cek));
131
132                } else if (alg.equals(JWEAlgorithm.RSA_OAEP)) {
133
134                        encryptedKey = Base64URL.encode(RSA_OAEP.encryptCEK(publicKey, cek));
135
136                } else {
137
138                        throw new JOSEException("Unsupported JWE algorithm, must be RSA1_5 or RSA-OAEP");
139                }
140
141
142                // Apply compression if instructed
143                byte[] plainText = DeflateHelper.applyCompression(readOnlyJWEHeader, bytes);
144
145                // Compose the AAD
146                byte[] aad = StringUtils.toByteArray(readOnlyJWEHeader.toBase64URL().toString());
147
148                // Encrypt the plain text according to the JWE enc
149                byte[] iv;
150                AuthenticatedCipherText authCipherText;
151                
152                if (enc.equals(EncryptionMethod.A128CBC_HS256) || enc.equals(EncryptionMethod.A256CBC_HS512)) {
153
154                        iv = AESCBC.generateIV(randomGen);
155
156                        authCipherText = AESCBC.encryptAuthenticated(cek, iv, plainText, aad);
157
158                } else if (enc.equals(EncryptionMethod.A128GCM) || enc.equals(EncryptionMethod.A256GCM)) {
159
160                        iv = AESGCM.generateIV(randomGen);
161
162                        authCipherText = AESGCM.encrypt(cek, iv, plainText, aad);
163
164                } else {
165
166                        throw new JOSEException("Unsupported encryption method, must be A128CBC_HS256, A256CBC_HS512, A128GCM or A128GCM");
167                }
168
169                return new JWECryptoParts(encryptedKey,  
170                                          Base64URL.encode(iv), 
171                                          Base64URL.encode(authCipherText.getCipherText()),
172                                          Base64URL.encode(authCipherText.getAuthenticationTag()));
173        }
174}