001    package com.nimbusds.jose;
002    
003    
004    import java.text.ParseException;
005    
006    import net.jcip.annotations.ThreadSafe;
007    
008    import 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
018    public 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    }