001package com.nimbusds.jose; 002 003 004import java.net.URI; 005import java.text.ParseException; 006import java.util.*; 007 008import net.jcip.annotations.Immutable; 009 010import net.minidev.json.JSONObject; 011 012import com.nimbusds.jose.jwk.JWK; 013import com.nimbusds.jose.util.Base64; 014import com.nimbusds.jose.util.Base64URL; 015import com.nimbusds.jose.util.JSONObjectUtils; 016import com.nimbusds.jose.util.X509CertChainUtils; 017 018 019/** 020 * JSON Web Signature (JWS) header. This class is immutable. 021 * 022 * <p>Supports all {@link #getRegisteredParameterNames registered header 023 * parameters} of the JWS specification: 024 * 025 * <ul> 026 * <li>alg 027 * <li>jku 028 * <li>jwk 029 * <li>x5u 030 * <li>x5t 031 * <li>x5t#S256 032 * <li>x5c 033 * <li>kid 034 * <li>typ 035 * <li>cty 036 * <li>crit 037 * </ul> 038 * 039 * <p>The header may also include {@link #getCustomParams custom 040 * parameters}; these will be serialised and parsed along the registered ones. 041 * 042 * <p>Example header of a JSON Web Signature (JWS) object using the 043 * {@link JWSAlgorithm#HS256 HMAC SHA-256 algorithm}: 044 * 045 * <pre> 046 * { 047 * "alg" : "HS256" 048 * } 049 * </pre> 050 * 051 * @author Vladimir Dzhuvinov 052 * @version 2015-04-15 053 */ 054@Immutable 055public final class JWSHeader extends CommonSEHeader { 056 057 058 private static final long serialVersionUID = 1L; 059 060 061 /** 062 * The registered parameter names. 063 */ 064 private static final Set<String> REGISTERED_PARAMETER_NAMES; 065 066 067 /** 068 * Initialises the registered parameter name set. 069 */ 070 static { 071 Set<String> p = new HashSet<>(); 072 073 p.add("alg"); 074 p.add("jku"); 075 p.add("jwk"); 076 p.add("x5u"); 077 p.add("x5t"); 078 p.add("x5t#S256"); 079 p.add("x5c"); 080 p.add("kid"); 081 p.add("typ"); 082 p.add("cty"); 083 p.add("crit"); 084 085 REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p); 086 } 087 088 089 /** 090 * Builder for constructing JSON Web Signature (JWS) headers. 091 * 092 * <p>Example usage: 093 * 094 * <pre> 095 * JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.HS256). 096 * contentType("text/plain"). 097 * customParam("exp", new Date().getTime()). 098 * build(); 099 * </pre> 100 */ 101 public static class Builder { 102 103 104 /** 105 * The JWS algorithm. 106 */ 107 private final JWSAlgorithm alg; 108 109 110 /** 111 * The JOSE object type. 112 */ 113 private JOSEObjectType typ; 114 115 116 /** 117 * The content type. 118 */ 119 private String cty; 120 121 122 /** 123 * The critical headers. 124 */ 125 private Set<String> crit; 126 127 128 /** 129 * JWK Set URL. 130 */ 131 private URI jku; 132 133 134 /** 135 * JWK. 136 */ 137 private JWK jwk; 138 139 140 /** 141 * X.509 certificate URL. 142 */ 143 private URI x5u; 144 145 146 /** 147 * X.509 certificate SHA-1 thumbprint. 148 */ 149 private Base64URL x5t; 150 151 152 /** 153 * X.509 certificate SHA-256 thumbprint. 154 */ 155 private Base64URL x5t256; 156 157 158 /** 159 * The X.509 certificate chain corresponding to the key used to 160 * sign the JWS object. 161 */ 162 private List<Base64> x5c; 163 164 165 /** 166 * Key ID. 167 */ 168 private String kid; 169 170 171 /** 172 * Custom header parameters. 173 */ 174 private Map<String,Object> customParams; 175 176 177 /** 178 * The parsed Base64URL. 179 */ 180 private Base64URL parsedBase64URL; 181 182 183 /** 184 * Creates a new JWS header builder. 185 * 186 * @param alg The JWS algorithm ({@code alg}) parameter. Must 187 * not be "none" or {@code null}. 188 */ 189 public Builder(final JWSAlgorithm alg) { 190 191 if (alg.getName().equals(Algorithm.NONE.getName())) { 192 throw new IllegalArgumentException("The JWS algorithm \"alg\" cannot be \"none\""); 193 } 194 195 this.alg = alg; 196 } 197 198 199 /** 200 * Creates a new JWS header builder with the parameters from 201 * the specified header. 202 * 203 * @param jwsHeader The JWS header to use. Must not not be 204 * {@code null}. 205 */ 206 public Builder(final JWSHeader jwsHeader) { 207 208 this(jwsHeader.getAlgorithm()); 209 210 typ = jwsHeader.getType(); 211 cty = jwsHeader.getContentType(); 212 crit = jwsHeader.getCriticalParams(); 213 customParams = jwsHeader.getCustomParams(); 214 215 jku = jwsHeader.getJWKURL(); 216 jwk = jwsHeader.getJWK(); 217 x5u = jwsHeader.getX509CertURL(); 218 x5t = jwsHeader.getX509CertThumbprint(); 219 x5t256 = jwsHeader.getX509CertSHA256Thumbprint(); 220 x5c = jwsHeader.getX509CertChain(); 221 kid = jwsHeader.getKeyID(); 222 customParams = jwsHeader.getCustomParams(); 223 } 224 225 226 /** 227 * Sets the type ({@code typ}) parameter. 228 * 229 * @param typ The type parameter, {@code null} if not 230 * specified. 231 * 232 * @return This builder. 233 */ 234 public Builder type(final JOSEObjectType typ) { 235 236 this.typ = typ; 237 return this; 238 } 239 240 241 /** 242 * Sets the content type ({@code cty}) parameter. 243 * 244 * @param cty The content type parameter, {@code null} if not 245 * specified. 246 * 247 * @return This builder. 248 */ 249 public Builder contentType(final String cty) { 250 251 this.cty = cty; 252 return this; 253 } 254 255 256 /** 257 * Sets the critical header parameters ({@code crit}) 258 * parameter. 259 * 260 * @param crit The names of the critical header parameters, 261 * empty set or {@code null} if none. 262 * 263 * @return This builder. 264 */ 265 public Builder criticalParams(final Set<String> crit) { 266 267 this.crit = crit; 268 return this; 269 } 270 271 272 /** 273 * Sets the JSON Web Key (JWK) Set URL ({@code jku}) parameter. 274 * 275 * @param jku The JSON Web Key (JWK) Set URL parameter, 276 * {@code null} if not specified. 277 * 278 * @return This builder. 279 */ 280 public Builder jwkURL(final URI jku) { 281 282 this.jku = jku; 283 return this; 284 } 285 286 287 /** 288 * Sets the JSON Web Key (JWK) ({@code jwk}) parameter. 289 * 290 * @param jwk The JSON Web Key (JWK) ({@code jwk}) parameter, 291 * {@code null} if not specified. 292 * 293 * @return This builder. 294 */ 295 public Builder jwk(final JWK jwk) { 296 297 this.jwk = jwk; 298 return this; 299 } 300 301 302 /** 303 * Sets the X.509 certificate URL ({@code x5u}) parameter. 304 * 305 * @param x5u The X.509 certificate URL parameter, {@code null} 306 * if not specified. 307 * 308 * @return This builder. 309 */ 310 public Builder x509CertURL(final URI x5u) { 311 312 this.x5u = x5u; 313 return this; 314 } 315 316 317 /** 318 * Sets the X.509 certificate SHA-1 thumbprint ({@code x5t}) 319 * parameter. 320 * 321 * @param x5t The X.509 certificate SHA-1 thumbprint parameter, 322 * {@code null} if not specified. 323 * 324 * @return This builder. 325 */ 326 public Builder x509CertThumbprint(final Base64URL x5t) { 327 328 this.x5t = x5t; 329 return this; 330 } 331 332 333 /** 334 * Sets the X.509 certificate SHA-256 thumbprint 335 * ({@code x5t#S256}) parameter. 336 * 337 * @param x5t256 The X.509 certificate SHA-256 thumbprint 338 * parameter, {@code null} if not specified. 339 * 340 * @return This builder. 341 */ 342 public Builder x509CertSHA256Thumbprint(final Base64URL x5t256) { 343 344 this.x5t256 = x5t256; 345 return this; 346 } 347 348 349 /** 350 * Sets the X.509 certificate chain parameter ({@code x5c}) 351 * corresponding to the key used to sign the JWS object. 352 * 353 * @param x5c The X.509 certificate chain parameter, 354 * {@code null} if not specified. 355 * 356 * @return This builder. 357 */ 358 public Builder x509CertChain(final List<Base64> x5c) { 359 360 this.x5c = x5c; 361 return this; 362 } 363 364 365 /** 366 * Sets the key ID ({@code kid}) parameter. 367 * 368 * @param kid The key ID parameter, {@code null} if not 369 * specified. 370 * 371 * @return This builder. 372 */ 373 public Builder keyID(final String kid) { 374 375 this.kid = kid; 376 return this; 377 } 378 379 380 /** 381 * Sets a custom (non-registered) parameter. 382 * 383 * @param name The name of the custom parameter. Must not 384 * match a registered parameter name and must not 385 * be {@code null}. 386 * @param value The value of the custom parameter, should map 387 * to a valid JSON entity, {@code null} if not 388 * specified. 389 * 390 * @return This builder. 391 * 392 * @throws IllegalArgumentException If the specified parameter 393 * name matches a registered 394 * parameter name. 395 */ 396 public Builder customParam(final String name, final Object value) { 397 398 if (getRegisteredParameterNames().contains(name)) { 399 throw new IllegalArgumentException("The parameter name \"" + name + "\" matches a registered name"); 400 } 401 402 if (customParams == null) { 403 customParams = new HashMap<>(); 404 } 405 406 customParams.put(name, value); 407 408 return this; 409 } 410 411 412 /** 413 * Sets the custom (non-registered) parameters. The values must 414 * be serialisable to a JSON entity, otherwise will be ignored. 415 * 416 * @param customParameters The custom parameters, empty map or 417 * {@code null} if none. 418 * 419 * @return This builder. 420 */ 421 public Builder customParams(final Map<String, Object> customParameters) { 422 423 this.customParams = customParameters; 424 return this; 425 } 426 427 428 /** 429 * Sets the parsed Base64URL. 430 * 431 * @param base64URL The parsed Base64URL, {@code null} if the 432 * header is created from scratch. 433 * 434 * @return This builder. 435 */ 436 public Builder parsedBase64URL(final Base64URL base64URL) { 437 438 this.parsedBase64URL = base64URL; 439 return this; 440 } 441 442 443 /** 444 * Builds a new JWS header. 445 * 446 * @return The JWS header. 447 */ 448 public JWSHeader build() { 449 450 return new JWSHeader( 451 alg, typ, cty, crit, 452 jku, jwk, x5u, x5t, x5t256, x5c, kid, 453 customParams, parsedBase64URL); 454 } 455 } 456 457 458 /** 459 * Creates a new minimal JSON Web Signature (JWS) header. 460 * 461 * <p>Note: Use {@link PlainHeader} to create a header with algorithm 462 * {@link Algorithm#NONE none}. 463 * 464 * @param alg The JWS algorithm ({@code alg}) parameter. Must not be 465 * "none" or {@code null}. 466 */ 467 public JWSHeader(final JWSAlgorithm alg) { 468 469 this(alg, null, null, null, null, null, null, null, null, null, null, null, null); 470 } 471 472 473 /** 474 * Creates a new JSON Web Signature (JWS) header. 475 * 476 * <p>Note: Use {@link PlainHeader} to create a header with algorithm 477 * {@link Algorithm#NONE none}. 478 * 479 * @param alg The JWS algorithm ({@code alg}) parameter. 480 * Must not be "none" or {@code null}. 481 * @param typ The type ({@code typ}) parameter, 482 * {@code null} if not specified. 483 * @param cty The content type ({@code cty}) parameter, 484 * {@code null} if not specified. 485 * @param crit The names of the critical header 486 * ({@code crit}) parameters, empty set or 487 * {@code null} if none. 488 * @param jku The JSON Web Key (JWK) Set URL ({@code jku}) 489 * parameter, {@code null} if not specified. 490 * @param jwk The X.509 certificate URL ({@code jwk}) 491 * parameter, {@code null} if not specified. 492 * @param x5u The X.509 certificate URL parameter 493 * ({@code x5u}), {@code null} if not specified. 494 * @param x5t The X.509 certificate SHA-1 thumbprint 495 * ({@code x5t}) parameter, {@code null} if not 496 * specified. 497 * @param x5t256 The X.509 certificate SHA-256 thumbprint 498 * ({@code x5t#S256}) parameter, {@code null} if 499 * not specified. 500 * @param x5c The X.509 certificate chain ({@code x5c}) 501 * parameter, {@code null} if not specified. 502 * @param kid The key ID ({@code kid}) parameter, 503 * {@code null} if not specified. 504 * @param customParams The custom parameters, empty map or 505 * {@code null} if none. 506 * @param parsedBase64URL The parsed Base64URL, {@code null} if the 507 * header is created from scratch. 508 */ 509 public JWSHeader(final JWSAlgorithm alg, 510 final JOSEObjectType typ, 511 final String cty, 512 final Set<String> crit, 513 final URI jku, 514 final JWK jwk, 515 final URI x5u, 516 final Base64URL x5t, 517 final Base64URL x5t256, 518 final List<Base64> x5c, 519 final String kid, 520 final Map<String,Object> customParams, 521 final Base64URL parsedBase64URL) { 522 523 super(alg, typ, cty, crit, jku, jwk, x5u, x5t, x5t256, x5c, kid, customParams, parsedBase64URL); 524 525 if (alg.getName().equals(Algorithm.NONE.getName())) { 526 throw new IllegalArgumentException("The JWS algorithm \"alg\" cannot be \"none\""); 527 } 528 } 529 530 531 /** 532 * Deep copy constructor. 533 * 534 * @param jwsHeader The JWS header to copy. Must not be {@code null}. 535 */ 536 public JWSHeader(final JWSHeader jwsHeader) { 537 538 this( 539 jwsHeader.getAlgorithm(), 540 jwsHeader.getType(), 541 jwsHeader.getContentType(), 542 jwsHeader.getCriticalParams(), 543 jwsHeader.getJWKURL(), 544 jwsHeader.getJWK(), 545 jwsHeader.getX509CertURL(), 546 jwsHeader.getX509CertThumbprint(), 547 jwsHeader.getX509CertSHA256Thumbprint(), 548 jwsHeader.getX509CertChain(), 549 jwsHeader.getKeyID(), 550 jwsHeader.getCustomParams(), 551 jwsHeader.getParsedBase64URL() 552 ); 553 } 554 555 556 /** 557 * Gets the registered parameter names for JWS headers. 558 * 559 * @return The registered parameter names, as an unmodifiable set. 560 */ 561 public static Set<String> getRegisteredParameterNames() { 562 563 return REGISTERED_PARAMETER_NAMES; 564 } 565 566 567 /** 568 * Gets the algorithm ({@code alg}) parameter. 569 * 570 * @return The algorithm parameter. 571 */ 572 @Override 573 public JWSAlgorithm getAlgorithm() { 574 575 return (JWSAlgorithm)super.getAlgorithm(); 576 } 577 578 579 /** 580 * Parses a JWS header from the specified JSON object. 581 * 582 * @param jsonObject The JSON object to parse. Must not be 583 * {@code null}. 584 * 585 * @return The JWS header. 586 * 587 * @throws ParseException If the specified JSON object doesn't 588 * represent a valid JWS header. 589 */ 590 public static JWSHeader parse(final JSONObject jsonObject) 591 throws ParseException { 592 593 return parse(jsonObject, null); 594 } 595 596 597 /** 598 * Parses a JWS header from the specified JSON object. 599 * 600 * @param jsonObject The JSON object to parse. Must not be 601 * {@code null}. 602 * @param parsedBase64URL The original parsed Base64URL, {@code null} 603 * if not applicable. 604 * 605 * @return The JWS header. 606 * 607 * @throws ParseException If the specified JSON object doesn't 608 * represent a valid JWS header. 609 */ 610 public static JWSHeader parse(final JSONObject jsonObject, 611 final Base64URL parsedBase64URL) 612 throws ParseException { 613 614 // Get the "alg" parameter 615 Algorithm alg = Header.parseAlgorithm(jsonObject); 616 617 if (! (alg instanceof JWSAlgorithm)) { 618 throw new ParseException("The algorithm \"alg\" header parameter must be for signatures", 0); 619 } 620 621 JWSHeader.Builder header = new Builder((JWSAlgorithm)alg).parsedBase64URL(parsedBase64URL); 622 623 // Parse optional + custom parameters 624 for (final String name: jsonObject.keySet()) { 625 626 if("alg".equals(name)) { 627 // skip 628 } else if("typ".equals(name)) { 629 header = header.type(new JOSEObjectType(JSONObjectUtils.getString(jsonObject, name))); 630 } else if("cty".equals(name)) { 631 header = header.contentType(JSONObjectUtils.getString(jsonObject, name)); 632 } else if("crit".equals(name)) { 633 header = header.criticalParams(new HashSet<>(JSONObjectUtils.getStringList(jsonObject, name))); 634 } else if("jku".equals(name)) { 635 header = header.jwkURL(JSONObjectUtils.getURI(jsonObject, name)); 636 } else if("jwk".equals(name)) { 637 header = header.jwk(JWK.parse(JSONObjectUtils.getJSONObject(jsonObject, name))); 638 } else if("x5u".equals(name)) { 639 header = header.x509CertURL(JSONObjectUtils.getURI(jsonObject, name)); 640 } else if("x5t".equals(name)) { 641 header = header.x509CertThumbprint(new Base64URL(JSONObjectUtils.getString(jsonObject, name))); 642 } else if("x5t#S256".equals(name)) { 643 header = header.x509CertSHA256Thumbprint(new Base64URL(JSONObjectUtils.getString(jsonObject, name))); 644 } else if("x5c".equals(name)) { 645 header = header.x509CertChain(X509CertChainUtils.parseX509CertChain(JSONObjectUtils.getJSONArray(jsonObject, name))); 646 } else if("kid".equals(name)) { 647 header = header.keyID(JSONObjectUtils.getString(jsonObject, name)); 648 } else { 649 header = header.customParam(name, jsonObject.get(name)); 650 } 651 } 652 653 return header.build(); 654 } 655 656 657 /** 658 * Parses a JWS header from the specified JSON object string. 659 * 660 * @param jsonString The JSON string to parse. Must not be 661 * {@code null}. 662 * 663 * @return The JWS header. 664 * 665 * @throws ParseException If the specified JSON object string doesn't 666 * represent a valid JWS header. 667 */ 668 public static JWSHeader parse(final String jsonString) 669 throws ParseException { 670 671 return parse(jsonString, null); 672 } 673 674 675 /** 676 * Parses a JWS header from the specified JSON object string. 677 * 678 * @param jsonString The JSON string to parse. Must not be 679 * {@code null}. 680 * @param parsedBase64URL The original parsed Base64URL, {@code null} 681 * if not applicable. 682 * 683 * @return The JWS header. 684 * 685 * @throws ParseException If the specified JSON object string doesn't 686 * represent a valid JWS header. 687 */ 688 public static JWSHeader parse(final String jsonString, 689 final Base64URL parsedBase64URL) 690 throws ParseException { 691 692 return parse(JSONObjectUtils.parseJSONObject(jsonString), parsedBase64URL); 693 } 694 695 696 /** 697 * Parses a JWS header from the specified Base64URL. 698 * 699 * @param base64URL The Base64URL to parse. Must not be {@code null}. 700 * 701 * @return The JWS header. 702 * 703 * @throws ParseException If the specified Base64URL doesn't represent 704 * a valid JWS header. 705 */ 706 public static JWSHeader parse(final Base64URL base64URL) 707 throws ParseException { 708 709 return parse(base64URL.decodeToString(), base64URL); 710 } 711}