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                switch (algFamily) {
179
180                        case AESKW:
181                                encryptedKey = Base64URL.encode(AESKW.encryptCEK(cek, kek));
182                                modifiableHeader = header; // simply copy ref
183                                break;
184
185                        case AESGCMKW:
186                                keyIV = AESGCM.generateIV(randomGen);
187                                authCiphCEK = AESGCMKW.encryptCEK(cek, keyIV, kek, keyEncryptionProvider);
188                                encryptedKey = Base64URL.encode(authCiphCEK.getCipherText());
189
190                                // Add iv and tag to the header
191                                modifiableHeader = new JWEHeader.Builder(header).
192                                        iv(Base64URL.encode(keyIV)).
193                                        authTag(Base64URL.encode(authCiphCEK.getAuthenticationTag())).
194                                        build();
195                                break;
196
197                        default:
198                                // This should never happen
199                                throw new JOSEException("Unsupported JWE algorithm, must be A128KW, A192KW, A256KW, A128GCMKW, A192GCMKW orA256GCMKW");
200                }
201
202                // Apply compression if instructed
203                byte[] plainText = DeflateHelper.applyCompression(modifiableHeader, bytes);
204
205                // Compose the AAD
206                byte[] aad = StringUtils.toByteArray(modifiableHeader.toBase64URL().toString());
207
208                // Encrypt the plain text according to the JWE enc
209                byte[] iv;
210                AuthenticatedCipherText authCipherText;
211
212                if (enc.equals(EncryptionMethod.A128CBC_HS256) ||
213                    enc.equals(EncryptionMethod.A192CBC_HS384) ||
214                    enc.equals(EncryptionMethod.A256CBC_HS512)   ) {
215
216                        iv = AESCBC.generateIV(randomGen);
217
218                        authCipherText = AESCBC.encryptAuthenticated(
219                                cek, iv, plainText, aad,
220                                contentEncryptionProvider, macProvider);
221
222                } else if (enc.equals(EncryptionMethod.A128GCM) ||
223                           enc.equals(EncryptionMethod.A192GCM) ||
224                           enc.equals(EncryptionMethod.A256GCM)    ) {
225
226                        iv = AESGCM.generateIV(randomGen);
227
228                        authCipherText = AESGCM.encrypt(
229                                cek, iv, plainText, aad,
230                                contentEncryptionProvider);
231
232                } else if (enc.equals(EncryptionMethod.A128CBC_HS256_DEPRECATED) ||
233                           enc.equals(EncryptionMethod.A256CBC_HS512_DEPRECATED)    ) {
234
235                        iv = AESCBC.generateIV(randomGen);
236
237                        authCipherText = AESCBC.encryptWithConcatKDF(
238                                modifiableHeader, cek, encryptedKey, iv, plainText,
239                                contentEncryptionProvider, macProvider);
240
241                } else {
242
243                        throw new JOSEException("Unsupported encryption method, must be A128CBC_HS256, A192CBC_HS384, A256CBC_HS512, A128GCM, A192GCM or A256GCM");
244                }
245
246                return new JWECryptoParts(
247                        modifiableHeader,
248                        encryptedKey,
249                        Base64URL.encode(iv),
250                        Base64URL.encode(authCipherText.getCipherText()),
251                        Base64URL.encode(authCipherText.getAuthenticationTag()));
252        }
253}