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