View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2016 ForgeRock AS.
15   */
16  
17  package org.forgerock.json.jose.jwe.handlers.encryption;
18  
19  import java.nio.ByteBuffer;
20  import java.nio.ByteOrder;
21  import java.security.GeneralSecurityException;
22  import java.security.Key;
23  import java.security.NoSuchAlgorithmException;
24  import java.util.Arrays;
25  import java.util.logging.Level;
26  import java.util.logging.Logger;
27  
28  import javax.crypto.Cipher;
29  import javax.crypto.KeyGenerator;
30  import javax.crypto.Mac;
31  import javax.crypto.SecretKey;
32  import javax.crypto.spec.IvParameterSpec;
33  import javax.crypto.spec.SecretKeySpec;
34  
35  import org.forgerock.json.jose.exceptions.JweDecryptionException;
36  import org.forgerock.json.jose.exceptions.JweEncryptionException;
37  import org.forgerock.json.jose.jwe.EncryptionMethod;
38  import org.forgerock.json.jose.jwe.JweEncryption;
39  import org.forgerock.json.jose.utils.Utils;
40  
41  /**
42   * Encrypts using AES in CBC mode with PKCS#5 padding and uses a separate HMAC-SHA2 tag to authenticate.
43   */
44  final class AESCBCHMACSHA2ContentEncryptionHandler extends ContentEncryptionHandler {
45      private static final Logger LOGGER = Logger.getLogger(AESCBCHMACSHA2ContentEncryptionHandler.class.getName());
46      private static final String RAW_KEY_FORMAT = "RAW";
47      private final EncryptionMethod method;
48  
49      AESCBCHMACSHA2ContentEncryptionHandler(final EncryptionMethod method) {
50          this.method = method;
51      }
52  
53      @Override
54      public JweEncryption encrypt(final Key key, final byte[] iv, final byte[] plainText, final byte[] additionalData) {
55  
56          final Key macKey = macKey(key, method);
57          final Key encryptionKey = encKey(key, method);
58  
59          try {
60              final Cipher cipher = Cipher.getInstance(method.getTransformation());
61              cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, new IvParameterSpec(iv));
62              final byte[] cipherText = cipher.doFinal(plainText);
63  
64              long alLength = additionalData.length * 8L;
65              byte[] al = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN).putLong(alLength).array();
66  
67              final Mac mac = Mac.getInstance(method.getMacAlgorithm());
68              mac.init(macKey);
69              mac.update(additionalData);
70              mac.update(iv);
71              mac.update(cipherText);
72              mac.update(al);
73  
74              byte[] authenticationTag = Arrays.copyOf(mac.doFinal(), method.getKeyOffset());
75  
76              return new JweEncryption(cipherText, authenticationTag);
77          } catch (GeneralSecurityException e) {
78              throw new JweEncryptionException(e);
79          }
80      }
81  
82      @Override
83      public byte[] decrypt(final Key key, final byte[] iv, final JweEncryption cipherText, final byte[] additionalData) {
84          final Key macKey = macKey(key, method);
85          final Key encKey = encKey(key, method);
86  
87          long alLength = additionalData.length * 8L;
88          byte[] al = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN).putLong(alLength).array();
89  
90          try {
91              final Mac mac = Mac.getInstance(method.getMacAlgorithm());
92              mac.init(macKey);
93              mac.update(additionalData);
94              mac.update(iv);
95              mac.update(cipherText.getCiphertext());
96              mac.update(al);
97  
98              final byte[] tag = Arrays.copyOf(mac.doFinal(), method.getKeyOffset());
99  
100             final boolean macValid = Utils.constantEquals(tag, cipherText.getAuthenticationTag());
101 
102             final Cipher cipher = Cipher.getInstance(method.getTransformation());
103             cipher.init(Cipher.DECRYPT_MODE, encKey, new IvParameterSpec(iv));
104             final byte[] plainText = cipher.doFinal(cipherText.getCiphertext());
105 
106             if (!macValid) {
107                 throw new GeneralSecurityException("MAC verification failed");
108             }
109 
110             return plainText;
111 
112         } catch (GeneralSecurityException ex) {
113             if (LOGGER.isLoggable(Level.FINE)) {
114                 LOGGER.log(Level.FINE, "Decryption failed: " + ex, ex);
115             }
116             throw new JweDecryptionException();
117         }
118     }
119 
120     @Override
121     Key generateEncryptionKey() {
122         // We need to generate a CEK sufficiently large to supply the key for the AES block cipher and the HMAC. As
123         // the HMAC algorithms are all truncated to half of their output size, it is sufficient to generate a CEK
124         // with size (AES key size) + (HMAC SHA Key Size / 2). For example, AES128HS256 will produce a CEK of length
125         // 128 + (256/2) = 256 bits, while AES256HS512 will use 256 + (512/2) = 512 bits. In the latter case, we
126         // cannot simply generate an "AES" key of 512 bits, as this is an invalid key size for AES (and will generate
127         // an exception if we try). So instead, we generate separate keys for the MAC and the AES cipher and
128         // concatenate them as CEK = MAC_KEY + ENC_KEY as per https://tools.ietf.org/html/rfc7518#section-5.2.2.1
129 
130         try {
131             // The keyOffset field gives the size of the MAC key in octets
132             final int macKeySize = method.getKeyOffset() * 8;
133             final KeyGenerator macKeyGenerator = KeyGenerator.getInstance(method.getMacAlgorithm());
134             macKeyGenerator.init(macKeySize);
135             final Key macKey = macKeyGenerator.generateKey();
136             if (!RAW_KEY_FORMAT.equals(macKey.getFormat())) {
137                 throw new IllegalStateException("HMAC KeyGenerator returned non-RAW key material!");
138             }
139 
140             final int encKeySize = method.getKeySize() - macKeySize;
141             final KeyGenerator encKeyGenerator = KeyGenerator.getInstance(method.getEncryptionAlgorithm());
142             encKeyGenerator.init(encKeySize);
143             final Key encKey = encKeyGenerator.generateKey();
144             if (!RAW_KEY_FORMAT.equals(macKey.getFormat())) {
145                 throw new IllegalStateException("AES KeyGenerator returned non-RAW key material!");
146             }
147 
148             final byte[] combinedKey = ByteBuffer.allocate(method.getKeySize() / 8)
149                                                  .put(macKey.getEncoded())
150                                                  .put(encKey.getEncoded())
151                                                  .array();
152 
153             return new SecretKeySpec(combinedKey, method.getEncryptionAlgorithm());
154         } catch (NoSuchAlgorithmException e) {
155             throw new JweEncryptionException("Unsupported Encryption Algorithm, "
156                     + method.getEncryptionAlgorithm(), e);
157         }
158 
159     }
160 
161     private static SecretKey macKey(final Key combinedKey, final EncryptionMethod method) {
162         return new SecretKeySpec(combinedKey.getEncoded(), 0, method.getKeyOffset(), method.getMacAlgorithm());
163     }
164 
165     private static SecretKey encKey(final Key combinedKey, final EncryptionMethod method) {
166         return new SecretKeySpec(combinedKey.getEncoded(), method.getKeyOffset(), method.getKeyOffset(),
167                 method.getEncryptionAlgorithm());
168     }
169 }