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