001    package com.nimbusds.jose;
002    
003    
004    import java.text.ParseException;
005    
006    import javax.mail.internet.ContentType;
007    import javax.mail.internet.ParameterList;
008    
009    import net.jcip.annotations.ThreadSafe;
010    
011    import com.nimbusds.jose.util.Base64URL;
012    
013    
014    /**
015     * JSON Web Encryption (JWE) object. This class is thread-safe.
016     *
017     * @author Vladimir Dzhuvinov
018     * @version $version$ (2013-05-05)
019     */
020    @ThreadSafe
021    public class JWEObject extends JOSEObject {
022    
023    
024            /**
025             * Enumeration of the states of a JSON Web Encryption (JWE) object.
026             */
027            public static enum State {
028    
029    
030                    /**
031                     * The JWE object is created but not encrypted yet.
032                     */
033                    UNENCRYPTED,
034    
035    
036                    /**
037                     * The JWE object is encrypted.
038                     */
039                    ENCRYPTED,
040    
041    
042                    /**
043                     * The JWE object is decrypted.
044                     */
045                    DECRYPTED;
046            }
047    
048    
049            /**
050             * The MIME type of JWE objects serialised to a compact form:
051             * {@code application/jwe; charset=UTF-8}
052             */
053            public static final ContentType MIME_TYPE_COMPACT;
054    
055    
056            /**
057             * The MIME type of JWE objects serialised to a JSON object form:
058             * {@code application/jwe-js; charset=UTF-8}
059             */
060            public static final ContentType MIME_TYPE_JS;
061    
062    
063            static {
064    
065                    final ParameterList params = new ParameterList();
066                    params.set("charset", "UTF-8");
067    
068                    MIME_TYPE_COMPACT = new ContentType("application", "jwe", params);
069    
070                    MIME_TYPE_JS = new ContentType("application", "jwe-js", params);
071            }
072    
073    
074            /**
075             * The header.
076             */
077            private final JWEHeader header;
078    
079    
080            /** 
081             * The encrypted key, {@code null} if not computed or applicable.
082             */
083            private Base64URL encryptedKey;
084    
085    
086            /**
087             * The initialisation vector, {@code null} if not generated or 
088             * applicable.
089             */
090            private Base64URL initializationVector;
091    
092    
093            /**
094             * The cipher text, {@code null} if not computed.
095             */
096            private Base64URL cipherText;
097    
098    
099            /**
100             * The authentication tag, {@code null} if not computed or applicable.
101             */
102            private Base64URL authenticationTag;
103    
104    
105            /**
106             * The JWE object state.
107             */
108            private State state;
109    
110    
111            /**
112             * Creates a new to-be-encrypted JSON Web Encryption (JWE) object with 
113             * the specified header and payload. The initial state will be 
114             * {@link State#UNENCRYPTED unencrypted}.
115             *
116             * @param header  The JWE header. Must not be {@code null}.
117             * @param payload The payload. Must not be {@code null}.
118             */
119            public JWEObject(final JWEHeader header, final Payload payload) {
120    
121                    if (header == null) {
122    
123                            throw new IllegalArgumentException("The JWE header must not be null");
124                    }
125    
126                    this.header = header;
127    
128                    if (payload == null) {
129    
130                            throw new IllegalArgumentException("The payload must not be null");
131                    }
132    
133                    setPayload(payload);
134    
135                    encryptedKey = null;
136    
137                    cipherText = null;
138    
139                    state = State.UNENCRYPTED;
140            }
141    
142    
143            /**
144             * Creates a new encrypted JSON Web Encryption (JWE) object with the 
145             * specified serialised parts. The state will be {@link State#ENCRYPTED 
146             * encrypted}.
147             *
148             * @param firstPart  The first part, corresponding to the JWE header. 
149             *                   Must not be {@code null}.
150             * @param secondPart The second part, corresponding to the encrypted 
151             *                   key. Empty or {@code null} if none.
152             * @param thirdPart  The third part, corresponding to the 
153             *                   initialisation vector. Empty or {@code null} if 
154             *                   none.
155             * @param fourthPart The fourth part, corresponding to the cipher text.
156             *                   Must not be {@code null}.
157             * @param fifthPart  The fifth part, corresponding to the 
158             *                   authentication tag. Empty of {@code null} if none.
159             *
160             * @throws ParseException If parsing of the serialised parts failed.
161             */
162            public JWEObject(final Base64URL firstPart, 
163                             final Base64URL secondPart, 
164                             final Base64URL thirdPart,
165                             final Base64URL fourthPart,
166                             final Base64URL fifthPart)
167                    throws ParseException {
168    
169                    if (firstPart == null) {
170    
171                            throw new IllegalArgumentException("The first part must not be null");
172                    }
173    
174                    try {
175                            this.header = JWEHeader.parse(firstPart);
176    
177                    } catch (ParseException e) {
178    
179                            throw new ParseException("Invalid JWE header: " + e.getMessage(), 0);
180                    }
181    
182                    if (secondPart == null || secondPart.toString().isEmpty()) {
183    
184                            encryptedKey = null;
185    
186                    } else {
187    
188                            encryptedKey = secondPart;
189                    }
190    
191                    if (thirdPart == null || thirdPart.toString().isEmpty()) {
192    
193                            initializationVector = null;
194    
195                    } else {
196    
197                            initializationVector = thirdPart;
198                    }
199    
200                    if (fourthPart == null) {
201    
202                            throw new IllegalArgumentException("The fourth part must not be null");
203                    }
204    
205                    cipherText = fourthPart;
206    
207                    if (fifthPart == null || fifthPart.toString().isEmpty()) {
208    
209                            authenticationTag = null;
210    
211                    } else {
212    
213                            authenticationTag = fifthPart;
214                    }
215    
216                    state = State.ENCRYPTED; // but not decrypted yet!
217    
218                    setParsedParts(firstPart, secondPart, thirdPart, fourthPart, fifthPart);
219            }
220    
221    
222            @Override
223            public ReadOnlyJWEHeader getHeader() {
224    
225                    return header;
226            }
227    
228    
229            /**
230             * Gets the encrypted key of this JWE object.
231             *
232             * @return The encrypted key, {@code null} not applicable or the JWE
233             *         object has not been encrypted yet.
234             */
235            public Base64URL getEncryptedKey() {
236    
237                    return encryptedKey;
238            }
239    
240    
241            /**
242             * Gets the initialisation vector (IV) of this JWE object.
243             *
244             * @return The initialisation vector (IV), {@code null} if not 
245             *         applicable or the JWE object has not been encrypted yet.
246             */
247            public Base64URL getInitializationVector() {
248    
249                    return initializationVector;
250            }
251    
252    
253            /**
254             * Gets the cipher text of this JWE object.
255             *
256             * @return The cipher text, {@code null} if the JWE object has not been
257             *         encrypted yet.
258             */
259            public Base64URL getCipherText() {
260    
261                    return cipherText;
262            }
263    
264    
265            /**
266             * Gets the authentication tag of this JWE object.
267             *
268             * @return The authentication tag, {@code null} if not applicable or 
269             *         the JWE object has not been encrypted yet.
270             */
271            public Base64URL getAuthenticationTag() {
272    
273                    return authenticationTag;
274            }
275    
276    
277            /**
278             * Use {@link #getAuthenticationTag} instead.
279             */
280            @Deprecated
281            public Base64URL getIntegrityValue() {
282    
283                    return getAuthenticationTag();
284            }
285    
286    
287            /**
288             * Gets the state of this JWE object.
289             *
290             * @return The state.
291             */
292            public State getState() {
293    
294                    return state;
295            }
296    
297    
298            /**
299             * Ensures the current state is {@link State#UNENCRYPTED unencrypted}.
300             *
301             * @throws IllegalStateException If the current state is not 
302             *                               unencrypted.
303             */
304            private void ensureUnencryptedState() {
305    
306                    if (state != State.UNENCRYPTED) {
307    
308                            throw new IllegalStateException("The JWE object must be in an unencrypted state");
309                    }
310            }
311    
312    
313            /**
314             * Ensures the current state is {@link State#ENCRYPTED encrypted}.
315             *
316             * @throws IllegalStateException If the current state is not encrypted.
317             */
318            private void ensureEncryptedState() {
319    
320                    if (state != State.ENCRYPTED) {
321    
322                            throw new IllegalStateException("The JWE object must be in an encrypted state");
323                    }
324            }
325    
326    
327            /**
328             * Ensures the current state is {@link State#ENCRYPTED encrypted} or
329             * {@link State#DECRYPTED decrypted}.
330             *
331             * @throws IllegalStateException If the current state is not encrypted 
332             *                               or decrypted.
333             */
334            private void ensureEncryptedOrDecryptedState() {
335    
336                    if (state != State.ENCRYPTED && state != State.DECRYPTED) {
337    
338                            throw new IllegalStateException("The JWE object must be in an encrypted or decrypted state");
339                    }
340            }
341    
342    
343            /**
344             * Ensures the specified JWE encrypter supports the algorithms of this 
345             * JWE object.
346             *
347             * @throws JOSEException If the JWE algorithms are not supported.
348             */
349            private void ensureJWEEncrypterSupport(final JWEEncrypter encrypter)
350                    throws JOSEException {
351    
352                    if (! encrypter.supportedAlgorithms().contains(getHeader().getAlgorithm())) {
353    
354                            throw new JOSEException("The \"" + getHeader().getAlgorithm() + 
355                                                    "\" algorithm is not supported by the JWE encrypter");
356                    }
357    
358                    if (! encrypter.supportedEncryptionMethods().contains(getHeader().getEncryptionMethod())) {
359    
360                            throw new JOSEException("The \"" + getHeader().getEncryptionMethod() + 
361                                                    "\" encryption method is not supported by the JWE encrypter");
362                    }
363            }
364    
365    
366            /**
367             * Ensures the specified JWE decrypter accepts the algorithms and the 
368             * headers of this JWE object.
369             *
370             * @throws JOSEException If the JWE algorithms or headers are not 
371             *                       accepted.
372             */
373            private void ensureJWEDecrypterAcceptance(final JWEDecrypter decrypter)
374                    throws JOSEException {
375    
376                    JWEHeaderFilter filter = decrypter.getJWEHeaderFilter();
377    
378                    if (filter == null) {
379    
380                            return;
381                    }
382    
383    
384                    if (! filter.getAcceptedAlgorithms().contains(getHeader().getAlgorithm())) {
385    
386                            throw new JOSEException("The \"" + getHeader().getAlgorithm() + 
387                                                    "\" algorithm is not accepted by the JWE decrypter");
388                    }
389    
390    
391                    if (! filter.getAcceptedEncryptionMethods().contains(getHeader().getEncryptionMethod())) {
392    
393                            throw new JOSEException("The \"" + getHeader().getEncryptionMethod() + 
394                                                    "\" encryption method is not accepted by the JWE decrypter");
395                    }               
396    
397                    // Header params
398    
399                    if (! filter.getAcceptedParameters().containsAll(getHeader().getIncludedParameters())) {
400    
401                            throw new JOSEException("One or more header parameters not accepted by the JWE decrypter");
402                    }
403            }
404    
405    
406            /**
407             * Encrypts this JWE object with the specified encrypter. The JWE 
408             * object must be in an {@link State#UNENCRYPTED unencrypted} state.
409             *
410             * @param encrypter The JWE encrypter. Must not be {@code null}.
411             *
412             * @throws IllegalStateException If the JWE object is not in an 
413             *                               {@link State#UNENCRYPTED unencrypted
414             *                               state}.
415             * @throws JOSEException         If the JWE object couldn't be 
416             *                               encrypted.
417             */
418            public synchronized void encrypt(final JWEEncrypter encrypter)
419                    throws JOSEException {
420    
421                    ensureUnencryptedState();
422    
423                    ensureJWEEncrypterSupport(encrypter);
424    
425                    JWECryptoParts parts = null;
426    
427                    try {
428                            parts = encrypter.encrypt(getHeader(), getPayload().toBytes());
429    
430                    } catch (JOSEException e) {
431    
432                            throw e;
433                    
434                    } catch (Exception e) {
435    
436                            // Prevent throwing unchecked exceptions at this point,
437                            // see issue #20
438                            throw new JOSEException(e.getMessage(), e);
439                    }
440    
441                    encryptedKey = parts.getEncryptedKey();
442                    initializationVector = parts.getInitializationVector();
443                    cipherText = parts.getCipherText();
444                    authenticationTag = parts.getAuthenticationTag();
445    
446                    state = State.ENCRYPTED;
447            }
448    
449    
450            /**
451             * Decrypts this JWE object with the specified decrypter. The JWE 
452             * object must be in a {@link State#ENCRYPTED encrypted} state.
453             *
454             * @param decrypter The JWE decrypter. Must not be {@code null}.
455             *
456             * @throws IllegalStateException If the JWE object is not in an 
457             *                               {@link State#ENCRYPTED encrypted
458             *                               state}.
459             * @throws JOSEException         If the JWE object couldn't be 
460             *                               decrypted.
461             */
462            public synchronized void decrypt(final JWEDecrypter decrypter)
463                    throws JOSEException {
464    
465                    ensureEncryptedState();
466    
467                    ensureJWEDecrypterAcceptance(decrypter);
468    
469                    try {
470                            setPayload(new Payload(decrypter.decrypt(getHeader(), 
471                                                   getEncryptedKey(), 
472                                                   getInitializationVector(),
473                                                   getCipherText(), 
474                                                   getAuthenticationTag())));
475    
476                    } catch (JOSEException e) {
477    
478                            throw e;
479    
480                    } catch (Exception e) {
481    
482                            // Prevent throwing unchecked exceptions at this point,
483                            // see issue #20
484                            throw new JOSEException(e.getMessage(), e);
485                    }
486    
487                    state = State.DECRYPTED;
488            }
489    
490    
491            /**
492             * Serialises this JWE object to its compact format consisting of 
493             * Base64URL-encoded parts delimited by period ('.') characters. It 
494             * must be in a {@link State#ENCRYPTED encrypted} or 
495             * {@link State#DECRYPTED decrypted} state.
496             *
497             * <pre>
498             * [header-base64url].[encryptedKey-base64url].[iv-base64url].[cipherText-base64url].[authTag-base64url]
499             * </pre>
500             *
501             * @return The serialised JWE object.
502             *
503             * @throws IllegalStateException If the JWE object is not in a 
504             *                               {@link State#ENCRYPTED encrypted} or
505             *                               {@link State#DECRYPTED decrypted 
506             *                               state}.
507             */
508            @Override
509            public String serialize() {
510    
511                    ensureEncryptedOrDecryptedState();
512    
513                    StringBuilder sb = new StringBuilder(header.toBase64URL().toString());
514                    sb.append('.');
515    
516                    if (encryptedKey != null) {
517                            
518                            sb.append(encryptedKey.toString());
519                    }
520    
521                    sb.append('.');
522    
523                    if (initializationVector != null) {
524    
525                            sb.append(initializationVector.toString());
526                    }
527    
528                    sb.append('.');
529    
530                    sb.append(cipherText.toString());
531    
532                    sb.append('.');
533    
534                    if (authenticationTag != null) {
535    
536                            sb.append(authenticationTag.toString());
537                    }
538    
539                    return sb.toString();
540            }
541    
542    
543            /**
544             * Parses a JWE object from the specified string in compact form. The 
545             * parsed JWE object will be given an {@link State#ENCRYPTED} state.
546             *
547             * @param s The string to parse. Must not be {@code null}.
548             *
549             * @return The JWE object.
550             *
551             * @throws ParseException If the string couldn't be parsed to a valid 
552             *                        JWE object.
553             */
554            public static JWEObject parse(final String s)
555                    throws ParseException {
556    
557                    Base64URL[] parts = JOSEObject.split(s);
558    
559                    if (parts.length != 5) {
560    
561                            throw new ParseException("Unexpected number of Base64URL parts, must be five", 0);
562                    }
563    
564                    return new JWEObject(parts[0], parts[1], parts[2], parts[3], parts[4]);
565            }
566    }