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