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. 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 /** 051 * The registered parameter names. 052 */ 053 private static final Set<String> REGISTERED_PARAMETER_NAMES; 054 055 056 /** 057 * Initialises the registered parameter name set. 058 */ 059 static { 060 Set<String> p = new HashSet<>(); 061 062 p.add("alg"); 063 p.add("typ"); 064 p.add("cty"); 065 p.add("crit"); 066 067 REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p); 068 } 069 070 071 /** 072 * Builder for constructing unsecured (plain) headers. 073 * 074 * <p>Example use: 075 * 076 * <pre> 077 * PlainHeader header = new PlainHeader.Builder(). 078 * contentType("text/plain"). 079 * customParam("exp", new Date().getTime()). 080 * build(); 081 * </pre> 082 */ 083 public static class Builder { 084 085 086 /** 087 * The JOSE object type. 088 */ 089 private JOSEObjectType typ; 090 091 092 /** 093 * The content type. 094 */ 095 private String cty; 096 097 098 /** 099 * The critical headers. 100 */ 101 private Set<String> crit; 102 103 104 /** 105 * Custom header parameters. 106 */ 107 private Map<String,Object> customParams; 108 109 110 /** 111 * The parsed Base64URL. 112 */ 113 private Base64URL parsedBase64URL; 114 115 116 /** 117 * Creates a new unsecured (plain) header builder. 118 */ 119 public Builder() { 120 121 } 122 123 124 /** 125 * Creates a new unsecured (plain) header builder with the 126 * parameters from the specified header. 127 * 128 * @param plainHeader The unsecured header to use. Must not be 129 * {@code null}. 130 */ 131 public Builder(final PlainHeader plainHeader) { 132 133 typ = plainHeader.getType(); 134 cty = plainHeader.getContentType(); 135 crit = plainHeader.getCriticalParams(); 136 customParams = plainHeader.getCustomParams(); 137 } 138 139 140 /** 141 * Sets the type ({@code typ}) parameter. 142 * 143 * @param typ The type parameter, {@code null} if not 144 * specified. 145 * 146 * @return This builder. 147 */ 148 public Builder type(final JOSEObjectType typ) { 149 150 this.typ = typ; 151 return this; 152 } 153 154 155 /** 156 * Sets the content type ({@code cty}) parameter. 157 * 158 * @param cty The content type parameter, {@code null} if not 159 * specified. 160 * 161 * @return This builder. 162 */ 163 public Builder contentType(final String cty) { 164 165 this.cty = cty; 166 return this; 167 } 168 169 170 /** 171 * Sets the critical header parameters ({@code crit}) 172 * parameter. 173 * 174 * @param crit The names of the critical header parameters, 175 * empty set or {@code null} if none. 176 * 177 * @return This builder. 178 */ 179 public Builder criticalParams(final Set<String> crit) { 180 181 this.crit = crit; 182 return this; 183 } 184 185 186 /** 187 * Sets a custom (non-registered) parameter. 188 * 189 * @param name The name of the custom parameter. Must not 190 * match a registered parameter name and must not 191 * be {@code null}. 192 * @param value The value of the custom parameter, should map 193 * to a valid JSON entity, {@code null} if not 194 * specified. 195 * 196 * @return This builder. 197 * 198 * @throws IllegalArgumentException If the specified parameter 199 * name matches a registered 200 * parameter name. 201 */ 202 public Builder customParam(final String name, final Object value) { 203 204 if (getRegisteredParameterNames().contains(name)) { 205 throw new IllegalArgumentException("The parameter name \"" + name + "\" matches a registered name"); 206 } 207 208 if (customParams == null) { 209 customParams = new HashMap<>(); 210 } 211 212 customParams.put(name, value); 213 214 return this; 215 } 216 217 218 /** 219 * Sets the custom (non-registered) parameters. The values must 220 * be serialisable to a JSON entity, otherwise will be ignored. 221 * 222 * @param customParameters The custom parameters, empty map or 223 * {@code null} if none. 224 * 225 * @return This builder. 226 */ 227 public Builder customParams(final Map<String, Object> customParameters) { 228 229 this.customParams = customParameters; 230 return this; 231 } 232 233 234 /** 235 * Sets the parsed Base64URL. 236 * 237 * @param base64URL The parsed Base64URL, {@code null} if the 238 * header is created from scratch. 239 * 240 * @return This builder. 241 */ 242 public Builder parsedBase64URL(final Base64URL base64URL) { 243 244 this.parsedBase64URL = base64URL; 245 return this; 246 } 247 248 249 /** 250 * Builds a new unsecured (plain) header. 251 * 252 * @return The unsecured header. 253 */ 254 public PlainHeader build() { 255 256 return new PlainHeader(typ, cty, crit, customParams, parsedBase64URL); 257 } 258 } 259 260 261 /** 262 * Creates a new minimal unsecured (plain) header with algorithm 263 * {@link Algorithm#NONE none}. 264 */ 265 public PlainHeader() { 266 267 this(null, null, null, null, null); 268 } 269 270 271 /** 272 * Creates a new unsecured (plain) header with algorithm 273 * {@link Algorithm#NONE none}. 274 * 275 * @param typ The type ({@code typ}) parameter, 276 * {@code null} if not specified. 277 * @param cty The content type ({@code cty}) parameter, 278 * {@code null} if not specified. 279 * @param crit The names of the critical header 280 * ({@code crit}) parameters, empty set or 281 * {@code null} if none. 282 * @param customParams The custom parameters, empty map or 283 * {@code null} if none. 284 * @param parsedBase64URL The parsed Base64URL, {@code null} if the 285 * header is created from scratch. 286 */ 287 public PlainHeader(final JOSEObjectType typ, 288 final String cty, 289 final Set<String> crit, 290 final Map<String, Object> customParams, 291 final Base64URL parsedBase64URL) { 292 293 super(Algorithm.NONE, typ, cty, crit, customParams, parsedBase64URL); 294 } 295 296 297 /** 298 * Deep copy constructor. 299 * 300 * @param plainHeader The unsecured header to copy. Must not be 301 * {@code null}. 302 */ 303 public PlainHeader(final PlainHeader plainHeader) { 304 305 this( 306 plainHeader.getType(), 307 plainHeader.getContentType(), 308 plainHeader.getCriticalParams(), 309 plainHeader.getCustomParams(), 310 plainHeader.getParsedBase64URL() 311 ); 312 } 313 314 315 /** 316 * Gets the registered parameter names for unsecured headers. 317 * 318 * @return The registered parameter names, as an unmodifiable set. 319 */ 320 public static Set<String> getRegisteredParameterNames() { 321 322 return REGISTERED_PARAMETER_NAMES; 323 } 324 325 326 /** 327 * Gets the algorithm ({@code alg}) parameter. 328 * 329 * @return {@link Algorithm#NONE}. 330 */ 331 @Override 332 public Algorithm getAlgorithm() { 333 334 return Algorithm.NONE; 335 } 336 337 338 /** 339 * Parses an unsecured header from the specified JSON object. 340 * 341 * @param jsonObject The JSON object to parse. Must not be {@code null}. 342 * 343 * @return The unsecured header. 344 * 345 * @throws ParseException If the specified JSON object doesn't 346 * represent a valid unsecured 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 an unsecured 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 unsecured header. 364 * 365 * @throws ParseException If the specified JSON object doesn't 366 * represent a valid unsecured 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 an unsecured 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 unsecured header. 410 * 411 * @throws ParseException If the specified JSON string doesn't 412 * represent a valid unsecured 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 an unsecured 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 unsecured header. 430 * 431 * @throws ParseException If the specified JSON string doesn't 432 * represent a valid unsecured 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 an unsecured header from the specified Base64URL. 444 * 445 * @param base64URL The Base64URL to parse. Must not be {@code null}. 446 * 447 * @return The unsecured header. 448 * 449 * @throws ParseException If the specified Base64URL doesn't represent 450 * a valid unsecured header. 451 */ 452 public static PlainHeader parse(final Base64URL base64URL) 453 throws ParseException { 454 455 return parse(base64URL.decodeToString(), base64URL); 456 } 457}