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