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) secured object. This class is thread-safe. 013 * 014 * @author Vladimir Dzhuvinov 015 * @version 2015-04-21 016 */ 017@ThreadSafe 018public class JWEObject extends JOSEObject { 019 020 021 private static final long serialVersionUID = 1L; 022 023 024 /** 025 * Enumeration of the states of a JSON Web Encryption (JWE) object. 026 */ 027 public 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 header. 051 */ 052 private JWEHeader header; 053 054 055 /** 056 * The encrypted key, {@code null} if not computed or applicable. 057 */ 058 private Base64URL encryptedKey; 059 060 061 /** 062 * The initialisation vector, {@code null} if not generated or 063 * applicable. 064 */ 065 private Base64URL iv; 066 067 068 /** 069 * The cipher text, {@code null} if not computed. 070 */ 071 private Base64URL cipherText; 072 073 074 /** 075 * The authentication tag, {@code null} if not computed or applicable. 076 */ 077 private Base64URL authTag; 078 079 080 /** 081 * The JWE object state. 082 */ 083 private State state; 084 085 086 /** 087 * Creates a new to-be-encrypted JSON Web Encryption (JWE) object with 088 * the specified header and payload. The initial state will be 089 * {@link State#UNENCRYPTED unencrypted}. 090 * 091 * @param header The JWE header. Must not be {@code null}. 092 * @param payload The payload. Must not be {@code null}. 093 */ 094 public JWEObject(final JWEHeader header, final Payload payload) { 095 096 if (header == null) { 097 098 throw new IllegalArgumentException("The JWE header must not be null"); 099 } 100 101 this.header = header; 102 103 if (payload == null) { 104 105 throw new IllegalArgumentException("The payload must not be null"); 106 } 107 108 setPayload(payload); 109 110 encryptedKey = null; 111 112 cipherText = null; 113 114 state = State.UNENCRYPTED; 115 } 116 117 118 /** 119 * Creates a new encrypted JSON Web Encryption (JWE) object with the 120 * specified serialised parts. The state will be {@link State#ENCRYPTED 121 * encrypted}. 122 * 123 * @param firstPart The first part, corresponding to the JWE header. 124 * Must not be {@code null}. 125 * @param secondPart The second part, corresponding to the encrypted 126 * key. Empty or {@code null} if none. 127 * @param thirdPart The third part, corresponding to the 128 * initialisation vector. Empty or {@code null} if 129 * none. 130 * @param fourthPart The fourth part, corresponding to the cipher text. 131 * Must not be {@code null}. 132 * @param fifthPart The fifth part, corresponding to the 133 * authentication tag. Empty of {@code null} if none. 134 * 135 * @throws ParseException If parsing of the serialised parts failed. 136 */ 137 public JWEObject(final Base64URL firstPart, 138 final Base64URL secondPart, 139 final Base64URL thirdPart, 140 final Base64URL fourthPart, 141 final Base64URL fifthPart) 142 throws ParseException { 143 144 if (firstPart == null) { 145 146 throw new IllegalArgumentException("The first part must not be null"); 147 } 148 149 try { 150 this.header = JWEHeader.parse(firstPart); 151 152 } catch (ParseException e) { 153 154 throw new ParseException("Invalid JWE header: " + e.getMessage(), 0); 155 } 156 157 if (secondPart == null || secondPart.toString().isEmpty()) { 158 159 encryptedKey = null; 160 161 } else { 162 163 encryptedKey = secondPart; 164 } 165 166 if (thirdPart == null || thirdPart.toString().isEmpty()) { 167 168 iv = null; 169 170 } else { 171 172 iv = thirdPart; 173 } 174 175 if (fourthPart == null) { 176 177 throw new IllegalArgumentException("The fourth part must not be null"); 178 } 179 180 cipherText = fourthPart; 181 182 if (fifthPart == null || fifthPart.toString().isEmpty()) { 183 184 authTag = null; 185 186 } else { 187 188 authTag = fifthPart; 189 } 190 191 state = State.ENCRYPTED; // but not decrypted yet! 192 193 setParsedParts(firstPart, secondPart, thirdPart, fourthPart, fifthPart); 194 } 195 196 197 @Override 198 public JWEHeader getHeader() { 199 200 return header; 201 } 202 203 204 /** 205 * Returns the encrypted key of this JWE object. 206 * 207 * @return The encrypted key, {@code null} not applicable or the JWE 208 * object has not been encrypted yet. 209 */ 210 public Base64URL getEncryptedKey() { 211 212 return encryptedKey; 213 } 214 215 216 /** 217 * Returns the initialisation vector (IV) of this JWE object. 218 * 219 * @return The initialisation vector (IV), {@code null} if not 220 * applicable or the JWE object has not been encrypted yet. 221 */ 222 public Base64URL getIV() { 223 224 return iv; 225 } 226 227 228 /** 229 * Returns the cipher text of this JWE object. 230 * 231 * @return The cipher text, {@code null} if the JWE object has not been 232 * encrypted yet. 233 */ 234 public Base64URL getCipherText() { 235 236 return cipherText; 237 } 238 239 240 /** 241 * Returns the authentication tag of this JWE object. 242 * 243 * @return The authentication tag, {@code null} if not applicable or 244 * the JWE object has not been encrypted yet. 245 */ 246 public Base64URL getAuthTag() { 247 248 return authTag; 249 } 250 251 252 /** 253 * Returns the state of this JWE object. 254 * 255 * @return The state. 256 */ 257 public State getState() { 258 259 return state; 260 } 261 262 263 /** 264 * Ensures the current state is {@link State#UNENCRYPTED unencrypted}. 265 * 266 * @throws IllegalStateException If the current state is not 267 * unencrypted. 268 */ 269 private void ensureUnencryptedState() { 270 271 if (state != State.UNENCRYPTED) { 272 273 throw new IllegalStateException("The JWE object must be in an unencrypted state"); 274 } 275 } 276 277 278 /** 279 * Ensures the current state is {@link State#ENCRYPTED encrypted}. 280 * 281 * @throws IllegalStateException If the current state is not encrypted. 282 */ 283 private void ensureEncryptedState() { 284 285 if (state != State.ENCRYPTED) { 286 287 throw new IllegalStateException("The JWE object must be in an encrypted state"); 288 } 289 } 290 291 292 /** 293 * Ensures the current state is {@link State#ENCRYPTED encrypted} or 294 * {@link State#DECRYPTED decrypted}. 295 * 296 * @throws IllegalStateException If the current state is not encrypted 297 * or decrypted. 298 */ 299 private void ensureEncryptedOrDecryptedState() { 300 301 if (state != State.ENCRYPTED && state != State.DECRYPTED) { 302 303 throw new IllegalStateException("The JWE object must be in an encrypted or decrypted state"); 304 } 305 } 306 307 308 /** 309 * Ensures the specified JWE encrypter supports the algorithms of this 310 * JWE object. 311 * 312 * @throws JOSEException If the JWE algorithms are not supported. 313 */ 314 private void ensureJWEEncrypterSupport(final JWEEncrypter encrypter) 315 throws JOSEException { 316 317 if (! encrypter.supportedJWEAlgorithms().contains(getHeader().getAlgorithm())) { 318 319 throw new JOSEException("The \"" + getHeader().getAlgorithm() + 320 "\" algorithm is not supported by the JWE encrypter"); 321 } 322 323 if (! encrypter.supportedEncryptionMethods().contains(getHeader().getEncryptionMethod())) { 324 325 throw new JOSEException("The \"" + getHeader().getEncryptionMethod() + 326 "\" encryption method is not supported by the JWE encrypter"); 327 } 328 } 329 330 331 /** 332 * Encrypts this JWE object with the specified encrypter. The JWE 333 * object must be in an {@link State#UNENCRYPTED unencrypted} state. 334 * 335 * @param encrypter The JWE encrypter. Must not be {@code null}. 336 * 337 * @throws IllegalStateException If the JWE object is not in an 338 * {@link State#UNENCRYPTED unencrypted 339 * state}. 340 * @throws JOSEException If the JWE object couldn't be 341 * encrypted. 342 */ 343 public synchronized void encrypt(final JWEEncrypter encrypter) 344 throws JOSEException { 345 346 ensureUnencryptedState(); 347 348 ensureJWEEncrypterSupport(encrypter); 349 350 JWECryptoParts parts; 351 352 try { 353 parts = encrypter.encrypt(getHeader(), getPayload().toBytes()); 354 355 } catch (JOSEException e) { 356 357 throw e; 358 359 } catch (Exception e) { 360 361 // Prevent throwing unchecked exceptions at this point, 362 // see issue #20 363 throw new JOSEException(e.getMessage(), e); 364 } 365 366 // Check if the header has been modified 367 if (parts.getHeader() != null) { 368 header = parts.getHeader(); 369 } 370 371 encryptedKey = parts.getEncryptedKey(); 372 iv = parts.getInitializationVector(); 373 cipherText = parts.getCipherText(); 374 authTag = parts.getAuthenticationTag(); 375 376 state = State.ENCRYPTED; 377 } 378 379 380 /** 381 * Decrypts this JWE object with the specified decrypter. The JWE 382 * object must be in a {@link State#ENCRYPTED encrypted} state. 383 * 384 * @param decrypter The JWE decrypter. Must not be {@code null}. 385 * 386 * @throws IllegalStateException If the JWE object is not in an 387 * {@link State#ENCRYPTED encrypted 388 * state}. 389 * @throws JOSEException If the JWE object couldn't be 390 * decrypted. 391 */ 392 public synchronized void decrypt(final JWEDecrypter decrypter) 393 throws JOSEException { 394 395 ensureEncryptedState(); 396 397 try { 398 setPayload(new Payload(decrypter.decrypt(getHeader(), 399 getEncryptedKey(), 400 getIV(), 401 getCipherText(), 402 getAuthTag()))); 403 404 } catch (JOSEException e) { 405 406 throw e; 407 408 } catch (Exception e) { 409 410 // Prevent throwing unchecked exceptions at this point, 411 // see issue #20 412 throw new JOSEException(e.getMessage(), e); 413 } 414 415 state = State.DECRYPTED; 416 } 417 418 419 /** 420 * Serialises this JWE object to its compact format consisting of 421 * Base64URL-encoded parts delimited by period ('.') characters. It 422 * must be in a {@link State#ENCRYPTED encrypted} or 423 * {@link State#DECRYPTED decrypted} state. 424 * 425 * <pre> 426 * [header-base64url].[encryptedKey-base64url].[iv-base64url].[cipherText-base64url].[authTag-base64url] 427 * </pre> 428 * 429 * @return The serialised JWE object. 430 * 431 * @throws IllegalStateException If the JWE object is not in a 432 * {@link State#ENCRYPTED encrypted} or 433 * {@link State#DECRYPTED decrypted 434 * state}. 435 */ 436 @Override 437 public String serialize() { 438 439 ensureEncryptedOrDecryptedState(); 440 441 StringBuilder sb = new StringBuilder(header.toBase64URL().toString()); 442 sb.append('.'); 443 444 if (encryptedKey != null) { 445 446 sb.append(encryptedKey.toString()); 447 } 448 449 sb.append('.'); 450 451 if (iv != null) { 452 453 sb.append(iv.toString()); 454 } 455 456 sb.append('.'); 457 458 sb.append(cipherText.toString()); 459 460 sb.append('.'); 461 462 if (authTag != null) { 463 464 sb.append(authTag.toString()); 465 } 466 467 return sb.toString(); 468 } 469 470 471 /** 472 * Parses a JWE object from the specified string in compact form. The 473 * parsed JWE object will be given an {@link State#ENCRYPTED} state. 474 * 475 * @param s The string to parse. Must not be {@code null}. 476 * 477 * @return The JWE object. 478 * 479 * @throws ParseException If the string couldn't be parsed to a valid 480 * JWE object. 481 */ 482 public static JWEObject parse(final String s) 483 throws ParseException { 484 485 Base64URL[] parts = JOSEObject.split(s); 486 487 if (parts.length != 5) { 488 489 throw new ParseException("Unexpected number of Base64URL parts, must be five", 0); 490 } 491 492 return new JWEObject(parts[0], parts[1], parts[2], parts[3], parts[4]); 493 } 494}