001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.jose.crypto.impl;
019
020
021import java.nio.ByteBuffer;
022import java.security.Provider;
023import java.security.SecureRandom;
024import java.util.Arrays;
025import javax.crypto.Cipher;
026import javax.crypto.SecretKey;
027import javax.crypto.spec.IvParameterSpec;
028import javax.crypto.spec.SecretKeySpec;
029
030import net.jcip.annotations.ThreadSafe;
031
032import com.nimbusds.jose.JOSEException;
033import com.nimbusds.jose.JWEHeader;
034import com.nimbusds.jose.crypto.utils.ConstantTimeUtils;
035import com.nimbusds.jose.util.Base64URL;
036import com.nimbusds.jose.util.ByteUtils;
037import com.nimbusds.jose.util.StandardCharset;
038
039
040/**
041 * AES/CBC/PKCS5Padding and AES/CBC/PKCS5Padding/HMAC-SHA2 encryption and 
042 * decryption methods. This class is thread-safe.
043 *
044 * <p>Also supports the deprecated AES/CBC/HMAC encryption using a custom
045 * concat KDF (JOSE draft suite 08).
046 *
047 * <p>See RFC 7518 (JWA), section 5.2.
048 *
049 * @author Vladimir Dzhuvinov
050 * @author Axel Nennker
051 * @version 2022-01-24
052 */
053@ThreadSafe
054public class AESCBC {
055
056
057        /**
058         * The standard Initialisation Vector (IV) length (128 bits).
059         */
060        public static final int IV_BIT_LENGTH = 128;
061
062
063        /**
064         * Generates a random 128 bit (16 byte) Initialisation Vector(IV) for
065         * use in AES-CBC encryption.
066         *
067         * @param randomGen The secure random generator to use. Must be 
068         *                  correctly initialised and not {@code null}.
069         *
070         * @return The random 128 bit IV, as 16 byte array.
071         */
072        public static byte[] generateIV(final SecureRandom randomGen) {
073                
074                byte[] bytes = new byte[ByteUtils.byteLength(IV_BIT_LENGTH)];
075                randomGen.nextBytes(bytes);
076                return bytes;
077        }
078
079
080        /**
081         * Creates a new AES/CBC/PKCS5Padding cipher.
082         *
083         * @param secretKey     The AES key. Must not be {@code null}.
084         * @param forEncryption If {@code true} creates an encryption cipher, 
085         *                      else creates a decryption cipher.
086         * @param iv            The initialisation vector (IV). Must not be
087         *                      {@code null}.
088         * @param provider      The JCA provider, {@code null} to use the
089         *                      default.
090         *
091         * @return The AES/CBC/PKCS5Padding cipher.
092         */
093        private static Cipher createAESCBCCipher(final SecretKey secretKey,
094                                                 final boolean forEncryption,
095                                                 final byte[] iv,
096                                                 final Provider provider)
097                throws JOSEException {
098
099                Cipher cipher;
100
101                try {
102                        cipher = CipherHelper.getInstance("AES/CBC/PKCS5Padding", provider);
103
104                        SecretKeySpec keyspec = new SecretKeySpec(secretKey.getEncoded(), "AES");
105
106                        IvParameterSpec ivSpec = new IvParameterSpec(iv);
107
108                        if (forEncryption) {
109
110                                cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivSpec);
111
112                        } else {
113
114                                cipher.init(Cipher.DECRYPT_MODE, keyspec, ivSpec);
115                        }
116
117                } catch (Exception e) {
118
119                        throw new JOSEException(e.getMessage(), e);
120                }
121
122                return cipher;
123        }
124
125
126        /**
127         * Encrypts the specified plain text using AES/CBC/PKCS5Padding.
128         *
129         * @param secretKey The AES key. Must not be {@code null}.
130         * @param iv        The initialisation vector (IV). Must not be
131         *                  {@code null}.
132         * @param plainText The plain text. Must not be {@code null}.
133         * @param provider  The JCA provider, {@code null} to use the default.
134         *
135         * @return The cipher text.
136         *
137         * @throws JOSEException If encryption failed.
138         */
139        public static byte[] encrypt(final SecretKey secretKey, 
140                                     final byte[] iv,
141                                     final byte[] plainText,
142                                     final Provider provider)
143                throws JOSEException {
144
145                Cipher cipher = createAESCBCCipher(secretKey, true, iv, provider);
146
147                try {
148                        return cipher.doFinal(plainText);       
149                
150                } catch (Exception e) {
151
152                        throw new JOSEException(e.getMessage(), e);
153                }
154        }
155
156
157        /**
158         * Encrypts the specified plain text using AES/CBC/PKCS5Padding/
159         * HMAC-SHA2.
160         * 
161         * <p>See RFC 7518 (JWA), section 5.2.2.1
162         *
163         * <p>See draft-mcgrew-aead-aes-cbc-hmac-sha2-01
164         *
165         * @param secretKey   The secret key. Must be 256 or 512 bits long.
166         *                    Must not be {@code null}.
167         * @param iv          The initialisation vector (IV). Must not be
168         *                    {@code null}.
169         * @param plainText   The plain text. Must not be {@code null}.
170         * @param aad         The additional authenticated data. Must not be
171         *                    {@code null}.
172         * @param ceProvider  The JCA provider for the content encryption, or
173         *                    {@code null} to use the default one.
174         * @param macProvider The JCA provider for the MAC computation, or
175         *                    {@code null} to use the default one.
176         *
177         * @return The authenticated cipher text.
178         *
179         * @throws JOSEException If encryption failed.
180         */
181        public static AuthenticatedCipherText encryptAuthenticated(final SecretKey secretKey,
182                                                                   final byte[] iv,
183                                                                   final byte[] plainText,
184                                                                   final byte[] aad,
185                                                                   final Provider ceProvider,
186                                                                   final Provider macProvider)
187                throws JOSEException {
188
189                // Extract MAC + AES/CBC keys from input secret key
190                CompositeKey compositeKey = new CompositeKey(secretKey);
191
192                // Encrypt plain text
193                byte[] cipherText = encrypt(compositeKey.getAESKey(), iv, plainText, ceProvider);
194
195                // AAD length to 8 byte array
196                byte[] al = AAD.computeLength(aad);
197
198                // Do MAC
199                int hmacInputLength = aad.length + iv.length + cipherText.length + al.length;
200                byte[] hmacInput = ByteBuffer.allocate(hmacInputLength).put(aad).put(iv).put(cipherText).put(al).array();
201                byte[] hmac = HMAC.compute(compositeKey.getMACKey(), hmacInput, macProvider);
202                byte[] authTag = Arrays.copyOf(hmac, compositeKey.getTruncatedMACByteLength());
203
204                return new AuthenticatedCipherText(cipherText, authTag);
205        }
206
207
208        /**
209         * Encrypts the specified plain text using the deprecated concat KDF
210         * from JOSE draft suite 09.
211         *
212         * @param header       The JWE header. Must not be {@code null}.
213         * @param secretKey    The secret key. Must be 256 or 512 bits long.
214         *                     Must not be {@code null}.
215         * @param encryptedKey The encrypted key. Must not be {@code null}.
216         * @param iv           The initialisation vector (IV). Must not be
217         *                     {@code null}.
218         * @param plainText    The plain text. Must not be {@code null}.
219         * @param ceProvider   The JCA provider for the content encryption, or
220         *                     {@code null} to use the default one.
221         * @param macProvider  The JCA provider for the MAC computation, or
222         *                     {@code null} to use the default one.
223         *
224         * @return The authenticated cipher text.
225         *
226         * @throws JOSEException If encryption failed.
227         */
228        public static AuthenticatedCipherText encryptWithConcatKDF(final JWEHeader header,
229                                                                   final SecretKey secretKey,
230                                                                   final Base64URL encryptedKey,
231                                                                   final byte[] iv,
232                                                                   final byte[] plainText,
233                                                                   final Provider ceProvider,
234                                                                   final Provider macProvider)
235                throws JOSEException {
236
237                byte[] epu = null;
238
239                if (header.getCustomParam("epu") instanceof String) {
240
241                        epu = new Base64URL((String)header.getCustomParam("epu")).decode();
242                }
243
244                byte[] epv = null;
245
246                if (header.getCustomParam("epv") instanceof String) {
247
248                        epv = new Base64URL((String)header.getCustomParam("epv")).decode();
249                }
250
251                // Generate alternative CEK using concat-KDF
252                SecretKey altCEK = LegacyConcatKDF.generateCEK(secretKey, header.getEncryptionMethod(), epu, epv);
253
254                byte[] cipherText = AESCBC.encrypt(altCEK, iv, plainText, ceProvider);
255
256                // Generate content integrity key for HMAC
257                SecretKey cik = LegacyConcatKDF.generateCIK(secretKey, header.getEncryptionMethod(), epu, epv);
258
259                String macInput = header.toBase64URL() + "." +
260                        encryptedKey + "." +
261                        Base64URL.encode(iv) + "." +
262                        Base64URL.encode(cipherText);
263
264                byte[] mac = HMAC.compute(cik, macInput.getBytes(StandardCharset.UTF_8), macProvider);
265
266                return new AuthenticatedCipherText(cipherText, mac);
267        }
268
269
270        /**
271         * Decrypts the specified cipher text using AES/CBC/PKCS5Padding.
272         *
273         * @param secretKey  The AES key. Must not be {@code null}.
274         * @param iv         The initialisation vector (IV). Must not be
275         *                   {@code null}.
276         * @param cipherText The cipher text. Must not be {@code null}.
277         * @param provider   The JCA provider, {@code null} to use the default.
278         *
279         * @return The decrypted plain text.
280         *
281         * @throws JOSEException If decryption failed.
282         */
283        public static byte[] decrypt(final SecretKey secretKey, 
284                                     final byte[] iv,
285                                     final byte[] cipherText,
286                                     final Provider provider)
287                throws JOSEException {
288
289                Cipher cipher = createAESCBCCipher(secretKey, false, iv, provider);
290
291                try {
292                        return cipher.doFinal(cipherText);
293
294                } catch (Exception e) {
295
296                        throw new JOSEException(e.getMessage(), e);
297                }
298        }
299
300
301        /**
302         * Decrypts the specified cipher text using AES/CBC/PKCS5Padding/
303         * HMAC-SHA2.
304         * 
305         * <p>See RFC 7518 (JWA), section 5.2.2.2
306         *
307         * <p>See draft-mcgrew-aead-aes-cbc-hmac-sha2-01
308         *
309         * @param secretKey   The secret key. Must be 256 or 512 bits long.
310         *                    Must not be {@code null}.
311         * @param iv          The initialisation vector (IV). Must not be
312         *                    {@code null}.
313         * @param cipherText  The cipher text. Must not be {@code null}.
314         * @param aad         The additional authenticated data. Must not be
315         *                    {@code null}.
316         * @param authTag     The authentication tag. Must not be {@code null}.
317         * @param ceProvider  The JCA provider for the content encryption, or
318         *                    {@code null} to use the default one.
319         * @param macProvider The JCA provider for the MAC computation, or
320         *                    {@code null} to use the default one.
321         *
322         * @return The decrypted plain text.
323         *
324         * @throws JOSEException If decryption failed.
325         */
326        public static byte[] decryptAuthenticated(final SecretKey secretKey,
327                                                  final byte[] iv,
328                                                  final byte[] cipherText,
329                                                  final byte[] aad,
330                                                  final byte[] authTag,
331                                                  final Provider ceProvider,
332                                                  final Provider macProvider)
333                throws JOSEException {
334
335
336                // Extract MAC + AES/CBC keys from input secret key
337                CompositeKey compositeKey = new CompositeKey(secretKey);
338
339                // AAD length to 8 byte array
340                byte[] al = AAD.computeLength(aad);
341
342                // Check MAC
343                int hmacInputLength = aad.length + iv.length + cipherText.length + al.length;
344                byte[] hmacInput = ByteBuffer.allocate(hmacInputLength).
345                        put(aad).
346                        put(iv).
347                        put(cipherText).
348                        put(al).
349                        array();
350                byte[] hmac = HMAC.compute(compositeKey.getMACKey(), hmacInput, macProvider);
351
352                byte[] expectedAuthTag = Arrays.copyOf(hmac, compositeKey.getTruncatedMACByteLength());
353
354                if (! ConstantTimeUtils.areEqual(expectedAuthTag, authTag)) {
355                        throw new JOSEException("MAC check failed");
356                }
357
358                return decrypt(compositeKey.getAESKey(), iv, cipherText, ceProvider);
359        }
360
361
362        /**
363         * Decrypts the specified cipher text using the deprecated concat KDF
364         * from JOSE draft suite 09.
365         *
366         * @param header       The JWE header. Must not be {@code null}.
367         * @param secretKey    The secret key. Must be 256 or 512 bits long.
368         *                     Must not be {@code null}.
369         * @param encryptedKey The encrypted key. Must not be {@code null}.
370         * @param iv           The initialisation vector (IV). Must not be
371         *                     {@code null}.
372         * @param cipherText   The cipher text. Must not be {@code null}.
373         * @param authTag      The authentication tag. Must not be {@code null}.
374         * @param ceProvider   The JCA provider for the content encryption, or
375         *                    {@code null} to use the default one.
376         * @param macProvider The JCA provider for the MAC computation, or
377         *                    {@code null} to use the default one.
378         *
379         * @return The decrypted plain text.
380         *
381         * @throws JOSEException If decryption failed.
382         */
383        public static byte[] decryptWithConcatKDF(final JWEHeader header,
384                                                  final SecretKey secretKey,
385                                                  final Base64URL encryptedKey,
386                                                  final Base64URL iv,
387                                                  final Base64URL cipherText,
388                                                  final Base64URL authTag,
389                                                  final Provider ceProvider,
390                                                  final Provider macProvider)
391                throws JOSEException {
392
393                byte[] epu = null;
394
395                if (header.getCustomParam("epu") instanceof String) {
396
397                        epu = new Base64URL((String)header.getCustomParam("epu")).decode();
398                }
399
400                byte[] epv = null;
401
402                if (header.getCustomParam("epv") instanceof String) {
403
404                        epv = new Base64URL((String)header.getCustomParam("epv")).decode();
405                }
406                
407                SecretKey cik = LegacyConcatKDF.generateCIK(secretKey, header.getEncryptionMethod(), epu, epv);
408                
409                String macInput = header.toBase64URL().toString() + "." +
410                        encryptedKey.toString() + "." +
411                        iv.toString() + "." +
412                        cipherText.toString();
413                
414                byte[] mac = HMAC.compute(cik, macInput.getBytes(StandardCharset.UTF_8), macProvider);
415                
416                if (! ConstantTimeUtils.areEqual(authTag.decode(), mac)) {
417                        throw new JOSEException("MAC check failed");
418                }
419
420                SecretKey cekAlt = LegacyConcatKDF.generateCEK(secretKey, header.getEncryptionMethod(), epu, epv);
421
422                return AESCBC.decrypt(cekAlt, iv.decode(), cipherText.decode(), ceProvider);
423        }
424
425
426        /**
427         * Prevents public instantiation.
428         */
429        private AESCBC() { }
430}