001/* 002 * nimbus-jose-jwt 003 * 004 * Copyright 2012-2016, Connect2id Ltd. 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; 019 020import com.nimbusds.jose.*; 021import com.nimbusds.jose.crypto.impl.AAD; 022import com.nimbusds.jose.crypto.impl.JWEHeaderValidation; 023import com.nimbusds.jose.crypto.impl.MultiCryptoProvider; 024import com.nimbusds.jose.jwk.JWK; 025import com.nimbusds.jose.jwk.JWKSet; 026import com.nimbusds.jose.jwk.KeyType; 027import com.nimbusds.jose.util.Base64URL; 028import com.nimbusds.jose.util.JSONArrayUtils; 029import com.nimbusds.jose.util.JSONObjectUtils; 030import net.jcip.annotations.ThreadSafe; 031 032import javax.crypto.SecretKey; 033import java.util.List; 034import java.util.Map; 035 036 037/** 038 * Multi-recipient encrypter of {@link com.nimbusds.jose.JWEObjectJSON JWE 039 * objects}. 040 * 041 * <p>This class is thread-safe. 042 * 043 * <p>Supports the following key management algorithms: 044 * 045 * <ul> 046 * <li>{@link com.nimbusds.jose.JWEAlgorithm#A128KW} 047 * <li>{@link com.nimbusds.jose.JWEAlgorithm#A192KW} 048 * <li>{@link com.nimbusds.jose.JWEAlgorithm#A256KW} 049 * <li>{@link com.nimbusds.jose.JWEAlgorithm#A128GCMKW} 050 * <li>{@link com.nimbusds.jose.JWEAlgorithm#A192GCMKW} 051 * <li>{@link com.nimbusds.jose.JWEAlgorithm#A256GCMKW} 052 * <li>{@link com.nimbusds.jose.JWEAlgorithm#DIR} 053 * <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A128KW} 054 * <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A192KW} 055 * <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A256KW} 056 * <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP_256} 057 * <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP_384} 058 * <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP_512} 059 * <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP} (deprecated) 060 * <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA1_5} (deprecated) 061 * </ul> 062 * 063 * <p>Supports the following elliptic curves: 064 * 065 * <ul> 066 * <li>{@link com.nimbusds.jose.jwk.Curve#P_256} 067 * <li>{@link com.nimbusds.jose.jwk.Curve#P_384} 068 * <li>{@link com.nimbusds.jose.jwk.Curve#P_521} 069 * <li>{@link com.nimbusds.jose.jwk.Curve#X25519} (Curve25519) 070 * </ul> 071 * 072 * <p>Supports the following content encryption algorithms: 073 * 074 * <ul> 075 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256} (requires 256 bit key) 076 * <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384} (requires 384 bit key) 077 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512} (requires 512 bit key) 078 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM} (requires 128 bit key) 079 * <li>{@link com.nimbusds.jose.EncryptionMethod#A192GCM} (requires 192 bit key) 080 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM} (requires 256 bit key) 081 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256_DEPRECATED} (requires 256 bit key) 082 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512_DEPRECATED} (requires 512 bit key) 083 * <li>{@link com.nimbusds.jose.EncryptionMethod#XC20P} (requires 256 bit key) 084 * </ul> 085 * 086 * @author Egor Puzanov 087 * @author Vladimir Dzhuvinov 088 * @version 2023-09-10 089 */ 090@ThreadSafe 091public class MultiEncrypter extends MultiCryptoProvider implements JWEEncrypter { 092 093 094 /** 095 * Common JWK and JWEHeader parameters. 096 */ 097 private static final String[] RECIPIENT_HEADER_PARAMS = { 098 HeaderParameterNames.KEY_ID, 099 HeaderParameterNames.ALGORITHM, 100 HeaderParameterNames.X_509_CERT_URL, 101 HeaderParameterNames.X_509_CERT_SHA_1_THUMBPRINT, 102 HeaderParameterNames.X_509_CERT_SHA_256_THUMBPRINT, 103 HeaderParameterNames.X_509_CERT_CHAIN 104 }; 105 106 107 /** 108 * The JWK public keys. 109 */ 110 private final JWKSet keys; 111 112 113 /** 114 * Creates a new multi-recipient encrypter. 115 * 116 * @param keys The keys to encrypt to. Must not be {@code null}. 117 * 118 * @throws KeyLengthException If the symmetric key length is not 119 * compatible. 120 */ 121 public MultiEncrypter(final JWKSet keys) 122 throws KeyLengthException { 123 124 this(keys, findDirectCEK(keys)); 125 } 126 127 128 /** 129 * Creates a new multi-recipient encrypter. 130 * 131 * @param keys The keys to encrypt to. Must not be 132 * {@code null}. 133 * @param contentEncryptionKey The content encryption key (CEK) to use. 134 * If specified its algorithm must be "AES" 135 * or "ChaCha20" and its length must match 136 * the expected for the JWE encryption 137 * method ("enc"). If {@code null} a CEK 138 * will be generated for each JWE. 139 * 140 * @throws KeyLengthException If the symmetric key length is not 141 * compatible. 142 */ 143 public MultiEncrypter(final JWKSet keys, final SecretKey contentEncryptionKey) 144 throws KeyLengthException { 145 146 super(contentEncryptionKey); 147 148 if (keys == null) { 149 throw new IllegalArgumentException("The JWK set must not be null"); 150 } 151 152 for (JWK jwk : keys.getKeys()) { 153 KeyType kty = jwk.getKeyType(); 154 if (jwk.getAlgorithm() == null) { 155 throw new IllegalArgumentException("Each JWK must specify a key encryption algorithm"); 156 } 157 JWEAlgorithm alg = JWEAlgorithm.parse(jwk.getAlgorithm().toString()); 158 if (JWEAlgorithm.DIR.equals(alg) 159 && KeyType.OCT.equals(kty) 160 && !jwk.toOctetSequenceKey().toSecretKey("AES").equals(contentEncryptionKey)) { 161 throw new IllegalArgumentException("Bad CEK"); 162 } 163 if (!((KeyType.RSA.equals(kty) && RSAEncrypter.SUPPORTED_ALGORITHMS.contains(alg)) 164 || (KeyType.EC.equals(kty) && ECDHEncrypter.SUPPORTED_ALGORITHMS.contains(alg)) 165 || (KeyType.OCT.equals(kty) && AESEncrypter.SUPPORTED_ALGORITHMS.contains(alg)) 166 || (KeyType.OCT.equals(kty) && DirectEncrypter.SUPPORTED_ALGORITHMS.contains(alg)) 167 || (KeyType.OKP.equals(kty) && X25519Encrypter.SUPPORTED_ALGORITHMS.contains(alg)))) { 168 throw new IllegalArgumentException("Unsupported key encryption algorithm: " + alg); 169 } 170 } 171 172 this.keys = keys; 173 } 174 175 176 /** 177 * Returns the {@link SecretKey} of the recipients with 178 * {@link JWEAlgorithm#DIR} if present. 179 * 180 * @param keys The public keys. Must not be {@code null}. 181 * 182 * @return The SecretKey. 183 */ 184 private static SecretKey findDirectCEK(final JWKSet keys) { 185 if (keys != null) { 186 for (JWK jwk : keys.getKeys()) { 187 if (JWEAlgorithm.DIR.equals(jwk.getAlgorithm()) && KeyType.OCT.equals(jwk.getKeyType())) { 188 return jwk.toOctetSequenceKey().toSecretKey("AES"); 189 } 190 } 191 } 192 return null; 193 } 194 195 196 /** 197 * Encrypts the specified clear text of a {@link JWEObject JWE object}. 198 * 199 * @param header The JSON Web Encryption (JWE) header. Must specify 200 * a supported JWE algorithm and method. Must not be 201 * {@code null}. 202 * @param clearText The clear text to encrypt. Must not be {@code null}. 203 * 204 * @return The resulting JWE crypto parts. 205 * 206 * @throws JOSEException If the JWE algorithm or method is not 207 * supported or if encryption failed for some 208 * other internal reason. 209 */ 210 @Deprecated 211 public JWECryptoParts encrypt(final JWEHeader header, final byte[] clearText) 212 throws JOSEException { 213 214 return encrypt(header, clearText, AAD.compute(header)); 215 } 216 217 218 @Override 219 public JWECryptoParts encrypt(final JWEHeader header, final byte[] clearText, final byte[] aad) 220 throws JOSEException { 221 222 if (aad == null) { 223 throw new JOSEException("Missing JWE additional authenticated data (AAD)"); 224 } 225 226 final EncryptionMethod enc = header.getEncryptionMethod(); 227 final SecretKey cek = getCEK(enc); 228 229 JWECryptoParts jweParts; 230 JWEEncrypter encrypter; 231 JWEHeader recipientHeader = null; 232 Base64URL encryptedKey = null; 233 Base64URL cipherText = null; 234 Base64URL iv = null; 235 Base64URL tag = null; 236 JWEAlgorithm alg; 237 Payload payload = new Payload(clearText); 238 List<Object> recipients = JSONArrayUtils.newJSONArray(); 239 240 for (JWK key : keys.getKeys()) { 241 KeyType kty = key.getKeyType(); 242 243 // build JWEHeader from protected header and recipients public key parameters 244 Map<String, Object> keyMap = key.toJSONObject(); 245 UnprotectedHeader.Builder unprotected = new UnprotectedHeader.Builder(); 246 for (String param : RECIPIENT_HEADER_PARAMS) { 247 if (keyMap.containsKey(param)) { 248 unprotected.param(param, keyMap.get(param)); 249 } 250 } 251 252 // create recipients JWEObject, select encrypter and encrypt the payload. 253 try { 254 recipientHeader = (JWEHeader) header.join(unprotected.build()); 255 } catch (Exception e) { 256 throw new JOSEException(e.getMessage(), e); 257 } 258 alg = JWEHeaderValidation.getAlgorithmAndEnsureNotNull(recipientHeader); 259 260 if (KeyType.RSA.equals(kty) && RSAEncrypter.SUPPORTED_ALGORITHMS.contains(alg)) { 261 encrypter = new RSAEncrypter(key.toRSAKey().toRSAPublicKey(), cek); 262 } else if (KeyType.EC.equals(kty) && ECDHEncrypter.SUPPORTED_ALGORITHMS.contains(alg)) { 263 encrypter = new ECDHEncrypter(key.toECKey().toECPublicKey(), cek); 264 } else if (KeyType.OCT.equals(kty) && AESEncrypter.SUPPORTED_ALGORITHMS.contains(alg)) { 265 encrypter = new AESEncrypter(key.toOctetSequenceKey().toSecretKey("AES"), cek); 266 } else if (KeyType.OCT.equals(kty) && DirectEncrypter.SUPPORTED_ALGORITHMS.contains(alg)) { 267 encrypter = new DirectEncrypter(key.toOctetSequenceKey().toSecretKey("AES")); 268 } else if (KeyType.OKP.equals(kty) && X25519Encrypter.SUPPORTED_ALGORITHMS.contains(alg)) { 269 encrypter = new X25519Encrypter(key.toOctetKeyPair().toPublicJWK(), cek); 270 } else { 271 continue; 272 } 273 jweParts = encrypter.encrypt(recipientHeader, payload.toBytes(), aad); 274 275 // build recipients header object by removing protected header params from recipients JWEHeader 276 Map<String, Object> recipientHeaderMap = jweParts.getHeader().toJSONObject(); 277 for (String param : header.getIncludedParams()) { 278 recipientHeaderMap.remove(param); 279 } 280 Map<String, Object> recipient = JSONObjectUtils.newJSONObject(); 281 recipient.put("header", recipientHeaderMap); 282 283 // do not put symmetric keys into JWE JSON object 284 if (!JWEAlgorithm.DIR.equals(alg)) { 285 recipient.put("encrypted_key", jweParts.getEncryptedKey().toString()); 286 } 287 recipients.add(recipient); 288 289 // update the iv, cipherText and tag parameters only after first round. Set payload to empty string. 290 if (recipients.size() == 1) { 291 payload = new Payload(""); 292 encryptedKey = jweParts.getEncryptedKey(); 293 iv = jweParts.getInitializationVector(); 294 cipherText = jweParts.getCipherText(); 295 tag = jweParts.getAuthenticationTag(); 296 } 297 } 298 if (recipients.size() > 1) { 299 Map<String, Object> jweJsonObject = JSONObjectUtils.newJSONObject(); 300 jweJsonObject.put("recipients", recipients); 301 encryptedKey = Base64URL.encode(JSONObjectUtils.toJSONString(jweJsonObject)); 302 } 303 return new JWECryptoParts(header, encryptedKey, iv, cipherText, tag); 304 } 305}