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                SUPPORTED_ENCRYPTION_METHODS = Collections.unmodifiableSet(methods);
066
067                Map<Integer,Set<EncryptionMethod>> encsMap = new HashMap<>();
068                Set<EncryptionMethod> bit128Encs = new HashSet<>();
069                Set<EncryptionMethod> bit192Encs = new HashSet<>();
070                Set<EncryptionMethod> bit256Encs = new HashSet<>();
071                Set<EncryptionMethod> bit384Encs = new HashSet<>();
072                Set<EncryptionMethod> bit512Encs = new HashSet<>();
073                bit128Encs.add(EncryptionMethod.A128GCM);
074                bit192Encs.add(EncryptionMethod.A192GCM);
075                bit256Encs.add(EncryptionMethod.A256GCM);
076                bit256Encs.add(EncryptionMethod.A128CBC_HS256);
077                bit256Encs.add(EncryptionMethod.A128CBC_HS256_DEPRECATED);
078                bit384Encs.add(EncryptionMethod.A192CBC_HS384);
079                bit512Encs.add(EncryptionMethod.A256CBC_HS512);
080                bit512Encs.add(EncryptionMethod.A256CBC_HS512_DEPRECATED);
081                encsMap.put(128,Collections.unmodifiableSet(bit128Encs));
082                encsMap.put(192,Collections.unmodifiableSet(bit192Encs));
083                encsMap.put(256,Collections.unmodifiableSet(bit256Encs));
084                encsMap.put(384,Collections.unmodifiableSet(bit384Encs));
085                encsMap.put(512, Collections.unmodifiableSet(bit512Encs));
086                COMPATIBLE_ENCRYPTION_METHODS = Collections.unmodifiableMap(encsMap);
087        }
088
089
090        /**
091         * Generates a Content Encryption Key (CEK) for the specified JOSE
092         * encryption method.
093         *
094         * @param enc       The encryption method. Must not be {@code null}.
095         * @param randomGen The secure random generator to use. Must not be
096         *                  {@code null}.
097         *
098         * @return The generated CEK (with algorithm "AES").
099         *
100         * @throws JOSEException If the encryption method is not supported.
101         */
102        public static SecretKey generateCEK(final EncryptionMethod enc, final SecureRandom randomGen)
103                throws JOSEException {
104
105                if (! SUPPORTED_ENCRYPTION_METHODS.contains(enc)) {
106                        throw new JOSEException(AlgorithmSupportMessage.unsupportedEncryptionMethod(enc, SUPPORTED_ENCRYPTION_METHODS));
107                }
108
109                final byte[] cekMaterial = new byte[ByteUtils.byteLength(enc.cekBitLength())];
110
111                randomGen.nextBytes(cekMaterial);
112
113                return new SecretKeySpec(cekMaterial, "AES");
114        }
115
116
117        /**
118         * Checks the length of the Content Encryption Key (CEK) according to
119         * the encryption method.
120         *
121         * @param cek The CEK. Must not be {@code null}.
122         * @param enc The encryption method. Must not be {@code null}.
123         *
124         * @throws KeyLengthException If the CEK length doesn't match the
125         *                            encryption method.
126         */
127        private static void checkCEKLength(final SecretKey cek, final EncryptionMethod enc)
128                throws KeyLengthException {
129
130                try {
131                        if (enc.cekBitLength() != ByteUtils.safeBitLength(cek.getEncoded())) {
132                                throw new KeyLengthException("The Content Encryption Key (CEK) length for " + enc + " must be " + enc.cekBitLength() + " bits");
133                        }
134                } catch (IntegerOverflowException e) {
135                        throw new KeyLengthException("The Content Encryption Key (CEK) is too long: " + e.getMessage());
136                }
137        }
138
139
140        /**
141         * Encrypts the specified clear text (content).
142         *
143         * @param header       The final JWE header. Must not be {@code null}.
144         * @param clearText    The clear text to encrypt and optionally
145         *                     compress. Must not be {@code null}.
146         * @param cek          The Content Encryption Key (CEK). Must not be
147         *                     {@code null}.
148         * @param encryptedKey The encrypted CEK, {@code null} if not required.
149         * @param jcaProvider  The JWE JCA provider specification. Must not be
150         *                     {@code null}.
151         *
152         * @return The JWE crypto parts.
153         *
154         * @throws JOSEException If encryption failed.
155         */
156        public static JWECryptoParts encrypt(final JWEHeader header,
157                                             final byte[] clearText,
158                                             final SecretKey cek,
159                                             final Base64URL encryptedKey,
160                                             final JWEJCAContext jcaProvider)
161                throws JOSEException {
162
163                checkCEKLength(cek, header.getEncryptionMethod());
164
165                // Apply compression if instructed
166                final byte[] plainText = DeflateHelper.applyCompression(header, clearText);
167
168                // Compose the AAD
169                final byte[] aad = AAD.compute(header);
170
171                // Encrypt the plain text according to the JWE enc
172                final byte[] iv;
173                final AuthenticatedCipherText authCipherText;
174
175                if (    header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256) ||
176                        header.getEncryptionMethod().equals(EncryptionMethod.A192CBC_HS384) ||
177                        header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512)    ) {
178
179                        iv = AESCBC.generateIV(jcaProvider.getSecureRandom());
180
181                        authCipherText = AESCBC.encryptAuthenticated(
182                                cek, iv, plainText, aad,
183                                jcaProvider.getContentEncryptionProvider(),
184                                jcaProvider.getMACProvider());
185
186                } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128GCM) ||
187                           header.getEncryptionMethod().equals(EncryptionMethod.A192GCM) ||
188                           header.getEncryptionMethod().equals(EncryptionMethod.A256GCM)    ) {
189
190                        Container<byte[]> ivContainer = new Container<>(AESGCM.generateIV(jcaProvider.getSecureRandom()));
191
192                        authCipherText = AESGCM.encrypt(
193                                cek, ivContainer, plainText, aad,
194                                jcaProvider.getContentEncryptionProvider());
195
196                        iv = ivContainer.get();
197
198                } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256_DEPRECATED) ||
199                           header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512_DEPRECATED)    ) {
200
201                        iv = AESCBC.generateIV(jcaProvider.getSecureRandom());
202
203                        authCipherText = AESCBC.encryptWithConcatKDF(
204                                header, cek, encryptedKey, iv, plainText,
205                                jcaProvider.getContentEncryptionProvider(),
206                                jcaProvider.getMACProvider());
207
208                } else {
209
210                        throw new JOSEException(AlgorithmSupportMessage.unsupportedEncryptionMethod(
211                                header.getEncryptionMethod(),
212                                SUPPORTED_ENCRYPTION_METHODS));
213                }
214
215                return new JWECryptoParts(
216                        header,
217                        encryptedKey,
218                        Base64URL.encode(iv),
219                        Base64URL.encode(authCipherText.getCipherText()),
220                        Base64URL.encode(authCipherText.getAuthenticationTag()));
221        }
222
223
224        /**
225         * Decrypts the specified cipher text.
226         *
227         * @param header       The JWE header. Must not be {@code null}.
228         * @param encryptedKey The encrypted key, {@code null} if not
229         *                     specified.
230         * @param iv           The initialisation vector (IV). Must not be
231         *                     {@code null}.
232         * @param cipherText   The cipher text. Must not be {@code null}.
233         * @param authTag      The authentication tag. Must not be
234         *                     {@code null}.
235         * @param cek          The Content Encryption Key (CEK). Must not be
236         *                     {@code null}.
237         * @param jcaProvider  The JWE JCA provider specification. Must not be
238         *                     {@code null}.
239         *
240         * @return The clear text.
241         *
242         * @throws JOSEException If decryption failed.
243         */
244        public static byte[] decrypt(final JWEHeader header,
245                                     final Base64URL encryptedKey,
246                                     final Base64URL iv,
247                                     final Base64URL cipherText,
248                                     final Base64URL authTag,
249                                     final SecretKey cek,
250                                     final JWEJCAContext jcaProvider)
251                throws JOSEException {
252
253                checkCEKLength(cek, header.getEncryptionMethod());
254
255                // Compose the AAD
256                byte[] aad = AAD.compute(header);
257
258                // Decrypt the cipher text according to the JWE enc
259
260                byte[] plainText;
261
262                if (header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256) ||
263                        header.getEncryptionMethod().equals(EncryptionMethod.A192CBC_HS384) ||
264                        header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512)) {
265
266                        plainText = AESCBC.decryptAuthenticated(
267                                cek,
268                                iv.decode(),
269                                cipherText.decode(),
270                                aad,
271                                authTag.decode(),
272                                jcaProvider.getContentEncryptionProvider(),
273                                jcaProvider.getMACProvider());
274
275                } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128GCM) ||
276                        header.getEncryptionMethod().equals(EncryptionMethod.A192GCM) ||
277                        header.getEncryptionMethod().equals(EncryptionMethod.A256GCM)) {
278
279                        plainText = AESGCM.decrypt(
280                                cek,
281                                iv.decode(),
282                                cipherText.decode(),
283                                aad,
284                                authTag.decode(),
285                                jcaProvider.getContentEncryptionProvider());
286
287                } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256_DEPRECATED) ||
288                        header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512_DEPRECATED)) {
289
290                        plainText = AESCBC.decryptWithConcatKDF(
291                                header,
292                                cek,
293                                encryptedKey,
294                                iv,
295                                cipherText,
296                                authTag,
297                                jcaProvider.getContentEncryptionProvider(),
298                                jcaProvider.getMACProvider());
299
300                } else {
301                        throw new JOSEException(AlgorithmSupportMessage.unsupportedEncryptionMethod(
302                                header.getEncryptionMethod(),
303                                SUPPORTED_ENCRYPTION_METHODS));
304                }
305
306
307                // Apply decompression if requested
308                return DeflateHelper.applyDecompression(header, plainText);
309        }
310}