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$ (2013-03-27)
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 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 initializationVector;
063
064
065        /**
066         * The cipher text, {@code null} if not computed.
067         */
068        private Base64URL cipherText;
069
070
071        /**
072         * The integrity value, {@code null} if not computed or applicable.
073         */
074        private Base64URL integrityValue;
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 integrity
130         *                   value. 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                        integrityValue = null;
182
183                } else {
184
185                        integrityValue = 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 integrity value of this JWE object.
239         *
240         * @return The integrity value, {@code null} if not applicable or the 
241         *         JWE object has not been encrypted yet.
242         */
243        public Base64URL getIntegrityValue() {
244
245                return integrityValue;
246        }
247
248
249        /**
250         * Gets 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                JWEHeaderFilter filter = decrypter.getJWEHeaderFilter();
339
340                if (filter == null) {
341
342                        return;
343                }
344
345
346                if (! filter.getAcceptedAlgorithms().contains(getHeader().getAlgorithm())) {
347
348                        throw new JOSEException("The \"" + getHeader().getAlgorithm() + 
349                                                "\" algorithm is not accepted by the JWE decrypter");
350                }
351
352
353                if (! filter.getAcceptedEncryptionMethods().contains(getHeader().getEncryptionMethod())) {
354
355                        throw new JOSEException("The \"" + getHeader().getEncryptionMethod() + 
356                                                "\" encryption method is not accepted by the JWE decrypter");
357                }               
358
359                // Header params
360
361                if (! filter.getAcceptedParameters().containsAll(getHeader().getIncludedParameters())) {
362
363                        throw new JOSEException("One or more header parameters not accepted by the JWE decrypter");
364                }
365        }
366
367
368        /**
369         * Encrypts this JWE object with the specified encrypter. The JWE 
370         * object must be in an {@link State#UNENCRYPTED unencrypted} state.
371         *
372         * @param encrypter The JWE encrypter. Must not be {@code null}.
373         *
374         * @throws IllegalStateException If the JWE object is not in an 
375         *                               {@link State#UNENCRYPTED unencrypted
376         *                               state}.
377         * @throws JOSEException         If the JWE object couldn't be 
378         *                               encrypted.
379         */
380        public synchronized void encrypt(final JWEEncrypter encrypter)
381                throws JOSEException {
382
383                ensureUnencryptedState();
384
385                ensureJWEEncrypterSupport(encrypter);
386
387                JWECryptoParts parts = null;
388
389                try {
390                        parts = encrypter.encrypt(getHeader(), getPayload().toBytes());
391
392                } catch (JOSEException e) {
393
394                        throw e;
395                
396                } catch (Exception e) {
397
398                        // Prevent throwing unchecked exceptions at this point,
399                        // see issue #20
400                        throw new JOSEException(e.getMessage(), e);
401                }
402
403                encryptedKey = parts.getEncryptedKey();
404                initializationVector = parts.getInitializationVector();
405                cipherText = parts.getCipherText();
406                integrityValue = parts.getIntegrityValue();
407
408                state = State.ENCRYPTED;
409        }
410
411
412        /**
413         * Decrypts this JWE object with the specified decrypter. The JWE 
414         * object must be in a {@link State#ENCRYPTED encrypted} state.
415         *
416         * @param decrypter The JWE decrypter. Must not be {@code null}.
417         *
418         * @throws IllegalStateException If the JWE object is not in an 
419         *                               {@link State#ENCRYPTED encrypted
420         *                               state}.
421         * @throws JOSEException         If the JWE object couldn't be 
422         *                               decrypted.
423         */
424        public synchronized void decrypt(final JWEDecrypter decrypter)
425                throws JOSEException {
426
427                ensureEncryptedState();
428
429                ensureJWEDecrypterAcceptance(decrypter);
430
431                try {
432                        setPayload(new Payload(decrypter.decrypt(getHeader(), 
433                                               getEncryptedKey(), 
434                                               getInitializationVector(),
435                                               getCipherText(), 
436                                               getIntegrityValue())));
437
438                } catch (JOSEException e) {
439
440                        throw e;
441
442                } catch (Exception e) {
443
444                        // Prevent throwing unchecked exceptions at this point,
445                        // see issue #20
446                        throw new JOSEException(e.getMessage(), e);
447                }
448
449                state = State.DECRYPTED;
450        }
451
452
453        /**
454         * Serialises this JWE object to its compact format consisting of 
455         * Base64URL-encoded parts delimited by period ('.') characters. It 
456         * must be in a {@link State#ENCRYPTED encrypted} or 
457         * {@link State#DECRYPTED decrypted} state.
458         *
459         * <pre>
460         * [header-base64url].[encryptedKey-base64url].[iv-base64url].[cipherText-base64url].[integrityValue-base64url]
461         * </pre>
462         *
463         * @return The serialised JWE object.
464         *
465         * @throws IllegalStateException If the JWE object is not in a 
466         *                               {@link State#ENCRYPTED encrypted} or
467         *                               {@link State#DECRYPTED decrypted 
468         *                               state}.
469         */
470        @Override
471        public String serialize() {
472
473                ensureEncryptedOrDecryptedState();
474
475                StringBuilder sb = new StringBuilder(header.toBase64URL().toString());
476                sb.append('.');
477
478                if (encryptedKey != null) {
479                        
480                        sb.append(encryptedKey.toString());
481                }
482
483                sb.append('.');
484
485                if (initializationVector != null) {
486
487                        sb.append(initializationVector.toString());
488                }
489
490                sb.append('.');
491
492                sb.append(cipherText.toString());
493
494                sb.append('.');
495
496                if (integrityValue != null) {
497
498                        sb.append(integrityValue.toString());
499                }
500
501                return sb.toString();
502        }
503
504
505        /**
506         * Parses a JWE object from the specified string in compact form. The 
507         * parsed JWE object will be given an {@link State#ENCRYPTED} state.
508         *
509         * @param s The string to parse. Must not be {@code null}.
510         *
511         * @return The JWE object.
512         *
513         * @throws ParseException If the string couldn't be parsed to a valid 
514         *                        JWE object.
515         */
516        public static JWEObject parse(final String s)
517                        throws ParseException {
518
519                Base64URL[] parts = JOSEObject.split(s);
520
521                if (parts.length != 5) {
522
523                        throw new ParseException("Unexpected number of Base64URL parts, must be five", 0);
524                }
525
526                return new JWEObject(parts[0], parts[1], parts[2], parts[3], parts[4]);
527        }
528}