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 }