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