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 2024-04-20 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 for (JWK jwk : keys.getKeys()) { 149 KeyType kty = jwk.getKeyType(); 150 if (jwk.getAlgorithm() == null) { 151 throw new IllegalArgumentException("Each JWK must specify a key encryption algorithm"); 152 } 153 JWEAlgorithm alg = JWEAlgorithm.parse(jwk.getAlgorithm().toString()); 154 if (JWEAlgorithm.DIR.equals(alg) 155 && KeyType.OCT.equals(kty) 156 && !jwk.toOctetSequenceKey().toSecretKey("AES").equals(contentEncryptionKey)) { 157 throw new IllegalArgumentException("Bad CEK"); 158 } 159 if (!((KeyType.RSA.equals(kty) && RSAEncrypter.SUPPORTED_ALGORITHMS.contains(alg)) 160 || (KeyType.EC.equals(kty) && ECDHEncrypter.SUPPORTED_ALGORITHMS.contains(alg)) 161 || (KeyType.OCT.equals(kty) && AESEncrypter.SUPPORTED_ALGORITHMS.contains(alg)) 162 || (KeyType.OCT.equals(kty) && DirectEncrypter.SUPPORTED_ALGORITHMS.contains(alg)) 163 || (KeyType.OKP.equals(kty) && X25519Encrypter.SUPPORTED_ALGORITHMS.contains(alg)))) { 164 throw new IllegalArgumentException("Unsupported key encryption algorithm: " + alg); 165 } 166 } 167 168 this.keys = keys; 169 } 170 171 172 /** 173 * Returns the {@link SecretKey} of the recipients with 174 * {@link JWEAlgorithm#DIR} if present. 175 * 176 * @param keys The public keys. Must not be {@code null}. 177 * 178 * @return The SecretKey. 179 */ 180 private static SecretKey findDirectCEK(final JWKSet keys) { 181 if (keys != null) { 182 for (JWK jwk : keys.getKeys()) { 183 if (JWEAlgorithm.DIR.equals(jwk.getAlgorithm()) && KeyType.OCT.equals(jwk.getKeyType())) { 184 return jwk.toOctetSequenceKey().toSecretKey("AES"); 185 } 186 } 187 } 188 return null; 189 } 190 191 192 /** 193 * Encrypts the specified clear text of a {@link JWEObject JWE object}. 194 * 195 * @param header The JSON Web Encryption (JWE) header. Must specify 196 * a supported JWE algorithm and method. Must not be 197 * {@code null}. 198 * @param clearText The clear text to encrypt. Must not be {@code null}. 199 * 200 * @return The resulting JWE crypto parts. 201 * 202 * @throws JOSEException If the JWE algorithm or method is not 203 * supported or if encryption failed for some 204 * other internal reason. 205 */ 206 @Deprecated 207 public JWECryptoParts encrypt(final JWEHeader header, final byte[] clearText) 208 throws JOSEException { 209 210 return encrypt(header, clearText, AAD.compute(header)); 211 } 212 213 214 @Override 215 public JWECryptoParts encrypt(final JWEHeader header, final byte[] clearText, final byte[] aad) 216 throws JOSEException { 217 218 if (aad == null) { 219 throw new JOSEException("Missing JWE additional authenticated data (AAD)"); 220 } 221 222 final EncryptionMethod enc = header.getEncryptionMethod(); 223 final SecretKey cek = getCEK(enc); 224 225 JWECryptoParts jweParts; 226 JWEEncrypter encrypter; 227 JWEHeader recipientHeader = null; 228 Base64URL encryptedKey = null; 229 Base64URL cipherText = null; 230 Base64URL iv = null; 231 Base64URL tag = null; 232 JWEAlgorithm alg; 233 Payload payload = new Payload(clearText); 234 List<Object> recipients = JSONArrayUtils.newJSONArray(); 235 236 for (JWK key : keys.getKeys()) { 237 KeyType kty = key.getKeyType(); 238 239 // build JWEHeader from protected header and recipients public key parameters 240 Map<String, Object> keyMap = key.toJSONObject(); 241 UnprotectedHeader.Builder unprotected = new UnprotectedHeader.Builder(); 242 for (String param : RECIPIENT_HEADER_PARAMS) { 243 if (keyMap.containsKey(param)) { 244 unprotected.param(param, keyMap.get(param)); 245 } 246 } 247 248 // create recipients JWEObject, select encrypter and encrypt the payload. 249 try { 250 recipientHeader = (JWEHeader) header.join(unprotected.build()); 251 } catch (Exception e) { 252 throw new JOSEException(e.getMessage(), e); 253 } 254 alg = JWEHeaderValidation.getAlgorithmAndEnsureNotNull(recipientHeader); 255 256 if (KeyType.RSA.equals(kty) && RSAEncrypter.SUPPORTED_ALGORITHMS.contains(alg)) { 257 encrypter = new RSAEncrypter(key.toRSAKey().toRSAPublicKey(), cek); 258 } else if (KeyType.EC.equals(kty) && ECDHEncrypter.SUPPORTED_ALGORITHMS.contains(alg)) { 259 encrypter = new ECDHEncrypter(key.toECKey().toECPublicKey(), cek); 260 } else if (KeyType.OCT.equals(kty) && AESEncrypter.SUPPORTED_ALGORITHMS.contains(alg)) { 261 encrypter = new AESEncrypter(key.toOctetSequenceKey().toSecretKey("AES"), cek); 262 } else if (KeyType.OCT.equals(kty) && DirectEncrypter.SUPPORTED_ALGORITHMS.contains(alg)) { 263 encrypter = new DirectEncrypter(key.toOctetSequenceKey().toSecretKey("AES")); 264 } else if (KeyType.OKP.equals(kty) && X25519Encrypter.SUPPORTED_ALGORITHMS.contains(alg)) { 265 encrypter = new X25519Encrypter(key.toOctetKeyPair().toPublicJWK(), cek); 266 } else { 267 continue; 268 } 269 jweParts = encrypter.encrypt(recipientHeader, payload.toBytes(), aad); 270 271 // build recipients header object by removing protected header params from recipients JWEHeader 272 Map<String, Object> recipientHeaderMap = jweParts.getHeader().toJSONObject(); 273 for (String param : header.getIncludedParams()) { 274 recipientHeaderMap.remove(param); 275 } 276 Map<String, Object> recipient = JSONObjectUtils.newJSONObject(); 277 recipient.put("header", recipientHeaderMap); 278 279 // do not put symmetric keys into JWE JSON object 280 if (!JWEAlgorithm.DIR.equals(alg)) { 281 recipient.put("encrypted_key", jweParts.getEncryptedKey().toString()); 282 } 283 recipients.add(recipient); 284 285 // update the iv, cipherText and tag parameters only after first round. Set payload to empty string. 286 if (recipients.size() == 1) { 287 payload = new Payload(""); 288 encryptedKey = jweParts.getEncryptedKey(); 289 iv = jweParts.getInitializationVector(); 290 cipherText = jweParts.getCipherText(); 291 tag = jweParts.getAuthenticationTag(); 292 } 293 } 294 if (recipients.size() > 1) { 295 Map<String, Object> jweJsonObject = JSONObjectUtils.newJSONObject(); 296 jweJsonObject.put("recipients", recipients); 297 encryptedKey = Base64URL.encode(JSONObjectUtils.toJSONString(jweJsonObject)); 298 } 299 return new JWECryptoParts(header, encryptedKey, iv, cipherText, tag); 300 } 301}