001package com.nimbusds.jose; 002 003 004import java.text.ParseException; 005import java.util.Collections; 006import java.util.HashMap; 007import java.util.HashSet; 008import java.util.Map; 009import java.util.Set; 010 011import net.jcip.annotations.Immutable; 012 013import net.minidev.json.JSONObject; 014 015import com.nimbusds.jose.util.Base64URL; 016import com.nimbusds.jose.util.JSONObjectUtils; 017 018 019/** 020 * Unsecured ({@code alg=none}) JOSE header. This class is immutable. 021 * 022 * <p>Supports all {@link #getRegisteredParameterNames registered header 023 * parameters} of the unsecured JOSE object specification: 024 * 025 * <ul> 026 * <li>alg (set to {@link Algorithm#NONE "none"}). 027 * <li>typ 028 * <li>cty 029 * <li>crit 030 * </ul> 031 * 032 * <p>The header may also carry {@link #getCustomParams custom parameters}; 033 * these will be serialised and parsed along the registered ones. 034 * 035 * <p>Example: 036 * 037 * <pre> 038 * { 039 * "alg" : "none" 040 * } 041 * </pre> 042 * 043 * @author Vladimir Dzhuvinov 044 * @version 2014-08-20 045 */ 046@Immutable 047public final class PlainHeader extends Header { 048 049 050 private static final long serialVersionUID = 1L; 051 052 053 /** 054 * The registered parameter names. 055 */ 056 private static final Set<String> REGISTERED_PARAMETER_NAMES; 057 058 059 /** 060 * Initialises the registered parameter name set. 061 */ 062 static { 063 Set<String> p = new HashSet<>(); 064 065 p.add("alg"); 066 p.add("typ"); 067 p.add("cty"); 068 p.add("crit"); 069 070 REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p); 071 } 072 073 074 /** 075 * Builder for constructing unsecured (plain) headers. 076 * 077 * <p>Example usage: 078 * 079 * <pre> 080 * PlainHeader header = new PlainHeader.Builder(). 081 * contentType("text/plain"). 082 * customParam("exp", new Date().getTime()). 083 * build(); 084 * </pre> 085 */ 086 public static class Builder { 087 088 089 /** 090 * The JOSE object type. 091 */ 092 private JOSEObjectType typ; 093 094 095 /** 096 * The content type. 097 */ 098 private String cty; 099 100 101 /** 102 * The critical headers. 103 */ 104 private Set<String> crit; 105 106 107 /** 108 * Custom header parameters. 109 */ 110 private Map<String,Object> customParams; 111 112 113 /** 114 * The parsed Base64URL. 115 */ 116 private Base64URL parsedBase64URL; 117 118 119 /** 120 * Creates a new unsecured (plain) header builder. 121 */ 122 public Builder() { 123 124 } 125 126 127 /** 128 * Creates a new unsecured (plain) header builder with the 129 * parameters from the specified header. 130 * 131 * @param plainHeader The unsecured header to use. Must not be 132 * {@code null}. 133 */ 134 public Builder(final PlainHeader plainHeader) { 135 136 typ = plainHeader.getType(); 137 cty = plainHeader.getContentType(); 138 crit = plainHeader.getCriticalParams(); 139 customParams = plainHeader.getCustomParams(); 140 } 141 142 143 /** 144 * Sets the type ({@code typ}) parameter. 145 * 146 * @param typ The type parameter, {@code null} if not 147 * specified. 148 * 149 * @return This builder. 150 */ 151 public Builder type(final JOSEObjectType typ) { 152 153 this.typ = typ; 154 return this; 155 } 156 157 158 /** 159 * Sets the content type ({@code cty}) parameter. 160 * 161 * @param cty The content type parameter, {@code null} if not 162 * specified. 163 * 164 * @return This builder. 165 */ 166 public Builder contentType(final String cty) { 167 168 this.cty = cty; 169 return this; 170 } 171 172 173 /** 174 * Sets the critical header parameters ({@code crit}) 175 * parameter. 176 * 177 * @param crit The names of the critical header parameters, 178 * empty set or {@code null} if none. 179 * 180 * @return This builder. 181 */ 182 public Builder criticalParams(final Set<String> crit) { 183 184 this.crit = crit; 185 return this; 186 } 187 188 189 /** 190 * Sets a custom (non-registered) parameter. 191 * 192 * @param name The name of the custom parameter. Must not 193 * match a registered parameter name and must not 194 * be {@code null}. 195 * @param value The value of the custom parameter, should map 196 * to a valid JSON entity, {@code null} if not 197 * specified. 198 * 199 * @return This builder. 200 * 201 * @throws IllegalArgumentException If the specified parameter 202 * name matches a registered 203 * parameter name. 204 */ 205 public Builder customParam(final String name, final Object value) { 206 207 if (getRegisteredParameterNames().contains(name)) { 208 throw new IllegalArgumentException("The parameter name \"" + name + "\" matches a registered name"); 209 } 210 211 if (customParams == null) { 212 customParams = new HashMap<>(); 213 } 214 215 customParams.put(name, value); 216 217 return this; 218 } 219 220 221 /** 222 * Sets the custom (non-registered) parameters. The values must 223 * be serialisable to a JSON entity, otherwise will be ignored. 224 * 225 * @param customParameters The custom parameters, empty map or 226 * {@code null} if none. 227 * 228 * @return This builder. 229 */ 230 public Builder customParams(final Map<String, Object> customParameters) { 231 232 this.customParams = customParameters; 233 return this; 234 } 235 236 237 /** 238 * Sets the parsed Base64URL. 239 * 240 * @param base64URL The parsed Base64URL, {@code null} if the 241 * header is created from scratch. 242 * 243 * @return This builder. 244 */ 245 public Builder parsedBase64URL(final Base64URL base64URL) { 246 247 this.parsedBase64URL = base64URL; 248 return this; 249 } 250 251 252 /** 253 * Builds a new unsecured (plain) header. 254 * 255 * @return The unsecured header. 256 */ 257 public PlainHeader build() { 258 259 return new PlainHeader(typ, cty, crit, customParams, parsedBase64URL); 260 } 261 } 262 263 264 /** 265 * Creates a new minimal unsecured (plain) header with algorithm 266 * {@link Algorithm#NONE none}. 267 */ 268 public PlainHeader() { 269 270 this(null, null, null, null, null); 271 } 272 273 274 /** 275 * Creates a new unsecured (plain) header with algorithm 276 * {@link Algorithm#NONE none}. 277 * 278 * @param typ The type ({@code typ}) parameter, 279 * {@code null} if not specified. 280 * @param cty The content type ({@code cty}) parameter, 281 * {@code null} if not specified. 282 * @param crit The names of the critical header 283 * ({@code crit}) parameters, empty set or 284 * {@code null} if none. 285 * @param customParams The custom parameters, empty map or 286 * {@code null} if none. 287 * @param parsedBase64URL The parsed Base64URL, {@code null} if the 288 * header is created from scratch. 289 */ 290 public PlainHeader(final JOSEObjectType typ, 291 final String cty, 292 final Set<String> crit, 293 final Map<String, Object> customParams, 294 final Base64URL parsedBase64URL) { 295 296 super(Algorithm.NONE, typ, cty, crit, customParams, parsedBase64URL); 297 } 298 299 300 /** 301 * Deep copy constructor. 302 * 303 * @param plainHeader The unsecured header to copy. Must not be 304 * {@code null}. 305 */ 306 public PlainHeader(final PlainHeader plainHeader) { 307 308 this( 309 plainHeader.getType(), 310 plainHeader.getContentType(), 311 plainHeader.getCriticalParams(), 312 plainHeader.getCustomParams(), 313 plainHeader.getParsedBase64URL() 314 ); 315 } 316 317 318 /** 319 * Gets the registered parameter names for unsecured headers. 320 * 321 * @return The registered parameter names, as an unmodifiable set. 322 */ 323 public static Set<String> getRegisteredParameterNames() { 324 325 return REGISTERED_PARAMETER_NAMES; 326 } 327 328 329 /** 330 * Gets the algorithm ({@code alg}) parameter. 331 * 332 * @return {@link Algorithm#NONE}. 333 */ 334 @Override 335 public Algorithm getAlgorithm() { 336 337 return Algorithm.NONE; 338 } 339 340 341 /** 342 * Parses an unsecured header from the specified JSON object. 343 * 344 * @param jsonObject The JSON object to parse. Must not be {@code null}. 345 * 346 * @return The unsecured header. 347 * 348 * @throws ParseException If the specified JSON object doesn't 349 * represent a valid unsecured header. 350 */ 351 public static PlainHeader parse(final JSONObject jsonObject) 352 throws ParseException { 353 354 return parse(jsonObject, null); 355 } 356 357 358 /** 359 * Parses an unsecured header from the specified JSON object. 360 * 361 * @param jsonObject The JSON object to parse. Must not be 362 * {@code null}. 363 * @param parsedBase64URL The original parsed Base64URL, {@code null} 364 * if not applicable. 365 * 366 * @return The unsecured header. 367 * 368 * @throws ParseException If the specified JSON object doesn't 369 * represent a valid unsecured header. 370 */ 371 public static PlainHeader parse(final JSONObject jsonObject, 372 final Base64URL parsedBase64URL) 373 throws ParseException { 374 375 // Get the "alg" parameter 376 Algorithm alg = Header.parseAlgorithm(jsonObject); 377 378 if (alg != Algorithm.NONE) { 379 throw new ParseException("The algorithm \"alg\" header parameter must be \"none\"", 0); 380 } 381 382 PlainHeader.Builder header = new Builder().parsedBase64URL(parsedBase64URL); 383 384 // Parse optional + custom parameters 385 for(final String name: jsonObject.keySet()) { 386 387 388 389 if("alg".equals(name)) { 390 // skip 391 } else if("typ".equals(name)) { 392 header = header.type(new JOSEObjectType(JSONObjectUtils.getString(jsonObject, name))); 393 } else if("cty".equals(name)) { 394 header = header.contentType(JSONObjectUtils.getString(jsonObject, name)); 395 } else if("crit".equals(name)) { 396 header = header.criticalParams(new HashSet<>(JSONObjectUtils.getStringList(jsonObject, name))); 397 } else { 398 header = header.customParam(name, jsonObject.get(name)); 399 } 400 } 401 402 return header.build(); 403 } 404 405 406 /** 407 * Parses an unsecured header from the specified JSON string. 408 * 409 * @param jsonString The JSON string to parse. Must not be 410 * {@code null}. 411 * 412 * @return The unsecured header. 413 * 414 * @throws ParseException If the specified JSON string doesn't 415 * represent a valid unsecured header. 416 */ 417 public static PlainHeader parse(final String jsonString) 418 throws ParseException { 419 420 return parse(jsonString, null); 421 } 422 423 424 /** 425 * Parses an unsecured header from the specified JSON string. 426 * 427 * @param jsonString The JSON string to parse. Must not be 428 * {@code null}. 429 * @param parsedBase64URL The original parsed Base64URL, {@code null} 430 * if not applicable. 431 * 432 * @return The unsecured header. 433 * 434 * @throws ParseException If the specified JSON string doesn't 435 * represent a valid unsecured header. 436 */ 437 public static PlainHeader parse(final String jsonString, 438 final Base64URL parsedBase64URL) 439 throws ParseException { 440 441 return parse(JSONObjectUtils.parseJSONObject(jsonString), parsedBase64URL); 442 } 443 444 445 /** 446 * Parses an unsecured header from the specified Base64URL. 447 * 448 * @param base64URL The Base64URL to parse. Must not be {@code null}. 449 * 450 * @return The unsecured header. 451 * 452 * @throws ParseException If the specified Base64URL doesn't represent 453 * a valid unsecured header. 454 */ 455 public static PlainHeader parse(final Base64URL base64URL) 456 throws ParseException { 457 458 return parse(base64URL.decodeToString(), base64URL); 459 } 460}