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}