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 this.alg = alg; 122 this.typ = typ; 123 this.cty = cty; 124 125 if (crit != null) { 126 // Copy and make unmodifiable 127 this.crit = Collections.unmodifiableSet(new HashSet<>(crit)); 128 } else { 129 this.crit = null; 130 } 131 132 if (customParams != null) { 133 // Copy and make unmodifiable 134 this.customParams = Collections.unmodifiableMap(new HashMap<>(customParams)); 135 } else { 136 this.customParams = EMPTY_CUSTOM_PARAMS; 137 } 138 139 this.parsedBase64URL = parsedBase64URL; 140 } 141 142 143 /** 144 * Deep copy constructor. 145 * 146 * @param header The header to copy. Must not be {@code null}. 147 */ 148 protected Header(final Header header) { 149 150 this( 151 header.getAlgorithm(), 152 header.getType(), 153 header.getContentType(), 154 header.getCriticalParams(), 155 header.getCustomParams(), 156 header.getParsedBase64URL()); 157 } 158 159 160 /** 161 * Gets the algorithm ({@code alg}) parameter. 162 * 163 * @return The algorithm parameter. 164 */ 165 public Algorithm getAlgorithm() { 166 167 return alg; 168 } 169 170 171 /** 172 * Gets the type ({@code typ}) parameter. 173 * 174 * @return The type parameter, {@code null} if not specified. 175 */ 176 public JOSEObjectType getType() { 177 178 return typ; 179 } 180 181 182 /** 183 * Gets the content type ({@code cty}) parameter. 184 * 185 * @return The content type parameter, {@code null} if not specified. 186 */ 187 public String getContentType() { 188 189 return cty; 190 } 191 192 193 /** 194 * Gets the critical header parameters ({@code crit}) parameter. 195 * 196 * @return The names of the critical header parameters, as a 197 * unmodifiable set, {@code null} if not specified. 198 */ 199 public Set<String> getCriticalParams() { 200 201 return crit; 202 } 203 204 205 /** 206 * Gets a custom (non-registered) parameter. 207 * 208 * @param name The name of the custom parameter. Must not be 209 * {@code null}. 210 * 211 * @return The custom parameter, {@code null} if not specified. 212 */ 213 public Object getCustomParam(final String name) { 214 215 return customParams.get(name); 216 } 217 218 219 /** 220 * Gets the custom (non-registered) parameters. 221 * 222 * @return The custom parameters, as a unmodifiable map, empty map if 223 * none. 224 */ 225 public Map<String,Object> getCustomParams() { 226 227 return customParams; 228 } 229 230 231 /** 232 * Gets the original Base64URL used to create this header. 233 * 234 * @return The parsed Base64URL, {@code null} if the header was created 235 * from scratch. 236 */ 237 public Base64URL getParsedBase64URL() { 238 239 return parsedBase64URL; 240 } 241 242 243 /** 244 * Gets the names of all included parameters (registered and custom) in 245 * the header instance. 246 * 247 * @return The included parameters. 248 */ 249 public Set<String> getIncludedParams() { 250 251 Set<String> includedParameters = 252 new HashSet<>(getCustomParams().keySet()); 253 254 if (getAlgorithm() != null) { 255 includedParameters.add(HeaderParameterNames.ALGORITHM); 256 } 257 258 if (getType() != null) { 259 includedParameters.add(HeaderParameterNames.TYPE); 260 } 261 262 if (getContentType() != null) { 263 includedParameters.add(HeaderParameterNames.CONTENT_TYPE); 264 } 265 266 if (getCriticalParams() != null && ! getCriticalParams().isEmpty()) { 267 includedParameters.add(HeaderParameterNames.CRITICAL); 268 } 269 270 return includedParameters; 271 } 272 273 274 /** 275 * Returns a JSON object representation of the header. All custom 276 * parameters are included if they serialise to a JSON entity and 277 * their names don't conflict with the registered ones. 278 * 279 * @return The JSON object representation of the header. 280 */ 281 public Map<String, Object> toJSONObject() { 282 283 // Include custom parameters, they will be overwritten if their 284 // names match specified registered ones 285 Map<String, Object> o = JSONObjectUtils.newJSONObject(); 286 o.putAll(customParams); 287 288 if (alg != null) { 289 o.put(HeaderParameterNames.ALGORITHM, alg.toString()); 290 } 291 292 if (typ != null) { 293 o.put(HeaderParameterNames.TYPE, typ.toString()); 294 } 295 296 if (cty != null) { 297 o.put(HeaderParameterNames.CONTENT_TYPE, cty); 298 } 299 300 if (crit != null && ! crit.isEmpty()) { 301 o.put(HeaderParameterNames.CRITICAL, new ArrayList<>(crit)); 302 } 303 304 return o; 305 } 306 307 308 /** 309 * Returns a JSON string representation of the header. All custom 310 * parameters will be included if they serialise to a JSON entity and 311 * their names don't conflict with the registered ones. 312 * 313 * @return The JSON string representation of the header. 314 */ 315 public String toString() { 316 317 return JSONObjectUtils.toJSONString(toJSONObject()); 318 } 319 320 321 /** 322 * Returns a Base64URL representation of the header. If the header was 323 * parsed always returns the original Base64URL (required for JWS 324 * validation and authenticated JWE decryption). 325 * 326 * @return The original parsed Base64URL representation of the header, 327 * or a new Base64URL representation if the header was created 328 * from scratch. 329 */ 330 public Base64URL toBase64URL() { 331 332 if (parsedBase64URL == null) { 333 334 // Header was created from scratch, return new Base64URL 335 return Base64URL.encode(toString()); 336 337 } else { 338 339 // Header was parsed, return original Base64URL 340 return parsedBase64URL; 341 } 342 } 343 344 345 /** 346 * Parses an algorithm ({@code alg}) parameter from the specified 347 * header JSON object. Intended for initial parsing of unsecured 348 * (plain), JWS and JWE headers. 349 * 350 * <p>The algorithm type (none, JWS or JWE) is determined by inspecting 351 * the algorithm name for "none" and the presence of an "enc" 352 * parameter. 353 * 354 * @param json The JSON object to parse. Must not be {@code null}. 355 * 356 * @return The algorithm, an instance of {@link Algorithm#NONE}, 357 * {@link JWSAlgorithm} or {@link JWEAlgorithm}. {@code null} 358 * if not found. 359 * 360 * @throws ParseException If the {@code alg} parameter couldn't be 361 * parsed. 362 */ 363 public static Algorithm parseAlgorithm(final Map<String, Object> json) 364 throws ParseException { 365 366 String algName = JSONObjectUtils.getString(json, HeaderParameterNames.ALGORITHM); 367 368 if (algName == null) { 369 throw new ParseException("Missing \"alg\" in header JSON object", 0); 370 } 371 372 // Infer algorithm type 373 if (algName.equals(Algorithm.NONE.getName())) { 374 // Plain 375 return Algorithm.NONE; 376 } else if (json.containsKey(HeaderParameterNames.ENCRYPTION_ALGORITHM)) { 377 // JWE 378 return JWEAlgorithm.parse(algName); 379 } else { 380 // JWS 381 return JWSAlgorithm.parse(algName); 382 } 383 } 384 385 386 /** 387 * Join a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader} 388 * with an Unprotected header. 389 * 390 * @param unprotected The Unprotected header. {@code null} 391 * if not applicable. 392 * 393 * @return The header. 394 * 395 * @throws ParseException If the specified Unprotected header can not be 396 * merged to protected header. 397 */ 398 public Header join(final UnprotectedHeader unprotected) 399 throws ParseException { 400 401 Map<String, Object> jsonObject = toJSONObject(); 402 try { 403 HeaderValidation.ensureDisjoint(this, unprotected); 404 } catch (IllegalHeaderException e) { 405 throw new ParseException(e.getMessage(), 0); 406 } 407 if (unprotected != null) { 408 jsonObject.putAll(unprotected.toJSONObject()); 409 } 410 return parse(jsonObject, null); 411 } 412 413 414 /** 415 * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader} 416 * from the specified JSON object. 417 * 418 * @param jsonObject The JSON object to parse. Must not be 419 * {@code null}. 420 * 421 * @return The header. 422 * 423 * @throws ParseException If the specified JSON object doesn't 424 * represent a valid header. 425 */ 426 public static Header parse(final Map<String, Object> jsonObject) 427 throws ParseException { 428 429 return parse(jsonObject, null); 430 } 431 432 433 /** 434 * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader} 435 * from the specified JSON object. 436 * 437 * @param jsonObject The JSON object to parse. Must not be 438 * {@code null}. 439 * @param parsedBase64URL The original parsed Base64URL, {@code null} 440 * if not applicable. 441 * 442 * @return The header. 443 * 444 * @throws ParseException If the specified JSON object doesn't 445 * represent a valid header. 446 */ 447 public static Header parse(final Map<String, Object> jsonObject, 448 final Base64URL parsedBase64URL) 449 throws ParseException { 450 451 452 String algName = JSONObjectUtils.getString(jsonObject, HeaderParameterNames.ALGORITHM); 453 454 if (jsonObject.containsKey(HeaderParameterNames.ENCRYPTION_ALGORITHM)) { 455 // JWE 456 return JWEHeader.parse(jsonObject, parsedBase64URL); 457 } else if (Algorithm.NONE.getName().equals(algName)) { 458 // Plain 459 return PlainHeader.parse(jsonObject, parsedBase64URL); 460 } else if (jsonObject.containsKey(HeaderParameterNames.ALGORITHM)) { 461 // JWS 462 return JWSHeader.parse(jsonObject, parsedBase64URL); 463 } else { 464 throw new ParseException("Missing \"alg\" in header JSON object", 0); 465 } 466 } 467 468 469 /** 470 * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader} 471 * from the specified JSON object string. 472 * 473 * @param jsonString The JSON object string to parse. Must not be 474 * {@code null}. 475 * 476 * @return The header. 477 * 478 * @throws ParseException If the specified JSON object string doesn't 479 * represent a valid header. 480 */ 481 public static Header parse(final String jsonString) 482 throws ParseException { 483 484 return parse(jsonString, null); 485 } 486 487 488 /** 489 * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader} 490 * from the specified JSON object string. 491 * 492 * @param jsonString The JSON object string to parse. Must not be 493 * {@code null}. 494 * @param parsedBase64URL The original parsed Base64URL, {@code null} 495 * if not applicable. 496 * 497 * @return The header. 498 * 499 * @throws ParseException If the specified JSON object string doesn't 500 * represent a valid header. 501 */ 502 public static Header parse(final String jsonString, 503 final Base64URL parsedBase64URL) 504 throws ParseException { 505 506 Map<String, Object> jsonObject = JSONObjectUtils.parse(jsonString, MAX_HEADER_STRING_LENGTH); 507 508 return parse(jsonObject, parsedBase64URL); 509 } 510 511 512 /** 513 * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader} 514 * from the specified Base64URL. 515 * 516 * @param base64URL The Base64URL to parse. Must not be {@code null}. 517 * 518 * @return The header. 519 * 520 * @throws ParseException If the specified Base64URL doesn't represent 521 * a valid header. 522 */ 523 public static Header parse(final Base64URL base64URL) 524 throws ParseException { 525 526 return parse(base64URL.decodeToString(), base64URL); 527 } 528}