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) secured object. This class is thread-safe.
013 *
014 * @author Vladimir Dzhuvinov
015 * @version 2015-04-21
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 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.supportedJWEAlgorithms().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         * Encrypts this JWE object with the specified encrypter. The JWE 
330         * object must be in an {@link State#UNENCRYPTED unencrypted} state.
331         *
332         * @param encrypter The JWE encrypter. Must not be {@code null}.
333         *
334         * @throws IllegalStateException If the JWE object is not in an 
335         *                               {@link State#UNENCRYPTED unencrypted
336         *                               state}.
337         * @throws JOSEException         If the JWE object couldn't be 
338         *                               encrypted.
339         */
340        public synchronized void encrypt(final JWEEncrypter encrypter)
341                throws JOSEException {
342
343                ensureUnencryptedState();
344
345                ensureJWEEncrypterSupport(encrypter);
346
347                JWECryptoParts parts;
348
349                try {
350                        parts = encrypter.encrypt(getHeader(), getPayload().toBytes());
351
352                } catch (JOSEException e) {
353
354                        throw e;
355                
356                } catch (Exception e) {
357
358                        // Prevent throwing unchecked exceptions at this point,
359                        // see issue #20
360                        throw new JOSEException(e.getMessage(), e);
361                }
362
363                // Check if the header has been modified
364                if (parts.getHeader() != null) {
365                        header = parts.getHeader();
366                }
367
368                encryptedKey = parts.getEncryptedKey();
369                iv = parts.getInitializationVector();
370                cipherText = parts.getCipherText();
371                authTag = parts.getAuthenticationTag();
372
373                state = State.ENCRYPTED;
374        }
375
376
377        /**
378         * Decrypts this JWE object with the specified decrypter. The JWE 
379         * object must be in a {@link State#ENCRYPTED encrypted} state.
380         *
381         * @param decrypter The JWE decrypter. Must not be {@code null}.
382         *
383         * @throws IllegalStateException If the JWE object is not in an 
384         *                               {@link State#ENCRYPTED encrypted
385         *                               state}.
386         * @throws JOSEException         If the JWE object couldn't be 
387         *                               decrypted.
388         */
389        public synchronized void decrypt(final JWEDecrypter decrypter)
390                throws JOSEException {
391
392                ensureEncryptedState();
393
394                try {
395                        setPayload(new Payload(decrypter.decrypt(getHeader(), 
396                                               getEncryptedKey(), 
397                                               getIV(),
398                                               getCipherText(), 
399                                               getAuthTag())));
400
401                } catch (JOSEException e) {
402
403                        throw e;
404
405                } catch (Exception e) {
406
407                        // Prevent throwing unchecked exceptions at this point,
408                        // see issue #20
409                        throw new JOSEException(e.getMessage(), e);
410                }
411
412                state = State.DECRYPTED;
413        }
414
415
416        /**
417         * Serialises this JWE object to its compact format consisting of 
418         * Base64URL-encoded parts delimited by period ('.') characters. It 
419         * must be in a {@link State#ENCRYPTED encrypted} or 
420         * {@link State#DECRYPTED decrypted} state.
421         *
422         * <pre>
423         * [header-base64url].[encryptedKey-base64url].[iv-base64url].[cipherText-base64url].[authTag-base64url]
424         * </pre>
425         *
426         * @return The serialised JWE object.
427         *
428         * @throws IllegalStateException If the JWE object is not in a 
429         *                               {@link State#ENCRYPTED encrypted} or
430         *                               {@link State#DECRYPTED decrypted 
431         *                               state}.
432         */
433        @Override
434        public String serialize() {
435
436                ensureEncryptedOrDecryptedState();
437
438                StringBuilder sb = new StringBuilder(header.toBase64URL().toString());
439                sb.append('.');
440
441                if (encryptedKey != null) {
442                        
443                        sb.append(encryptedKey.toString());
444                }
445
446                sb.append('.');
447
448                if (iv != null) {
449
450                        sb.append(iv.toString());
451                }
452
453                sb.append('.');
454
455                sb.append(cipherText.toString());
456
457                sb.append('.');
458
459                if (authTag != null) {
460
461                        sb.append(authTag.toString());
462                }
463
464                return sb.toString();
465        }
466
467
468        /**
469         * Parses a JWE object from the specified string in compact form. The 
470         * parsed JWE object will be given an {@link State#ENCRYPTED} state.
471         *
472         * @param s The string to parse. Must not be {@code null}.
473         *
474         * @return The JWE object.
475         *
476         * @throws ParseException If the string couldn't be parsed to a valid 
477         *                        JWE object.
478         */
479        public static JWEObject parse(final String s)
480                throws ParseException {
481
482                Base64URL[] parts = JOSEObject.split(s);
483
484                if (parts.length != 5) {
485
486                        throw new ParseException("Unexpected number of Base64URL parts, must be five", 0);
487                }
488
489                return new JWEObject(parts[0], parts[1], parts[2], parts[3], parts[4]);
490        }
491}