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