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-01-17)
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 final ReadOnlyJWEHeader 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 initializationVector;
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 authenticationTag;
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 ReadOnlyJWEHeader 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                        initializationVector = null;
166
167                } else {
168
169                        initializationVector = 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                        authenticationTag = null;
182
183                } else {
184
185                        authenticationTag = 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 ReadOnlyJWEHeader getHeader() {
196
197                return header;
198        }
199
200
201        /**
202         * Gets 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         * Gets 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 getInitializationVector() {
220
221                return initializationVector;
222        }
223
224
225        /**
226         * Gets 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         * Gets 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 getAuthenticationTag() {
244
245                return authenticationTag;
246        }
247
248
249        /**
250         * Use {@link #getAuthenticationTag} instead.
251         */
252        @Deprecated
253        public Base64URL getIntegrityValue() {
254
255                return getAuthenticationTag();
256        }
257
258
259        /**
260         * Gets the state of this JWE object.
261         *
262         * @return The state.
263         */
264        public State getState() {
265
266                return state;
267        }
268
269
270        /**
271         * Ensures the current state is {@link State#UNENCRYPTED unencrypted}.
272         *
273         * @throws IllegalStateException If the current state is not 
274         *                               unencrypted.
275         */
276        private void ensureUnencryptedState() {
277
278                if (state != State.UNENCRYPTED) {
279
280                        throw new IllegalStateException("The JWE object must be in an unencrypted state");
281                }
282        }
283
284
285        /**
286         * Ensures the current state is {@link State#ENCRYPTED encrypted}.
287         *
288         * @throws IllegalStateException If the current state is not encrypted.
289         */
290        private void ensureEncryptedState() {
291
292                if (state != State.ENCRYPTED) {
293
294                        throw new IllegalStateException("The JWE object must be in an encrypted state");
295                }
296        }
297
298
299        /**
300         * Ensures the current state is {@link State#ENCRYPTED encrypted} or
301         * {@link State#DECRYPTED decrypted}.
302         *
303         * @throws IllegalStateException If the current state is not encrypted 
304         *                               or decrypted.
305         */
306        private void ensureEncryptedOrDecryptedState() {
307
308                if (state != State.ENCRYPTED && state != State.DECRYPTED) {
309
310                        throw new IllegalStateException("The JWE object must be in an encrypted or decrypted state");
311                }
312        }
313
314
315        /**
316         * Ensures the specified JWE encrypter supports the algorithms of this 
317         * JWE object.
318         *
319         * @throws JOSEException If the JWE algorithms are not supported.
320         */
321        private void ensureJWEEncrypterSupport(final JWEEncrypter encrypter)
322                throws JOSEException {
323
324                if (! encrypter.supportedAlgorithms().contains(getHeader().getAlgorithm())) {
325
326                        throw new JOSEException("The \"" + getHeader().getAlgorithm() + 
327                                                "\" algorithm is not supported by the JWE encrypter");
328                }
329
330                if (! encrypter.supportedEncryptionMethods().contains(getHeader().getEncryptionMethod())) {
331
332                        throw new JOSEException("The \"" + getHeader().getEncryptionMethod() + 
333                                                "\" encryption method is not supported by the JWE encrypter");
334                }
335        }
336
337
338        /**
339         * Ensures the specified JWE decrypter accepts the algorithms and the 
340         * headers of this JWE object.
341         *
342         * @throws JOSEException If the JWE algorithms or headers are not 
343         *                       accepted.
344         */
345        private void ensureJWEDecrypterAcceptance(final JWEDecrypter decrypter)
346                throws JOSEException {
347
348                JWEHeaderFilter filter = decrypter.getJWEHeaderFilter();
349
350                if (filter == null) {
351
352                        return;
353                }
354
355
356                if (! filter.getAcceptedAlgorithms().contains(getHeader().getAlgorithm())) {
357
358                        throw new JOSEException("The \"" + getHeader().getAlgorithm() + 
359                                                "\" algorithm is not accepted by the JWE decrypter");
360                }
361
362
363                if (! filter.getAcceptedEncryptionMethods().contains(getHeader().getEncryptionMethod())) {
364
365                        throw new JOSEException("The \"" + getHeader().getEncryptionMethod() + 
366                                                "\" encryption method is not accepted by the JWE decrypter");
367                }               
368
369                // Header params
370
371                if (! filter.getAcceptedParameters().containsAll(getHeader().getIncludedParameters())) {
372
373                        throw new JOSEException("One or more header parameters not accepted by the JWE decrypter");
374                }
375        }
376
377
378        /**
379         * Encrypts this JWE object with the specified encrypter. The JWE 
380         * object must be in an {@link State#UNENCRYPTED unencrypted} state.
381         *
382         * @param encrypter The JWE encrypter. Must not be {@code null}.
383         *
384         * @throws IllegalStateException If the JWE object is not in an 
385         *                               {@link State#UNENCRYPTED unencrypted
386         *                               state}.
387         * @throws JOSEException         If the JWE object couldn't be 
388         *                               encrypted.
389         */
390        public synchronized void encrypt(final JWEEncrypter encrypter)
391                throws JOSEException {
392
393                ensureUnencryptedState();
394
395                ensureJWEEncrypterSupport(encrypter);
396
397                JWECryptoParts parts = null;
398
399                try {
400                        parts = encrypter.encrypt(getHeader(), getPayload().toBytes());
401
402                } catch (JOSEException e) {
403
404                        throw e;
405                
406                } catch (Exception e) {
407
408                        // Prevent throwing unchecked exceptions at this point,
409                        // see issue #20
410                        throw new JOSEException(e.getMessage(), e);
411                }
412
413                encryptedKey = parts.getEncryptedKey();
414                initializationVector = parts.getInitializationVector();
415                cipherText = parts.getCipherText();
416                authenticationTag = parts.getAuthenticationTag();
417
418                state = State.ENCRYPTED;
419        }
420
421
422        /**
423         * Decrypts this JWE object with the specified decrypter. The JWE 
424         * object must be in a {@link State#ENCRYPTED encrypted} state.
425         *
426         * @param decrypter The JWE decrypter. Must not be {@code null}.
427         *
428         * @throws IllegalStateException If the JWE object is not in an 
429         *                               {@link State#ENCRYPTED encrypted
430         *                               state}.
431         * @throws JOSEException         If the JWE object couldn't be 
432         *                               decrypted.
433         */
434        public synchronized void decrypt(final JWEDecrypter decrypter)
435                throws JOSEException {
436
437                ensureEncryptedState();
438
439                ensureJWEDecrypterAcceptance(decrypter);
440
441                try {
442                        setPayload(new Payload(decrypter.decrypt(getHeader(), 
443                                               getEncryptedKey(), 
444                                               getInitializationVector(),
445                                               getCipherText(), 
446                                               getAuthenticationTag())));
447
448                } catch (JOSEException e) {
449
450                        throw e;
451
452                } catch (Exception e) {
453
454                        // Prevent throwing unchecked exceptions at this point,
455                        // see issue #20
456                        throw new JOSEException(e.getMessage(), e);
457                }
458
459                state = State.DECRYPTED;
460        }
461
462
463        /**
464         * Serialises this JWE object to its compact format consisting of 
465         * Base64URL-encoded parts delimited by period ('.') characters. It 
466         * must be in a {@link State#ENCRYPTED encrypted} or 
467         * {@link State#DECRYPTED decrypted} state.
468         *
469         * <pre>
470         * [header-base64url].[encryptedKey-base64url].[iv-base64url].[cipherText-base64url].[authTag-base64url]
471         * </pre>
472         *
473         * @return The serialised JWE object.
474         *
475         * @throws IllegalStateException If the JWE object is not in a 
476         *                               {@link State#ENCRYPTED encrypted} or
477         *                               {@link State#DECRYPTED decrypted 
478         *                               state}.
479         */
480        @Override
481        public String serialize() {
482
483                ensureEncryptedOrDecryptedState();
484
485                StringBuilder sb = new StringBuilder(header.toBase64URL().toString());
486                sb.append('.');
487
488                if (encryptedKey != null) {
489                        
490                        sb.append(encryptedKey.toString());
491                }
492
493                sb.append('.');
494
495                if (initializationVector != null) {
496
497                        sb.append(initializationVector.toString());
498                }
499
500                sb.append('.');
501
502                sb.append(cipherText.toString());
503
504                sb.append('.');
505
506                if (authenticationTag != null) {
507
508                        sb.append(authenticationTag.toString());
509                }
510
511                return sb.toString();
512        }
513
514
515        /**
516         * Parses a JWE object from the specified string in compact form. The 
517         * parsed JWE object will be given an {@link State#ENCRYPTED} state.
518         *
519         * @param s The string to parse. Must not be {@code null}.
520         *
521         * @return The JWE object.
522         *
523         * @throws ParseException If the string couldn't be parsed to a valid 
524         *                        JWE object.
525         */
526        public static JWEObject parse(final String s)
527                throws ParseException {
528
529                Base64URL[] parts = JOSEObject.split(s);
530
531                if (parts.length != 5) {
532
533                        throw new ParseException("Unexpected number of Base64URL parts, must be five", 0);
534                }
535
536                return new JWEObject(parts[0], parts[1], parts[2], parts[3], parts[4]);
537        }
538}