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 }