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