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