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 2017-06-01
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                try {
133                        if (enc.cekBitLength() != ByteUtils.safeBitLength(cek.getEncoded())) {
134                                throw new KeyLengthException("The Content Encryption Key (CEK) length for " + enc + " must be " + enc.cekBitLength() + " bits");
135                        }
136                } catch (IntegerOverflowException e) {
137                        throw new KeyLengthException("The Content Encryption Key (CEK) is too long: " + e.getMessage());
138                }
139        }
140
141
142        /**
143         * Encrypts the specified clear text (content).
144         *
145         * @param header       The final JWE header. Must not be {@code null}.
146         * @param clearText    The clear text to encrypt and optionally
147         *                     compress. Must not be {@code null}.
148         * @param cek          The Content Encryption Key (CEK). Must not be
149         *                     {@code null}.
150         * @param encryptedKey The encrypted CEK, {@code null} if not required.
151         * @param jcaProvider  The JWE JCA provider specification. Must not be
152         *                     {@code null}.
153         *
154         * @return The JWE crypto parts.
155         *
156         * @throws JOSEException If encryption failed.
157         */
158        public static JWECryptoParts encrypt(final JWEHeader header,
159                                             final byte[] clearText,
160                                             final SecretKey cek,
161                                             final Base64URL encryptedKey,
162                                             final JWEJCAContext jcaProvider)
163                throws JOSEException {
164
165                checkCEKLength(cek, header.getEncryptionMethod());
166
167                // Apply compression if instructed
168                final byte[] plainText = DeflateHelper.applyCompression(header, clearText);
169
170                // Compose the AAD
171                final byte[] aad = AAD.compute(header);
172
173                // Encrypt the plain text according to the JWE enc
174                final byte[] iv;
175                final AuthenticatedCipherText authCipherText;
176
177                if (    header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256) ||
178                        header.getEncryptionMethod().equals(EncryptionMethod.A192CBC_HS384) ||
179                        header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512)    ) {
180
181                        iv = AESCBC.generateIV(jcaProvider.getSecureRandom());
182
183                        authCipherText = AESCBC.encryptAuthenticated(
184                                cek, iv, plainText, aad,
185                                jcaProvider.getContentEncryptionProvider(),
186                                jcaProvider.getMACProvider());
187
188                } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128GCM) ||
189                           header.getEncryptionMethod().equals(EncryptionMethod.A192GCM) ||
190                           header.getEncryptionMethod().equals(EncryptionMethod.A256GCM)    ) {
191
192                        Container<byte[]> ivContainer = new Container<>(AESGCM.generateIV(jcaProvider.getSecureRandom()));
193
194                        authCipherText = AESGCM.encrypt(
195                                cek, ivContainer, plainText, aad,
196                                jcaProvider.getContentEncryptionProvider());
197
198                        iv = ivContainer.get();
199
200                } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256_DEPRECATED) ||
201                           header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512_DEPRECATED)    ) {
202
203                        iv = AESCBC.generateIV(jcaProvider.getSecureRandom());
204
205                        authCipherText = AESCBC.encryptWithConcatKDF(
206                                header, cek, encryptedKey, iv, plainText,
207                                jcaProvider.getContentEncryptionProvider(),
208                                jcaProvider.getMACProvider());
209
210                } else if (header.getEncryptionMethod().equals(EncryptionMethod.XC20P)) {
211
212                        Container<byte[]> ivContainer = new Container<>(null);
213
214                        authCipherText = XC20P.encryptAuthenticated(cek, ivContainer, plainText, aad);
215
216                        iv = ivContainer.get();
217
218                } else {
219
220                        throw new JOSEException(AlgorithmSupportMessage.unsupportedEncryptionMethod(
221                                header.getEncryptionMethod(),
222                                SUPPORTED_ENCRYPTION_METHODS));
223                }
224
225                return new JWECryptoParts(
226                        header,
227                        encryptedKey,
228                        Base64URL.encode(iv),
229                        Base64URL.encode(authCipherText.getCipherText()),
230                        Base64URL.encode(authCipherText.getAuthenticationTag()));
231        }
232
233
234        /**
235         * Decrypts the specified cipher text.
236         *
237         * @param header       The JWE header. Must not be {@code null}.
238         * @param encryptedKey The encrypted key, {@code null} if not
239         *                     specified.
240         * @param iv           The initialisation vector (IV). Must not be
241         *                     {@code null}.
242         * @param cipherText   The cipher text. Must not be {@code null}.
243         * @param authTag      The authentication tag. Must not be
244         *                     {@code null}.
245         * @param cek          The Content Encryption Key (CEK). Must not be
246         *                     {@code null}.
247         * @param jcaProvider  The JWE JCA provider specification. Must not be
248         *                     {@code null}.
249         *
250         * @return The clear text.
251         *
252         * @throws JOSEException If decryption failed.
253         */
254        public static byte[] decrypt(final JWEHeader header,
255                                     final Base64URL encryptedKey,
256                                     final Base64URL iv,
257                                     final Base64URL cipherText,
258                                     final Base64URL authTag,
259                                     final SecretKey cek,
260                                     final JWEJCAContext jcaProvider)
261                throws JOSEException {
262
263                checkCEKLength(cek, header.getEncryptionMethod());
264
265                // Compose the AAD
266                byte[] aad = AAD.compute(header);
267
268                // Decrypt the cipher text according to the JWE enc
269
270                byte[] plainText;
271
272                if (header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256) ||
273                        header.getEncryptionMethod().equals(EncryptionMethod.A192CBC_HS384) ||
274                        header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512)) {
275
276                        plainText = AESCBC.decryptAuthenticated(
277                                cek,
278                                iv.decode(),
279                                cipherText.decode(),
280                                aad,
281                                authTag.decode(),
282                                jcaProvider.getContentEncryptionProvider(),
283                                jcaProvider.getMACProvider());
284
285                } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128GCM) ||
286                        header.getEncryptionMethod().equals(EncryptionMethod.A192GCM) ||
287                        header.getEncryptionMethod().equals(EncryptionMethod.A256GCM)) {
288
289                        plainText = AESGCM.decrypt(
290                                cek,
291                                iv.decode(),
292                                cipherText.decode(),
293                                aad,
294                                authTag.decode(),
295                                jcaProvider.getContentEncryptionProvider());
296
297                } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256_DEPRECATED) ||
298                        header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512_DEPRECATED)) {
299
300                        plainText = AESCBC.decryptWithConcatKDF(
301                                header,
302                                cek,
303                                encryptedKey,
304                                iv,
305                                cipherText,
306                                authTag,
307                                jcaProvider.getContentEncryptionProvider(),
308                                jcaProvider.getMACProvider());
309
310                } else if (header.getEncryptionMethod().equals(EncryptionMethod.XC20P)) {
311
312                        plainText = XC20P.decryptAuthenticated(
313                                        cek,
314                                        iv.decode(),
315                                        cipherText.decode(),
316                                        aad,
317                                        authTag.decode()
318                        );
319
320                } else {
321                        throw new JOSEException(AlgorithmSupportMessage.unsupportedEncryptionMethod(
322                                header.getEncryptionMethod(),
323                                SUPPORTED_ENCRYPTION_METHODS));
324                }
325
326
327                // Apply decompression if requested
328                return DeflateHelper.applyDecompression(header, plainText);
329        }
330}