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.*;
022import java.security.spec.InvalidParameterSpecException;
023import javax.crypto.*;
024import javax.crypto.spec.GCMParameterSpec;
025
026import com.nimbusds.jose.JOSEException;
027import com.nimbusds.jose.util.ByteUtils;
028import com.nimbusds.jose.util.Container;
029import com.nimbusds.jose.util.KeyUtils;
030import net.jcip.annotations.ThreadSafe;
031
032
033/**
034 * AES/GSM/NoPadding encryption and decryption methods. Falls back to the
035 * BouncyCastle.org provider on Java 6. This class is thread-safe.
036 *
037 * <p>See RFC 7518 (JWA), section 5.1 and appendix 3.
038 *
039 * @author Vladimir Dzhuvinov
040 * @author Axel Nennker
041 * @author Dimitar A. Stoikov
042 * @version 2018-01-11
043 */
044@ThreadSafe
045public class AESGCM {
046
047
048        /**
049         * The standard Initialisation Vector (IV) length (96 bits).
050         */
051        public static final int IV_BIT_LENGTH = 96;
052
053
054        /**
055         * The standard authentication tag length (128 bits).
056         */
057        public static final int AUTH_TAG_BIT_LENGTH = 128;
058
059
060        /**
061         * Generates a random 96 bit (12 byte) Initialisation Vector(IV) for
062         * use in AES-GCM encryption.
063         *
064         * <p>See RFC 7518 (JWA), section 5.3.
065         *
066         * @param randomGen The secure random generator to use. Must be 
067         *                  correctly initialised and not {@code null}.
068         *
069         * @return The random 96 bit IV, as 12 byte array.
070         */
071        public static byte[] generateIV(final SecureRandom randomGen) {
072                
073                byte[] bytes = new byte[IV_BIT_LENGTH / 8];
074                randomGen.nextBytes(bytes);
075                return bytes;
076        }
077
078
079        /**
080         * Encrypts the specified plain text using AES/GCM/NoPadding.
081         *
082         * @param secretKey   The AES key. Must not be {@code null}.
083         * @param plainText   The plain text. Must not be {@code null}.
084         * @param ivContainer The initialisation vector (IV). Must not be
085         *                    {@code null}. This is both input and output
086         *                    parameter. On input, it carries externally
087         *                    generated IV; on output, it carries the IV the
088         *                    cipher actually used. JCA/JCE providers may
089         *                    prefer to use an internally generated IV, e.g. as
090         *                    described in
091         *                    <a href="http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf">NIST
092         *                    Special Publication 800-38D </a>.
093         * @param authData    The authenticated data. Must not be {@code null}.
094         *
095         * @return The authenticated cipher text.
096         *
097         * @throws JOSEException If encryption failed.
098         */
099        public static AuthenticatedCipherText encrypt(final SecretKey secretKey,
100                                                      final Container<byte[]> ivContainer,
101                                                      final byte[] plainText,
102                                                      final byte[] authData,
103                                                      final Provider provider)
104                throws JOSEException {
105
106                // Key alg must be "AES"
107                final SecretKey aesKey = KeyUtils.toAESKey(secretKey);
108                
109                Cipher cipher;
110
111                byte[] iv = ivContainer.get();
112
113                try {
114                        if (provider != null) {
115                                cipher = Cipher.getInstance("AES/GCM/NoPadding", provider);
116                        } else {
117                                cipher = Cipher.getInstance("AES/GCM/NoPadding");
118                        }
119
120                        GCMParameterSpec gcmSpec = new GCMParameterSpec(AUTH_TAG_BIT_LENGTH, iv);
121                        cipher.init(Cipher.ENCRYPT_MODE, aesKey, gcmSpec);
122
123                } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) {
124
125                        throw new JOSEException("Couldn't create AES/GCM/NoPadding cipher: " + e.getMessage(), e);
126
127                } catch (NoClassDefFoundError e) {
128                        // We have Java 6, GCMParameterSpec not available,
129                        // switch to BouncyCastle API
130                        return LegacyAESGCM.encrypt(aesKey, iv, plainText, authData);
131                }
132
133                cipher.updateAAD(authData);
134
135                byte[] cipherOutput;
136
137                try {
138                        cipherOutput = cipher.doFinal(plainText);
139
140                } catch (IllegalBlockSizeException | BadPaddingException e) {
141
142                        throw new JOSEException("Couldn't encrypt with AES/GCM/NoPadding: " + e.getMessage(), e);
143                }
144
145                final int tagPos = cipherOutput.length - ByteUtils.byteLength(AUTH_TAG_BIT_LENGTH);
146
147                byte[] cipherText = ByteUtils.subArray(cipherOutput, 0, tagPos);
148                byte[] authTag = ByteUtils.subArray(cipherOutput, tagPos, ByteUtils.byteLength(AUTH_TAG_BIT_LENGTH));
149
150                // retrieve the actual IV used by the cipher -- it may be internally-generated.
151                ivContainer.set(actualIVOf(cipher));
152
153                return new AuthenticatedCipherText(cipherText, authTag);
154        }
155
156        
157        /**
158         * Retrieves the actual algorithm parameters and validates them.
159         *
160         * @param cipher The cipher to interrogate for the parameters it
161         *               actually used.
162         *
163         * @return The IV used by the specified cipher.
164         *
165         * @throws JOSEException If retrieval of the algorithm parameters from
166         *                       the cipher failed, or the parameters are
167         *                       deemed unusable.
168         *
169         * @see {@link #actualParamsOf(Cipher)}
170         * @see #validate(byte[], int)
171         */
172        private static byte[] actualIVOf(final Cipher cipher)
173                throws JOSEException {
174                
175                GCMParameterSpec actualParams = actualParamsOf(cipher);
176
177                byte[] iv = actualParams.getIV();
178                int tLen = actualParams.getTLen();
179
180                validate(iv, tLen);
181
182                return iv;
183        }
184
185        
186        /**
187         * Validates the specified IV and authentication tag according to the
188         * AES GCM requirements in
189         * <a href="https://tools.ietf.org/html/rfc7518#section-5.3">JWA RFC</a>.
190         *
191         * @param iv            The IV to check for compliance.
192         * @param authTagLength The authentication tag length to check for
193         *                      compliance.
194         *
195         * @throws JOSEException If the parameters don't match the JWA
196         *                       requirements.
197         *
198         * @see #IV_BIT_LENGTH
199         * @see #AUTH_TAG_BIT_LENGTH
200         */
201        private static void validate(final byte[] iv, final int authTagLength)
202                throws JOSEException {
203                
204                if (ByteUtils.safeBitLength(iv) != IV_BIT_LENGTH) {
205                        throw new JOSEException(String.format("IV length of %d bits is required, got %d", IV_BIT_LENGTH, ByteUtils.safeBitLength(iv)));
206                }
207
208                if (authTagLength != AUTH_TAG_BIT_LENGTH) {
209                        throw new JOSEException(String.format("Authentication tag length of %d bits is required, got %d", AUTH_TAG_BIT_LENGTH, authTagLength));
210                }
211        }
212
213        
214        /**
215         * Retrieves the actual AES GCM parameters used by the specified
216         * cipher.
217         *
218         * @param cipher The cipher to interrogate. Non-{@code null}.
219         *
220         * @return The AES GCM parameters. Non-{@code null}.
221         *
222         * @throws JOSEException If the parameters cannot be retrieved, are
223         * uninitialized, or are not in the correct form. We want to have the
224         * actual parameters used by the cipher and not rely on the assumption
225         * that they were the same as those we supplied it with. If at runtime
226         * the assumption is incorrect, the ciphertext would not be
227         * decryptable.
228         */
229        private static GCMParameterSpec actualParamsOf(final Cipher cipher)
230                throws JOSEException {
231                
232                AlgorithmParameters algorithmParameters = cipher.getParameters();
233                
234                if (algorithmParameters == null) {
235                        throw new JOSEException("AES GCM ciphers are expected to make use of algorithm parameters");
236                }
237
238                try {
239                        // Note: GCMParameterSpec appears in Java 7
240                        return algorithmParameters.getParameterSpec(GCMParameterSpec.class);
241                } catch (InvalidParameterSpecException shouldNotHappen) {
242                        throw new JOSEException(shouldNotHappen.getMessage(), shouldNotHappen);
243                }
244        }
245
246        
247        /**
248         * Decrypts the specified cipher text using AES/GCM/NoPadding.
249         *
250         * @param secretKey  The AES key. Must not be {@code null}.
251         * @param iv         The initialisation vector (IV). Must not be
252         *                   {@code null}.
253         * @param cipherText The cipher text. Must not be {@code null}.
254         * @param authData   The authenticated data. Must not be {@code null}.
255         * @param authTag    The authentication tag. Must not be {@code null}.
256         *
257         * @return The decrypted plain text.
258         *
259         * @throws JOSEException If decryption failed.
260         */
261        public static byte[] decrypt(final SecretKey secretKey, 
262                                     final byte[] iv,
263                                     final byte[] cipherText,
264                                     final byte[] authData,
265                                     final byte[] authTag,
266                                     final Provider provider)
267                throws JOSEException {
268                
269                // Key alg must be "AES"
270                final SecretKey aesKey = KeyUtils.toAESKey(secretKey);
271                
272                Cipher cipher;
273
274                try {
275                        if (provider != null) {
276                                cipher = Cipher.getInstance("AES/GCM/NoPadding", provider);
277                        } else {
278                                cipher = Cipher.getInstance("AES/GCM/NoPadding");
279                        }
280
281                        GCMParameterSpec gcmSpec = new GCMParameterSpec(AUTH_TAG_BIT_LENGTH, iv);
282                        cipher.init(Cipher.DECRYPT_MODE, aesKey, gcmSpec);
283
284                } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) {
285
286                        throw new JOSEException("Couldn't create AES/GCM/NoPadding cipher: " + e.getMessage(), e);
287
288                } catch (NoClassDefFoundError e) {
289                        // We have Java 6, GCMParameterSpec not available,
290                        // switch to BouncyCastle API
291                        return LegacyAESGCM.decrypt(aesKey, iv, cipherText, authData, authTag);
292                }
293
294                cipher.updateAAD(authData);
295
296                try {
297                        return cipher.doFinal(ByteUtils.concat(cipherText, authTag));
298
299                } catch (IllegalBlockSizeException | BadPaddingException e) {
300
301                        throw new JOSEException("AES/GCM/NoPadding decryption failed: " + e.getMessage(), e);
302                }
303        }
304
305
306        /**
307         * Prevents public instantiation.
308         */
309        private AESGCM() { }
310}