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-08-20) 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 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 iv; 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 authTag; 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, 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 iv = null; 166 167 } else { 168 169 iv = 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 authTag = null; 182 183 } else { 184 185 authTag = 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 JWEHeader getHeader() { 196 197 return header; 198 } 199 200 201 /** 202 * Returns 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 * Returns 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 getIV() { 220 221 return iv; 222 } 223 224 225 /** 226 * Returns 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 * Returns 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 getAuthTag() { 244 245 return authTag; 246 } 247 248 249 /** 250 * Returns the state of this JWE object. 251 * 252 * @return The state. 253 */ 254 public State getState() { 255 256 return state; 257 } 258 259 260 /** 261 * Ensures the current state is {@link State#UNENCRYPTED unencrypted}. 262 * 263 * @throws IllegalStateException If the current state is not 264 * unencrypted. 265 */ 266 private void ensureUnencryptedState() { 267 268 if (state != State.UNENCRYPTED) { 269 270 throw new IllegalStateException("The JWE object must be in an unencrypted state"); 271 } 272 } 273 274 275 /** 276 * Ensures the current state is {@link State#ENCRYPTED encrypted}. 277 * 278 * @throws IllegalStateException If the current state is not encrypted. 279 */ 280 private void ensureEncryptedState() { 281 282 if (state != State.ENCRYPTED) { 283 284 throw new IllegalStateException("The JWE object must be in an encrypted state"); 285 } 286 } 287 288 289 /** 290 * Ensures the current state is {@link State#ENCRYPTED encrypted} or 291 * {@link State#DECRYPTED decrypted}. 292 * 293 * @throws IllegalStateException If the current state is not encrypted 294 * or decrypted. 295 */ 296 private void ensureEncryptedOrDecryptedState() { 297 298 if (state != State.ENCRYPTED && state != State.DECRYPTED) { 299 300 throw new IllegalStateException("The JWE object must be in an encrypted or decrypted state"); 301 } 302 } 303 304 305 /** 306 * Ensures the specified JWE encrypter supports the algorithms of this 307 * JWE object. 308 * 309 * @throws JOSEException If the JWE algorithms are not supported. 310 */ 311 private void ensureJWEEncrypterSupport(final JWEEncrypter encrypter) 312 throws JOSEException { 313 314 if (! encrypter.supportedAlgorithms().contains(getHeader().getAlgorithm())) { 315 316 throw new JOSEException("The \"" + getHeader().getAlgorithm() + 317 "\" algorithm is not supported by the JWE encrypter"); 318 } 319 320 if (! encrypter.supportedEncryptionMethods().contains(getHeader().getEncryptionMethod())) { 321 322 throw new JOSEException("The \"" + getHeader().getEncryptionMethod() + 323 "\" encryption method is not supported by the JWE encrypter"); 324 } 325 } 326 327 328 /** 329 * Ensures the specified JWE decrypter accepts the algorithms and the 330 * headers of this JWE object. 331 * 332 * @throws JOSEException If the JWE algorithms or headers are not 333 * accepted. 334 */ 335 private void ensureJWEDecrypterAcceptance(final JWEDecrypter decrypter) 336 throws JOSEException { 337 338 339 if (! decrypter.getAcceptedAlgorithms().contains(getHeader().getAlgorithm())) { 340 341 throw new JOSEException("The \"" + getHeader().getAlgorithm() + 342 "\" algorithm is not accepted by the JWE decrypter"); 343 } 344 345 346 if (! decrypter.getAcceptedEncryptionMethods().contains(getHeader().getEncryptionMethod())) { 347 348 throw new JOSEException("The \"" + getHeader().getEncryptionMethod() + 349 "\" encryption method is not accepted by the JWE decrypter"); 350 } 351 } 352 353 354 /** 355 * Encrypts this JWE object with the specified encrypter. The JWE 356 * object must be in an {@link State#UNENCRYPTED unencrypted} state. 357 * 358 * @param encrypter The JWE encrypter. Must not be {@code null}. 359 * 360 * @throws IllegalStateException If the JWE object is not in an 361 * {@link State#UNENCRYPTED unencrypted 362 * state}. 363 * @throws JOSEException If the JWE object couldn't be 364 * encrypted. 365 */ 366 public synchronized void encrypt(final JWEEncrypter encrypter) 367 throws JOSEException { 368 369 ensureUnencryptedState(); 370 371 ensureJWEEncrypterSupport(encrypter); 372 373 JWECryptoParts parts = null; 374 375 try { 376 parts = encrypter.encrypt(getHeader(), getPayload().toBytes()); 377 378 } catch (JOSEException e) { 379 380 throw e; 381 382 } catch (Exception e) { 383 384 // Prevent throwing unchecked exceptions at this point, 385 // see issue #20 386 throw new JOSEException(e.getMessage(), e); 387 } 388 389 // Check if the header has been modified 390 if (parts.getHeader() != null) { 391 header = parts.getHeader(); 392 } 393 394 encryptedKey = parts.getEncryptedKey(); 395 iv = parts.getInitializationVector(); 396 cipherText = parts.getCipherText(); 397 authTag = parts.getAuthenticationTag(); 398 399 state = State.ENCRYPTED; 400 } 401 402 403 /** 404 * Decrypts this JWE object with the specified decrypter. The JWE 405 * object must be in a {@link State#ENCRYPTED encrypted} state. 406 * 407 * @param decrypter The JWE decrypter. Must not be {@code null}. 408 * 409 * @throws IllegalStateException If the JWE object is not in an 410 * {@link State#ENCRYPTED encrypted 411 * state}. 412 * @throws JOSEException If the JWE object couldn't be 413 * decrypted. 414 */ 415 public synchronized void decrypt(final JWEDecrypter decrypter) 416 throws JOSEException { 417 418 ensureEncryptedState(); 419 420 ensureJWEDecrypterAcceptance(decrypter); 421 422 try { 423 setPayload(new Payload(decrypter.decrypt(getHeader(), 424 getEncryptedKey(), 425 getIV(), 426 getCipherText(), 427 getAuthTag()))); 428 429 } catch (JOSEException e) { 430 431 throw e; 432 433 } catch (Exception e) { 434 435 // Prevent throwing unchecked exceptions at this point, 436 // see issue #20 437 throw new JOSEException(e.getMessage(), e); 438 } 439 440 state = State.DECRYPTED; 441 } 442 443 444 /** 445 * Serialises this JWE object to its compact format consisting of 446 * Base64URL-encoded parts delimited by period ('.') characters. It 447 * must be in a {@link State#ENCRYPTED encrypted} or 448 * {@link State#DECRYPTED decrypted} state. 449 * 450 * <pre> 451 * [header-base64url].[encryptedKey-base64url].[iv-base64url].[cipherText-base64url].[authTag-base64url] 452 * </pre> 453 * 454 * @return The serialised JWE object. 455 * 456 * @throws IllegalStateException If the JWE object is not in a 457 * {@link State#ENCRYPTED encrypted} or 458 * {@link State#DECRYPTED decrypted 459 * state}. 460 */ 461 @Override 462 public String serialize() { 463 464 ensureEncryptedOrDecryptedState(); 465 466 StringBuilder sb = new StringBuilder(header.toBase64URL().toString()); 467 sb.append('.'); 468 469 if (encryptedKey != null) { 470 471 sb.append(encryptedKey.toString()); 472 } 473 474 sb.append('.'); 475 476 if (iv != null) { 477 478 sb.append(iv.toString()); 479 } 480 481 sb.append('.'); 482 483 sb.append(cipherText.toString()); 484 485 sb.append('.'); 486 487 if (authTag != null) { 488 489 sb.append(authTag.toString()); 490 } 491 492 return sb.toString(); 493 } 494 495 496 /** 497 * Parses a JWE object from the specified string in compact form. The 498 * parsed JWE object will be given an {@link State#ENCRYPTED} state. 499 * 500 * @param s The string to parse. Must not be {@code null}. 501 * 502 * @return The JWE object. 503 * 504 * @throws ParseException If the string couldn't be parsed to a valid 505 * JWE object. 506 */ 507 public static JWEObject parse(final String s) 508 throws ParseException { 509 510 Base64URL[] parts = JOSEObject.split(s); 511 512 if (parts.length != 5) { 513 514 throw new ParseException("Unexpected number of Base64URL parts, must be five", 0); 515 } 516 517 return new JWEObject(parts[0], parts[1], parts[2], parts[3], parts[4]); 518 } 519}