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