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