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 2022-09-20 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 checkCEKLength(cek, header.getEncryptionMethod()); 175 176 // Apply compression if instructed 177 final byte[] plainText = DeflateHelper.applyCompression(header, clearText); 178 179 // Compose the AAD 180 final byte[] aad = AAD.compute(header); 181 182 // Encrypt the plain text according to the JWE enc 183 final byte[] iv; 184 final AuthenticatedCipherText authCipherText; 185 186 if ( header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256) || 187 header.getEncryptionMethod().equals(EncryptionMethod.A192CBC_HS384) || 188 header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512) ) { 189 190 iv = AESCBC.generateIV(jcaProvider.getSecureRandom()); 191 192 authCipherText = AESCBC.encryptAuthenticated( 193 cek, iv, plainText, aad, 194 jcaProvider.getContentEncryptionProvider(), 195 jcaProvider.getMACProvider()); 196 197 } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128GCM) || 198 header.getEncryptionMethod().equals(EncryptionMethod.A192GCM) || 199 header.getEncryptionMethod().equals(EncryptionMethod.A256GCM) ) { 200 201 Container<byte[]> ivContainer = new Container<>(AESGCM.generateIV(jcaProvider.getSecureRandom())); 202 203 authCipherText = AESGCM.encrypt( 204 cek, ivContainer, plainText, aad, 205 jcaProvider.getContentEncryptionProvider()); 206 207 iv = ivContainer.get(); 208 209 } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256_DEPRECATED) || 210 header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512_DEPRECATED) ) { 211 212 iv = AESCBC.generateIV(jcaProvider.getSecureRandom()); 213 214 authCipherText = AESCBC.encryptWithConcatKDF( 215 header, cek, encryptedKey, iv, plainText, 216 jcaProvider.getContentEncryptionProvider(), 217 jcaProvider.getMACProvider()); 218 219 } else if (header.getEncryptionMethod().equals(EncryptionMethod.XC20P)) { 220 221 Container<byte[]> ivContainer = new Container<>(null); 222 223 authCipherText = XC20P.encryptAuthenticated(cek, ivContainer, plainText, aad); 224 225 iv = ivContainer.get(); 226 227 } else { 228 229 throw new JOSEException(AlgorithmSupportMessage.unsupportedEncryptionMethod( 230 header.getEncryptionMethod(), 231 SUPPORTED_ENCRYPTION_METHODS)); 232 } 233 234 return new JWECryptoParts( 235 header, 236 encryptedKey, 237 Base64URL.encode(iv), 238 Base64URL.encode(authCipherText.getCipherText()), 239 Base64URL.encode(authCipherText.getAuthenticationTag())); 240 } 241 242 243 /** 244 * Decrypts the specified cipher text. 245 * 246 * @param header The JWE header. Must not be {@code null}. 247 * @param encryptedKey The encrypted key, {@code null} if not 248 * specified. 249 * @param iv The initialisation vector (IV). Must not be 250 * {@code null}. 251 * @param cipherText The cipher text. Must not be {@code null}. 252 * @param authTag The authentication tag. Must not be 253 * {@code null}. 254 * @param cek The Content Encryption Key (CEK). Must not be 255 * {@code null}. 256 * @param jcaProvider The JWE JCA provider specification. Must not be 257 * {@code null}. 258 * 259 * @return The clear text. 260 * 261 * @throws JOSEException If decryption failed. 262 */ 263 public static byte[] decrypt(final JWEHeader header, 264 final Base64URL encryptedKey, 265 final Base64URL iv, 266 final Base64URL cipherText, 267 final Base64URL authTag, 268 final SecretKey cek, 269 final JWEJCAContext jcaProvider) 270 throws JOSEException { 271 272 checkCEKLength(cek, header.getEncryptionMethod()); 273 274 // Compose the AAD 275 byte[] aad = AAD.compute(header); 276 277 // Decrypt the cipher text according to the JWE enc 278 279 byte[] plainText; 280 281 if (header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256) || 282 header.getEncryptionMethod().equals(EncryptionMethod.A192CBC_HS384) || 283 header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512)) { 284 285 plainText = AESCBC.decryptAuthenticated( 286 cek, 287 iv.decode(), 288 cipherText.decode(), 289 aad, 290 authTag.decode(), 291 jcaProvider.getContentEncryptionProvider(), 292 jcaProvider.getMACProvider()); 293 294 } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128GCM) || 295 header.getEncryptionMethod().equals(EncryptionMethod.A192GCM) || 296 header.getEncryptionMethod().equals(EncryptionMethod.A256GCM)) { 297 298 plainText = AESGCM.decrypt( 299 cek, 300 iv.decode(), 301 cipherText.decode(), 302 aad, 303 authTag.decode(), 304 jcaProvider.getContentEncryptionProvider()); 305 306 } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256_DEPRECATED) || 307 header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512_DEPRECATED)) { 308 309 plainText = AESCBC.decryptWithConcatKDF( 310 header, 311 cek, 312 encryptedKey, 313 iv, 314 cipherText, 315 authTag, 316 jcaProvider.getContentEncryptionProvider(), 317 jcaProvider.getMACProvider()); 318 319 } else if (header.getEncryptionMethod().equals(EncryptionMethod.XC20P)) { 320 321 plainText = XC20P.decryptAuthenticated( 322 cek, 323 iv.decode(), 324 cipherText.decode(), 325 aad, 326 authTag.decode() 327 ); 328 329 } else { 330 throw new JOSEException(AlgorithmSupportMessage.unsupportedEncryptionMethod( 331 header.getEncryptionMethod(), 332 SUPPORTED_ENCRYPTION_METHODS)); 333 } 334 335 336 // Apply decompression if requested 337 return DeflateHelper.applyDecompression(header, plainText); 338 } 339}