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 SUPPORTED_ENCRYPTION_METHODS = Collections.unmodifiableSet(methods); 066 067 Map<Integer,Set<EncryptionMethod>> encsMap = new HashMap<>(); 068 Set<EncryptionMethod> bit128Encs = new HashSet<>(); 069 Set<EncryptionMethod> bit192Encs = new HashSet<>(); 070 Set<EncryptionMethod> bit256Encs = new HashSet<>(); 071 Set<EncryptionMethod> bit384Encs = new HashSet<>(); 072 Set<EncryptionMethod> bit512Encs = new HashSet<>(); 073 bit128Encs.add(EncryptionMethod.A128GCM); 074 bit192Encs.add(EncryptionMethod.A192GCM); 075 bit256Encs.add(EncryptionMethod.A256GCM); 076 bit256Encs.add(EncryptionMethod.A128CBC_HS256); 077 bit256Encs.add(EncryptionMethod.A128CBC_HS256_DEPRECATED); 078 bit384Encs.add(EncryptionMethod.A192CBC_HS384); 079 bit512Encs.add(EncryptionMethod.A256CBC_HS512); 080 bit512Encs.add(EncryptionMethod.A256CBC_HS512_DEPRECATED); 081 encsMap.put(128,Collections.unmodifiableSet(bit128Encs)); 082 encsMap.put(192,Collections.unmodifiableSet(bit192Encs)); 083 encsMap.put(256,Collections.unmodifiableSet(bit256Encs)); 084 encsMap.put(384,Collections.unmodifiableSet(bit384Encs)); 085 encsMap.put(512, Collections.unmodifiableSet(bit512Encs)); 086 COMPATIBLE_ENCRYPTION_METHODS = Collections.unmodifiableMap(encsMap); 087 } 088 089 090 /** 091 * Generates a Content Encryption Key (CEK) for the specified JOSE 092 * encryption method. 093 * 094 * @param enc The encryption method. Must not be {@code null}. 095 * @param randomGen The secure random generator to use. Must not be 096 * {@code null}. 097 * 098 * @return The generated CEK (with algorithm "AES"). 099 * 100 * @throws JOSEException If the encryption method is not supported. 101 */ 102 public static SecretKey generateCEK(final EncryptionMethod enc, final SecureRandom randomGen) 103 throws JOSEException { 104 105 if (! SUPPORTED_ENCRYPTION_METHODS.contains(enc)) { 106 throw new JOSEException(AlgorithmSupportMessage.unsupportedEncryptionMethod(enc, SUPPORTED_ENCRYPTION_METHODS)); 107 } 108 109 final byte[] cekMaterial = new byte[ByteUtils.byteLength(enc.cekBitLength())]; 110 111 randomGen.nextBytes(cekMaterial); 112 113 return new SecretKeySpec(cekMaterial, "AES"); 114 } 115 116 117 /** 118 * Checks the length of the Content Encryption Key (CEK) according to 119 * the encryption method. 120 * 121 * @param cek The CEK. Must not be {@code null}. 122 * @param enc The encryption method. Must not be {@code null}. 123 * 124 * @throws KeyLengthException If the CEK length doesn't match the 125 * encryption method. 126 */ 127 private static void checkCEKLength(final SecretKey cek, final EncryptionMethod enc) 128 throws KeyLengthException { 129 130 try { 131 if (enc.cekBitLength() != ByteUtils.safeBitLength(cek.getEncoded())) { 132 throw new KeyLengthException("The Content Encryption Key (CEK) length for " + enc + " must be " + enc.cekBitLength() + " bits"); 133 } 134 } catch (IntegerOverflowException e) { 135 throw new KeyLengthException("The Content Encryption Key (CEK) is too long: " + e.getMessage()); 136 } 137 } 138 139 140 /** 141 * Encrypts the specified clear text (content). 142 * 143 * @param header The final JWE header. Must not be {@code null}. 144 * @param clearText The clear text to encrypt and optionally 145 * compress. Must not be {@code null}. 146 * @param cek The Content Encryption Key (CEK). Must not be 147 * {@code null}. 148 * @param encryptedKey The encrypted CEK, {@code null} if not required. 149 * @param jcaProvider The JWE JCA provider specification. Must not be 150 * {@code null}. 151 * 152 * @return The JWE crypto parts. 153 * 154 * @throws JOSEException If encryption failed. 155 */ 156 public static JWECryptoParts encrypt(final JWEHeader header, 157 final byte[] clearText, 158 final SecretKey cek, 159 final Base64URL encryptedKey, 160 final JWEJCAContext jcaProvider) 161 throws JOSEException { 162 163 checkCEKLength(cek, header.getEncryptionMethod()); 164 165 // Apply compression if instructed 166 final byte[] plainText = DeflateHelper.applyCompression(header, clearText); 167 168 // Compose the AAD 169 final byte[] aad = AAD.compute(header); 170 171 // Encrypt the plain text according to the JWE enc 172 final byte[] iv; 173 final AuthenticatedCipherText authCipherText; 174 175 if ( header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256) || 176 header.getEncryptionMethod().equals(EncryptionMethod.A192CBC_HS384) || 177 header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512) ) { 178 179 iv = AESCBC.generateIV(jcaProvider.getSecureRandom()); 180 181 authCipherText = AESCBC.encryptAuthenticated( 182 cek, iv, plainText, aad, 183 jcaProvider.getContentEncryptionProvider(), 184 jcaProvider.getMACProvider()); 185 186 } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128GCM) || 187 header.getEncryptionMethod().equals(EncryptionMethod.A192GCM) || 188 header.getEncryptionMethod().equals(EncryptionMethod.A256GCM) ) { 189 190 Container<byte[]> ivContainer = new Container<>(AESGCM.generateIV(jcaProvider.getSecureRandom())); 191 192 authCipherText = AESGCM.encrypt( 193 cek, ivContainer, plainText, aad, 194 jcaProvider.getContentEncryptionProvider()); 195 196 iv = ivContainer.get(); 197 198 } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256_DEPRECATED) || 199 header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512_DEPRECATED) ) { 200 201 iv = AESCBC.generateIV(jcaProvider.getSecureRandom()); 202 203 authCipherText = AESCBC.encryptWithConcatKDF( 204 header, cek, encryptedKey, iv, plainText, 205 jcaProvider.getContentEncryptionProvider(), 206 jcaProvider.getMACProvider()); 207 208 } else { 209 210 throw new JOSEException(AlgorithmSupportMessage.unsupportedEncryptionMethod( 211 header.getEncryptionMethod(), 212 SUPPORTED_ENCRYPTION_METHODS)); 213 } 214 215 return new JWECryptoParts( 216 header, 217 encryptedKey, 218 Base64URL.encode(iv), 219 Base64URL.encode(authCipherText.getCipherText()), 220 Base64URL.encode(authCipherText.getAuthenticationTag())); 221 } 222 223 224 /** 225 * Decrypts the specified cipher text. 226 * 227 * @param header The JWE header. Must not be {@code null}. 228 * @param encryptedKey The encrypted key, {@code null} if not 229 * specified. 230 * @param iv The initialisation vector (IV). Must not be 231 * {@code null}. 232 * @param cipherText The cipher text. Must not be {@code null}. 233 * @param authTag The authentication tag. Must not be 234 * {@code null}. 235 * @param cek The Content Encryption Key (CEK). Must not be 236 * {@code null}. 237 * @param jcaProvider The JWE JCA provider specification. Must not be 238 * {@code null}. 239 * 240 * @return The clear text. 241 * 242 * @throws JOSEException If decryption failed. 243 */ 244 public static byte[] decrypt(final JWEHeader header, 245 final Base64URL encryptedKey, 246 final Base64URL iv, 247 final Base64URL cipherText, 248 final Base64URL authTag, 249 final SecretKey cek, 250 final JWEJCAContext jcaProvider) 251 throws JOSEException { 252 253 checkCEKLength(cek, header.getEncryptionMethod()); 254 255 // Compose the AAD 256 byte[] aad = AAD.compute(header); 257 258 // Decrypt the cipher text according to the JWE enc 259 260 byte[] plainText; 261 262 if (header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256) || 263 header.getEncryptionMethod().equals(EncryptionMethod.A192CBC_HS384) || 264 header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512)) { 265 266 plainText = AESCBC.decryptAuthenticated( 267 cek, 268 iv.decode(), 269 cipherText.decode(), 270 aad, 271 authTag.decode(), 272 jcaProvider.getContentEncryptionProvider(), 273 jcaProvider.getMACProvider()); 274 275 } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128GCM) || 276 header.getEncryptionMethod().equals(EncryptionMethod.A192GCM) || 277 header.getEncryptionMethod().equals(EncryptionMethod.A256GCM)) { 278 279 plainText = AESGCM.decrypt( 280 cek, 281 iv.decode(), 282 cipherText.decode(), 283 aad, 284 authTag.decode(), 285 jcaProvider.getContentEncryptionProvider()); 286 287 } else if (header.getEncryptionMethod().equals(EncryptionMethod.A128CBC_HS256_DEPRECATED) || 288 header.getEncryptionMethod().equals(EncryptionMethod.A256CBC_HS512_DEPRECATED)) { 289 290 plainText = AESCBC.decryptWithConcatKDF( 291 header, 292 cek, 293 encryptedKey, 294 iv, 295 cipherText, 296 authTag, 297 jcaProvider.getContentEncryptionProvider(), 298 jcaProvider.getMACProvider()); 299 300 } else { 301 throw new JOSEException(AlgorithmSupportMessage.unsupportedEncryptionMethod( 302 header.getEncryptionMethod(), 303 SUPPORTED_ENCRYPTION_METHODS)); 304 } 305 306 307 // Apply decompression if requested 308 return DeflateHelper.applyDecompression(header, plainText); 309 } 310}