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