001/* 002 * nimbus-jose-jwt 003 * 004 * Copyright 2012-2026, 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 020 021import java.net.URI; 022import java.util.Collections; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027import com.nimbusds.jose.*; 028import com.nimbusds.jose.crypto.impl.AAD; 029import com.nimbusds.jose.crypto.impl.CriticalHeaderParamsDeferral; 030import com.nimbusds.jose.crypto.impl.JWEHeaderValidation; 031import com.nimbusds.jose.crypto.impl.MultiCryptoProvider; 032import com.nimbusds.jose.jwk.JWK; 033import com.nimbusds.jose.jwk.KeyType; 034import com.nimbusds.jose.util.Base64; 035import com.nimbusds.jose.util.Base64URL; 036import com.nimbusds.jose.util.JSONObjectUtils; 037import net.jcip.annotations.ThreadSafe; 038 039 040/** 041 * Multi-recipient decrypter of {@link com.nimbusds.jose.JWEObjectJSON JWE objects}. 042 * 043 * <p>This class is thread-safe. 044 * 045 * <p>Supports the following key management algorithms: 046 * 047 * <ul> 048 * <li>{@link com.nimbusds.jose.JWEAlgorithm#A128KW} 049 * <li>{@link com.nimbusds.jose.JWEAlgorithm#A192KW} 050 * <li>{@link com.nimbusds.jose.JWEAlgorithm#A256KW} 051 * <li>{@link com.nimbusds.jose.JWEAlgorithm#A128GCMKW} 052 * <li>{@link com.nimbusds.jose.JWEAlgorithm#A192GCMKW} 053 * <li>{@link com.nimbusds.jose.JWEAlgorithm#A256GCMKW} 054 * <li>{@link com.nimbusds.jose.JWEAlgorithm#DIR} 055 * <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A128KW} 056 * <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A192KW} 057 * <li>{@link com.nimbusds.jose.JWEAlgorithm#ECDH_ES_A256KW} 058 * <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP_256} 059 * <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP_384} 060 * <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP_512} 061 * <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA_OAEP} (deprecated) 062 * <li>{@link com.nimbusds.jose.JWEAlgorithm#RSA1_5} (deprecated) 063 * </ul> 064 * 065 * <p>Supports the following elliptic curves: 066 * 067 * <ul> 068 * <li>{@link com.nimbusds.jose.jwk.Curve#P_256} 069 * <li>{@link com.nimbusds.jose.jwk.Curve#P_384} 070 * <li>{@link com.nimbusds.jose.jwk.Curve#P_521} 071 * <li>{@link com.nimbusds.jose.jwk.Curve#X25519} (Curve25519) 072 * </ul> 073 * 074 * <p>Supports the following content encryption algorithms: 075 * 076 * <ul> 077 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256} (requires 256 bit key) 078 * <li>{@link com.nimbusds.jose.EncryptionMethod#A192CBC_HS384} (requires 384 bit key) 079 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512} (requires 512 bit key) 080 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128GCM} (requires 128 bit key) 081 * <li>{@link com.nimbusds.jose.EncryptionMethod#A192GCM} (requires 192 bit key) 082 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256GCM} (requires 256 bit key) 083 * <li>{@link com.nimbusds.jose.EncryptionMethod#A128CBC_HS256_DEPRECATED} (requires 256 bit key) 084 * <li>{@link com.nimbusds.jose.EncryptionMethod#A256CBC_HS512_DEPRECATED} (requires 512 bit key) 085 * <li>{@link com.nimbusds.jose.EncryptionMethod#XC20P} (requires 256 bit key) 086 * </ul> 087 * 088 * @author Egor Puzanov 089 * @version 2026-09-16 090 */ 091@ThreadSafe 092public class MultiDecrypter extends MultiCryptoProvider implements JWEDecrypter, CriticalHeaderParamsAware { 093 094 095 /** 096 * The private JWK key. 097 */ 098 private final JWK jwk; 099 100 101 /** 102 * The key id of the private JWK key. 103 */ 104 private final String kid; 105 106 107 /** 108 * The Cerificate URL of the private JWK key. 109 */ 110 private final URI x5u; 111 112 113 /** 114 * The Certificate thumbprint of the private JWK key. 115 */ 116 private final Base64URL x5t; 117 118 119 /** 120 * The Certificate SHA256 thumbprint of the private JWK key. 121 */ 122 private final Base64URL x5t256; 123 124 125 /** 126 * The Certificate chain of the private JWK key. 127 */ 128 private final List<Base64> x5c; 129 130 131 /** 132 * The Thumbprint of the private JWK key. 133 */ 134 private final Base64URL thumbprint; 135 136 137 /** 138 * The critical header policy. 139 */ 140 private final CriticalHeaderParamsDeferral critPolicy = new CriticalHeaderParamsDeferral(); 141 142 143 /** 144 * The configured options, empty set if none. 145 */ 146 private final Set<JWEDecrypterOption> opts; 147 148 149 /** 150 * Creates a new multi-recipient decrypter. 151 * 152 * @param jwk The JSON Web Key (JWK). Must contain a private part. Must 153 * not be {@code null}. 154 * 155 * @throws KeyLengthException If the symmetric key length is not 156 * compatible. 157 * @throws JOSEException If an internal exception is encountered. 158 */ 159 public MultiDecrypter(final JWK jwk) 160 throws JOSEException, KeyLengthException { 161 162 this(jwk, null, Collections.<JWEDecrypterOption>emptySet()); 163 } 164 165 166 /** 167 * Creates a new multi-recipient decrypter. 168 * 169 * @param jwk The JSON Web Key (JWK). Must contain a private 170 * part. Must not be {@code null}. 171 * @param defCritHeaders The names of the critical header parameters 172 * that are deferred to the application for 173 * processing, empty set or {@code null} if none. 174 * 175 * @throws KeyLengthException If the symmetric key length is not 176 * compatible. 177 * @throws JOSEException If an internal exception is encountered. 178 */ 179 public MultiDecrypter(final JWK jwk, final Set<String> defCritHeaders) 180 throws JOSEException, KeyLengthException { 181 182 this(jwk, defCritHeaders, Collections.<JWEDecrypterOption>emptySet()); 183 } 184 185 186 /** 187 * Creates a new multi-recipient decrypter. 188 * 189 * @param jwk The JSON Web Key (JWK). Must contain a private 190 * part. Must not be {@code null}. 191 * @param defCritHeaders The names of the critical header parameters 192 * that are deferred to the application for 193 * processing, empty set or {@code null} if none. 194 * @param opts The decryption options, empty or {@code null} 195 * if none. 196 * 197 * @throws KeyLengthException If the symmetric key length is not 198 * compatible. 199 * @throws JOSEException If an internal exception is encountered. 200 */ 201 public MultiDecrypter(final JWK jwk, 202 final Set<String> defCritHeaders, 203 final Set<JWEDecrypterOption> opts) 204 throws JOSEException, KeyLengthException { 205 206 super(null); 207 208 if (jwk == null) { 209 throw new IllegalArgumentException("The private key (JWK) must not be null"); 210 } 211 this.jwk = jwk; 212 this.kid = jwk.getKeyID(); 213 this.x5c = jwk.getX509CertChain(); 214 this.x5u = jwk.getX509CertURL(); 215 this.x5t = jwk.getX509CertThumbprint(); 216 this.x5t256 = jwk.getX509CertSHA256Thumbprint(); 217 this.thumbprint = jwk.computeThumbprint(); 218 219 this.opts = opts != null ? opts : Collections.<JWEDecrypterOption>emptySet(); 220 221 critPolicy.setDeferredCriticalHeaderParams(defCritHeaders); 222 } 223 224 225 @Override 226 public Set<String> getProcessedCriticalHeaderParams() { 227 228 return critPolicy.getProcessedCriticalHeaderParams(); 229 } 230 231 232 @Override 233 public Set<String> getDeferredCriticalHeaderParams() { 234 235 return critPolicy.getDeferredCriticalHeaderParams(); 236 } 237 238 239 private boolean jwkMatched(final JWEHeader recipientHeader) 240 throws JOSEException { 241 242 if (thumbprint.toString().equals(recipientHeader.getKeyID())) { 243 return true; 244 } 245 JWK rjwk = recipientHeader.getJWK(); 246 if (rjwk != null && thumbprint.equals(rjwk.computeThumbprint())) { 247 return true; 248 } 249 if (x5u != null && x5u.equals(recipientHeader.getX509CertURL())) { 250 return true; 251 } 252 if (x5t != null && x5t.equals(recipientHeader.getX509CertThumbprint())) { 253 return true; 254 } 255 if (x5t256 != null && x5t256.equals(recipientHeader.getX509CertSHA256Thumbprint())) { 256 return true; 257 } 258 List<Base64> rx5c = recipientHeader.getX509CertChain(); 259 if (x5c != null && rx5c != null && x5c.containsAll(rx5c) && rx5c.containsAll(x5c)) { 260 return true; 261 } 262 if (kid != null && kid.equals(recipientHeader.getKeyID())) { 263 return true; 264 } 265 return false; 266 } 267 268 269 /** 270 * Decrypts the specified cipher text of a {@link JWEObject JWE Object}. 271 * 272 * @param header The JSON Web Encryption (JWE) header. Must 273 * specify a supported JWE algorithm and method. 274 * Must not be {@code null}. 275 * @param encryptedKey The encrypted key, {@code null} if not required 276 * by the JWE algorithm. 277 * @param iv The initialisation vector, {@code null} if not 278 * required by the JWE algorithm. 279 * @param cipherText The cipher text to decrypt. Must not be 280 * {@code null}. 281 * @param authTag The authentication tag, {@code null} if not 282 * required. 283 * 284 * @return The clear text. 285 * 286 * @throws JOSEException If the JWE algorithm or method is not 287 * supported, if a critical header parameter is 288 * not supported or marked for deferral to the 289 * application, or if decryption failed for some 290 * other reason. 291 */ 292 @Deprecated 293 public byte[] decrypt(final JWEHeader header, 294 final Base64URL encryptedKey, 295 final Base64URL iv, 296 final Base64URL cipherText, 297 final Base64URL authTag) 298 throws JOSEException { 299 300 return decrypt(header, encryptedKey, iv, cipherText, authTag, AAD.compute(header)); 301 } 302 303 304 @Override 305 public byte[] decrypt(final JWEHeader header, 306 final Base64URL encryptedKey, 307 final Base64URL iv, 308 final Base64URL cipherText, 309 final Base64URL authTag, 310 final byte[] aad) 311 throws JOSEException { 312 313 if (iv == null) { 314 throw new JOSEException("Unexpected present JWE initialization vector (IV)"); 315 } 316 317 if (authTag == null) { 318 throw new JOSEException("Missing JWE authentication tag"); 319 } 320 321 if (aad == null) { 322 throw new JOSEException("Missing JWE additional authenticated data (AAD)"); 323 } 324 325 final JWEDecrypter decrypter; 326 final KeyType kty = jwk.getKeyType(); 327 final Set<String> defCritHeaders = critPolicy.getDeferredCriticalHeaderParams(); 328 JWEObjectJSON.Recipient recipient = null; 329 JWEHeader recipientHeader = null; 330 try { 331 // The encryptedKey value contains the Base64URL encoded JSON string 332 // {"recipients":[{recipient1},{recipient2}]} if multiple recipients are used. 333 for (Object recipientMap : JSONObjectUtils.getJSONArray((JSONObjectUtils.parse(encryptedKey.decodeToString())), "recipients")) { 334 try { 335 recipient = JWEObjectJSON.Recipient.parse((Map<String, Object>) recipientMap); 336 recipientHeader = (JWEHeader) header.join(recipient.getUnprotectedHeader()); 337 } catch (Exception e) { 338 throw new JOSEException(e.getMessage()); 339 } 340 if (jwkMatched(recipientHeader)) { 341 break; 342 } 343 recipientHeader = null; 344 } 345 } catch (Exception e) { 346 // If encryptedKey can not be parsed as a JSON Object, it means the encryptedKey contains the RAW encrypted key value. 347 recipientHeader = header; 348 recipient = new JWEObjectJSON.Recipient(null, encryptedKey); 349 } 350 351 if (recipientHeader == null) { 352 throw new JOSEException("No recipient found"); 353 } 354 355 final JWEAlgorithm alg = JWEHeaderValidation.getAlgorithmAndEnsureNotNull(recipientHeader); 356 critPolicy.ensureHeaderPasses(recipientHeader); 357 358 if (KeyType.RSA.equals(kty) && RSADecrypter.SUPPORTED_ALGORITHMS.contains(alg)) { 359 decrypter = new RSADecrypter(jwk.toRSAKey().toRSAPrivateKey(), defCritHeaders, opts); 360 } else if (KeyType.EC.equals(kty) && ECDHDecrypter.SUPPORTED_ALGORITHMS.contains(alg)) { 361 decrypter = new ECDHDecrypter(jwk.toECKey().toECPrivateKey(), defCritHeaders, opts); 362 } else if (KeyType.OCT.equals(kty) && AESDecrypter.SUPPORTED_ALGORITHMS.contains(alg)) { 363 decrypter = new AESDecrypter(jwk.toOctetSequenceKey().toSecretKey("AES"), defCritHeaders, opts); 364 } else if (KeyType.OCT.equals(kty) && DirectDecrypter.SUPPORTED_ALGORITHMS.contains(alg)) { 365 decrypter = new DirectDecrypter(jwk.toOctetSequenceKey().toSecretKey("AES"), defCritHeaders, opts); 366 } else if (KeyType.OKP.equals(kty) && X25519Decrypter.SUPPORTED_ALGORITHMS.contains(alg)) { 367 decrypter = new X25519Decrypter(jwk.toOctetKeyPair(), defCritHeaders, opts); 368 } else { 369 throw new JOSEException("Unsupported algorithm"); 370 } 371 372 return decrypter.decrypt(recipientHeader, recipient.getEncryptedKey(), iv, cipherText, authTag, aad); 373 } 374}