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