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 231 jku = jwsHeader.getJWKURL(); 232 jwk = jwsHeader.getJWK(); 233 x5u = jwsHeader.getX509CertURL(); 234 x5t = jwsHeader.getX509CertThumbprint(); 235 x5t256 = jwsHeader.getX509CertSHA256Thumbprint(); 236 x5c = jwsHeader.getX509CertChain(); 237 kid = jwsHeader.getKeyID(); 238 customParams = jwsHeader.getCustomParams(); 239 } 240 241 242 /** 243 * Sets the type ({@code typ}) parameter. 244 * 245 * @param typ The type parameter, {@code null} if not 246 * specified. 247 * 248 * @return This builder. 249 */ 250 public Builder type(final JOSEObjectType typ) { 251 252 this.typ = typ; 253 return this; 254 } 255 256 257 /** 258 * Sets the content type ({@code cty}) parameter. 259 * 260 * @param cty The content type parameter, {@code null} if not 261 * specified. 262 * 263 * @return This builder. 264 */ 265 public Builder contentType(final String cty) { 266 267 this.cty = cty; 268 return this; 269 } 270 271 272 /** 273 * Sets the critical header parameters ({@code crit}) 274 * parameter. 275 * 276 * @param crit The names of the critical header parameters, 277 * empty set or {@code null} if none. 278 * 279 * @return This builder. 280 */ 281 public Builder criticalParams(final Set<String> crit) { 282 283 this.crit = crit; 284 return this; 285 } 286 287 288 /** 289 * Sets the JSON Web Key (JWK) Set URL ({@code jku}) parameter. 290 * 291 * @param jku The JSON Web Key (JWK) Set URL parameter, 292 * {@code null} if not specified. 293 * 294 * @return This builder. 295 */ 296 public Builder jwkURL(final URI jku) { 297 298 this.jku = jku; 299 return this; 300 } 301 302 303 /** 304 * Sets the JSON Web Key (JWK) ({@code jwk}) parameter. 305 * 306 * @param jwk The JSON Web Key (JWK) ({@code jwk}) parameter, 307 * {@code null} if not specified. 308 * 309 * @return This builder. 310 */ 311 public Builder jwk(final JWK jwk) { 312 313 this.jwk = jwk; 314 return this; 315 } 316 317 318 /** 319 * Sets the X.509 certificate URL ({@code x5u}) parameter. 320 * 321 * @param x5u The X.509 certificate URL parameter, {@code null} 322 * if not specified. 323 * 324 * @return This builder. 325 */ 326 public Builder x509CertURL(final URI x5u) { 327 328 this.x5u = x5u; 329 return this; 330 } 331 332 333 /** 334 * Sets the X.509 certificate SHA-1 thumbprint ({@code x5t}) 335 * parameter. 336 * 337 * @param x5t The X.509 certificate SHA-1 thumbprint parameter, 338 * {@code null} if not specified. 339 * 340 * @return This builder. 341 */ 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}