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 2023-03-21 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 final int cekBitLength; 133 try { 134 cekBitLength = ByteUtils.safeBitLength(cek.getEncoded()); 135 } catch (IntegerOverflowException e) { 136 throw new KeyLengthException("The Content Encryption Key (CEK) is too long: " + e.getMessage()); 137 } 138 139 if (cekBitLength == 0) { 140 // Suspect HSM that doesn't expose key material 141 // https://bitbucket.org/connect2id/nimbus-jose-jwt/issues/490/jwe-with-shared-key-support-for-android 142 return; 143 } 144 145 if (enc.cekBitLength() != cekBitLength) { 146 throw new KeyLengthException("The Content Encryption Key (CEK) length for " + enc + " must be " + enc.cekBitLength() + " bits"); 147 } 148 } 149 150 151 /** 152 * Encrypts the specified clear text (content). 153 * 154 * @param header The final JWE header. Must not be {@code null}. 155 * @param clearText The clear text to encrypt and optionally 156 * compress. Must not be {@code null}. 157 * @param cek The Content Encryption Key (CEK). Must not be 158 * {@code null}. 159 * @param encryptedKey The encrypted CEK, {@code null} if not required. 160 * @param jcaProvider The JWE JCA provider specification. Must not be 161 * {@code null}. 162 * 163 * @return The JWE crypto parts. 164 * 165 * @throws JOSEException If encryption failed. 166 */ 167 public static JWECryptoParts encrypt(final JWEHeader header, 168 final byte[] clearText, 169 final SecretKey cek, 170 final Base64URL encryptedKey, 171 final JWEJCAContext jcaProvider) 172 throws JOSEException { 173 174 return encrypt(header, clearText, null, cek, encryptedKey, jcaProvider); 175 } 176 177 178 /** 179 * Encrypts the specified clear text (content). 180 * 181 * @param header The final JWE header. Must not be {@code null}. 182 * @param clearText The clear text to encrypt and optionally 183 * compress. Must not be {@code null}. 184 * @param aad The Additional Authenticated Data (AAD), if 185 * {@code null} the JWE header becomes the AAD. 186 * @param cek The Content Encryption Key (CEK). Must not be 187 * {@code null}. 188 * @param encryptedKey The encrypted CEK, {@code null} if not required. 189 * @param jcaProvider The JWE JCA provider specification. Must not be 190 * {@code null}. 191 * 192 * @return The JWE crypto parts. 193 * 194 * @throws JOSEException If encryption failed. 195 */ 196 public static JWECryptoParts encrypt(final JWEHeader header, 197 final byte[] clearText, 198 final byte[] aad, 199 final SecretKey cek, 200 final Base64URL encryptedKey, 201 final JWEJCAContext jcaProvider) 202 throws JOSEException { 203 204 205 if (aad == null) { 206 // The AAD is the JWE header 207 return encrypt(header, clearText, AAD.compute(header), cek, encryptedKey, jcaProvider); 208 } 209 210 checkCEKLength(cek, header.getEncryptionMethod()); 211 212 // Apply compression if instructed 213 final byte[] plainText = DeflateHelper.applyCompression(header, clearText); 214 215 // Encrypt the plain text according to the JWE enc 216 final byte[] iv; 217 final AuthenticatedCipherText authCipherText; 218 219 if ( header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256) || 220 header.getEncryptionMethod().equals(EncryptionMethod.A192CBC_HS384) || 221 header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512) ) { 222 223 iv = AESCBC.generateIV(jcaProvider.getSecureRandom()); 224 225 authCipherText = AESCBC.encryptAuthenticated( 226 cek, iv, plainText, aad, 227 jcaProvider.getContentEncryptionProvider(), 228 jcaProvider.getMACProvider()); 229 230 } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128GCM) || 231 header.getEncryptionMethod().equals(EncryptionMethod.A192GCM) || 232 header.getEncryptionMethod().equals(EncryptionMethod.A256GCM) ) { 233 234 Container<byte[]> ivContainer = new Container<>(AESGCM.generateIV(jcaProvider.getSecureRandom())); 235 236 authCipherText = AESGCM.encrypt( 237 cek, ivContainer, plainText, aad, 238 jcaProvider.getContentEncryptionProvider()); 239 240 iv = ivContainer.get(); 241 242 } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256_DEPRECATED) || 243 header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512_DEPRECATED) ) { 244 245 iv = AESCBC.generateIV(jcaProvider.getSecureRandom()); 246 247 authCipherText = AESCBC.encryptWithConcatKDF( 248 header, cek, encryptedKey, iv, plainText, 249 jcaProvider.getContentEncryptionProvider(), 250 jcaProvider.getMACProvider()); 251 252 } else if (header.getEncryptionMethod().equals(EncryptionMethod.XC20P)) { 253 254 Container<byte[]> ivContainer = new Container<>(null); 255 256 authCipherText = XC20P.encryptAuthenticated(cek, ivContainer, plainText, aad); 257 258 iv = ivContainer.get(); 259 260 } else { 261 262 throw new JOSEException(AlgorithmSupportMessage.unsupportedEncryptionMethod( 263 header.getEncryptionMethod(), 264 SUPPORTED_ENCRYPTION_METHODS)); 265 } 266 267 return new JWECryptoParts( 268 header, 269 encryptedKey, 270 Base64URL.encode(iv), 271 Base64URL.encode(authCipherText.getCipherText()), 272 Base64URL.encode(authCipherText.getAuthenticationTag())); 273 } 274 275 276 /** 277 * Decrypts the specified cipher text. 278 * 279 * @param header The JWE header. Must not be {@code null}. 280 * @param encryptedKey The encrypted key, {@code null} if not 281 * specified. 282 * @param iv The initialisation vector (IV). Must not be 283 * {@code null}. 284 * @param cipherText The cipher text. Must not be {@code null}. 285 * @param authTag The authentication tag. Must not be 286 * {@code null}. 287 * @param cek The Content Encryption Key (CEK). Must not be 288 * {@code null}. 289 * @param jcaProvider The JWE JCA provider specification. Must not be 290 * {@code null}. 291 * 292 * @return The clear text. 293 * 294 * @throws JOSEException If decryption failed. 295 */ 296 public static byte[] decrypt(final JWEHeader header, 297 final Base64URL encryptedKey, 298 final Base64URL iv, 299 final Base64URL cipherText, 300 final Base64URL authTag, 301 final SecretKey cek, 302 final JWEJCAContext jcaProvider) 303 throws JOSEException { 304 305 return decrypt(header, null, encryptedKey, iv, cipherText, authTag, cek, jcaProvider); 306 } 307 308 309 /** 310 * Decrypts the specified cipher text. 311 * 312 * @param header The JWE header. Must not be {@code null}. 313 * @param aad The Additional Authenticated Data (AAD), if 314 * {@code null} the JWE header becomes the AAD. 315 * @param encryptedKey The encrypted key, {@code null} if not 316 * specified. 317 * @param iv The initialisation vector (IV). Must not be 318 * {@code null}. 319 * @param cipherText The cipher text. Must not be {@code null}. 320 * @param authTag The authentication tag. Must not be 321 * {@code null}. 322 * @param cek The Content Encryption Key (CEK). Must not be 323 * {@code null}. 324 * @param jcaProvider The JWE JCA provider specification. Must not be 325 * {@code null}. 326 * 327 * @return The clear text. 328 * 329 * @throws JOSEException If decryption failed. 330 */ 331 public static byte[] decrypt(final JWEHeader header, 332 final byte[] aad, 333 final Base64URL encryptedKey, 334 final Base64URL iv, 335 final Base64URL cipherText, 336 final Base64URL authTag, 337 final SecretKey cek, 338 final JWEJCAContext jcaProvider) 339 throws JOSEException { 340 341 if (aad == null) { 342 // The AAD is the JWE header 343 return decrypt(header, AAD.compute(header), encryptedKey, iv, cipherText, authTag, cek, jcaProvider); 344 } 345 346 checkCEKLength(cek, header.getEncryptionMethod()); 347 348 // Decrypt the cipher text according to the JWE enc 349 350 byte[] plainText; 351 352 if (header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256) || 353 header.getEncryptionMethod().equals(EncryptionMethod.A192CBC_HS384) || 354 header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512)) { 355 356 plainText = AESCBC.decryptAuthenticated( 357 cek, 358 iv.decode(), 359 cipherText.decode(), 360 aad, 361 authTag.decode(), 362 jcaProvider.getContentEncryptionProvider(), 363 jcaProvider.getMACProvider()); 364 365 } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128GCM) || 366 header.getEncryptionMethod().equals(EncryptionMethod.A192GCM) || 367 header.getEncryptionMethod().equals(EncryptionMethod.A256GCM)) { 368 369 plainText = AESGCM.decrypt( 370 cek, 371 iv.decode(), 372 cipherText.decode(), 373 aad, 374 authTag.decode(), 375 jcaProvider.getContentEncryptionProvider()); 376 377 } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256_DEPRECATED) || 378 header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512_DEPRECATED)) { 379 380 plainText = AESCBC.decryptWithConcatKDF( 381 header, 382 cek, 383 encryptedKey, 384 iv, 385 cipherText, 386 authTag, 387 jcaProvider.getContentEncryptionProvider(), 388 jcaProvider.getMACProvider()); 389 390 } else if (header.getEncryptionMethod().equals(EncryptionMethod.XC20P)) { 391 392 plainText = XC20P.decryptAuthenticated( 393 cek, 394 iv.decode(), 395 cipherText.decode(), 396 aad, 397 authTag.decode() 398 ); 399 400 } else { 401 throw new JOSEException(AlgorithmSupportMessage.unsupportedEncryptionMethod( 402 header.getEncryptionMethod(), 403 SUPPORTED_ENCRYPTION_METHODS)); 404 } 405 406 407 // Apply decompression if requested 408 return DeflateHelper.applyDecompression(header, plainText); 409 } 410}