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 2014-08-21 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}. 352 * 353 * @throws ParseException If the {@code alg} parameter couldn't be 354 * parsed. 355 */ 356 public static Algorithm parseAlgorithm(final JSONObject json) 357 throws ParseException { 358 359 String algName = JSONObjectUtils.getString(json, "alg"); 360 361 // Infer algorithm type 362 363 if (algName.equals(Algorithm.NONE.getName())) { 364 // Plain 365 return Algorithm.NONE; 366 } else if (json.containsKey("enc")) { 367 // JWE 368 return JWEAlgorithm.parse(algName); 369 } else { 370 // JWS 371 return JWSAlgorithm.parse(algName); 372 } 373 } 374 375 376 /** 377 * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader} 378 * from the specified JSON object. 379 * 380 * @param jsonObject The JSON object to parse. Must not be 381 * {@code null}. 382 * 383 * @return The header. 384 * 385 * @throws ParseException If the specified JSON object doesn't 386 * represent a valid header. 387 */ 388 public static Header parse(final JSONObject jsonObject) 389 throws ParseException { 390 391 return parse(jsonObject, null); 392 } 393 394 395 /** 396 * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader} 397 * from the specified JSON object. 398 * 399 * @param jsonObject The JSON object to parse. Must not be 400 * {@code null}. 401 * @param parsedBase64URL The original parsed Base64URL, {@code null} 402 * if not applicable. 403 * 404 * @return The header. 405 * 406 * @throws ParseException If the specified JSON object doesn't 407 * represent a valid header. 408 */ 409 public static Header parse(final JSONObject jsonObject, 410 final Base64URL parsedBase64URL) 411 throws ParseException { 412 413 Algorithm alg = parseAlgorithm(jsonObject); 414 415 if (alg.equals(Algorithm.NONE)) { 416 417 return PlainHeader.parse(jsonObject, parsedBase64URL); 418 419 } else if (alg instanceof JWSAlgorithm) { 420 421 return JWSHeader.parse(jsonObject, parsedBase64URL); 422 423 } else if (alg instanceof JWEAlgorithm) { 424 425 return JWEHeader.parse(jsonObject, parsedBase64URL); 426 427 } else { 428 429 throw new AssertionError("Unexpected algorithm type: " + alg); 430 } 431 } 432 433 434 /** 435 * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader} 436 * from the specified JSON object string. 437 * 438 * @param jsonString The JSON object string to parse. Must not be 439 * {@code null}. 440 * 441 * @return The header. 442 * 443 * @throws ParseException If the specified JSON object string doesn't 444 * represent a valid header. 445 */ 446 public static Header parse(final String jsonString) 447 throws ParseException { 448 449 return parse(jsonString, null); 450 } 451 452 453 /** 454 * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader} 455 * from the specified JSON object string. 456 * 457 * @param jsonString The JSON object string to parse. Must not be 458 * {@code null}. 459 * @param parsedBase64URL The original parsed Base64URL, {@code null} 460 * if not applicable. 461 * 462 * @return The header. 463 * 464 * @throws ParseException If the specified JSON object string doesn't 465 * represent a valid header. 466 */ 467 public static Header parse(final String jsonString, 468 final Base64URL parsedBase64URL) 469 throws ParseException { 470 471 JSONObject jsonObject = JSONObjectUtils.parse(jsonString); 472 473 return parse(jsonObject, parsedBase64URL); 474 } 475 476 477 /** 478 * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader} 479 * from the specified Base64URL. 480 * 481 * @param base64URL The Base64URL to parse. Must not be {@code null}. 482 * 483 * @return The header. 484 * 485 * @throws ParseException If the specified Base64URL doesn't represent 486 * a valid header. 487 */ 488 public static Header parse(final Base64URL base64URL) 489 throws ParseException { 490 491 return parse(base64URL.decodeToString(), base64URL); 492 } 493}