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.security.SecureRandom;
022import java.util.*;
023import javax.crypto.SecretKey;
024import javax.crypto.spec.SecretKeySpec;
025
026import com.nimbusds.jose.*;
027import com.nimbusds.jose.jca.JWEJCAContext;
028import com.nimbusds.jose.util.Base64URL;
029import com.nimbusds.jose.util.ByteUtils;
030import com.nimbusds.jose.util.Container;
031import com.nimbusds.jose.util.IntegerOverflowException;
032
033
034/**
035 * JWE content encryption / decryption provider.
036 *
037 * @author Vladimir Dzhuvinov
038 * @version 2023-03-21
039 */
040public class ContentCryptoProvider {
041
042
043        /**
044         * The supported encryption methods.
045         */
046        public static final Set<EncryptionMethod> SUPPORTED_ENCRYPTION_METHODS;
047
048
049        /**
050         * The encryption methods compatible with each key size in bits.
051         */
052        public static final Map<Integer,Set<EncryptionMethod>> COMPATIBLE_ENCRYPTION_METHODS;
053
054
055        static {
056                Set<EncryptionMethod> methods = new LinkedHashSet<>();
057                methods.add(EncryptionMethod.A128CBC_HS256);
058                methods.add(EncryptionMethod.A192CBC_HS384);
059                methods.add(EncryptionMethod.A256CBC_HS512);
060                methods.add(EncryptionMethod.A128GCM);
061                methods.add(EncryptionMethod.A192GCM);
062                methods.add(EncryptionMethod.A256GCM);
063                methods.add(EncryptionMethod.A128CBC_HS256_DEPRECATED);
064                methods.add(EncryptionMethod.A256CBC_HS512_DEPRECATED);
065                methods.add(EncryptionMethod.XC20P);
066                SUPPORTED_ENCRYPTION_METHODS = Collections.unmodifiableSet(methods);
067
068                Map<Integer,Set<EncryptionMethod>> encsMap = new HashMap<>();
069                Set<EncryptionMethod> bit128Encs = new HashSet<>();
070                Set<EncryptionMethod> bit192Encs = new HashSet<>();
071                Set<EncryptionMethod> bit256Encs = new HashSet<>();
072                Set<EncryptionMethod> bit384Encs = new HashSet<>();
073                Set<EncryptionMethod> bit512Encs = new HashSet<>();
074                bit128Encs.add(EncryptionMethod.A128GCM);
075                bit192Encs.add(EncryptionMethod.A192GCM);
076                bit256Encs.add(EncryptionMethod.A256GCM);
077                bit256Encs.add(EncryptionMethod.A128CBC_HS256);
078                bit256Encs.add(EncryptionMethod.A128CBC_HS256_DEPRECATED);
079                bit256Encs.add(EncryptionMethod.XC20P);
080                bit384Encs.add(EncryptionMethod.A192CBC_HS384);
081                bit512Encs.add(EncryptionMethod.A256CBC_HS512);
082                bit512Encs.add(EncryptionMethod.A256CBC_HS512_DEPRECATED);
083                encsMap.put(128,Collections.unmodifiableSet(bit128Encs));
084                encsMap.put(192,Collections.unmodifiableSet(bit192Encs));
085                encsMap.put(256,Collections.unmodifiableSet(bit256Encs));
086                encsMap.put(384,Collections.unmodifiableSet(bit384Encs));
087                encsMap.put(512, Collections.unmodifiableSet(bit512Encs));
088                COMPATIBLE_ENCRYPTION_METHODS = Collections.unmodifiableMap(encsMap);
089        }
090
091
092        /**
093         * Generates a Content Encryption Key (CEK) for the specified JOSE
094         * encryption method.
095         *
096         * @param enc       The encryption method. Must not be {@code null}.
097         * @param randomGen The secure random generator to use. Must not be
098         *                  {@code null}.
099         *
100         * @return The generated CEK (with algorithm "AES").
101         *
102         * @throws JOSEException If the encryption method is not supported.
103         */
104        public static SecretKey generateCEK(final EncryptionMethod enc, final SecureRandom randomGen)
105                throws JOSEException {
106
107                if (! SUPPORTED_ENCRYPTION_METHODS.contains(enc)) {
108                        throw new JOSEException(AlgorithmSupportMessage.unsupportedEncryptionMethod(enc, SUPPORTED_ENCRYPTION_METHODS));
109                }
110
111                final byte[] cekMaterial = new byte[ByteUtils.byteLength(enc.cekBitLength())];
112
113                randomGen.nextBytes(cekMaterial);
114
115                return new SecretKeySpec(cekMaterial, "AES");
116        }
117
118
119        /**
120         * Checks the length of the Content Encryption Key (CEK) according to
121         * the encryption method.
122         *
123         * @param cek The CEK. Must not be {@code null}.
124         * @param enc The encryption method. Must not be {@code null}.
125         *
126         * @throws KeyLengthException If the CEK length doesn't match the
127         *                            encryption method.
128         */
129        private static void checkCEKLength(final SecretKey cek, final EncryptionMethod enc)
130                throws KeyLengthException {
131                
132                final int cekBitLength;
133                try {
134                        cekBitLength = ByteUtils.safeBitLength(cek.getEncoded());
135                } catch (IntegerOverflowException e) {
136                        throw new KeyLengthException("The Content Encryption Key (CEK) is too long: " + e.getMessage());
137                }
138                
139                if (cekBitLength == 0) {
140                        // Suspect HSM that doesn't expose key material
141                        // https://bitbucket.org/connect2id/nimbus-jose-jwt/issues/490/jwe-with-shared-key-support-for-android
142                        return;
143                }
144                
145                if (enc.cekBitLength() != cekBitLength) {
146                        throw new KeyLengthException("The Content Encryption Key (CEK) length for " + enc + " must be " + enc.cekBitLength() + " bits");
147                }
148        }
149
150
151        /**
152         * Encrypts the specified clear text (content).
153         *
154         * @param header       The final JWE header. Must not be {@code null}.
155         * @param clearText    The clear text to encrypt and optionally
156         *                     compress. Must not be {@code null}.
157         * @param cek          The Content Encryption Key (CEK). Must not be
158         *                     {@code null}.
159         * @param encryptedKey The encrypted CEK, {@code null} if not required.
160         * @param jcaProvider  The JWE JCA provider specification. Must not be
161         *                     {@code null}.
162         *
163         * @return The JWE crypto parts.
164         *
165         * @throws JOSEException If encryption failed.
166         */
167        public static JWECryptoParts encrypt(final JWEHeader header,
168                                             final byte[] clearText,
169                                             final SecretKey cek,
170                                             final Base64URL encryptedKey,
171                                             final JWEJCAContext jcaProvider)
172                throws JOSEException {
173
174                return encrypt(header, clearText, null, cek, encryptedKey, jcaProvider);
175        }
176
177
178        /**
179         * Encrypts the specified clear text (content).
180         *
181         * @param header       The final JWE header. Must not be {@code null}.
182         * @param clearText    The clear text to encrypt and optionally
183         *                     compress. Must not be {@code null}.
184         * @param aad          The Additional Authenticated Data (AAD), if
185         *                     {@code null} the JWE header becomes the AAD.
186         * @param cek          The Content Encryption Key (CEK). Must not be
187         *                     {@code null}.
188         * @param encryptedKey The encrypted CEK, {@code null} if not required.
189         * @param jcaProvider  The JWE JCA provider specification. Must not be
190         *                     {@code null}.
191         *
192         * @return The JWE crypto parts.
193         *
194         * @throws JOSEException If encryption failed.
195         */
196        public static JWECryptoParts encrypt(final JWEHeader header,
197                                             final byte[] clearText,
198                                             final byte[] aad,
199                                             final SecretKey cek,
200                                             final Base64URL encryptedKey,
201                                             final JWEJCAContext jcaProvider)
202                throws JOSEException {
203
204                
205                if (aad == null) {
206                        // The AAD is the JWE header
207                        return encrypt(header, clearText, AAD.compute(header), cek, encryptedKey, jcaProvider);
208                }
209
210                checkCEKLength(cek, header.getEncryptionMethod());
211
212                // Apply compression if instructed
213                final byte[] plainText = DeflateHelper.applyCompression(header, clearText);
214
215                // Encrypt the plain text according to the JWE enc
216                final byte[] iv;
217                final AuthenticatedCipherText authCipherText;
218
219                if (    header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256) ||
220                        header.getEncryptionMethod().equals(EncryptionMethod.A192CBC_HS384) ||
221                        header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512)    ) {
222
223                        iv = AESCBC.generateIV(jcaProvider.getSecureRandom());
224
225                        authCipherText = AESCBC.encryptAuthenticated(
226                                cek, iv, plainText, aad,
227                                jcaProvider.getContentEncryptionProvider(),
228                                jcaProvider.getMACProvider());
229
230                } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128GCM) ||
231                           header.getEncryptionMethod().equals(EncryptionMethod.A192GCM) ||
232                           header.getEncryptionMethod().equals(EncryptionMethod.A256GCM)    ) {
233
234                        Container<byte[]> ivContainer = new Container<>(AESGCM.generateIV(jcaProvider.getSecureRandom()));
235
236                        authCipherText = AESGCM.encrypt(
237                                cek, ivContainer, plainText, aad,
238                                jcaProvider.getContentEncryptionProvider());
239
240                        iv = ivContainer.get();
241
242                } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256_DEPRECATED) ||
243                           header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512_DEPRECATED)    ) {
244
245                        iv = AESCBC.generateIV(jcaProvider.getSecureRandom());
246
247                        authCipherText = AESCBC.encryptWithConcatKDF(
248                                header, cek, encryptedKey, iv, plainText,
249                                jcaProvider.getContentEncryptionProvider(),
250                                jcaProvider.getMACProvider());
251
252                } else if (header.getEncryptionMethod().equals(EncryptionMethod.XC20P)) {
253
254                        Container<byte[]> ivContainer = new Container<>(null);
255
256                        authCipherText = XC20P.encryptAuthenticated(cek, ivContainer, plainText, aad);
257
258                        iv = ivContainer.get();
259
260                } else {
261
262                        throw new JOSEException(AlgorithmSupportMessage.unsupportedEncryptionMethod(
263                                header.getEncryptionMethod(),
264                                SUPPORTED_ENCRYPTION_METHODS));
265                }
266
267                return new JWECryptoParts(
268                        header,
269                        encryptedKey,
270                        Base64URL.encode(iv),
271                        Base64URL.encode(authCipherText.getCipherText()),
272                        Base64URL.encode(authCipherText.getAuthenticationTag()));
273        }
274
275
276        /**
277         * Decrypts the specified cipher text.
278         *
279         * @param header       The JWE header. Must not be {@code null}.
280         * @param encryptedKey The encrypted key, {@code null} if not
281         *                     specified.
282         * @param iv           The initialisation vector (IV). Must not be
283         *                     {@code null}.
284         * @param cipherText   The cipher text. Must not be {@code null}.
285         * @param authTag      The authentication tag. Must not be
286         *                     {@code null}.
287         * @param cek          The Content Encryption Key (CEK). Must not be
288         *                     {@code null}.
289         * @param jcaProvider  The JWE JCA provider specification. Must not be
290         *                     {@code null}.
291         *
292         * @return The clear text.
293         *
294         * @throws JOSEException If decryption failed.
295         */
296        public static byte[] decrypt(final JWEHeader header,
297                                     final Base64URL encryptedKey,
298                                     final Base64URL iv,
299                                     final Base64URL cipherText,
300                                     final Base64URL authTag,
301                                     final SecretKey cek,
302                                     final JWEJCAContext jcaProvider)
303                throws JOSEException {
304
305                return decrypt(header, null, encryptedKey, iv, cipherText, authTag, cek, jcaProvider);
306        }
307
308
309        /**
310         * Decrypts the specified cipher text.
311         *
312         * @param header       The JWE header. Must not be {@code null}.
313         * @param aad          The Additional Authenticated Data (AAD), if
314         *                     {@code null} the JWE header becomes the AAD.
315         * @param encryptedKey The encrypted key, {@code null} if not
316         *                     specified.
317         * @param iv           The initialisation vector (IV). Must not be
318         *                     {@code null}.
319         * @param cipherText   The cipher text. Must not be {@code null}.
320         * @param authTag      The authentication tag. Must not be
321         *                     {@code null}.
322         * @param cek          The Content Encryption Key (CEK). Must not be
323         *                     {@code null}.
324         * @param jcaProvider  The JWE JCA provider specification. Must not be
325         *                     {@code null}.
326         *
327         * @return The clear text.
328         *
329         * @throws JOSEException If decryption failed.
330         */
331        public static byte[] decrypt(final JWEHeader header,
332                                     final byte[] aad,
333                                     final Base64URL encryptedKey,
334                                     final Base64URL iv,
335                                     final Base64URL cipherText,
336                                     final Base64URL authTag,
337                                     final SecretKey cek,
338                                     final JWEJCAContext jcaProvider)
339                throws JOSEException {
340                
341                if (aad == null) {
342                        // The AAD is the JWE header
343                        return decrypt(header, AAD.compute(header), encryptedKey, iv, cipherText, authTag, cek, jcaProvider);
344                }
345
346                checkCEKLength(cek, header.getEncryptionMethod());
347
348                // Decrypt the cipher text according to the JWE enc
349
350                byte[] plainText;
351
352                if (header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256) ||
353                        header.getEncryptionMethod().equals(EncryptionMethod.A192CBC_HS384) ||
354                        header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512)) {
355
356                        plainText = AESCBC.decryptAuthenticated(
357                                cek,
358                                iv.decode(),
359                                cipherText.decode(),
360                                aad,
361                                authTag.decode(),
362                                jcaProvider.getContentEncryptionProvider(),
363                                jcaProvider.getMACProvider());
364
365                } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128GCM) ||
366                        header.getEncryptionMethod().equals(EncryptionMethod.A192GCM) ||
367                        header.getEncryptionMethod().equals(EncryptionMethod.A256GCM)) {
368
369                        plainText = AESGCM.decrypt(
370                                cek,
371                                iv.decode(),
372                                cipherText.decode(),
373                                aad,
374                                authTag.decode(),
375                                jcaProvider.getContentEncryptionProvider());
376
377                } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256_DEPRECATED) ||
378                        header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512_DEPRECATED)) {
379
380                        plainText = AESCBC.decryptWithConcatKDF(
381                                header,
382                                cek,
383                                encryptedKey,
384                                iv,
385                                cipherText,
386                                authTag,
387                                jcaProvider.getContentEncryptionProvider(),
388                                jcaProvider.getMACProvider());
389
390                } else if (header.getEncryptionMethod().equals(EncryptionMethod.XC20P)) {
391
392                        plainText = XC20P.decryptAuthenticated(
393                                        cek,
394                                        iv.decode(),
395                                        cipherText.decode(),
396                                        aad,
397                                        authTag.decode()
398                        );
399
400                } else {
401                        throw new JOSEException(AlgorithmSupportMessage.unsupportedEncryptionMethod(
402                                header.getEncryptionMethod(),
403                                SUPPORTED_ENCRYPTION_METHODS));
404                }
405
406
407                // Apply decompression if requested
408                return DeflateHelper.applyDecompression(header, plainText);
409        }
410}