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