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}