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