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