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