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