001package com.nimbusds.jose.crypto;
002
003import java.security.SecureRandom;
004
005import javax.crypto.SecretKey;
006import javax.crypto.spec.SecretKeySpec;
007
008import net.jcip.annotations.ThreadSafe;
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.JWEHeader;
016import com.nimbusds.jose.util.Base64URL;
017import com.nimbusds.jose.util.StringUtils;
018
019
020/**
021 * AES encrypter 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#A128KW}
028 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#A192KW}
029 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#A256KW}
030 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#A128GCMKW}
031 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#A192GCMKW}
032 *     <li>{@link com.nimbusds.jose.JWEAlgorithm#A256GCMKW}
033 * </ul>
034 *
035 * <p>Supports the following encryption methods:
036 *
037 * <ul>
038 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256}
039 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384}
040 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512}
041 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM}
042 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A192GCM}
043 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM}
044 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256_DEPRECATED}
045 *     <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512_DEPRECATED}
046 * </ul>
047 *
048 * @author Melisa Halsband 
049 * @version $version$ (2014-08-20)
050 */
051@ThreadSafe
052public class AESEncrypter extends AESCryptoProvider implements JWEEncrypter {
053
054
055        /**
056         * Constants used for clarity.
057         */
058        private static enum AlgFamily {
059
060                AESKW, AESGCMKW
061        }
062
063
064        /**
065         * The key encrypting key.
066         */
067        private final SecretKey kek;
068
069
070        /**
071         * Creates a new AES encrypter.
072         *
073         * @param kek The Key Encrypting Key. Must be 128 bits (16 bytes), 192
074         *            bits (24 bytes) or 256 bits (32 bytes). Must not be
075         *            {@code null}.
076         */
077        public AESEncrypter(final SecretKey kek) {
078
079                if (kek == null) {
080                        throw new IllegalArgumentException("The Key Encrypting Key must not be null");
081                }
082
083                this.kek = kek;
084        }
085
086        /**
087         * Creates a new AES encrypter.
088         *
089         * @param keyBytes The Key Encrypting Key, as a byte array. Must be 128
090         *                 bits (16 bytes), 192 bits (24 bytes) or 256 bits (32
091         *                 bytes). Must not be {@code null}.
092         */
093        public AESEncrypter(final byte[] keyBytes) {
094
095                this(new SecretKeySpec(keyBytes, "AES"));
096        }
097
098
099        /**
100         * Gets the Key Encrypting Key.
101         *
102         * @return The Key Encrypting Key.
103         */
104        public SecretKey getKey() {
105
106                return kek;
107        }
108
109
110        @Override
111        public JWECryptoParts encrypt(final JWEHeader header, final byte[] bytes)
112                throws JOSEException {
113
114                final JWEAlgorithm alg = header.getAlgorithm();
115                final EncryptionMethod enc = header.getEncryptionMethod();
116
117                // Generate and encrypt the CEK according to the enc method
118                final SecureRandom randomGen = getSecureRandom();
119                final SecretKey cek = AES.generateKey(enc.cekBitLength(), randomGen);
120                byte[] keyIV;
121
122                final AuthenticatedCipherText authCiphCEK;
123
124                AlgFamily algFamily;
125
126                Base64URL encryptedKey; // The second JWE part
127
128                if (alg.equals(JWEAlgorithm.A128KW)) {
129
130                        if(kek.getEncoded().length != 16){
131                                throw new JOSEException("The Key Encryption Key (KEK) length must be 128 bits for A128KW encryption");
132                        }
133                        algFamily = AlgFamily.AESKW;
134
135                } else if (alg.equals(JWEAlgorithm.A192KW)) {
136
137                        if(kek.getEncoded().length != 24){
138                                throw new JOSEException("The Key Encryption Key (KEK) length must be 192 bits for A192KW encryption");
139                        }
140                        algFamily = AlgFamily.AESKW;
141
142                } else if (alg.equals(JWEAlgorithm.A256KW)) {
143
144                        if (kek.getEncoded().length != 32) {
145                                throw new JOSEException("The Key Encryption Key (KEK) length must be 256 bits for A256KW encryption");
146                        }
147                        algFamily = AlgFamily.AESKW;
148
149                } else if (alg.equals(JWEAlgorithm.A128GCMKW)) {
150
151                        if(kek.getEncoded().length != 16){
152                                throw new JOSEException("The Key Encryption Key (KEK) length must be 128 bits for A128GCMKW encryption");
153                        }
154                        algFamily = AlgFamily.AESGCMKW;
155
156                } else if (alg.equals(JWEAlgorithm.A192GCMKW)) {
157
158                        if(kek.getEncoded().length != 24){
159                                throw new JOSEException("The Key Encryption Key (KEK) length must be 192 bits for A192GCMKW encryption");
160                        }
161                        algFamily = AlgFamily.AESGCMKW;
162
163                } else if (alg.equals(JWEAlgorithm.A256GCMKW)) {
164
165                        if(kek.getEncoded().length != 32){
166                                throw new JOSEException("The Key Encryption Key (KEK) length must be 256 bits for A256GCMKW encryption");
167                        }
168                        algFamily = AlgFamily.AESGCMKW;
169
170                } else {
171
172                        throw new JOSEException("Unsupported JWE algorithm, must be A128KW, A192KW, A256KW, A128GCMKW, A192GCMKW orA256GCMKW");
173                }
174
175                // We need to work on the header
176                JWEHeader modifiableHeader;
177
178                if(AlgFamily.AESKW.equals(algFamily)) {
179                        encryptedKey = Base64URL.encode(AESKW.encryptCEK(cek, kek));
180                        modifiableHeader = header; // simply copy ref
181                } else if(AlgFamily.AESGCMKW.equals(algFamily)) {
182                        keyIV = AESGCM.generateIV(randomGen);
183                        authCiphCEK = AESGCMKW.encryptCEK(cek, keyIV, kek, keyEncryptionProvider);
184                        encryptedKey = Base64URL.encode(authCiphCEK.getCipherText());
185
186                        // Add iv and tag to the header
187                        modifiableHeader = new JWEHeader.Builder(header).
188                                iv(Base64URL.encode(keyIV)).
189                                authTag(Base64URL.encode(authCiphCEK.getAuthenticationTag())).
190                                build();
191                } else {
192                        // This should never happen
193                        throw new JOSEException("Unsupported JWE algorithm, must be A128KW, A192KW, A256KW, A128GCMKW, A192GCMKW orA256GCMKW");
194                }
195
196                // Apply compression if instructed
197                byte[] plainText = DeflateHelper.applyCompression(modifiableHeader, bytes);
198
199                // Compose the AAD
200                byte[] aad = StringUtils.toByteArray(modifiableHeader.toBase64URL().toString());
201
202                // Encrypt the plain text according to the JWE enc
203                byte[] iv;
204                AuthenticatedCipherText authCipherText;
205
206                if (enc.equals(EncryptionMethod.A128CBC_HS256) ||
207                    enc.equals(EncryptionMethod.A192CBC_HS384) ||
208                    enc.equals(EncryptionMethod.A256CBC_HS512)   ) {
209
210                        iv = AESCBC.generateIV(randomGen);
211
212                        authCipherText = AESCBC.encryptAuthenticated(
213                                cek, iv, plainText, aad,
214                                contentEncryptionProvider, macProvider);
215
216                } else if (enc.equals(EncryptionMethod.A128GCM) ||
217                           enc.equals(EncryptionMethod.A192GCM) ||
218                           enc.equals(EncryptionMethod.A256GCM)    ) {
219
220                        iv = AESGCM.generateIV(randomGen);
221
222                        authCipherText = AESGCM.encrypt(
223                                cek, iv, plainText, aad,
224                                contentEncryptionProvider);
225
226                } else if (enc.equals(EncryptionMethod.A128CBC_HS256_DEPRECATED) ||
227                           enc.equals(EncryptionMethod.A256CBC_HS512_DEPRECATED)    ) {
228
229                        iv = AESCBC.generateIV(randomGen);
230
231                        authCipherText = AESCBC.encryptWithConcatKDF(
232                                modifiableHeader, cek, encryptedKey, iv, plainText,
233                                contentEncryptionProvider, macProvider);
234
235                } else {
236
237                        throw new JOSEException("Unsupported encryption method, must be A128CBC_HS256, A192CBC_HS384, A256CBC_HS512, A128GCM, A192GCM or A256GCM");
238                }
239
240                return new JWECryptoParts(
241                        modifiableHeader,
242                        encryptedKey,
243                        Base64URL.encode(iv),
244                        Base64URL.encode(authCipherText.getCipherText()),
245                        Base64URL.encode(authCipherText.getAuthenticationTag()));
246        }
247}