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$ (2012-10-23)
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, Payload payload) {
092    
093                    if (header == null) {
094                            throw new IllegalArgumentException("The JWE header must not be null");
095                    }
096    
097                    this.header = header;
098    
099                    if (payload == null) {
100                            throw new IllegalArgumentException("The payload must not be null");
101                    }
102    
103                    setPayload(payload);
104    
105                    encryptedKey = null;
106    
107                    cipherText = null;
108    
109                    state = State.UNENCRYPTED;
110            }
111    
112    
113            /**
114             * Creates a new encrypted JSON Web Encryption (JWE) object with the 
115             * specified serialised parts. The state will be {@link State#ENCRYPTED 
116             * encrypted}.
117             *
118             * @param firstPart  The first part, corresponding to the JWE header. 
119             *                   Must not be {@code null}.
120             * @param secondPart The second part, corresponding to the encrypted 
121             *                   key. Empty or {@code null} if none.
122             * @param thirdPart  The third part, corresponding to the initialisation
123             *                   vector. Empty or {@code null} if none.
124             * @param fourthPart The fourth part, corresponding to the cipher text.
125             *                   Must not be {@code null}.
126             * @param fifthPart  The fifth part, corresponding to the integrity
127             *                   value. Empty of {@code null} if none.
128             *
129             * @throws ParseException If parsing of the serialised parts failed.
130             */
131            public JWEObject(final Base64URL firstPart, 
132                            final Base64URL secondPart, 
133                            final Base64URL thirdPart,
134                            final Base64URL fourthPart,
135                            final Base64URL fifthPart)
136                                            throws ParseException {
137    
138                    if (firstPart == null) {
139                            throw new IllegalArgumentException("The first part must not be null");
140                    }
141    
142                    try {
143                            this.header = JWEHeader.parse(firstPart);
144    
145                    } catch (ParseException e) {
146    
147                            throw new ParseException("Invalid JWE header: " + e.getMessage(), 0);
148                    }
149    
150                    if (secondPart == null || secondPart.toString().isEmpty()) {
151                            encryptedKey = null;
152                    } else {
153                            encryptedKey = secondPart;
154                    }
155    
156                    if (thirdPart == null || thirdPart.toString().isEmpty()) {
157                            initializationVector = null;
158                    } else {
159                            initializationVector = thirdPart;
160                    }
161    
162                    if (fourthPart == null) {
163                            throw new IllegalArgumentException("The fourth part must not be null");
164                    }
165    
166                    cipherText = fourthPart;
167    
168                    if (fifthPart == null || fifthPart.toString().isEmpty()) {
169                            integrityValue = null;
170                    } else {
171                            integrityValue = fifthPart;
172                    }
173    
174                    state = State.ENCRYPTED; // but not decrypted yet!
175    
176                    setParsedParts(firstPart, secondPart, thirdPart, fourthPart, fifthPart);
177            }
178    
179    
180            @Override
181            public ReadOnlyJWEHeader getHeader() {
182    
183                    return header;
184            }
185    
186    
187            /**
188             * Gets the encrypted key of this JWE object.
189             *
190             * @return The encrypted key, {@code null} not applicable or the JWE
191             *         object has not been encrypted yet.
192             */
193            public Base64URL getEncryptedKey() {
194    
195                    return encryptedKey;
196            }
197    
198    
199            /**
200             * Gets the initialisation vector (IV) of this JWE object.
201             *
202             * @return The initialisation vector (IV), {@code null} if not 
203             *         applicable or the JWE object has not been encrypted yet.
204             */
205            public Base64URL getInitializationVector() {
206    
207                    return initializationVector;
208            }
209    
210    
211            /**
212             * Gets the cipher text of this JWE object.
213             *
214             * @return The cipher text, {@code null} if the JWE object has not been
215             *         encrypted yet.
216             */
217            public Base64URL getCipherText() {
218    
219                    return cipherText;
220            }
221    
222    
223            /**
224             * Gets the integrity value of this JWE object.
225             *
226             * @return The integrity value, {@code null} if not applicable or the 
227             *         JWE object has not been encrypted yet.
228             */
229            public Base64URL getIntegrityValue() {
230    
231                    return integrityValue;
232            }
233    
234    
235            /**
236             * Gets the state of this JWE object.
237             *
238             * @return The state.
239             */
240            public State getState() {
241    
242                    return state;
243            }
244    
245    
246            /**
247             * Ensures the current state is {@link State#UNENCRYPTED unencrypted}.
248             *
249             * @throws IllegalStateException If the current state is not 
250             *                               unencrypted.
251             */
252            private void ensureUnencryptedState() {
253    
254                    if (state != State.UNENCRYPTED) {
255                            throw new IllegalStateException("The JWE object must be in an unencrypted state");
256                    }
257            }
258    
259    
260            /**
261             * Ensures the current state is {@link State#ENCRYPTED encrypted}.
262             *
263             * @throws IllegalStateException If the current state is not encrypted.
264             */
265            private void ensureEncryptedState() {
266    
267                    if (state != State.ENCRYPTED) {
268                            throw new IllegalStateException("The JWE object must be in an encrypted state");
269                    }
270            }
271    
272    
273            /**
274             * Ensures the current state is {@link State#ENCRYPTED encrypted} or
275             * {@link State#DECRYPTED decrypted}.
276             *
277             * @throws IllegalStateException If the current state is not encrypted 
278             *                               or decrypted.
279             */
280            private void ensureEncryptedOrDecryptedState() {
281    
282                    if (state != State.ENCRYPTED && state != State.DECRYPTED) {
283                            throw new IllegalStateException("The JWE object must be in an encrypted or decrypted state");
284                    }
285            }
286    
287    
288            /**
289             * Ensures the specified JWE encrypter supports the algorithms of this 
290             * JWE object.
291             *
292             * @throws JOSEException If the JWE algorithms are not supported.
293             */
294            private void ensureJWEEncrypterSupport(final JWEEncrypter encrypter)
295                            throws JOSEException {
296    
297                    if (! encrypter.supportedAlgorithms().contains(getHeader().getAlgorithm())) {
298    
299                            throw new JOSEException("The \"" + getHeader().getAlgorithm() + 
300                                            "\" algorithm is not supported by the JWE encrypter");
301                    }
302    
303                    if (! encrypter.supportedEncryptionMethods().contains(getHeader().getEncryptionMethod())) {
304    
305                            throw new JOSEException("The \"" + getHeader().getEncryptionMethod() + 
306                                            "\" encryption method is not supported by the JWE encrypter");
307                    }
308            }
309    
310    
311            /**
312             * Ensures the specified JWE decrypter accepts the algorithms and the 
313             * headers of this JWE object.
314             *
315             * @throws JOSEException If the JWE algorithms or headers are not 
316             *                       accepted.
317             */
318            private void ensureJWEDecrypterAcceptance(final JWEDecrypter decrypter)
319                            throws JOSEException {
320    
321                    JWEHeaderFilter filter = decrypter.getJWEHeaderFilter();
322    
323                    if (filter == null) {
324                            return;
325                    }
326    
327    
328                    if (! filter.getAcceptedAlgorithms().contains(getHeader().getAlgorithm())) {
329    
330                            throw new JOSEException("The \"" + getHeader().getAlgorithm() + 
331                                            "\" algorithm is not accepted by the JWE decrypter");
332                    }
333    
334    
335                    if (! filter.getAcceptedEncryptionMethods().contains(getHeader().getEncryptionMethod())) {
336    
337                            throw new JOSEException("The \"" + getHeader().getEncryptionMethod() + 
338                                            "\" encryption method is not accepted by the JWE decrypter");
339                    }               
340    
341                    // Header params
342    
343                    if (! filter.getAcceptedParameters().containsAll(getHeader().getIncludedParameters())) {
344    
345                            throw new JOSEException("One or more header parameters not accepted by the JWE decrypter");
346                    }
347            }
348    
349    
350            /**
351             * Encrypts this JWE object with the specified encrypter. The JWE object
352             * must be in an {@link State#UNENCRYPTED unencrypted} state.
353             *
354             * @param encrypter The JWE encrypter. Must not be {@code null}.
355             *
356             * @throws IllegalStateException If the JWE object is not in an 
357             *                               {@link State#UNENCRYPTED unencrypted
358             *                               state}.
359             * @throws JOSEException         If the JWE object couldn't be 
360             *                               encrypted.
361             */
362            public synchronized void encrypt(final JWEEncrypter encrypter)
363                            throws JOSEException {
364    
365                    ensureUnencryptedState();
366    
367                    ensureJWEEncrypterSupport(encrypter);
368    
369                    JWECryptoParts parts = encrypter.encrypt(getHeader(), getPayload().toBytes());
370    
371                    encryptedKey = parts.getEncryptedKey();
372                    initializationVector = parts.getInitializationVector();
373                    cipherText = parts.getCipherText();
374                    integrityValue = parts.getIntegrityValue();
375    
376                    state = State.ENCRYPTED;
377            }
378    
379    
380            /**
381             * Decrypts this JWE object with the specified decrypter. The JWE object
382             * must be in a {@link State#ENCRYPTED encrypted} state.
383             *
384             * @param decrypter The JWE decrypter. Must not be {@code null}.
385             *
386             * @throws IllegalStateException If the JWE object is not in an 
387             *                               {@link State#ENCRYPTED encrypted
388             *                               state}.
389             * @throws JOSEException         If the JWE object couldn't be 
390             *                               decrypted.
391             */
392            public synchronized void decrypt(final JWEDecrypter decrypter)
393                            throws JOSEException {
394    
395                    ensureEncryptedState();
396    
397                    ensureJWEDecrypterAcceptance(decrypter);
398    
399                    setPayload(new Payload(decrypter.decrypt(getHeader(), 
400                                    getEncryptedKey(), 
401                                    getInitializationVector(),
402                                    getCipherText(), 
403                                    getIntegrityValue())));
404    
405                    state = State.DECRYPTED;
406            }
407    
408    
409            /**
410             * Serialises this JWE object to its compact format consisting of 
411             * Base64URL-encoded parts delimited by period ('.') characters. It must 
412             * be in a {@link State#ENCRYPTED encrypted} or 
413             * {@link State#DECRYPTED decrypted} state.
414             *
415             * <pre>
416             * [header-base64url].[encryptedKey-base64url].[iv-base64url].[cipherText-base64url].[integrityValue-base64url]
417             * </pre>
418             *
419             * @return The serialised JWE object.
420             *
421             * @throws IllegalStateException If the JWE object is not in a 
422             *                               {@link State#ENCRYPTED encrypted} or
423             *                               {@link State#DECRYPTED decrypted 
424             *                               state}.
425             */
426            @Override
427            public String serialize() {
428    
429                    ensureEncryptedOrDecryptedState();
430    
431                    StringBuilder sb = new StringBuilder(header.toBase64URL().toString());
432                    sb.append('.');
433    
434                    if (encryptedKey != null) {
435                            sb.append(encryptedKey.toString());
436                    }
437    
438                    sb.append('.');
439    
440                    if (initializationVector != null) {
441                            sb.append(initializationVector.toString());
442                    }
443    
444                    sb.append('.');
445    
446                    sb.append(cipherText.toString());
447    
448                    sb.append('.');
449    
450                    if (integrityValue != null) {
451                            sb.append(integrityValue.toString());
452                    }
453    
454                    return sb.toString();
455            }
456    
457    
458            /**
459             * Parses a JWE object from the specified string in compact form. The 
460             * parsed JWE object will be given an {@link State#ENCRYPTED} state.
461             *
462             * @param s The string to parse. Must not be {@code null}.
463             *
464             * @return The JWE object.
465             *
466             * @throws ParseException If the string couldn't be parsed to a valid JWE
467             *                        object.
468             */
469            public static JWEObject parse(String s)
470                            throws ParseException {
471    
472                    Base64URL[] parts = JOSEObject.split(s);
473    
474                    if (parts.length != 5) {
475                            throw new ParseException("Unexpected number of Base64URL parts, must be five", 0);
476                    }
477    
478                    return new JWEObject(parts[0], parts[1], parts[2], parts[3], parts[4]);
479            }
480    }