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.util.Base64URL; 022import com.nimbusds.jose.util.JSONObjectUtils; 023import com.nimbusds.jose.util.StandardCharset; 024import com.nimbusds.jwt.SignedJWT; 025import net.jcip.annotations.Immutable; 026 027import java.io.Serializable; 028import java.text.ParseException; 029import java.util.Map; 030import java.util.Objects; 031 032 033/** 034 * Payload of an unsecured (plain), JSON Web Signature (JWS) or JSON Web 035 * Encryption (JWE) object. Supports JSON object, string, byte array, 036 * Base64URL, JWS object and signed JWT payload representations. This class is 037 * immutable. 038 * 039 * <p>UTF-8 is the character set for all conversions between strings and byte 040 * arrays. 041 * 042 * <p>Conversion relations: 043 * 044 * <pre> 045 * JSON object <=> String <=> Base64URL 046 * <=> byte[] 047 * <=> JWSObject 048 * <=> SignedJWT 049 * </pre> 050 * 051 * @author Vladimir Dzhuvinov 052 * @version 2024-04-20 053 */ 054@Immutable 055public final class Payload implements Serializable { 056 057 058 /** 059 * Enumeration of the original data types used to create a 060 * {@link Payload}. 061 */ 062 public enum Origin { 063 064 065 /** 066 * The payload was created from a JSON object. 067 */ 068 JSON, 069 070 071 /** 072 * The payload was created from a string. 073 */ 074 STRING, 075 076 077 /** 078 * The payload was created from a byte array. 079 */ 080 BYTE_ARRAY, 081 082 083 /** 084 * The payload was created from a Base64URL-encoded object. 085 */ 086 BASE64URL, 087 088 089 /** 090 * The payload was created from a JWS object. 091 */ 092 JWS_OBJECT, 093 094 095 /** 096 * The payload was created from a signed JSON Web Token (JWT). 097 */ 098 SIGNED_JWT 099 } 100 101 102 private static final long serialVersionUID = 1L; 103 104 105 /** 106 * The original payload data type. 107 */ 108 private final Origin origin; 109 110 111 /** 112 * The JSON object representation. 113 */ 114 private final Map<String, Object> jsonObject; 115 116 117 /** 118 * The string representation. 119 */ 120 private final String string; 121 122 123 /** 124 * The byte array representation. 125 */ 126 private final byte[] bytes; 127 128 129 /** 130 * The Base64URL representation. 131 */ 132 private final Base64URL base64URL; 133 134 135 /** 136 * The JWS object representation. 137 */ 138 private final JWSObject jwsObject; 139 140 141 /** 142 * The signed JWT representation. 143 */ 144 private final SignedJWT signedJWT; 145 146 147 /** 148 * Converts a byte array to a string using {@code UTF-8}. 149 * 150 * @param bytes The byte array to convert. May be {@code null}. 151 * 152 * @return The resulting string, {@code null} if conversion failed. 153 */ 154 private static String byteArrayToString(final byte[] bytes) { 155 156 return bytes != null ? new String(bytes, StandardCharset.UTF_8) : null; 157 } 158 159 160 /** 161 * Converts a string to a byte array using {@code UTF-8}. 162 * 163 * @param string The string to convert. May be {@code null}. 164 * 165 * @return The resulting byte array, {@code null} if conversion failed. 166 */ 167 private static byte[] stringToByteArray(final String string) { 168 169 return string != null ? string.getBytes(StandardCharset.UTF_8) : null; 170 } 171 172 173 /** 174 * Creates a new payload from the specified JSON object. 175 * 176 * @param jsonObject The JSON object representing the payload. Must not 177 * be {@code null}. 178 */ 179 public Payload(final Map<String, Object> jsonObject) { 180 181 this.jsonObject = JSONObjectUtils.newJSONObject(); 182 this.jsonObject.putAll(Objects.requireNonNull(jsonObject, "The JSON object must not be null")); 183 string = null; 184 bytes = null; 185 base64URL = null; 186 jwsObject = null; 187 signedJWT = null; 188 189 origin = Origin.JSON; 190 } 191 192 193 /** 194 * Creates a new payload from the specified string. 195 * 196 * @param string The string representing the payload. Must not be 197 * {@code null}. 198 */ 199 public Payload(final String string) { 200 201 jsonObject = null; 202 this.string = Objects.requireNonNull(string, "The string must not be null"); 203 bytes = null; 204 base64URL = null; 205 jwsObject = null; 206 signedJWT = null; 207 208 origin = Origin.STRING; 209 } 210 211 212 /** 213 * Creates a new payload from the specified byte array. 214 * 215 * @param bytes The byte array representing the payload. Must not be 216 * {@code null}. 217 */ 218 public Payload(final byte[] bytes) { 219 220 jsonObject = null; 221 string = null; 222 this.bytes = Objects.requireNonNull(bytes, "The byte array must not be null"); 223 base64URL = null; 224 jwsObject = null; 225 signedJWT = null; 226 227 origin = Origin.BYTE_ARRAY; 228 } 229 230 231 /** 232 * Creates a new payload from the specified Base64URL-encoded object. 233 * 234 * @param base64URL The Base64URL-encoded object representing the 235 * payload. Must not be {@code null}. 236 */ 237 public Payload(final Base64URL base64URL) { 238 239 jsonObject = null; 240 string = null; 241 bytes = null; 242 this.base64URL = Objects.requireNonNull(base64URL, "The Base64URL-encoded object must not be null"); 243 jwsObject = null; 244 signedJWT = null; 245 246 origin = Origin.BASE64URL; 247 } 248 249 250 /** 251 * Creates a new payload from the specified JWS object. Intended for 252 * signed then encrypted JOSE objects. 253 * 254 * @param jwsObject The JWS object representing the payload. Must be in 255 * a signed state and not {@code null}. 256 */ 257 public Payload(final JWSObject jwsObject) { 258 259 if (jwsObject.getState() == JWSObject.State.UNSIGNED) { 260 throw new IllegalArgumentException("The JWS object must be signed"); 261 } 262 263 jsonObject = null; 264 string = null; 265 bytes = null; 266 base64URL = null; 267 this.jwsObject = jwsObject; 268 signedJWT = null; 269 270 origin = Origin.JWS_OBJECT; 271 } 272 273 274 /** 275 * Creates a new payload from the specified signed JSON Web Token 276 * (JWT). Intended for signed then encrypted JWTs. 277 * 278 * @param signedJWT The signed JWT representing the payload. Must be in 279 * a signed state and not {@code null}. 280 */ 281 public Payload(final SignedJWT signedJWT) { 282 283 if (signedJWT.getState() == JWSObject.State.UNSIGNED) { 284 throw new IllegalArgumentException("The JWT must be signed"); 285 } 286 287 jsonObject = null; 288 string = null; 289 bytes = null; 290 base64URL = null; 291 this.signedJWT = signedJWT; 292 jwsObject = signedJWT; // The signed JWT is also a JWS 293 294 origin = Origin.SIGNED_JWT; 295 } 296 297 298 /** 299 * Gets the original data type used to create this payload. 300 * 301 * @return The payload origin. 302 */ 303 public Origin getOrigin() { 304 305 return origin; 306 } 307 308 309 /** 310 * Returns a JSON object representation of this payload. 311 * 312 * @return The JSON object representation, {@code null} if the payload 313 * couldn't be converted to a JSON object. 314 */ 315 public Map<String, Object> toJSONObject() { 316 317 if (jsonObject != null) { 318 return jsonObject; 319 } 320 321 // Convert 322 323 String s = toString(); 324 325 if (s == null) { 326 // to string conversion failed 327 return null; 328 } 329 330 try { 331 return JSONObjectUtils.parse(s); 332 333 } catch (ParseException e) { 334 // Payload not a JSON object 335 return null; 336 } 337 } 338 339 /** 340 * Returns a string representation of this payload. 341 * 342 * @return The string representation. 343 */ 344 @Override 345 public String toString() { 346 347 if (string != null) { 348 349 return string; 350 } 351 352 // Convert 353 if (jwsObject != null) { 354 355 if (jwsObject.getParsedString() != null) { 356 return jwsObject.getParsedString(); 357 } else { 358 return jwsObject.serialize(); 359 } 360 361 } else if (jsonObject != null) { 362 363 return JSONObjectUtils.toJSONString(jsonObject); 364 365 } else if (bytes != null) { 366 367 return byteArrayToString(bytes); 368 369 } else if (base64URL != null) { 370 371 return base64URL.decodeToString(); 372 } else { 373 return null; // should never happen 374 } 375 } 376 377 378 /** 379 * Returns a byte array representation of this payload. 380 * 381 * @return The byte array representation. 382 */ 383 public byte[] toBytes() { 384 385 if (bytes != null) { 386 return bytes; 387 } 388 389 // Convert 390 if (base64URL != null) { 391 return base64URL.decode(); 392 393 } 394 395 return stringToByteArray(toString()); 396 } 397 398 399 /** 400 * Returns a Base64URL representation of this payload, as required for 401 * JOSE serialisation (see RFC 7515, section 7). 402 * 403 * @return The Base64URL representation. 404 */ 405 public Base64URL toBase64URL() { 406 407 if (base64URL != null) { 408 return base64URL; 409 } 410 411 // Convert 412 return Base64URL.encode(toBytes()); 413 } 414 415 416 /** 417 * Returns a JWS object representation of this payload. Intended for 418 * signed then encrypted JOSE objects. 419 * 420 * @return The JWS object representation, {@code null} if the payload 421 * couldn't be converted to a JWS object. 422 */ 423 public JWSObject toJWSObject() { 424 425 if (jwsObject != null) { 426 return jwsObject; 427 } 428 429 try { 430 return JWSObject.parse(toString()); 431 432 } catch (ParseException e) { 433 434 return null; 435 } 436 } 437 438 439 /** 440 * Returns a signed JSON Web Token (JWT) representation of this 441 * payload. Intended for signed then encrypted JWTs. 442 * 443 * @return The signed JWT representation, {@code null} if the payload 444 * couldn't be converted to a signed JWT. 445 */ 446 public SignedJWT toSignedJWT() { 447 448 if (signedJWT != null) { 449 return signedJWT; 450 } 451 452 try { 453 return SignedJWT.parse(toString()); 454 455 } catch (ParseException e) { 456 457 return null; 458 } 459 } 460 461 462 /** 463 * Returns a transformation of this payload. 464 * 465 * @param <T> Type of the result. 466 * @param transformer The payload transformer. Must not be 467 * {@code null}. 468 * 469 * @return The transformed payload. 470 */ 471 public <T> T toType(final PayloadTransformer<T> transformer) { 472 473 return transformer.transform(this); 474 } 475}