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