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