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