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