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