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