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