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;
019
020
021import com.nimbusds.jose.crypto.impl.AAD;
022import com.nimbusds.jose.crypto.opts.MaxCompressedCipherTextLength;
023import com.nimbusds.jose.util.Base64URL;
024import net.jcip.annotations.ThreadSafe;
025
026import java.text.ParseException;
027import java.util.Objects;
028import java.util.Set;
029
030
031/**
032 * JSON Web Encryption (JWE) secured object with
033 * <a href="https://datatracker.ietf.org/doc/html/rfc7516#section-7.1">compact
034 * serialisation</a>.
035 *
036 * <p>This class is thread-safe.
037 *
038 * @author Vladimir Dzhuvinov
039 * @author Egor Puzanov
040 * @version 2026-09-16
041 */
042@ThreadSafe
043public class JWEObject extends JOSEObject {
044
045
046        private static final long serialVersionUID = 1L;
047
048
049        /**
050         * The maximum allowed character length of compressed cipher text, in
051         * bytes. Passive (preemptive) safety limit against zip-bomb attacks.
052         */
053        public static final int MAX_COMPRESSED_CIPHER_TEXT_LENGTH = 100_000;
054
055
056        /**
057         * The maximum allowed length of decompressed plain text, in bytes.
058         * Active (dynamic) safety limit against zip-bomb attacks.
059         */
060        public static final int MAX_DECOMPRESSED_PLAIN_TEXT_LENGTH = 1_000_000;
061        
062        
063        /**
064         * Enumeration of the states of a JSON Web Encryption (JWE) secured
065         * object.
066         */
067        public enum State {
068                
069                
070                /**
071                 * The JWE secured object is created but not encrypted yet.
072                 */
073                UNENCRYPTED,
074                
075                
076                /**
077                 * The JWE secured object is encrypted.
078                 */
079                ENCRYPTED,
080                
081                
082                /**
083                 * The JWE secured object is decrypted.
084                 */
085                DECRYPTED
086        }
087
088
089        /**
090         * The header.
091         */
092        private JWEHeader header;
093
094
095        /** 
096         * The encrypted key, {@code null} if not computed or applicable.
097         */
098        private Base64URL encryptedKey;
099
100
101        /**
102         * The initialisation vector, {@code null} if not generated or 
103         * applicable.
104         */
105        private Base64URL iv;
106
107
108        /**
109         * The cipher text, {@code null} if not computed.
110         */
111        private Base64URL cipherText;
112
113
114        /**
115         * The authentication tag, {@code null} if not computed or applicable.
116         */
117        private Base64URL authTag;
118
119
120        /**
121         * The JWE object state.
122         */
123        private State state;
124
125
126        /**
127         * Creates a new to-be-encrypted JSON Web Encryption (JWE) object with 
128         * the specified header and payload. The initial state will be 
129         * {@link State#UNENCRYPTED unencrypted}.
130         *
131         * @param header  The JWE header. Must not be {@code null}.
132         * @param payload The payload. Must not be {@code null}.
133         */
134        public JWEObject(final JWEHeader header, final Payload payload) {
135
136                this.header = Objects.requireNonNull(header);
137                setPayload(Objects.requireNonNull(payload));
138                encryptedKey = null;
139                cipherText = null;
140                state = State.UNENCRYPTED;
141        }
142
143
144        /**
145         * Creates a new encrypted JSON Web Encryption (JWE) object with the 
146         * specified serialised parts. The state will be {@link State#ENCRYPTED 
147         * encrypted}.
148         *
149         * @param firstPart  The first part, corresponding to the JWE header. 
150         *                   Must not be {@code null}.
151         * @param secondPart The second part, corresponding to the encrypted 
152         *                   key. Empty or {@code null} if none.
153         * @param thirdPart  The third part, corresponding to the 
154         *                   initialisation vector. Empty or {@code null} if 
155         *                   none.
156         * @param fourthPart The fourth part, corresponding to the cipher text.
157         *                   Must not be {@code null}.
158         * @param fifthPart  The fifth part, corresponding to the 
159         *                   authentication tag. Empty of {@code null} if none.
160         *
161         * @throws ParseException If parsing of the serialised parts failed.
162         */
163        public JWEObject(final Base64URL firstPart, 
164                         final Base64URL secondPart, 
165                         final Base64URL thirdPart,
166                         final Base64URL fourthPart,
167                         final Base64URL fifthPart)
168                throws ParseException {
169
170                try {
171                        this.header = JWEHeader.parse(Objects.requireNonNull(firstPart));
172                } catch (ParseException e) {
173                        throw new ParseException("Invalid JWE header: " + e.getMessage(), 0);
174                }
175
176                if (secondPart == null || secondPart.toString().isEmpty()) {
177
178                        encryptedKey = null;
179
180                } else {
181
182                        encryptedKey = secondPart;
183                }
184
185                if (thirdPart == null || thirdPart.toString().isEmpty()) {
186
187                        iv = null;
188
189                } else {
190
191                        iv = thirdPart;
192                }
193
194                cipherText = Objects.requireNonNull(fourthPart);
195
196                if (fifthPart == null || fifthPart.toString().isEmpty()) {
197
198                        authTag = null;
199
200                } else {
201
202                        authTag = fifthPart;
203                }
204
205                state = State.ENCRYPTED; // but not decrypted yet!
206
207                setParsedParts(firstPart, secondPart, thirdPart, fourthPart, fifthPart);
208        }
209
210
211        @Override
212        public JWEHeader getHeader() {
213
214                return header;
215        }
216
217
218        /**
219         * Returns the encrypted key of this JWE object.
220         *
221         * @return The encrypted key, {@code null} not applicable or the JWE
222         *         object has not been encrypted yet.
223         */
224        public Base64URL getEncryptedKey() {
225
226                return encryptedKey;
227        }
228
229
230        /**
231         * Returns the initialisation vector (IV) of this JWE object.
232         *
233         * @return The initialisation vector (IV), {@code null} if not 
234         *         applicable or the JWE object has not been encrypted yet.
235         */
236        public Base64URL getIV() {
237
238                return iv;
239        }
240
241
242        /**
243         * Returns the cipher text of this JWE object.
244         *
245         * @return The cipher text, {@code null} if the JWE object has not been
246         *         encrypted yet.
247         */
248        public Base64URL getCipherText() {
249
250                return cipherText;
251        }
252
253
254        /**
255         * Returns the authentication tag of this JWE object.
256         *
257         * @return The authentication tag, {@code null} if not applicable or
258         *         the JWE object has not been encrypted yet.
259         */
260        public Base64URL getAuthTag() {
261
262                return authTag;
263        }
264        
265        
266        /**
267         * Returns the state of the JWE secured object.
268         *
269         * @return The state.
270         */
271        public State getState() {
272
273                return state;
274        }
275
276
277        /**
278         * Ensures the current state is {@link State#UNENCRYPTED unencrypted}.
279         *
280         * @throws IllegalStateException If the current state is not 
281         *                               unencrypted.
282         */
283        private void ensureUnencryptedState() {
284
285                if (state != State.UNENCRYPTED) {
286
287                        throw new IllegalStateException("The JWE object must be in an unencrypted state");
288                }
289        }
290
291
292        /**
293         * Ensures the current state is {@link State#ENCRYPTED encrypted}.
294         *
295         * @throws IllegalStateException If the current state is not encrypted.
296         */
297        private void ensureEncryptedState() {
298
299                if (state != State.ENCRYPTED) {
300
301                        throw new IllegalStateException("The JWE object must be in an encrypted state");
302                }
303        }
304
305
306        /**
307         * Ensures the current state is {@link State#ENCRYPTED encrypted} or
308         * {@link State#DECRYPTED decrypted}.
309         *
310         * @throws IllegalStateException If the current state is not encrypted 
311         *                               or decrypted.
312         */
313        private void ensureEncryptedOrDecryptedState() {
314
315                if (state != State.ENCRYPTED && state != State.DECRYPTED) {
316
317                        throw new IllegalStateException("The JWE object must be in an encrypted or decrypted state");
318                }
319        }
320
321
322        /**
323         * Ensures the specified JWE encrypter supports the algorithms of this 
324         * JWE object.
325         *
326         * @throws JOSEException If the JWE algorithms are not supported.
327         */
328        private void ensureJWEEncrypterSupport(final JWEEncrypter encrypter)
329                throws JOSEException {
330
331                if (! encrypter.supportedJWEAlgorithms().contains(getHeader().getAlgorithm())) {
332
333                        throw new JOSEException("The " + getHeader().getAlgorithm() +
334                                                " algorithm is not supported by the JWE encrypter: Supported algorithms: " + encrypter.supportedJWEAlgorithms());
335                }
336
337                if (! encrypter.supportedEncryptionMethods().contains(getHeader().getEncryptionMethod())) {
338
339                        throw new JOSEException("The " + getHeader().getEncryptionMethod() +
340                                                " encryption method or key size is not supported by the JWE encrypter: Supported methods: " + encrypter.supportedEncryptionMethods());
341                }
342        }
343
344
345        /**
346         * Encrypts this JWE object with the specified encrypter. The JWE 
347         * object must be in an {@link State#UNENCRYPTED unencrypted} state.
348         *
349         * @param encrypter The JWE encrypter. Must not be {@code null}.
350         *
351         * @throws IllegalStateException If the JWE object is not in an 
352         *                               {@link State#UNENCRYPTED unencrypted
353         *                               state}.
354         * @throws JOSEException         If the JWE object couldn't be 
355         *                               encrypted.
356         */
357        public synchronized void encrypt(final JWEEncrypter encrypter)
358                throws JOSEException {
359
360                ensureUnencryptedState();
361
362                ensureJWEEncrypterSupport(encrypter);
363
364                JWECryptoParts parts;
365
366                try {
367                        parts = encrypter.encrypt(getHeader(), getPayload().toBytes(), AAD.compute(getHeader()));
368
369                } catch (JOSEException e) {
370
371                        throw e;
372                
373                } catch (Exception e) {
374
375                        // Prevent throwing unchecked exceptions at this point,
376                        // see issue #20
377                        throw new JOSEException(e.getMessage(), e);
378                }
379
380                // Check if the header has been modified
381                if (parts.getHeader() != null) {
382                        header = parts.getHeader();
383                }
384
385                encryptedKey = parts.getEncryptedKey();
386                iv = parts.getInitializationVector();
387                cipherText = parts.getCipherText();
388                authTag = parts.getAuthenticationTag();
389
390                state = State.ENCRYPTED;
391        }
392
393
394        /**
395         * Decrypts this JWE object with the specified decrypter. The JWE 
396         * object must be in a {@link State#ENCRYPTED encrypted} state.
397         *
398         * @param decrypter The JWE decrypter. Must not be {@code null}.
399         *
400         * @throws IllegalStateException If the JWE object is not in an 
401         *                               {@link State#ENCRYPTED encrypted
402         *                               state}.
403         * @throws JOSEException         If the JWE object couldn't be 
404         *                               decrypted.
405         */
406        public synchronized void decrypt(final JWEDecrypter decrypter)
407                throws JOSEException {
408
409                decrypt(decrypter, null);
410        }
411
412
413        /**
414         * Decrypts this JWE object with the specified decrypter and options.
415         * The JWE object must be in a {@link State#ENCRYPTED encrypted} state.
416         *
417         * @param decrypter The JWE decrypter. Must not be {@code null}.
418         * @param opts      The JWE decrypter options, {@code null} if none.
419         *
420         * @throws IllegalStateException If the JWE object is not in an 
421         *                               {@link State#ENCRYPTED encrypted
422         *                               state}.
423         * @throws JOSEException         If the JWE object couldn't be 
424         *                               decrypted.
425         */
426        public synchronized void decrypt(final JWEDecrypter decrypter,
427                                         final Set<JWEDecrypterOption> opts)
428                throws JOSEException {
429
430                ensureEncryptedState();
431
432                if (getHeader().getCompressionAlgorithm() != null) {
433
434                        int maxLength = resolveMaxCompressedCipherTextLength(opts);
435
436                        if (getCipherText().toString().length() > maxLength) {
437
438                                throw new JOSEException(
439                                        "The JWE compressed cipher text exceeds the " +
440                                        "maximum allowed length of " +
441                                        maxLength +
442                                        " characters");
443                        }
444                }
445
446                try {
447                        setPayload(new Payload(decrypter.decrypt(getHeader(), 
448                                               getEncryptedKey(), 
449                                               getIV(),
450                                               getCipherText(), 
451                                               getAuthTag(),
452                                               AAD.compute(getHeader()))));
453
454                } catch (JOSEException e) {
455
456                        throw e;
457
458                } catch (Exception e) {
459
460                        // Prevent throwing unchecked exceptions at this point,
461                        // see issue #20
462                        throw new JOSEException(e.getMessage(), e);
463                }
464
465                state = State.DECRYPTED;
466        }
467
468
469        /**
470         * Resolves the maximum compressed cipher text length from the
471         * specified options.
472         *
473         * @param opts The options, {@code null} if none.
474         *
475         * @return The maximum compressed cipher text length.
476         */
477        private static int resolveMaxCompressedCipherTextLength(final Set<JWEDecrypterOption> opts) {
478
479                if (opts != null) {
480                        for (JWEDecrypterOption opt : opts) {
481                                if (opt instanceof MaxCompressedCipherTextLength) {
482                                        return ((MaxCompressedCipherTextLength) opt).getMaxLength();
483                                }
484                        }
485                }
486                return MAX_COMPRESSED_CIPHER_TEXT_LENGTH;
487        }
488
489
490        /**
491         * Serialises this JWE object to its compact format consisting of 
492         * Base64URL-encoded parts delimited by period ('.') characters. It 
493         * must be in a {@link State#ENCRYPTED encrypted} or 
494         * {@link State#DECRYPTED decrypted} state.
495         *
496         * <pre>
497         * [header-base64url].[encryptedKey-base64url].[iv-base64url].[cipherText-base64url].[authTag-base64url]
498         * </pre>
499         *
500         * @return The serialised JWE object.
501         *
502         * @throws IllegalStateException If the JWE object is not in a 
503         *                               {@link State#ENCRYPTED encrypted} or
504         *                               {@link State#DECRYPTED decrypted 
505         *                               state}.
506         */
507        @Override
508        public String serialize() {
509
510                ensureEncryptedOrDecryptedState();
511
512                StringBuilder sb = new StringBuilder(header.toBase64URL().toString());
513                sb.append('.');
514
515                if (encryptedKey != null) {
516                        sb.append(encryptedKey);
517                }
518
519                sb.append('.');
520
521                if (iv != null) {
522                        sb.append(iv);
523                }
524
525                sb.append('.');
526                sb.append(cipherText);
527                sb.append('.');
528
529                if (authTag != null) {
530                        sb.append(authTag);
531                }
532
533                return sb.toString();
534        }
535
536
537        /**
538         * Parses a JWE object from the specified string in compact form. The 
539         * parsed JWE object will be given an {@link State#ENCRYPTED} state.
540         *
541         * @param s The string to parse. Must not be {@code null}.
542         *
543         * @return The JWE object.
544         *
545         * @throws ParseException If the string couldn't be parsed to a valid 
546         *                        JWE object.
547         */
548        public static JWEObject parse(final String s)
549                throws ParseException {
550
551                Base64URL[] parts = JOSEObject.split(s);
552
553                if (parts.length != 5) {
554
555                        throw new ParseException("Unexpected number of Base64URL parts, must be five", 0);
556                }
557
558                return new JWEObject(parts[0], parts[1], parts[2], parts[3], parts[4]);
559        }
560}