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