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 /** 022 * Enumeration of the states of a JSON Web Encryption (JWE) object. 023 */ 024 public 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.supportedJWEAlgorithms().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 * Encrypts this JWE object with the specified encrypter. The JWE 330 * object must be in an {@link State#UNENCRYPTED unencrypted} state. 331 * 332 * @param encrypter The JWE encrypter. Must not be {@code null}. 333 * 334 * @throws IllegalStateException If the JWE object is not in an 335 * {@link State#UNENCRYPTED unencrypted 336 * state}. 337 * @throws JOSEException If the JWE object couldn't be 338 * encrypted. 339 */ 340 public synchronized void encrypt(final JWEEncrypter encrypter) 341 throws JOSEException { 342 343 ensureUnencryptedState(); 344 345 ensureJWEEncrypterSupport(encrypter); 346 347 JWECryptoParts parts; 348 349 try { 350 parts = encrypter.encrypt(getHeader(), getPayload().toBytes()); 351 352 } catch (JOSEException e) { 353 354 throw e; 355 356 } catch (Exception e) { 357 358 // Prevent throwing unchecked exceptions at this point, 359 // see issue #20 360 throw new JOSEException(e.getMessage(), e); 361 } 362 363 // Check if the header has been modified 364 if (parts.getHeader() != null) { 365 header = parts.getHeader(); 366 } 367 368 encryptedKey = parts.getEncryptedKey(); 369 iv = parts.getInitializationVector(); 370 cipherText = parts.getCipherText(); 371 authTag = parts.getAuthenticationTag(); 372 373 state = State.ENCRYPTED; 374 } 375 376 377 /** 378 * Decrypts this JWE object with the specified decrypter. The JWE 379 * object must be in a {@link State#ENCRYPTED encrypted} state. 380 * 381 * @param decrypter The JWE decrypter. Must not be {@code null}. 382 * 383 * @throws IllegalStateException If the JWE object is not in an 384 * {@link State#ENCRYPTED encrypted 385 * state}. 386 * @throws JOSEException If the JWE object couldn't be 387 * decrypted. 388 */ 389 public synchronized void decrypt(final JWEDecrypter decrypter) 390 throws JOSEException { 391 392 ensureEncryptedState(); 393 394 try { 395 setPayload(new Payload(decrypter.decrypt(getHeader(), 396 getEncryptedKey(), 397 getIV(), 398 getCipherText(), 399 getAuthTag()))); 400 401 } catch (JOSEException e) { 402 403 throw e; 404 405 } catch (Exception e) { 406 407 // Prevent throwing unchecked exceptions at this point, 408 // see issue #20 409 throw new JOSEException(e.getMessage(), e); 410 } 411 412 state = State.DECRYPTED; 413 } 414 415 416 /** 417 * Serialises this JWE object to its compact format consisting of 418 * Base64URL-encoded parts delimited by period ('.') characters. It 419 * must be in a {@link State#ENCRYPTED encrypted} or 420 * {@link State#DECRYPTED decrypted} state. 421 * 422 * <pre> 423 * [header-base64url].[encryptedKey-base64url].[iv-base64url].[cipherText-base64url].[authTag-base64url] 424 * </pre> 425 * 426 * @return The serialised JWE object. 427 * 428 * @throws IllegalStateException If the JWE object is not in a 429 * {@link State#ENCRYPTED encrypted} or 430 * {@link State#DECRYPTED decrypted 431 * state}. 432 */ 433 @Override 434 public String serialize() { 435 436 ensureEncryptedOrDecryptedState(); 437 438 StringBuilder sb = new StringBuilder(header.toBase64URL().toString()); 439 sb.append('.'); 440 441 if (encryptedKey != null) { 442 443 sb.append(encryptedKey.toString()); 444 } 445 446 sb.append('.'); 447 448 if (iv != null) { 449 450 sb.append(iv.toString()); 451 } 452 453 sb.append('.'); 454 455 sb.append(cipherText.toString()); 456 457 sb.append('.'); 458 459 if (authTag != null) { 460 461 sb.append(authTag.toString()); 462 } 463 464 return sb.toString(); 465 } 466 467 468 /** 469 * Parses a JWE object from the specified string in compact form. The 470 * parsed JWE object will be given an {@link State#ENCRYPTED} state. 471 * 472 * @param s The string to parse. Must not be {@code null}. 473 * 474 * @return The JWE object. 475 * 476 * @throws ParseException If the string couldn't be parsed to a valid 477 * JWE object. 478 */ 479 public static JWEObject parse(final String s) 480 throws ParseException { 481 482 Base64URL[] parts = JOSEObject.split(s); 483 484 if (parts.length != 5) { 485 486 throw new ParseException("Unexpected number of Base64URL parts, must be five", 0); 487 } 488 489 return new JWEObject(parts[0], parts[1], parts[2], parts[3], parts[4]); 490 } 491}