001package com.nimbusds.jose; 002 003 004import java.io.Serializable; 005import java.text.ParseException; 006import java.util.*; 007 008import net.minidev.json.JSONObject; 009 010import com.nimbusds.jose.util.Base64URL; 011import com.nimbusds.jose.util.JSONObjectUtils; 012 013 014/** 015 * The base abstract class for unsecured ({@code alg=none}), JSON Web Signature 016 * (JWS) and JSON Web Encryption (JWE) headers. 017 * 018 * <p>The header may also include {@link #getCustomParams custom 019 * parameters}; these will be serialised and parsed along the registered ones. 020 * 021 * @author Vladimir Dzhuvinov 022 * @version 2014-08-21 023 */ 024public abstract class Header implements Serializable { 025 026 027 private static final long serialVersionUID = 1L; 028 029 030 /** 031 * The algorithm ({@code alg}) parameter. 032 */ 033 private final Algorithm alg; 034 035 036 /** 037 * The JOSE object type ({@code typ}) parameter. 038 */ 039 private final JOSEObjectType typ; 040 041 042 /** 043 * The content type ({@code cty}) parameter. 044 */ 045 private final String cty; 046 047 048 /** 049 * The critical headers ({@code crit}) parameter. 050 */ 051 private final Set<String> crit; 052 053 054 /** 055 * Custom header parameters. 056 */ 057 private final Map<String,Object> customParams; 058 059 060 /** 061 * Empty custom parameters constant. 062 */ 063 private static final Map<String,Object> EMPTY_CUSTOM_PARAMS = 064 Collections.unmodifiableMap(new HashMap<String,Object>()); 065 066 067 /** 068 * The original parsed Base64URL, {@code null} if the header was 069 * created from scratch. 070 */ 071 private final Base64URL parsedBase64URL; 072 073 074 /** 075 * Creates a new abstract header. 076 * 077 * @param alg The algorithm ({@code alg}) parameter. Must 078 * not be {@code null}. 079 * @param typ The type ({@code typ}) parameter, 080 * {@code null} if not specified. 081 * @param cty The content type ({@code cty}) parameter, 082 * {@code null} if not specified. 083 * @param crit The names of the critical header 084 * ({@code crit}) parameters, empty set or 085 * {@code null} if none. 086 * @param customParams The custom parameters, empty map or 087 * {@code null} if none. 088 * @param parsedBase64URL The parsed Base64URL, {@code null} if the 089 * header is created from scratch. 090 */ 091 protected Header(final Algorithm alg, 092 final JOSEObjectType typ, 093 final String cty, Set<String> crit, 094 final Map<String,Object> customParams, 095 final Base64URL parsedBase64URL) { 096 097 if (alg == null) { 098 throw new IllegalArgumentException("The algorithm \"alg\" header parameter must not be null"); 099 } 100 101 this.alg = alg; 102 103 this.typ = typ; 104 this.cty = cty; 105 106 if (crit != null) { 107 // Copy and make unmodifiable 108 this.crit = Collections.unmodifiableSet(new HashSet<>(crit)); 109 } else { 110 this.crit = null; 111 } 112 113 if (customParams != null) { 114 // Copy and make unmodifiable 115 this.customParams = Collections.unmodifiableMap(new HashMap<>(customParams)); 116 } else { 117 this.customParams = EMPTY_CUSTOM_PARAMS; 118 } 119 120 this.parsedBase64URL = parsedBase64URL; 121 } 122 123 124 /** 125 * Deep copy constructor. 126 * 127 * @param header The header to copy. Must not be {@code null}. 128 */ 129 protected Header(final Header header) { 130 131 this( 132 header.getAlgorithm(), 133 header.getType(), 134 header.getContentType(), 135 header.getCriticalParams(), 136 header.getCustomParams(), 137 header.getParsedBase64URL()); 138 } 139 140 141 /** 142 * Gets the algorithm ({@code alg}) parameter. 143 * 144 * @return The algorithm parameter. 145 */ 146 public Algorithm getAlgorithm() { 147 148 return alg; 149 } 150 151 152 /** 153 * Gets the type ({@code typ}) parameter. 154 * 155 * @return The type parameter, {@code null} if not specified. 156 */ 157 public JOSEObjectType getType() { 158 159 return typ; 160 } 161 162 163 /** 164 * Gets the content type ({@code cty}) parameter. 165 * 166 * @return The content type parameter, {@code null} if not specified. 167 */ 168 public String getContentType() { 169 170 return cty; 171 } 172 173 174 /** 175 * Gets the critical header parameters ({@code crit}) parameter. 176 * 177 * @return The names of the critical header parameters, as a 178 * unmodifiable set, {@code null} if not specified. 179 */ 180 public Set<String> getCriticalParams() { 181 182 return crit; 183 } 184 185 186 /** 187 * Gets a custom (non-registered) parameter. 188 * 189 * @param name The name of the custom parameter. Must not be 190 * {@code null}. 191 * 192 * @return The custom parameter, {@code null} if not specified. 193 */ 194 public Object getCustomParam(final String name) { 195 196 return customParams.get(name); 197 } 198 199 200 /** 201 * Gets the custom (non-registered) parameters. 202 * 203 * @return The custom parameters, as a unmodifiable map, empty map if 204 * none. 205 */ 206 public Map<String,Object> getCustomParams() { 207 208 return customParams; 209 } 210 211 212 /** 213 * Gets the original Base64URL used to create this header. 214 * 215 * @return The parsed Base64URL, {@code null} if the header was created 216 * from scratch. 217 */ 218 public Base64URL getParsedBase64URL() { 219 220 return parsedBase64URL; 221 } 222 223 224 /** 225 * Gets the names of all included parameters (registered and custom) in 226 * the header instance. 227 * 228 * @return The included parameters. 229 */ 230 public Set<String> getIncludedParams() { 231 232 Set<String> includedParameters = 233 new HashSet<>(getCustomParams().keySet()); 234 235 includedParameters.add("alg"); 236 237 if (getType() != null) { 238 includedParameters.add("typ"); 239 } 240 241 if (getContentType() != null) { 242 includedParameters.add("cty"); 243 } 244 245 if (getCriticalParams() != null && ! getCriticalParams().isEmpty()) { 246 includedParameters.add("crit"); 247 } 248 249 return includedParameters; 250 } 251 252 253 /** 254 * Returns a JSON object representation of the header. All custom 255 * parameters are included if they serialise to a JSON entity and 256 * their names don't conflict with the registered ones. 257 * 258 * @return The JSON object representation of the header. 259 */ 260 public JSONObject toJSONObject() { 261 262 // Include custom parameters, they will be overwritten if their 263 // names match specified registered ones 264 JSONObject o = new JSONObject(customParams); 265 266 // Alg is always defined 267 o.put("alg", alg.toString()); 268 269 if (typ != null) { 270 o.put("typ", typ.toString()); 271 } 272 273 if (cty != null) { 274 o.put("cty", cty); 275 } 276 277 if (crit != null && ! crit.isEmpty()) { 278 o.put("crit", new ArrayList<>(crit)); 279 } 280 281 return o; 282 } 283 284 285 /** 286 * Returns a JSON string representation of the header. All custom 287 * parameters will be included if they serialise to a JSON entity and 288 * their names don't conflict with the registered ones. 289 * 290 * @return The JSON string representation of the header. 291 */ 292 public String toString() { 293 294 return toJSONObject().toString(); 295 } 296 297 298 /** 299 * Returns a Base64URL representation of the header. If the header was 300 * parsed always returns the original Base64URL (required for JWS 301 * validation and authenticated JWE decryption). 302 * 303 * @return The original parsed Base64URL representation of the header, 304 * or a new Base64URL representation if the header was created 305 * from scratch. 306 */ 307 public Base64URL toBase64URL() { 308 309 if (parsedBase64URL == null) { 310 311 // Header was created from scratch, return new Base64URL 312 return Base64URL.encode(toString()); 313 314 } else { 315 316 // Header was parsed, return original Base64URL 317 return parsedBase64URL; 318 } 319 } 320 321 322 /** 323 * Parses an algorithm ({@code alg}) parameter from the specified 324 * header JSON object. Intended for initial parsing of unsecured 325 * (plain), JWS and JWE headers. 326 * 327 * <p>The algorithm type (none, JWS or JWE) is determined by inspecting 328 * the algorithm name for "none" and the presence of an "enc" 329 * parameter. 330 * 331 * @param json The JSON object to parse. Must not be {@code null}. 332 * 333 * @return The algorithm, an instance of {@link Algorithm#NONE}, 334 * {@link JWSAlgorithm} or {@link JWEAlgorithm}. 335 * 336 * @throws ParseException If the {@code alg} parameter couldn't be 337 * parsed. 338 */ 339 public static Algorithm parseAlgorithm(final JSONObject json) 340 throws ParseException { 341 342 String algName = JSONObjectUtils.getString(json, "alg"); 343 344 // Infer algorithm type 345 346 if (algName.equals(Algorithm.NONE.getName())) { 347 // Plain 348 return Algorithm.NONE; 349 } else if (json.containsKey("enc")) { 350 // JWE 351 return JWEAlgorithm.parse(algName); 352 } else { 353 // JWS 354 return JWSAlgorithm.parse(algName); 355 } 356 } 357 358 359 /** 360 * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader} 361 * from the specified JSON object. 362 * 363 * @param jsonObject The JSON object to parse. Must not be 364 * {@code null}. 365 * 366 * @return The header. 367 * 368 * @throws ParseException If the specified JSON object doesn't 369 * represent a valid header. 370 */ 371 public static Header parse(final JSONObject jsonObject) 372 throws ParseException { 373 374 return parse(jsonObject, null); 375 } 376 377 378 /** 379 * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader} 380 * from the specified JSON object. 381 * 382 * @param jsonObject The JSON object to parse. Must not be 383 * {@code null}. 384 * @param parsedBase64URL The original parsed Base64URL, {@code null} 385 * if not applicable. 386 * 387 * @return The header. 388 * 389 * @throws ParseException If the specified JSON object doesn't 390 * represent a valid header. 391 */ 392 public static Header parse(final JSONObject jsonObject, 393 final Base64URL parsedBase64URL) 394 throws ParseException { 395 396 Algorithm alg = parseAlgorithm(jsonObject); 397 398 if (alg.equals(Algorithm.NONE)) { 399 400 return PlainHeader.parse(jsonObject, parsedBase64URL); 401 402 } else if (alg instanceof JWSAlgorithm) { 403 404 return JWSHeader.parse(jsonObject, parsedBase64URL); 405 406 } else if (alg instanceof JWEAlgorithm) { 407 408 return JWEHeader.parse(jsonObject, parsedBase64URL); 409 410 } else { 411 412 throw new AssertionError("Unexpected algorithm type: " + alg); 413 } 414 } 415 416 417 /** 418 * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader} 419 * from the specified JSON object string. 420 * 421 * @param jsonString The JSON object string to parse. Must not be 422 * {@code null}. 423 * 424 * @return The header. 425 * 426 * @throws ParseException If the specified JSON object string doesn't 427 * represent a valid header. 428 */ 429 public static Header parse(final String jsonString) 430 throws ParseException { 431 432 return parse(jsonString, null); 433 } 434 435 436 /** 437 * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader} 438 * from the specified JSON object string. 439 * 440 * @param jsonString The JSON object string to parse. Must not be 441 * {@code null}. 442 * @param parsedBase64URL The original parsed Base64URL, {@code null} 443 * if not applicable. 444 * 445 * @return The header. 446 * 447 * @throws ParseException If the specified JSON object string doesn't 448 * represent a valid header. 449 */ 450 public static Header parse(final String jsonString, 451 final Base64URL parsedBase64URL) 452 throws ParseException { 453 454 JSONObject jsonObject = JSONObjectUtils.parseJSONObject(jsonString); 455 456 return parse(jsonObject, parsedBase64URL); 457 } 458 459 460 /** 461 * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader} 462 * from the specified Base64URL. 463 * 464 * @param base64URL The Base64URL to parse. Must not be {@code null}. 465 * 466 * @return The header. 467 * 468 * @throws ParseException If the specified Base64URL doesn't represent 469 * a valid header. 470 */ 471 public static Header parse(final Base64URL base64URL) 472 throws ParseException { 473 474 return parse(base64URL.decodeToString(), base64URL); 475 } 476}