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