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